const electron = require('electron').remote;
const {clipboard, shell, nativeImage} = require('electron');
const app = electron.app;
const fs = require('fs');
const zlib = require('zlib');
const exec = require('child_process').exec;
const originalFs = require('original-fs');
const https = require('https');
const currentwindow = electron.getCurrentWindow();
var dialog_win = null,
latest_version = false,
preventClosing = true;
recent_projects= undefined;
$(document).ready(function() {
//Setup
$('.open-in-browser').click((event) => {
event.preventDefault();
shell.openExternal(event.target.href);
return true;
});
Prop.zoom = 100 + currentwindow.webContents._getZoomLevel()*12
if (fs.existsSync(app.getPath('userData')+osfs+'backups') === false) {
fs.mkdirSync( app.getPath('userData')+osfs+'backups')
}
createBackup(true)
$('.web_only').remove()
if (__dirname.includes('C:\\xampp\\htdocs\\blockbench')) {
Blockbench.addFlag('dev')
}
//Load Model
var model_loaded = false
if (electron.process.argv.length >= 2) {
var extension = pathToExtension(electron.process.argv[1])
if (['json', 'bbmodel', 'jem', 'jpm'].includes(extension)) {
Blockbench.read([electron.process.argv[1]], {}, (files) => {
loadModel(files[0].content, files[0].path || files[0].path)
addRecentProject({name: pathToName(files[0].path, 'mobs_id'), path: files[0].path})
model_loaded = true
})
}
}
if (!model_loaded && localStorage.getItem('backup_model') && !currentwindow.webContents.second_instance) {
var backup_model = localStorage.getItem('backup_model')
localStorage.removeItem('backup_model')
Blockbench.showMessageBox({
translateKey: 'recover_backup',
icon: 'fa-archive',
buttons: [tl('dialog.continue'), tl('dialog.cancel')],
confirm: 0,
cancel: 1
}, function(result) {
if (result === 0) {
loadModel(backup_model, 'backup.bbmodel')
}
})
}
});
(function() {
console.log('Electron '+process.versions.electron+', Node '+process.versions.node)
getLatestVersion(true)
})()
//Called on start to show message
function getLatestVersion(init) {
if (process.platform == 'linux') return;
$.getJSON('https://raw.githubusercontent.com/JannisX11/blockbench/master/package.json', (data) => {
if (data.version) {
latest_version = data.version
if (compareVersions(latest_version, appVersion) && init === true && !open_dialog) {
Blockbench.showMessageBox({
translateKey: 'update_notification',
message: tl('message.update_notification.message', [latest_version]),
icon: 'update',
buttons: [tl('message.update_notification.install'), tl('message.update_notification.later')],
confirm: 0, cancel: 1
}, (result) => {
if (result === 0) {
checkForUpdates(true)
}
})
} else if (init === false) {
checkForUpdates()
}
}
}).fail(function() {
latest_version = false
})
}
//Recent Projects
function updateRecentProjects() {
if (recent_projects === undefined) {
//Setup
recent_projects = []
var raw = localStorage.getItem('recent_projects')
if (raw) {
recent_projects = JSON.parse(raw)
}
}
//Set Local Storage
localStorage.setItem('recent_projects', JSON.stringify(recent_projects))
}
function addRecentProject(data) {
var i = recent_projects.length-1
while (i >= 0) {
var p = recent_projects[i]
if (p.path === data.path) {
recent_projects.splice(i, 1)
}
i--;
}
var icon_id = pathToExtension(data.path) === 'bbmodel' ? 1 : 0;
if (data.name.substr(0,4) === 'mobs') {
icon_id = 2;
}
recent_projects.push({name: data.name, path: data.path, icon_id})
if (recent_projects.length > 12) {
recent_projects.shift()
}
updateRecentProjects()
}
//Updates
function checkForUpdates(instant) {
showDialog('updater')
setProgressBar('update_bar', 0, 1)
var data;
if (latest_version === false) {
data = `
cloud_off${tl('dialog.update.no_connection')}
`;
} else if (latest_version !== appVersion) {
data =
`${tl('dialog.update.latest')}: ${latest_version}
${tl('dialog.update.installed')}: ${appVersion}
`;
if (instant) {
setTimeout(function() {
installUpdate()
}, 60)
}
} else {
data =
`
check
${tl('dialog.update.up_to_date')}
`;
}
$('#updater_content').html(data)
}
function refreshUpdateDialog() {
currentwindow.webContents.session.clearCache(function() {
data = 'refresh'+tl('dialog.update.connecting')+'
'
$('#updater_content').html(data)
getLatestVersion(false)
})
}
function installUpdate() {
console.log('Starting Update')
var received_bytes = 0;
var total_bytes = 0;
$('.uc_btn').css('visibility', 'hidden')
var asar_path = __dirname;
if (asar_path.includes('.asar') === false) {
asar_path = asar_path + osfs+'resources'+osfs+'app.asar';
}
var file = originalFs.createWriteStream(asar_path);
var request = https.get("https://blockbench.net/api/app.asar", function(response) {
response.pipe(file);
total_bytes = parseInt(response.headers['content-length']);
response.on('end', updateInstallationEnd)
response.on('data', function(chunk) {
received_bytes += chunk.length;
setProgressBar('update_bar', received_bytes / total_bytes, 1);
})
});
}
function updateInstallationEnd() {
hideDialog();
Blockbench.addFlag('update_restart');
var exe_path = __dirname.split(osfs);
exe_path.splice(-2);
exe_path = exe_path.join(osfs)+osfs+'blockbench.exe';
if (showSaveDialog(true)) {
exec(exe_path);
} else {
Blockbench.showQuickMessage('message.restart_to_update');
}
}
//Image Editor
function changeImageEditor(texture) {
var dialog = new Dialog({
title: tl('message.image_editor.title'),
id: 'image_editor',
lines: [''],
draggable: true,
onConfirm: function() {
var id = $('.dialog#image_editor option:selected').attr('id')
var path;
switch (id) {
case 'ps': path = 'C:\\Program Files\\Adobe\\Adobe Photoshop CC 2018\\Photoshop.exe'; break;
case 'gimp':path = 'C:\\Program Files\\GIMP 2\\bin\\gimp-2.10.exe'; break;
case 'pdn': path = 'C:\\Program Files\\paint.net\\PaintDotNet.exe'; break;
}
if (id === 'other') {
selectImageEditorFile(texture)
} else if (path) {
settings.image_editor.value = path
if (texture) {
texture.openEditor()
}
}
dialog.hide()
}
}).show()
}
function selectImageEditorFile(texture) {
electron.dialog.showOpenDialog(currentwindow, {
title: tl('message.image_editor.exe'),
filters: [{name: 'Executable Program', extensions: ['exe', 'app']}]
}, function(filePaths) {
if (filePaths) {
settings.image_editor.value = filePaths[0]
if (texture) {
texture.openEditor()
}
}
})
}
//Default Pack
function openDefaultTexturePath() {
var answer = electron.dialog.showMessageBox(currentwindow, {
type: 'info',
buttons: (
settings.default_path.value ? [tl('dialog.cancel'), tl('message.default_textures.continue'), tl('message.default_textures.remove')]
: [tl('dialog.cancel'), tl('message.default_textures.continue')]
),
noLink: true,
title: tl('message.default_textures.title'),
message: tl('message.default_textures.message'),
detail: tl('message.default_textures.detail'),
})
if (answer === 0) {
return;
} else if (answer === 1) {
electron.dialog.showOpenDialog(currentwindow, {
title: tl('message.default_textures.select'),
properties: ['openDirectory'],
}, function(filePaths) {
if (filePaths) {
settings.default_path.value = filePaths[0]
}
})
} else {
settings.default_path.value = false
}
}
function findEntityTexture(mob, return_path) {
var textures = {
'llamaspit': 'llama/spit',
'llama': 'llama/llama_creamy',
'dragon': 'dragon/dragon',
'ghast': 'ghast/ghast',
'slime': 'slime/slime',
'slime.armor': 'slime/slime',
'lavaslime': 'slime/magmacube',
'shulker': 'shulker/shulker_undyed',
'rabbit': 'rabbit/brown',
'horse': 'horse/horse_brown',
'horse.v2': 'horse2/horse_brown',
'humanoid': 'steve',
'creeper': 'creeper/creeper',
'enderman': 'enderman/enderman',
'zombie': 'zombie/zombie',
'zombie.husk': 'zombie/husk',
'zombie.drowned': 'zombie/drowned',
'pigzombie': 'pig/pigzombie',
'pigzombie.baby': 'pig/pigzombie',
'skeleton': 'skeleton/skeleton',
'skeleton.wither': 'skeleton/wither_skeleton',
'skeleton.stray': 'skeleton/stray',
'spider': 'spider/spider',
'cow': 'cow/cow',
'mooshroom': 'cow/mooshroom',
'sheep.sheared': 'sheep/sheep',
'sheep': 'sheep/sheep',
'pig': 'pig/pig',
'irongolem': 'iron_golem',
'snowgolem': 'snow_golem',
'zombie.villager': 'zombie_villager/zombie_farmer',
'evoker': 'illager/evoker',
'vex': 'vex/vex',
'wolf': 'wolf/wolf',
'ocelot': 'cat/ocelot',
'cat': 'cat/siamese',
'turtle': 'sea_turtle',
'villager': 'villager/farmer',
'villager.witch': 'witch',
'witherBoss': 'wither_boss/wither',
'parrot': 'parrot/parrot_red_blue',
'bed': 'bed/white',
'player_head': 'steve',
'mob_head': 'skeleton/skeleton',
'dragon_head': 'dragon/dragon',
'boat': 'boat/boat_oak',
'cod': 'fish/fish',
'pufferfish.small': 'fish/pufferfish',
'pufferfish.mid': 'fish/pufferfish',
'pufferfish.large': 'fish/pufferfish',
'salmon': 'fish/salmon',
'tropicalfish_a': 'fish/tropical_a',
'tropicalfish_b': 'fish/tropical_b',
'panda': 'panda/panda',
}
mob = mob.split(':')[0].replace(/^geometry\./, '')
var path = textures[mob]
if (!path) {
path = mob
}
if (path) {
var texture_path = Prop.file_path.split(osfs)
var index = texture_path.lastIndexOf('models') - texture_path.length
texture_path.splice(index)
texture_path = [...texture_path, 'textures', 'entity', ...path.split('/')].join(osfs)
if (return_path === true) {
return texture_path+'.png';
} else if (return_path === 'raw') {
return ['entity', ...path.split('/')].join(osfs)
} else {
function tryItWith(extension) {
if (fs.existsSync(texture_path+'.'+extension)) {
var texture = new Texture({keep_size: true}).fromPath(texture_path+'.'+extension).add()
}
}
if (!tryItWith('png') && !tryItWith('tga')) {
if (settings.default_path && settings.default_path.value) {
texture_path = settings.default_path.value + osfs + 'entity' + osfs + path.split('/').join(osfs)
tryItWith('png') || tryItWith('tga')
}
}
}
}
}
function findBedrockAnimation() {
var animation_path = Prop.file_path.split(osfs)
var index = animation_path.lastIndexOf('models')
animation_path.splice(index)
animation_path = [...animation_path, 'animations', pathToName(Prop.file_path)+'.json'].join(osfs)
if (fs.existsSync(animation_path)) {
Blockbench.read([animation_path], {}, (files) => {
Animator.loadFile(files[0])
})
}
}
//Writers
function saveFile(props) {
if (Prop.file_path) {
var extension = pathToExtension(Prop.file_path)
if (Blockbench.entity_mode === false) {
Blockbench.writeFile(Prop.file_path, {
project_file: true,
content: buildBlockModel()
})
} else {
writeFileEntity(buildEntityModel({raw: true}), Prop.file_path)
}
} else {
if (Blockbench.entity_mode === false) {
BarItems.export_blockmodel.trigger()
} else {
BarItems.export_entity.trigger()
}
}
if (Blockbench.entity_mode && Prop.animation_path) {
Blockbench.writeFile(Prop.animation_path, {
content: autoStringify(Animator.buildFile())
})
}
}
function writeFileEntity(content, filepath) {
Prop.file_path = filepath
var model_name = 'geometry.' + (Project.parent.replace(/^geometry\./, '')||'unknown')
var data;
try {
data = fs.readFileSync(filepath, 'utf-8')
} catch (err) {}
var obj = {
format_version: '1.10.0'
}
if (data) {
try {
obj = JSON.parse(data.replace(/\/\*[^(\*\/)]*\*\/|\/\/.*/g, ''))
} catch (err) {
err = err+''
var answer = electron.dialog.showMessageBox(currentwindow, {
type: 'warning',
buttons: [
tl('message.bedrock_overwrite_error.backup_overwrite'),
tl('message.bedrock_overwrite_error.overwrite'),
tl('dialog.cancel')
],
title: 'Blockbench',
message: tl('message.bedrock_overwrite_error.message'),
detail: err,
noLink: false
})
if (answer === 0) {
var backup_file_name = pathToName(filepath, true) + ' backup ' + new Date().toLocaleString().split(':').join('_')
backup_file_name = filepath.replace(pathToName(filepath, false), backup_file_name)
fs.writeFile(backup_file_name, data, function (err2) {
if (err2) {
console.log('Error saving backup model: ', err2)
}
})
}
if (answer === 2) {
return;
}
}
if (typeof obj === 'object') {
for (var key in obj) {
if (obj.hasOwnProperty(key) &&
obj[key].bones &&
typeof obj[key].bones === 'object' &&
obj[key].bones.constructor.name === 'Array'
) {
obj[key].bones.forEach(function(bone) {
if (typeof bone.cubes === 'object' &&
bone.cubes.constructor.name === 'Array'
) {
bone.cubes.forEach(function(c, ci) {
bone.cubes[ci] = new oneLiner(c)
})
}
})
}
}
}
}
obj[model_name] = content
content = autoStringify(obj)
try {
fs.writeFileSync(filepath, content)
} catch (err) {
console.log('Error Saving Entity Model: '+err)
return;
}
Blockbench.showQuickMessage('message.save_entity')
Prop.project_saved = true;
setProjectTitle(pathToName(filepath, false))
addRecentProject({name: pathToName(filepath, 'mobs_id'), path: filepath})
if (Blockbench.hasFlag('close_after_saving')) {
closeBlockbenchWindow()
}
}
function writeFileObj(content, filepath) {
if (filepath === undefined) {
return;
}
var content = buildOBJModel(pathToName(filepath, false))
//OBJECT
fs.writeFile(filepath, content.obj, function (err) {})
//MATERIAL
fs.writeFile(filepath.split('.obj').join('.mtl'), content.mtl, function (err) {})
//IMAGES
if (settings.obj_textures.value === true) {
for (var key in content.images) {
var texture = content.images[key]
if (texture && texture.path) {
if (texture.mode === 'link') {
var native_image_instance = nativeImage.createFromPath(texture.path)
} else {
var native_image_instance = nativeImage.createFromDataURL(texture.source)
}
var image = native_image_instance.toPNG()
var image_path = filepath.split(osfs)
image_path.pop()
image_path = image_path.join(osfs) + osfs + texture.name
if (image_path.substr(-4) !== '.png') {
image_path = image_path + '.png'
}
fs.writeFile(image_path, image, function (err) {})
}
}
}
Blockbench.showQuickMessage('message.save_obj')
}
//Open
function readFile(filepath, makeNew) {
fs.readFile(filepath, 'utf-8', function (err, data) {
if (err) {
console.log(err)
Blockbench.showMessageBox({
translateKey: 'file_not_found',
icon: 'error_outline'
})
return;
}
addRecentProject({name: pathToName(filepath, 'mobs_id'), path: filepath})
loadModel(data, filepath, !makeNew)
})
}
//Backup
function createBackup(init) {
setTimeout(createBackup, limitNumber(parseFloat(settings.backup_interval.value), 1, 10e8)*60000)
var duration = parseInt(settings.backup_retain.value)+1
var folder_path = app.getPath('userData')+osfs+'backups'
var d = new Date()
var days = d.getDate() + (d.getMonth()+1)*30.44 + (d.getYear()-100)*365.25
if (init) {
//Clear old backups
fs.readdir(folder_path, (err, files) => {
if (!err) {
files.forEach((name, i) => {
var date = name.split('_')[1]
if (date) {
var nums = date.split('.')
nums.forEach((n, ni) => {
nums[ni] = parseInt(n)
})
var b_days = nums[0] + nums[1]*30.44 + nums[2]*365.25
if (!isNaN(b_days) && days - b_days > duration) {
try {
fs.unlinkSync(folder_path +osfs+ name)
} catch (err) {console.log(err)}
}
}
})
}
})
}
if (init || elements.length === 0) return;
var model = buildBBModel()
localStorage.setItem('backup_model', model)
var file_name = 'backup_'+d.getDate()+'.'+(d.getMonth()+1)+'.'+(d.getYear()-100)+'_'+d.getHours()+'.'+d.getMinutes()
var file_path = folder_path+osfs+file_name+'.bbmodel'
fs.writeFile(file_path, model, function (err) {
if (err) {
console.log('Error creating backup: '+err)
}
})
}
//Close
window.onbeforeunload = function() {
if (preventClosing === true) {
setTimeout(function() {
showSaveDialog(true)
}, 2)
return true;
}
}
function showSaveDialog(close) {
if (Blockbench.hasFlag('allow_reload')) {
close = false
}
var unsaved_textures = 0;
textures.forEach(function(t) {
if (!t.saved) {
unsaved_textures++;
}
})
if ((Prop.project_saved === false && elements.length > 0) || unsaved_textures) {
var answer = electron.dialog.showMessageBox(currentwindow, {
type: 'question',
buttons: [tl('dialog.save'), tl('dialog.discard'), tl('dialog.cancel')],
title: 'Blockbench',
message: tl('message.close_warning.message'),
noLink: true
})
if (answer === 0) {
if (close === true) {
Blockbench.addFlag('close_after_saving')
}
BarItems.save.trigger()
return true;
} else if (answer === 2) {
return false;
} else {
if (close === true) {
closeBlockbenchWindow()
}
return true;
}
} else {
if (close === true) {
closeBlockbenchWindow()
}
return true;
}
}
function closeBlockbenchWindow() {
preventClosing = false;
Blockbench.dispatchEvent('before_closing')
localStorage.removeItem('backup_model')
if (!Blockbench.hasFlag('update_restart')) {
return currentwindow.close();
}
setTimeout(function() {
currentwindow.close();
}, 240)
}