js/Engines.js

  1. /*
  2. HANDLER LIST
  3. ------------
  4. onLoadTrans : when opening trans or creating new project
  5. onUnloadTrans : when closing trans
  6. injectHandler : when injecting translation
  7. return true to halt default process;
  8. exportHandler : when exporting translation
  9. return true to halt default process;
  10. onOpenInjectDialog
  11. */
  12. /**
  13. * Game engine class
  14. * @class
  15. * @param {String} name - Name of the engine
  16. * @param {Object} options - Additional option
  17. */
  18. var Engine = function(name, options) {
  19. /* engine */
  20. options = options||{};
  21. this.name = name;
  22. for (let i in options) {
  23. this[i] = options[i];
  24. }
  25. common.addEventHandler(this);
  26. this.init();
  27. }
  28. Engine.prototype.addProperty = function(name, obj) {
  29. this[name] = obj;
  30. }
  31. Engine.prototype.getProperty = function(name) {
  32. return this[name];
  33. }
  34. Engine.prototype.addHandler = function(name, handler) {
  35. if (typeof handler !== 'function') console.warn("handler must be a function");
  36. this[name] = handler;
  37. }
  38. Engine.prototype.hasHandler = function(handlerName) {
  39. if (typeof this[handlerName] == "function") return true;
  40. return false;
  41. }
  42. Engine.prototype.getHandler = function(handlerName) {
  43. if (!this.hasHandler(handlerName)) return function(){};
  44. return this[handlerName];
  45. }
  46. Engine.prototype.triggerHandler = async function(handlerName, thisScope, args) {
  47. if (!this.hasHandler(handlerName)) return;
  48. if (common.isArguments(args)) {
  49. args = common.argumentsToArray(args);
  50. }
  51. if (!Array.isArray(args)) return console.warn("Expected array for args "+typeof args+ " given!");
  52. this.trigger(handlerName, args);
  53. return await this.getHandler(handlerName).apply(thisScope, args);
  54. }
  55. Engine.prototype.appendList = function() {
  56. var $container = $('#gameEngines')
  57. $container.append($('<option value="'+common.htmlEntities(this.name)+'" />'))
  58. }
  59. Engine.prototype.init = function() {
  60. this.appendList();
  61. }
  62. /**
  63. * Collection of engine
  64. * @class
  65. * @param {} name
  66. * @param {} options
  67. */
  68. var Engines = function(name, options) {
  69. /* collection of engine*/
  70. var that = this;
  71. this.add = function(name, options) {
  72. that[name] = new Engine(name, options);
  73. that.list = that.list || [];
  74. that.list.push(that[name]);
  75. that.length = that.list.length;
  76. }
  77. if (typeof name !== 'undefined') {
  78. this.add(name, options);
  79. }
  80. }
  81. Engines.group = {
  82. 'unityTrans' : ["rmxp", "rmvx", "rmvxace"]
  83. }
  84. Engines.characteristics = [
  85. async function(gamepath) {
  86. //rmmv
  87. var dir = nwPath.dirname(gamepath);
  88. if (common.isDir(gamepath)) dir = gamepath;
  89. if (common.isFile(nwPath.join(dir, "js", "rpg_core.js"))) return "rmmv";
  90. if (common.isFile(nwPath.join(dir, "www", "js", "rpg_core.js"))) return "rmmv";
  91. return false;
  92. },
  93. async function(gamepath) {
  94. //rmmz
  95. var dir = nwPath.dirname(gamepath);
  96. if (common.isDir(gamepath)) dir = gamepath;
  97. if (common.isFile(nwPath.join(dir, "js", "rmmz_core.js"))) return "rmmz";
  98. return false;
  99. },
  100. async function(gamepath) {
  101. //rmmz
  102. //var dir = nwPath.dirname(gamepath);
  103. var isEnigma = await php.isEnigma(gamepath);
  104. if (isEnigma.result) return "enigma";
  105. return false;
  106. },
  107. async function(gamepath) {
  108. //vxAce
  109. //Library=System\RGSS3*
  110. var ini = ini || require('ini');
  111. var iniPath = nwPath.join(nwPath.dirname(gamepath), "Game.ini");
  112. if (!common.isFile(iniPath)) return false;
  113. var iniFile = await window.afs.readFile(iniPath)
  114. var iniContent = ini.parse(iniFile.toString());
  115. var libString = iniContent['Game']['Library'] || iniContent['Game']['library'];
  116. if (libString.includes('RGSS3')) return "rmvxace";
  117. if (libString.includes('RGSS2')) return "rmvx";
  118. if (libString.includes('RGSS1')) return "rmxp";
  119. return false
  120. }
  121. ]
  122. Engines.detect = async function(gamepath) {
  123. for (var i in Engines.characteristics) {
  124. var engines = await Engines.characteristics[i].call(this, gamepath);
  125. if (engines) return engines;
  126. }
  127. return "";
  128. }
  129. Engines.prototype.addHandler = function(engines, handlerName, handler) {
  130. // apply handler to one or more engine
  131. // create the engine if the engine is not exist;
  132. if (typeof handlerName !== 'string') console.warn("handler name must be a string")
  133. if (!handlerName) console.warn("handler name must be specified")
  134. if (typeof handler !== 'function') console.warn("handler must be function")
  135. if (Array.isArray(engines) == false) engines = [engines]
  136. for (var i in engines) {
  137. var thisEngine = engines[i]
  138. if (Boolean(thisEngine) == false) continue;
  139. if (typeof thisEngine !== 'string') continue;
  140. if (Boolean(this[thisEngine]) == false) this.add(thisEngine);
  141. this[thisEngine].addHandler(handlerName, handler);
  142. }
  143. }
  144. Engines.prototype.hasHandler = function(handler, engineName) {
  145. engineName = engineName || trans.project.gameEngine;
  146. if (typeof this[engineName] == 'undefined') return false;
  147. if (typeof this[engineName][handler] == 'function') return true;
  148. return false;
  149. }
  150. Engines.prototype.current = function() {
  151. var result = new Engine("");
  152. if (typeof trans.project == 'undefined') return result;
  153. if (typeof trans.project.gameEngine == 'undefined') return result;
  154. if (this[trans.project.gameEngine]) return this[trans.project.gameEngine];
  155. // looking from the group
  156. for (var engineName in Engines.group) {
  157. if (Engines.group[engineName].includes(trans.project.gameEngine)) return this[engineName];
  158. }
  159. return result
  160. }
  161. Engines.prototype.handler = function(handler, engineName) {
  162. engineName = engineName || trans.project.gameEngine;
  163. if (!this[engineName]) return function() {}
  164. if (typeof this[engineName][handler] !== 'function') return function() {};
  165. return this[engineName][handler];
  166. }
  167. Engines.prototype.hasEngine = function(engineName) {
  168. if (!this[engineName]) return false;
  169. if (this[engineName].constructor.name !== "Engine") return false;
  170. return true;
  171. }
  172. Engines.prototype.getEngine = function(engineName) {
  173. if (this.hasEngine(engineName)) return this[engineName];
  174. return new Engine("NullEngine");
  175. }
  176. var engines = new Engines();
  177. engines.add("rmmv", {});
  178. var ApplyTranslation = function() {
  179. this.__supportList = ["rmxp", "rmvx", "rmvxace", "rmmv", "wolf"]
  180. }
  181. ApplyTranslation.prototype.getSupported = function() {
  182. return this.__supportList;
  183. }
  184. ApplyTranslation.prototype.isSupported = function(thisEngine) {
  185. thisEngine = thisEngine || trans.gameEngine;
  186. return this.__supportList.includes(thisEngine);
  187. }
  188. ApplyTranslation.prototype.start = async function(targetDir, sourceMaterial, options) {
  189. var fs = fs||require("fs");
  190. options = options||{};
  191. options.options = options.options||{};
  192. options.mode = options.mode||"dir";
  193. options.onDone = options.onDone||function() {};
  194. options.dataPath = options.dataPath || ""; // location of data path (data folder). Default is using cache
  195. options.transPath = options.transPath || ""; // location of .trans path to process. Default is using autosave on cache folder
  196. options.options.filterTag = options.options.filterTag||options.filterTag||{};
  197. options.options.filterTagMode = options.options.filterTagMode||options.filterTagMode||""; // whitelist or blacklist
  198. console.log("Applying translation!", arguments)
  199. //return console.log("HALTED", arguments);
  200. //check if target dir is writable
  201. var checkPath = php.checkPathSync(targetDir);
  202. var isHandled = false; // handle check;
  203. if (checkPath.accessible == false) return alert(targetDir+" is not accessible!");
  204. if (checkPath.writable == false) return alert(targetDir+" is not writable!");
  205. var that = this;
  206. var mainArgs = arguments;
  207. if (engines.hasHandler('injectHandler')) {
  208. console.log("This engine has inject handler")
  209. var blocking = await engines.handler('injectHandler').apply(this, arguments);
  210. if (blocking) return;
  211. }
  212. // legacy parser start
  213. var enginePattern = {};
  214. enginePattern['rpgMaker'] = ["rmxp", "rmvx", "rmvxace"];
  215. enginePattern['rmmv'] = ["rmmv", "rmmz"];
  216. enginePattern['wolf'] = ["wolf"];
  217. if (enginePattern['rpgMaker'].includes(trans.gameEngine)) {
  218. let transPath = targetDir+"\\translation.trans";
  219. let child_process = require('child_process');
  220. // remove existing data directory
  221. if (trans.gameEngine == 'rmmv') {
  222. child_process.spawnSync("RMDIR", [targetDir+"\\www\\Data", "/S", "/Q"]);
  223. } else {
  224. child_process.spawnSync("RMDIR", [targetDir+"\\Data", "/S", "/Q"]);
  225. }
  226. //ui.loadingClearButton();
  227. ui.showLoading();
  228. ui.loadingProgress("Loading", "Copying data...", {consoleOnly:true, mode:'consoleOutput'});
  229. //ncp(sourceMaterial, targetDir, function (err) {
  230. // if (err) return alert(err);
  231. php.copyTree(sourceMaterial, targetDir, {
  232. onData: function(data) {
  233. ui.loadingProgress("Loading", data, {consoleOnly:true, mode:'consoleOutput'});
  234. },
  235. onDone: function() {
  236. var autofillFiles = [];
  237. var checkbox = $(".fileList .data-selector .fileCheckbox:checked");
  238. for (var i=0; i<checkbox.length; i++) {
  239. autofillFiles.push(checkbox.eq(i).attr("value"));
  240. }
  241. options.files = options.files||autofillFiles||[];
  242. var hasError = false;
  243. trans.save(transPath,
  244. {
  245. onSuccess:function() {
  246. //ui.showLoading();
  247. php.spawn("apply.php", {
  248. args:{
  249. gameFolder:trans.gameFolder,
  250. gameTitle:trans.gameTitle,
  251. projectId:trans.projectId,
  252. gameEngine:trans.gameEngine,
  253. files:options.files,
  254. exportMode:options.mode,
  255. options:options.options,
  256. rpgTransFormat:trans.config.rpgTransFormat,
  257. transPath:transPath,
  258. targetPath:targetDir
  259. },
  260. onData:function(buffer) {
  261. ui.loadingProgress("Loading", buffer, {consoleOnly:true, mode:'consoleOutput'});
  262. },
  263. onError:function(buffer) {
  264. ui.loadingProgress("Loading", buffer, {consoleOnly:true, mode:'consoleOutput', classStr:'stderr'});
  265. console.warn("stderr", buffer.toString());
  266. hasError = true;
  267. },
  268. onDone: async function(data) {
  269. //console.log(data);
  270. console.log("done")
  271. //ui.hideLoading(true);
  272. ui.loadingEnd("Finished", "All process finished!", {consoleOnly:false, mode:'consoleOutput', error:hasError});
  273. ui.LoadingAddButton("Open folder", function() {
  274. nw.Shell.showItemInFolder(transPath);
  275. },{
  276. class: "icon-folder-open"
  277. });
  278. ui.LoadingAddButton("Play!", function() {
  279. console.log("Opening game");
  280. nw.Shell.openItem(targetDir+"\\Game.exe");
  281. },{
  282. class: "icon-play"
  283. });
  284. if (engines.hasHandler('afterInjectHandler')) {
  285. var blocking = await engines.handler('afterInjectHandler').apply(that, mainArgs);
  286. if (blocking) return;
  287. }
  288. ui.showCloseButton();
  289. if (hasError) {
  290. 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?");
  291. if (conf) nw.Shell.openExternal(nw.App.manifest.localConfig.defaultDocsUrl+trans.gameEngine);
  292. }
  293. options.onDone.call(trans, data);
  294. }
  295. })
  296. }
  297. });
  298. }});
  299. isHandled = true;
  300. } else if (enginePattern['rmmv'].includes(trans.gameEngine)) {
  301. let transPath = targetDir+"\\translation.trans";
  302. let child_process = require('child_process');
  303. // remove existing data directory
  304. child_process.spawnSync("RMDIR", [targetDir+"\\www\\Data", "/S", "/Q"]);
  305. //ui.loadingClearButton();
  306. ui.showLoading();
  307. ui.loadingProgress("Loading", "Copying data...", {consoleOnly:true, mode:'consoleOutput'});
  308. php.extractEnigma(sourceMaterial, targetDir, {
  309. onData: function(data) {
  310. ui.loadingProgress("Loading", data, {consoleOnly:true, mode:'consoleOutput'});
  311. },
  312. onDone: function() {
  313. var autofillFiles = [];
  314. var checkbox = $(".fileList .data-selector .fileCheckbox:checked");
  315. for (var i=0; i<checkbox.length; i++) {
  316. autofillFiles.push(checkbox.eq(i).attr("value"));
  317. }
  318. options.files = options.files||autofillFiles||[];
  319. var hasError = false;
  320. trans.save(transPath,
  321. {
  322. onSuccess:function() {
  323. //ui.showLoading();
  324. php.spawn("apply.php", {
  325. args:{
  326. gameFolder:trans.gameFolder,
  327. gameTitle:trans.gameTitle,
  328. projectId:trans.projectId,
  329. gameEngine:trans.gameEngine,
  330. files:options.files,
  331. exportMode:options.mode,
  332. options:options.options,
  333. rpgTransFormat:trans.config.rpgTransFormat,
  334. transPath:transPath,
  335. targetPath:targetDir
  336. },
  337. onData:function(buffer) {
  338. ui.loadingProgress("Loading", buffer, {consoleOnly:true, mode:'consoleOutput'});
  339. },
  340. onError:function(buffer) {
  341. ui.loadingProgress("Loading", buffer, {consoleOnly:true, mode:'consoleOutput', classStr:'stderr'});
  342. hasError = true;
  343. },
  344. onDone: async function(data) {
  345. //console.log(data);
  346. console.log("done")
  347. //ui.hideLoading(true);
  348. ui.loadingEnd("Finished", "All process finished!", {consoleOnly:false, mode:'consoleOutput', error:hasError});
  349. ui.LoadingAddButton("Open folder", function() {
  350. nw.Shell.showItemInFolder(transPath);
  351. },{
  352. class: "icon-folder-open"
  353. });
  354. ui.LoadingAddButton("Play!", function() {
  355. console.log("Opening game");
  356. nw.Shell.openItem(targetDir+"\\Game.exe");
  357. },{
  358. class: "icon-play"
  359. });
  360. if (engines.hasHandler('afterInjectHandler')) {
  361. var blocking = await engines.handler('afterInjectHandler').apply(that, mainArgs);
  362. if (blocking) return;
  363. }
  364. ui.showCloseButton();
  365. if (hasError) {
  366. 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?");
  367. if (conf) nw.Shell.openExternal(nw.App.manifest.localConfig.defaultDocsUrl+trans.gameEngine);
  368. }
  369. options.onDone.call(trans, data);
  370. }
  371. })
  372. }
  373. });
  374. }});
  375. isHandled = true;
  376. } else if (enginePattern['wolf'].includes(trans.gameEngine)) {
  377. let transPath = targetDir+"\\translation.trans";
  378. let child_process = require('child_process');
  379. // remove existing data directory
  380. child_process.spawnSync("RMDIR", [targetDir+"\\Data", "/S", "/Q"]);
  381. //ui.loadingClearButton();
  382. ui.showLoading();
  383. ui.loadingProgress("Loading", "Copying data...", {consoleOnly:true, mode:'consoleOutput'});
  384. php.copyTree(sourceMaterial, targetDir, {
  385. onData: function(data) {
  386. ui.loadingProgress("Loading", data, {consoleOnly:true, mode:'consoleOutput'});
  387. },
  388. onDone: function() {
  389. var autofillFiles = [];
  390. var checkbox = $(".fileList .data-selector .fileCheckbox:checked");
  391. for (var i=0; i<checkbox.length; i++) {
  392. autofillFiles.push(checkbox.eq(i).attr("value"));
  393. }
  394. options.files = options.files||autofillFiles||[];
  395. var hasError = false;
  396. trans.save(transPath,
  397. {
  398. onSuccess:function() {
  399. //ui.showLoading();
  400. php.spawn("apply.php", {
  401. args:{
  402. gameFolder:trans.gameFolder,
  403. gameTitle:trans.gameTitle,
  404. projectId:trans.projectId,
  405. gameEngine:trans.gameEngine,
  406. files:options.files,
  407. exportMode:options.mode,
  408. options:options.options,
  409. rpgTransFormat:trans.config.rpgTransFormat,
  410. transPath:transPath,
  411. targetPath:targetDir
  412. },
  413. onData:function(buffer) {
  414. ui.loadingProgress("Loading", buffer, {consoleOnly:true, mode:'consoleOutput'});
  415. },
  416. onError:function(buffer) {
  417. ui.loadingProgress("Loading", buffer, {consoleOnly:true, mode:'consoleOutput', classStr:'stderr'});
  418. hasError = true;
  419. },
  420. onDone: async function(data) {
  421. //console.log(data);
  422. console.log("done")
  423. //ui.hideLoading(true);
  424. //console.log("has Error?", hasError);
  425. ui.loadingEnd("Finished", "All process finished!", {consoleOnly:false, mode:'consoleOutput', error:hasError});
  426. engines.wolf.onApplySuccess(targetDir);
  427. ui.LoadingAddButton("Open folder", function() {
  428. nw.Shell.showItemInFolder(transPath);
  429. },{
  430. class: "icon-folder-open"
  431. });
  432. ui.LoadingAddButton("Play!", function() {
  433. console.log("Opening game");
  434. nw.Shell.openItem(targetDir+"\\Game.exe");
  435. },{
  436. class: "icon-play"
  437. });
  438. if (engines.hasHandler('afterInjectHandler')) {
  439. var blocking = await engines.handler('afterInjectHandler').apply(that, mainArgs);
  440. if (blocking) return;
  441. }
  442. ui.showCloseButton();
  443. if (hasError) {
  444. 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?");
  445. if (conf) nw.Shell.openExternal(nw.App.manifest.localConfig.defaultDocsUrl+trans.gameEngine);
  446. }
  447. options.onDone.call(trans, data);
  448. }
  449. })
  450. }
  451. });
  452. }});
  453. isHandled = true;
  454. }
  455. if (trans.gameEngine == "spreadsheet") {
  456. alert("Sorry, but currently the injector handler for this engine not yet implemented. Please try to export instead. The result will be same.");
  457. } else {
  458. if (!isHandled) alert("No handler for engine : "+trans.gameEngine+"\nPlease get the latest version of Translator++");
  459. }
  460. }
  461. $(document).ready(function() {
  462. trans.applyTranslation = new ApplyTranslation();
  463. });