js/Engines.js

/*
	HANDLER LIST
	------------
	onLoadTrans : when opening trans or creating new project
	onUnloadTrans : when closing trans
	
	injectHandler : when injecting translation
		return true to halt default process;
	exportHandler : when exporting translation
		return true to halt default process;
		
	onOpenInjectDialog
*/

/**
 * Game engine class
 * @class
 * @param {String} name - Name of the engine
 * @param {Object} options - Additional option
 */
var Engine = function(name, options) {
	/* engine */
	options = options||{};
	this.name = name;
	for (let i in options) {
		this[i] = options[i];
	}
	
	common.addEventHandler(this);
	this.init();
}

Engine.prototype.addProperty = function(name, obj) {
	this[name] = obj;
}

Engine.prototype.getProperty = function(name) {
	return this[name];
} 

Engine.prototype.addHandler = function(name, handler) {
	if (typeof handler !== 'function') console.warn("handler must be a function");
	this[name] = handler;
}
Engine.prototype.hasHandler = function(handlerName) {
	if (typeof this[handlerName] == "function") return true;
	return false;
}

Engine.prototype.getHandler = function(handlerName) {
	if (!this.hasHandler(handlerName)) return function(){};
	return this[handlerName];
}

Engine.prototype.triggerHandler = async function(handlerName, thisScope, args) {
	if (!this.hasHandler(handlerName)) return;
	if (common.isArguments(args)) {
		args = common.argumentsToArray(args);
	}
	if (!Array.isArray(args)) return console.warn("Expected array for args "+typeof args+ " given!");
	this.trigger(handlerName, args);
	return await this.getHandler(handlerName).apply(thisScope, args);
}

Engine.prototype.appendList = function() {
	var $container = $('#gameEngines')
	$container.append($('<option value="'+common.htmlEntities(this.name)+'" />'))
}

Engine.prototype.init = function() {
	this.appendList();
}


/**
 * Collection of engine
 * @class
 * @param  {} name
 * @param  {} options
 */
var Engines = function(name, options) {
	/* collection of engine*/
	var that = this;
	
	this.add = function(name, options) {
		that[name] = new Engine(name, options);
		that.list = that.list || [];
		that.list.push(that[name]);
		that.length = that.list.length;
	}
	
	if (typeof name !== 'undefined') {
		this.add(name, options);
	}
}
Engines.group = {
	'unityTrans' : ["rmxp", "rmvx", "rmvxace"]
}
Engines.characteristics = [
	async function(gamepath) {
		//rmmv
		var dir = nwPath.dirname(gamepath);
		if (common.isDir(gamepath)) dir = gamepath;
		if (common.isFile(nwPath.join(dir, "js", "rpg_core.js"))) return "rmmv";
		if (common.isFile(nwPath.join(dir, "www", "js", "rpg_core.js"))) return "rmmv";
		return false;
	},
	async function(gamepath) {
		//rmmz
		var dir = nwPath.dirname(gamepath);
		if (common.isDir(gamepath)) dir = gamepath;
		if (common.isFile(nwPath.join(dir, "js", "rmmz_core.js"))) return "rmmz";
		return false;
	},
	async function(gamepath) {
		//rmmz
		//var dir = nwPath.dirname(gamepath);
		var isEnigma = await php.isEnigma(gamepath);
		if (isEnigma.result) return "enigma";
		return false;
	},
	async function(gamepath) {
		//vxAce
		//Library=System\RGSS3*
		var ini = ini || require('ini');
		var iniPath = nwPath.join(nwPath.dirname(gamepath), "Game.ini");
		if (!common.isFile(iniPath)) return false; 
		var iniFile = await window.afs.readFile(iniPath) 
		var iniContent = ini.parse(iniFile.toString());
		var libString = iniContent['Game']['Library'] || iniContent['Game']['library'];
		if (libString.includes('RGSS3')) return "rmvxace";
		if (libString.includes('RGSS2')) return "rmvx";
		if (libString.includes('RGSS1')) return "rmxp";
		
		return false
	}
]


Engines.detect = async function(gamepath) {
	for (var i in Engines.characteristics) {
		var engines = await Engines.characteristics[i].call(this, gamepath);
		if (engines) return engines;
	}
	return "";
}

Engines.prototype.addHandler = function(engines, handlerName, handler) {
	// apply handler to one or more engine
	// create the engine if the engine is not exist;
	if (typeof handlerName !== 'string') console.warn("handler name must be a string")
	if (!handlerName) console.warn("handler name must be specified")
	if (typeof handler !== 'function') console.warn("handler must be function")
	if (Array.isArray(engines) == false) engines = [engines]
	for (var i in engines) {
		var thisEngine = engines[i]
		if (Boolean(thisEngine) == false) continue;
		if (typeof thisEngine !== 'string') continue;
		
		if (Boolean(this[thisEngine]) == false) this.add(thisEngine);
		this[thisEngine].addHandler(handlerName, handler);
	}
}

Engines.prototype.hasHandler = function(handler, engineName) {
	engineName = engineName || trans.project.gameEngine;
	if (typeof this[engineName] == 'undefined') return false;
	if (typeof this[engineName][handler] == 'function') return true;
	
	return false;
}

Engines.prototype.current = function() {
	var result = new Engine("");
	if (typeof trans.project == 'undefined') return result;
	if (typeof trans.project.gameEngine == 'undefined') return result;
	
	if (this[trans.project.gameEngine]) return this[trans.project.gameEngine];
	
	// looking from the group
	for (var engineName in Engines.group) {
		if (Engines.group[engineName].includes(trans.project.gameEngine)) return this[engineName];
	}
	
	return result
}

Engines.prototype.handler = function(handler, engineName) {
	engineName = engineName || trans.project.gameEngine;
	if (!this[engineName]) return function() {}
	if (typeof this[engineName][handler] !== 'function') return function() {};
	
	return this[engineName][handler];
}

Engines.prototype.hasEngine = function(engineName) {
	if (!this[engineName]) return false;
	if (this[engineName].constructor.name !== "Engine") return false;
	return true;
}

Engines.prototype.getEngine = function(engineName) {
	if (this.hasEngine(engineName)) return this[engineName];
	return new Engine("NullEngine");
}


var engines = new Engines();

engines.add("rmmv", {});




var ApplyTranslation = function() {
	this.__supportList = ["rmxp", "rmvx", "rmvxace", "rmmv", "wolf"]
}
ApplyTranslation.prototype.getSupported = function() {
	return this.__supportList;
}	

ApplyTranslation.prototype.isSupported = function(thisEngine) {
	thisEngine = thisEngine || trans.gameEngine;
	return this.__supportList.includes(thisEngine);
}


ApplyTranslation.prototype.start = async function(targetDir, sourceMaterial, options) {
	var fs = fs||require("fs");
	options = options||{};
	options.options = options.options||{};
	options.mode = options.mode||"dir";
	options.onDone = options.onDone||function() {};
	options.dataPath = options.dataPath || ""; // location of data path (data folder). Default is using cache
	options.transPath = options.transPath || ""; // location of .trans path to process. Default is using autosave on cache folder
	options.options.filterTag = options.options.filterTag||options.filterTag||{};
	options.options.filterTagMode = options.options.filterTagMode||options.filterTagMode||""; // whitelist or blacklist

	console.log("Applying translation!", arguments)

	//return console.log("HALTED", arguments);
	//check if target dir is writable
	var checkPath = php.checkPathSync(targetDir);
	var isHandled = false; // handle check;
	
	if (checkPath.accessible == false) return alert(targetDir+" is not accessible!");
	if (checkPath.writable == false) return alert(targetDir+" is not writable!");
	
	var that = this;
	var mainArgs = arguments;
	if (engines.hasHandler('injectHandler')) {
		console.log("This engine has inject handler")
		var blocking = await engines.handler('injectHandler').apply(this, arguments);
		if (blocking) return;
	}
	
	// legacy parser start


	var enginePattern = {};
	enginePattern['rpgMaker'] = ["rmxp", "rmvx", "rmvxace"];
	enginePattern['rmmv'] = ["rmmv", "rmmz"];
	enginePattern['wolf'] = ["wolf"];
	
	

	if (enginePattern['rpgMaker'].includes(trans.gameEngine)) {
		let transPath = targetDir+"\\translation.trans";
		let child_process = require('child_process');
		// remove existing data directory
		
		if (trans.gameEngine == 'rmmv') {
			child_process.spawnSync("RMDIR", [targetDir+"\\www\\Data", "/S", "/Q"]);
		} else {
			child_process.spawnSync("RMDIR", [targetDir+"\\Data", "/S", "/Q"]);
		}
		
		//ui.loadingClearButton();
		ui.showLoading();
		ui.loadingProgress("Loading", "Copying data...", {consoleOnly:true, mode:'consoleOutput'});
		
		//ncp(sourceMaterial, targetDir, function (err) {
		//	if (err) return alert(err);
		
		php.copyTree(sourceMaterial, targetDir, {
		onData: function(data) {
			ui.loadingProgress("Loading", data, {consoleOnly:true, mode:'consoleOutput'});
		},
		onDone: function() {
		
			
			var autofillFiles = [];
			var checkbox = $(".fileList .data-selector .fileCheckbox:checked");
			for (var i=0; i<checkbox.length; i++) {
				autofillFiles.push(checkbox.eq(i).attr("value"));
			}
			options.files = options.files||autofillFiles||[];
			var hasError = false;
			trans.save(transPath,
			{
				onSuccess:function() {
					//ui.showLoading();
					php.spawn("apply.php", {
						args:{
							gameFolder:trans.gameFolder,
							gameTitle:trans.gameTitle,
							projectId:trans.projectId,
							gameEngine:trans.gameEngine,
							files:options.files,
							exportMode:options.mode,
							options:options.options,
							rpgTransFormat:trans.config.rpgTransFormat,
							transPath:transPath,
							targetPath:targetDir
						},
						onData:function(buffer) {
							ui.loadingProgress("Loading", buffer, {consoleOnly:true, mode:'consoleOutput'});
							
						},
						onError:function(buffer) {
							ui.loadingProgress("Loading", buffer, {consoleOnly:true, mode:'consoleOutput', classStr:'stderr'});
							console.warn("stderr", buffer.toString());
							hasError = true;
						},
						onDone: async function(data) {
							//console.log(data); 
							console.log("done")
							//ui.hideLoading(true);
							ui.loadingEnd("Finished", "All process finished!", {consoleOnly:false, mode:'consoleOutput', error:hasError});
							
							ui.LoadingAddButton("Open folder", function() {
								nw.Shell.showItemInFolder(transPath);
							},{
								class: "icon-folder-open"
							});
							ui.LoadingAddButton("Play!", function() {
								console.log("Opening game");
								nw.Shell.openItem(targetDir+"\\Game.exe");
							},{
								class: "icon-play"
							});

							if (engines.hasHandler('afterInjectHandler')) {
								var blocking = await engines.handler('afterInjectHandler').apply(that, mainArgs);
								if (blocking) return;
							}

							ui.showCloseButton();
							
							if (hasError) {
								var conf = confirm("An error has occured when applying your translation\r\nYour game might can not be played properly.\r\nDo you want to read the online documentation?");
								if (conf) nw.Shell.openExternal(nw.App.manifest.localConfig.defaultDocsUrl+trans.gameEngine);
							}
							options.onDone.call(trans, data);
						}
					})
					
				}
			});
					
			
		}});		
		isHandled = true;
	} else if (enginePattern['rmmv'].includes(trans.gameEngine)) {
		let transPath = targetDir+"\\translation.trans";
		let child_process = require('child_process');
		// remove existing data directory
		
		child_process.spawnSync("RMDIR", [targetDir+"\\www\\Data", "/S", "/Q"]);
		
		//ui.loadingClearButton();
		ui.showLoading();
		ui.loadingProgress("Loading", "Copying data...", {consoleOnly:true, mode:'consoleOutput'});
		
		
		php.extractEnigma(sourceMaterial, targetDir, {
			onData: function(data) {
				ui.loadingProgress("Loading", data, {consoleOnly:true, mode:'consoleOutput'});
			},
			onDone: function() {
			
			var autofillFiles = [];
			var checkbox = $(".fileList .data-selector .fileCheckbox:checked");
			for (var i=0; i<checkbox.length; i++) {
				autofillFiles.push(checkbox.eq(i).attr("value"));
			}
			options.files = options.files||autofillFiles||[];
			var hasError = false;
			trans.save(transPath,
			{
				onSuccess:function() {
					//ui.showLoading();
					php.spawn("apply.php", {
						args:{
							gameFolder:trans.gameFolder,
							gameTitle:trans.gameTitle,
							projectId:trans.projectId,
							gameEngine:trans.gameEngine,
							files:options.files,
							exportMode:options.mode,
							options:options.options,
							rpgTransFormat:trans.config.rpgTransFormat,
							transPath:transPath,
							targetPath:targetDir
						},
						onData:function(buffer) {
							ui.loadingProgress("Loading", buffer, {consoleOnly:true, mode:'consoleOutput'});
							
						},
						onError:function(buffer) {
							ui.loadingProgress("Loading", buffer, {consoleOnly:true, mode:'consoleOutput', classStr:'stderr'});
							hasError = true;
						},
						onDone: async function(data) {
							//console.log(data); 
							console.log("done")
							//ui.hideLoading(true);
							ui.loadingEnd("Finished", "All process finished!", {consoleOnly:false, mode:'consoleOutput', error:hasError});
							ui.LoadingAddButton("Open folder", function() {
								nw.Shell.showItemInFolder(transPath);
							},{
								class: "icon-folder-open"
							});
							ui.LoadingAddButton("Play!", function() {
								console.log("Opening game");
								nw.Shell.openItem(targetDir+"\\Game.exe");
							},{
								class: "icon-play"
							});

							if (engines.hasHandler('afterInjectHandler')) {
								var blocking = await engines.handler('afterInjectHandler').apply(that, mainArgs);
								if (blocking) return;
							}

							ui.showCloseButton();
							if (hasError) {
								var conf = confirm("An error has occured when applying your translation\r\nYour game might can not be played properly.\r\nDo you want to read the online documentation?");
								if (conf) nw.Shell.openExternal(nw.App.manifest.localConfig.defaultDocsUrl+trans.gameEngine);
							}
							
							options.onDone.call(trans, data);
						}
					})
					
				}
			});
					
			
		}});		
		
		isHandled = true;
	} else if (enginePattern['wolf'].includes(trans.gameEngine)) {
		let transPath = targetDir+"\\translation.trans";
		let child_process = require('child_process');
		// remove existing data directory
		
		child_process.spawnSync("RMDIR", [targetDir+"\\Data", "/S", "/Q"]);
		
		//ui.loadingClearButton();
		ui.showLoading();
		ui.loadingProgress("Loading", "Copying data...", {consoleOnly:true, mode:'consoleOutput'});
		
		
		php.copyTree(sourceMaterial, targetDir, {
			onData: function(data) {
				ui.loadingProgress("Loading", data, {consoleOnly:true, mode:'consoleOutput'});
			},
			onDone: function() {
			
			var autofillFiles = [];
			var checkbox = $(".fileList .data-selector .fileCheckbox:checked");
			for (var i=0; i<checkbox.length; i++) {
				autofillFiles.push(checkbox.eq(i).attr("value"));
			}
			options.files = options.files||autofillFiles||[];


			var hasError = false;
			trans.save(transPath,
			{
				onSuccess:function() {
					//ui.showLoading();
					php.spawn("apply.php", {
						args:{
							gameFolder:trans.gameFolder,
							gameTitle:trans.gameTitle,
							projectId:trans.projectId,
							gameEngine:trans.gameEngine,
							files:options.files,
							exportMode:options.mode,
							options:options.options,
							rpgTransFormat:trans.config.rpgTransFormat,
							transPath:transPath,
							targetPath:targetDir
						},
						onData:function(buffer) {
							ui.loadingProgress("Loading", buffer, {consoleOnly:true, mode:'consoleOutput'});
							
						},
						onError:function(buffer) {
							ui.loadingProgress("Loading", buffer, {consoleOnly:true, mode:'consoleOutput', classStr:'stderr'});
							hasError = true;
						},
						onDone: async function(data) {
							//console.log(data); 
							console.log("done")
							//ui.hideLoading(true);
							//console.log("has Error?", hasError);
							ui.loadingEnd("Finished", "All process finished!", {consoleOnly:false, mode:'consoleOutput', error:hasError});
							engines.wolf.onApplySuccess(targetDir);

							ui.LoadingAddButton("Open folder", function() {
								nw.Shell.showItemInFolder(transPath);
							},{
								class: "icon-folder-open"
							});
							ui.LoadingAddButton("Play!", function() {
								console.log("Opening game");
								nw.Shell.openItem(targetDir+"\\Game.exe");
							},{
								class: "icon-play"
							});

							if (engines.hasHandler('afterInjectHandler')) {
								var blocking = await engines.handler('afterInjectHandler').apply(that, mainArgs);
								if (blocking) return;
							}

							ui.showCloseButton();
							if (hasError) {
								var conf = confirm("An error has occured when applying your translation\r\nYour game might can not be played properly.\r\nDo you want to read the online documentation?");
								if (conf) nw.Shell.openExternal(nw.App.manifest.localConfig.defaultDocsUrl+trans.gameEngine);
							}
							options.onDone.call(trans, data);
						}
					})
					
				}
			});
					
			
		}});		
		
		
		isHandled = true;
		
	}		
	
	if (trans.gameEngine == "spreadsheet") {
		alert("Sorry, but currently the injector handler for this engine not yet implemented. Please try to export instead. The result will be same.");
	} else {
		if (!isHandled) alert("No handler for engine : "+trans.gameEngine+"\nPlease get the latest version of Translator++");
	}
}


$(document).ready(function() {
	trans.applyTranslation = new ApplyTranslation();

});