/** * Author: Peter Dragicevic [peter@petschko.org] * Authors-Website: http://petschko.org/ * Date: 30.03.2017 * Time: 23:05 */ // Globals var zip; /** * Try to Reads the Encryption-Code and insert it to the given input element * * @param {string} systemFileElId - File-Picker-Id for the System.json-File * @param {string} codeTextElId - Text-Input-Id for the Encryption-Key * @param {string} headerLenId - Text-Input for the Header-Length */ function getCode(systemFileElId, codeTextElId, headerLenId) { var systemFileEl = document.getElementById(systemFileElId); var codeTextEl = document.getElementById(codeTextElId); var headerLen = document.getElementById(headerLenId); if(systemFileEl.files.length < 1) { alert(lang.strings['error.detect.noFile'] .replace('{0}', 'System(.json)') .replace('{1}', '.rpgmvp / .png_')); return; } Decrypter.detectEncryptionCode(new RPGFile(systemFileEl.files[0], null), parseInt(headerLen.value), function(key) { if(key === null) { alert(lang.strings['error.detect.invalidJsonFile'].replace('{0}', '.json/.rpgmvp/.png_')); return; } if(typeof key === 'string' && key.length > 0) { codeTextEl.value = key; codeTextEl.className = removeValidationCssClasses(codeTextEl.className); codeTextEl.className = addCssClass(codeTextEl.className, 'valid'); alert(lang.strings['info.detect.keyFound'].replace('{0}', key)); } else window.prompt( lang.strings['error.detect.keyNotFound.1'] + '\n\n' + lang.strings['error.detect.keyNotFound.2'].replace('{0}', '.rpgmvp / .png_') + '\n' + '-------------------------------------\n' + lang.strings['error.detect.keyNotFound.3'] + '\n' + '1. ' + lang.strings['error.detect.keyNotFound.3.1'] + '\n' + '2. ' + lang.strings['error.detect.keyNotFound.3.2'].replace('{0}', './www/js/rpg_core(.js)') + '\n' + '3. ' + lang.strings['error.detect.keyNotFound.3.3'] + '\n' + '4. ' + lang.strings['error.detect.keyNotFound.3.4'] + '\n\n', 'https://pastebin.com/nCrzCpzD' ); }); } /** * Removes a CSS-Class * * @param {string} elementClasses - Element CSS-Classes * @param {string} removeClass - Class to remove * @returns {string} - New CSS-Classes */ function removeCssClass(elementClasses, removeClass) { var newClassNames = ''; var classes = elementClasses.split(' '); // Check if element is in array else exit if(classes.indexOf(removeClass) === -1) return elementClasses; for(var i = 0; i < classes.length; i++) { if(classes[i] !== removeClass) newClassNames += classes[i] + ' '; } return newClassNames.trim(); } /** * Adds a CSS-Class, it never adds an existing class twice * * @param {string} elementClasses - Element CSS-Classes * @param {string} addClass - Class to add * @returns {string} - New CSS-Classes */ function addCssClass(elementClasses, addClass) { var classes = elementClasses.split(' '); if(classes.indexOf(addClass) !== -1) return elementClasses; return (elementClasses + ' ' + addClass).trim(); } /** * Check if a the searched Class is within this class-string * * @param {string} elementClasses - Element CSS-Classes * @param {string} searchClass - Class to check * @returns {boolean} - true if the class exist else false */ function hasCssClass(elementClasses, searchClass) { var classes = elementClasses.split(' '); return (classes.indexOf(searchClass) !== -1); } /** * Get the Value of a Radio-Button-Group * * @param {string} radioButtonGroupName - Radio-Button-Group-Name * @param {string} fallback - Fallback-Value * @returns {string} - Current Value or Fallback-Value if group doesn't exists or nothing is selected */ function getRadioButtonValue(radioButtonGroupName, fallback) { var radioButtons = document.getElementsByName(radioButtonGroupName); var currentValue = fallback; for(var i = 0; i < radioButtons.length; i++) { if(radioButtons[i].checked) { currentValue = radioButtons[i].value; break; } } return currentValue; } /** * Removes all Validation-Classes * * @param {string} currentClasses - Current Element CSS-Classes * @returns {string} - CSS-Classes without validation ones */ function removeValidationCssClasses(currentClasses) { currentClasses = removeCssClass(currentClasses, 'invalid'); currentClasses = removeCssClass(currentClasses, 'manualChange'); currentClasses = removeCssClass(currentClasses, 'valid'); return currentClasses; } /** * Changes the validation class to manualChange * * @param {string} elementId - Element-Id to where to change */ function manualChange(elementId) { var element = document.getElementById(elementId); element.className = removeValidationCssClasses(element.className); element.className = addCssClass(element.className, 'manualChange'); } /** * Shows/Hides a Spoiler-Element * * @param {string} spoilerTextElId - Element-Id of the Spoiler-Text * @param {string} spoilerText - Text of the Spoiler-Text-Element (without show/hide) * @param {string} spoilerElId - Spoiler-Element-Id */ function spoiler(spoilerTextElId, spoilerText, spoilerElId) { var spoilerTextEl = document.getElementById(spoilerTextElId); var spoilerEl = document.getElementById(spoilerElId); var showHideSpan = spoilerTextEl.getElementsByTagName('span')[0]; if(hasCssClass(spoilerEl.className, 'hidden')) { spoilerEl.className = removeCssClass(spoilerEl.className, 'hidden'); showHideSpan.setAttribute(lang.translateableText, 'enDecrypt.button.headerValues.show.hide'); showHideSpan.innerHTML = html(lang.strings['enDecrypt.button.headerValues.show.hide']); } else { spoilerEl.className = addCssClass(spoilerEl.className, 'hidden'); showHideSpan.setAttribute(lang.translateableText, 'enDecrypt.button.headerValues.show.show'); showHideSpan.innerHTML = html(lang.strings['enDecrypt.button.headerValues.show.show']); } } /** * Adds all Action-Listener */ function init() { var addMethod = window.addEventListener ? 'addEventListener' : 'attachEvent'; // Get Elements var detectButton = document.getElementById('detectButton'); var inputCode = document.getElementById('decryptCode'); var decryptButton = document.getElementById('decrypt'); var encryptButtonMv = document.getElementById('encrypt-mv'); var encryptButtonMz = document.getElementById('encrypt-mz'); var restoreButton = document.getElementById('restoreEncryptedImages'); var fullGameZipButton = document.getElementById('decryptFullGameZip'); var spoilerHeader = document.getElementById('spoilerHeaderInfoText'); var headerRadioButtons = document.getElementsByName('checkFakeHeader'); var headerAreaEl = document.getElementById('headerValuesEditArea'); var headerResetButton = document.getElementById('resetHeader'); var zipSaveButtons = document.getElementsByClassName('zipSave'); var clearFileListButtons = document.getElementsByClassName('clearFileList'); // Language Init lang = new Language(languageList); // Prepare stuff if(! parseInt(getRadioButtonValue('checkFakeHeader', '1'))) headerAreaEl.className = addCssClass(headerAreaEl.className, 'hidden'); setHeaderDefaultValues(false); // Add Listener headerRadioButtons[0][addMethod](window.addEventListener ? 'click' : 'onclick', function() { // Yes Button headerAreaEl.className = removeCssClass(headerAreaEl.className, 'hidden'); }, false); headerRadioButtons[1][addMethod](window.addEventListener ? 'click' : 'onclick', function() { // No Button headerAreaEl.className = addCssClass(headerAreaEl.className, 'hidden'); }, false); headerResetButton[addMethod](window.addEventListener ? 'click' : 'onclick', function() { setHeaderDefaultValues(true); }, false); spoilerHeader[addMethod](window.addEventListener ? 'click' : 'onclick', function() { spoiler('spoilerHeaderInfoText', 'Header-Values', 'headerInfo'); }, false); detectButton[addMethod](window.addEventListener ? 'click' : 'onclick', function() { getCode('systemFile', 'decryptCode', 'headerLen'); }, false); decryptButton[addMethod](window.addEventListener ? 'click' : 'onclick', function() { processFiles( 'encryptedFiles', 'decryptCode', 'blob-list', true, !! parseInt(getRadioButtonValue('checkFakeHeader', '0')), 'headerLen', 'signature', 'version', 'remain', RPGFile.typeMV ); }, false); encryptButtonMv[addMethod](window.addEventListener ? 'click' : 'onclick', function() { processFiles( 'encryptedFiles', 'decryptCode', 'blob-list', false, true, 'headerLen', 'signature', 'version', 'remain', RPGFile.typeMV ); }, false); encryptButtonMz[addMethod](window.addEventListener ? 'click' : 'onclick', function() { processFiles( 'encryptedFiles', 'decryptCode', 'blob-list', false, true, 'headerLen', 'signature', 'version', 'remain', RPGFile.typeMZ ); }, false); inputCode[addMethod](window.addEventListener ? 'change' : 'onchange', function() { manualChange('decryptCode'); }, false); restoreButton[addMethod](window.addEventListener ? 'click' : 'onclick', function() { processRestoreFiles('encryptedImg', 'blob-list'); }, false); if(fullGameZipButton) { fullGameZipButton[addMethod](window.addEventListener ? 'click' : 'onclick', function() { processFullGameZip(); }, false); } // File-List Listener var i; for(i = 0; i < zipSaveButtons.length; i++) { zipSaveButtons[i][addMethod](window.addEventListener ? 'click' : 'onclick', function() { saveZip(); }, false); } for(i = 0; i < clearFileListButtons.length; i++) { clearFileListButtons[i][addMethod](window.addEventListener ? 'click' : 'onclick', function() { clearFileList('blob-list', 'zipSave', 'clearFileList'); }, false); } zip = new ZIP(); } document.body[window.addEventListener ? 'addEventListener' : 'attachEvent']( window.addEventListener ? 'load' : 'onload', init(), false); /** * Set all Values to default within the Header-Values-Area * * @param {boolean} confirmDialog - Ask before reset */ function setHeaderDefaultValues(confirmDialog) { if(confirmDialog) { if(! confirm(lang.strings['confirm.resetHeaderValues'])) return; } // Get all Elements var headerLenEl = document.getElementById('headerLen'); var headerSigEl = document.getElementById('signature'); var headerVerEl = document.getElementById('version'); var headerRemainEl = document.getElementById('remain'); // Get a dummy Decrypter Class var decrypter = new Decrypter('000000'); // Set Values headerLenEl.value = decrypter.defaultHeaderLen; headerLenEl.placeholder = decrypter.defaultHeaderLen; headerSigEl.value = decrypter.defaultSignature; headerSigEl.placeholder = decrypter.defaultSignature; headerVerEl.value = decrypter.defaultVersion; headerVerEl.placeholder = decrypter.defaultVersion; headerRemainEl.value = decrypter.defaultRemain; headerRemainEl.placeholder = decrypter.defaultRemain; } /** * Restores images from MV-Encrypted-Files * * @param {string} fileUrlElId - Element-Id of the File(s)-Picker * @param {string} outputElClass - Output-Element-Class */ function processRestoreFiles(fileUrlElId, outputElClass) { var outputEls = document.getElementsByClassName(outputElClass); var fileUrlEl = document.getElementById(fileUrlElId); // Check if at least 1 File is given if(fileUrlEl.files.length < 1) { alert(lang.strings['error.restore.noFileSelected']); return; } var decrypter = new Decrypter(null); decrypter.headerLen = 16; // Process all Files var buttonsEnabled = false; // Just trigger that event one time this loop for(var i = 0; i < fileUrlEl.files.length; i++) { var rpgFile = new RPGFile(fileUrlEl.files[i], null); decrypter.restoreHeader(rpgFile, function(rpgFile, exception) { // Output Decrypted file for(var n = 0; n < outputEls.length; n++) { if(exception !== null) outputEls[n].appendChild(rpgFile.createOutPut(exception.toString())); else { rpgFile.convertExtension(true); outputEls[n].appendChild(rpgFile.createOutPut(null)); zip.addFile(rpgFile); } } if(! buttonsEnabled && exception === null) { enableFileButtons('clearFileList', 'zipSave'); buttonsEnabled = true; } }); } } /** * Decrypt a bunch of MV-Encrypted-Files * * @param {string} fileUrlElId - Element-Id of the File(s)-Picker * @param {string} decryptCodeElId - Element-Id of the Decryption-Code Input Field * @param {string} outputElClass - Output-Element-Class * @param {boolean} decrypt - Decrypt (true decrypts false encrypts) * @param {boolean} verifyHeader - Verify Header * @param {string} headerLenElId - Element-Id of the Header-Length * @param {string} signatureElId - Element-Id of the Signature * @param {string} versionElId - Element-Id of the Version * @param {string} remainElId - Element-Id of the Remain * @param {int} rpgMakerType - Type of the RPG-Maker */ function processFiles( fileUrlElId, decryptCodeElId, outputElClass, decrypt, verifyHeader, headerLenElId, signatureElId, versionElId, remainElId, rpgMakerType ) { var outputEls = document.getElementsByClassName(outputElClass); var fileUrlEl = document.getElementById(fileUrlElId); var encryptCodeEl = document.getElementById(decryptCodeElId); var encryptionCode = encryptCodeEl.value; var headerLen = null; var signature = null; var version = null; var remain = null; // On encryption verify-header is req if(! decrypt) verifyHeader = true; if(verifyHeader) { var headerLenEl = document.getElementById(headerLenElId); var signatureEl = document.getElementById(signatureElId); var versionEl = document.getElementById(versionElId); var remainEl = document.getElementById(remainElId); if(headerLenEl) headerLen = headerLenEl.value; if(signatureEl) signature = signatureEl.value; if(versionEl) version = versionEl.value; if(remainEl) remain = remainEl.value; } // Check if all required stuff is given if(! encryptionCode) { alert(lang.strings['error.enDecrypt.noCode']); encryptCodeEl.className = removeValidationCssClasses(encryptCodeEl.className); encryptCodeEl.className = addCssClass(encryptCodeEl.className, 'invalid'); return; } // Check if code just contain HEX-Chars if(! Decrypter.checkHexChars(encryptionCode)) { alert(lang.strings['error.enDecrypt.invalidCode'].replace('{0}', '0-9 & A-F & a-f')); encryptCodeEl.className = removeValidationCssClasses(encryptCodeEl.className); encryptCodeEl.className = addCssClass(encryptCodeEl.className, 'invalid'); return; } // Set valid encryption class encryptCodeEl.className = removeValidationCssClasses(encryptCodeEl.className); encryptCodeEl.className = addCssClass(encryptCodeEl.className, 'valid'); // Check if at least 1 File is given if(fileUrlEl.files.length < 1) { alert(lang.strings['error.enDecrypt.noFileSelected']); return; } var decrypter = new Decrypter(encryptionCode); decrypter.ignoreFakeHeader = ! verifyHeader; if(verifyHeader) { // Handle Header details headerLenEl.className = removeValidationCssClasses(headerLenEl.className); if(! isNaN(headerLen) && Math.floor(headerLen) > 0) decrypter.headerLen = Math.floor(headerLen); else if(headerLen) { headerLenEl.className = addCssClass(headerLenEl.className, 'invalid'); alert(lang.strings['warn.enDecrypt.header.invalidLength'].replace('{0}', decrypter.defaultHeaderLen)); } signatureEl.className = removeValidationCssClasses(signatureEl.className); if(Decrypter.checkHexChars(signature)) decrypter.signature = signature; else if(signature) { signatureEl.className = addCssClass(signatureEl.className, 'invalid'); alert(lang.strings['warn.enDecrypt.header.invalidSignature'] .replace('{0}', '0-9 & A-F & a-f') .replace('{1}', decrypter.defaultSignature)); } versionEl.className = removeValidationCssClasses(versionEl.className); if(Decrypter.checkHexChars(version)) decrypter.version = version; else if(version) { versionEl.className = addCssClass(versionEl.className, 'invalid'); alert(lang.strings['warn.enDecrypt.header.invalidVersion'] .replace('{0}', '0-9 & A-F & a-f') .replace('{1}', decrypter.defaultVersion)); } remainEl.className = removeValidationCssClasses(remainEl.className); if(Decrypter.checkHexChars(remain)) decrypter.remain = remain; else if(remain) { remainEl.className = addCssClass(remainEl.className, 'invalid'); alert(lang.strings['warn.enDecrypt.header.invalidRemain'] .replace('{0}', '0-9 & A-F & a-f') .replace('{1}', decrypter.defaultRemain)); } } // Process all Files var buttonsEnabled = false; // Just trigger that event one time this loop for(var i = 0; i < fileUrlEl.files.length; i++) { var rpgFile = new RPGFile(fileUrlEl.files[i], null); rpgFile.rpgMakerMz = rpgMakerType === RPGFile.typeMZ; if(decrypt) { decrypter.decryptFile(rpgFile, function(rpgFile, exception) { // Output Decrypted file for(var n = 0; n < outputEls.length; n++) { if(exception !== null) outputEls[n].appendChild(rpgFile.createOutPut(exception.toString())); else { rpgFile.convertExtension(true); outputEls[n].appendChild(rpgFile.createOutPut(null)); zip.addFile(rpgFile); } } if(! buttonsEnabled && exception === null) { enableFileButtons('clearFileList', 'zipSave'); buttonsEnabled = true; } }); } else { decrypter.encryptFile(rpgFile, function(rpgFile, exception) { // Output Encrypted file for(var n = 0; n < outputEls.length; n++) { if(exception !== null) outputEls[n].appendChild(rpgFile.createOutPut(exception.toString())); else { rpgFile.convertExtension(false); outputEls[n].appendChild(rpgFile.createOutPut(null)); zip.addFile(rpgFile); } } if(! buttonsEnabled && exception === null) { enableFileButtons('clearFileList', 'zipSave'); buttonsEnabled = true; } }); } } } /** * Clears the File-List & disables the ZIP-Save-Button * * @param {string} fileListClass - Class-Name of the File-List * @param {string} zipSaveButtonClass - Class-Name of the ZIP-Save Button * @param {string} clearFileListButtonClass - Class-Name of the Clear-File-List Button */ function clearFileList(fileListClass, zipSaveButtonClass, clearFileListButtonClass) { var fileListEls = document.getElementsByClassName(fileListClass); var zipSaveButtonEls = document.getElementsByClassName(zipSaveButtonClass); var clearFileListButtonEls = document.getElementsByClassName(clearFileListButtonClass); var i; for(i = 0; i < fileListEls.length; i++) fileListEls[i].innerHTML = ''; for(i = 0; i < zipSaveButtonEls.length; i++) zipSaveButtonEls[i].disabled = 'disabled'; for(i = 0; i < clearFileListButtonEls.length; i++) clearFileListButtonEls[i].disabled = 'disabled'; // Dispose old ZIP Object (Clear Memory) zip.dispose(); } /** * ReEnables the Buttons for the File-List * * @param {string} clearFileListButtonClass - Class-Name of the Clear-File-List Button * @param {string} zipSaveButtonClass - Class-Name of the ZIP-Save Button */ function enableFileButtons(clearFileListButtonClass, zipSaveButtonClass) { var clearFileListButtonEls = document.getElementsByClassName(clearFileListButtonClass); var zipSaveButtonEls = document.getElementsByClassName(zipSaveButtonClass); var i; for(i = 0; i < clearFileListButtonEls.length; i++) clearFileListButtonEls[i].disabled = ''; for(i = 0; i < zipSaveButtonEls.length; i++) zipSaveButtonEls[i].disabled = ''; } /** * Encodes text HTML-Special Characters * * @param {string} s - Unencoded String * @returns {string} - Encoded String */ function html(s) { var el = document.createElement("div"); el.innerText = el.textContent = s; s = el.innerHTML; return s.replaceAll(/"/g, '"') .replaceAll(/'/g, ''') .replaceAll('#', '#'); } /** * Escapes Titles/Alt-Tags * * @param {string} s - Enencoded String * @returns {string} - Encoded String */ function escapeTitle(s) { s = html(s); return s.replaceAll('"', '\'\'') .replaceAll(''', '\'') .replaceAll('#', '#') .replaceAll('&', '&'); } /** * Saves the current Files as ZIP */ function saveZip() { zip.save(); } /** * Full-game ZIP workflow. * Reads a complete RPG Maker game ZIP, detects the encryption key, decrypts all * encrypted resources while preserving their original directory paths, removes * the encrypted entries, and writes the decrypted resources back into the same * paths (with normal extensions) before generating one output ZIP. */ function processFullGameZip() { var input = document.getElementById('fullGameZip'); var status = document.getElementById('fullGameZipStatus'); var button = document.getElementById('decryptFullGameZip'); if(!input || !input.files || input.files.length < 1) { alert('Please select a full game ZIP file.'); return; } var file = input.files[0]; if(!/\.zip$/i.test(file.name)) { alert('The selected file is not a ZIP archive.'); return; } var setStatus = function(message) { if(status) status.textContent = message; }; var oldText = button ? button.textContent : ''; if(button) { button.disabled = true; button.textContent = 'Processing...'; } var finish = function() { if(button) { button.disabled = false; button.textContent = oldText || 'Decrypt Full Game ZIP'; } }; setStatus('กำลังอ่าน ZIP เกม...'); JSZip.loadAsync(file).then(function(gameZip) { var entries = Object.keys(gameZip.files); var systemPath = findGameZipFile(entries, /(^|\/)system\.json$/i); var headerLen = parseInt(document.getElementById('headerLen') ? document.getElementById('headerLen').value : '16'); if(isNaN(headerLen) || headerLen < 1) headerLen = 16; // systemPath looks like "/data/System.json" (MZ) or "/www/data/System.json" (MV). // The game root is the folder that contains data/, img/, audio/, js/ as SIBLINGS — // i.e. one level above the "data" folder, not the data folder itself. var gameRoot = systemPath ? systemPath.substring(0, systemPath.lastIndexOf('/') + 1).replace(/[^\/]+\/$/, '') : ''; return detectFullGameZipKey(gameZip, entries, systemPath, headerLen).then(function(keyInfo) { if(!keyInfo || !keyInfo.key) { throw new Error('ไม่พบ encryptionKey ในเกม ZIP (ลองตรวจว่าเป็น RPG Maker MV/MZ ที่เข้ารหัสหรือไม่)'); } var decrypter = new Decrypter(keyInfo.key); decrypter.headerLen = headerLen; decrypter.ignoreFakeHeader = !parseInt(getRadioButtonValue('checkFakeHeader', '0')); var signatureEl = document.getElementById('signature'); var versionEl = document.getElementById('version'); var remainEl = document.getElementById('remain'); if(parseInt(getRadioButtonValue('checkFakeHeader', '0'))) { if(signatureEl && Decrypter.checkHexChars(signatureEl.value)) decrypter.signature = signatureEl.value; if(versionEl && Decrypter.checkHexChars(versionEl.value)) decrypter.version = versionEl.value; if(remainEl && Decrypter.checkHexChars(remainEl.value)) decrypter.remain = remainEl.value; } var encrypted = entries.filter(function(path) { return !gameZip.files[path].dir && isFullGameEncryptedPath(path) && isWithinGameRoot(path, gameRoot); }); if(encrypted.length === 0) throw new Error('ไม่พบไฟล์เข้ารหัส (.rpgmvp/.rpgmvm/.rpgmvo/.png_/.ogg_/.m4a_) ใน ZIP'); setStatus('พบไฟล์เข้ารหัส ' + encrypted.length + ' ไฟล์ กำลังถอดรหัส...'); var output = {}; var completed = 0; // Keep every original entry except encrypted resources. Encrypted // resources are inserted later using their original directory + normal extension. entries.forEach(function(path) { if(gameZip.files[path].dir) { output[path] = {dir:true}; } else if(!isFullGameEncryptedPath(path) || !isWithinGameRoot(path, gameRoot)) { output[path] = {zipObject:gameZip.files[path]}; } }); var tasks = encrypted.map(function(path) { return gameZip.files[path].async('arraybuffer').then(function(buffer) { var decrypted = decrypter.decrypt(buffer); var outputPath = fullGameNormalPath(path); output[outputPath] = {data:decrypted}; completed++; setStatus('ถอดรหัส ' + completed + '/' + encrypted.length + ' ไฟล์...'); }); }); return Promise.all(tasks).then(function() { var resultZip = new JSZip(); Object.keys(output).forEach(function(path) { var item = output[path]; if(item.dir) resultZip.folder(path.replace(/\/$/, '')); else if(item.data) resultZip.file(path, item.data); else resultZip.file(path, item.zipObject.async('arraybuffer')); }); setStatus('ถอดรหัสเสร็จ กำลังสร้าง ZIP เกมเต็ม...'); return resultZip.generateAsync({type:'blob', compression:'DEFLATE'}).then(function(blob) { var outputName = file.name.replace(/\.zip$/i, '') + '_decrypted.zip'; saveAs(blob, outputName); setStatus('เสร็จแล้ว: ' + outputName + ' — ถอดรหัส ' + encrypted.length + ' ไฟล์ และคงโครงสร้างโฟลเดอร์เดิม'); }); }); }); }).catch(function(error) { console.error(error); setStatus('ผิดพลาด: ' + (error && error.message ? error.message : error)); alert('ถอดรหัส ZIP ไม่สำเร็จ: ' + (error && error.message ? error.message : error)); }).then(finish, finish); } function findGameZipFile(entries, regex) { var matches = entries.filter(function(path) { return regex.test(path); }); if(matches.length === 0) return null; // Prefer the shallowest System.json (normal MV: www/data/System.json, MZ: data/System.json). matches.sort(function(a,b) { return a.split('/').length - b.split('/').length; }); return matches[0]; } function detectFullGameZipKey(gameZip, entries, systemPath, headerLen) { var makeFile = function(path, data, type) { return data.then(function(buffer) { return new File([buffer], path.split('/').pop(), {type:type || 'application/octet-stream'}); }); }; if(systemPath) { return makeFile(systemPath, gameZip.files[systemPath].async('arraybuffer'), 'application/json').then(function(file) { return new Promise(function(resolve) { Decrypter.detectEncryptionCode(new RPGFile(file, null), headerLen, function(key) { resolve({key:key, source:systemPath}); }); }); }); } var imagePath = findGameZipFile(entries, /\.(rpgmvp|png_)$/i); if(imagePath) { return gameZip.files[imagePath].async('arraybuffer').then(function(buffer) { return {key:Decrypter.getKeyFromPNG(headerLen, buffer), source:imagePath}; }); } return Promise.resolve(null); } function isWithinGameRoot(path, root) { if(!root) return true; return path.indexOf(root) === 0; } function isFullGameEncryptedPath(path) { return /\.(rpgmvp|rpgmvm|rpgmvo|png_|ogg_|m4a_)$/i.test(path); } function fullGameNormalPath(path) { return path.replace(/\.(rpgmvp|png_)$/i, '.png') .replace(/\.(rpgmvm|m4a_)$/i, '.m4a') .replace(/\.(rpgmvo|ogg_)$/i, '.ogg'); }