1
0
Fork 0
mirror of https://github.com/marcrobledo/RomPatcher.js.git synced 2025-06-27 16:25:54 +00:00
RomPatcher.js/rom-patcher-js/RomPatcher.webapp.js

2126 lines
No EOL
76 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Rom Patcher JS - Webapp implementation
* A web implementation for Rom Patcher JS
* By Marc Robledo https://www.marcrobledo.com
* Sourcecode: https://github.com/marcrobledo/RomPatcher.js
* License:
*
* MIT License
*
* Copyright (c) 2016-2024 Marc Robledo
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/*
to-do list:
- allow multiple instances of RomPatcherWeb?
- switch to ES6 classes and modules?
*/
const ROM_PATCHER_JS_PATH = './rom-patcher-js/';
const RomPatcherWeb = (function () {
const SCRIPT_DEPENDENCIES = [
'modules/BinFile.js',
'modules/HashCalculator.js',
'modules/RomPatcher.format.ips.js',
'modules/RomPatcher.format.ups.js',
'modules/RomPatcher.format.aps_n64.js',
'modules/RomPatcher.format.aps_gba.js',
'modules/RomPatcher.format.bps.js',
'modules/RomPatcher.format.rup.js',
'modules/RomPatcher.format.ppf.js',
'modules/RomPatcher.format.pmsr.js',
'modules/RomPatcher.format.vcdiff.js',
'modules/zip.js/zip.min.js',
'RomPatcher.js'
];
const WEB_CRYPTO_AVAILABLE = window.crypto && window.crypto.subtle && window.crypto.subtle.digest;
const settings = {
language: typeof navigator.language === 'string' ? navigator.language.substring(0, 2) : 'en',
outputSuffix: true,
fixChecksum: false,
requireValidation: false,
allowDropFiles: false,
oninitialize: null,
onloadrom: null,
onvalidaterom: null,
onloadpatch: null,
onpatch: null
};
var romFile, patch;
const isBrowserSafari = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent); /* Safari userAgent does not include word Chrome, Chrome includes both! */
const isBrowserMobile = /Mobile(\/\S+)? /.test(navigator.userAgent);
/* embeded patches */
var currentEmbededPatches = null;
const _parseEmbededPatchInfo = function (embededPatchInfo) {
const parsedPatch = {
file: embededPatchInfo.file.trim(),
name: null,
description: null,
outputName: null,
outputExtension: null,
patches: null
};
if (typeof embededPatchInfo.name === 'string') {
parsedPatch.name = embededPatchInfo.name.trim();
} else {
parsedPatch.name = embededPatchInfo.file.replace(/(.*?\/)+/g, '');
}
if (typeof embededPatchInfo.description === 'string') {
parsedPatch.description = embededPatchInfo.description;
}
parsedPatch.optional = !!embededPatchInfo.optional;
if (typeof embededPatchInfo.outputName === 'string') {
parsedPatch.outputName = embededPatchInfo.outputName;
}
if (typeof embededPatchInfo.outputExtension === 'string') {
parsedPatch.outputExtension = embededPatchInfo.outputExtension;
}
if (typeof embededPatchInfo.inputMd5 !== 'undefined') {
if (!Array.isArray(embededPatchInfo.inputMd5))
embededPatchInfo.inputMd5 = [embededPatchInfo.inputMd5];
const validMd5s = embededPatchInfo.inputMd5.reduce(function (acc, md5) {
if (typeof md5 === 'string' && /^(0x)?[0-9a-fA-F]{32}$/i.test(md5.trim())) {
acc.push(md5.trim().toLowerCase());
} else {
console.warn('Rom Patcher JS: inputMd5 must be a string (32 characters long, characters allowed: 0-9, a-f)');
}
return acc;
}, []);
if (validMd5s.length) {
parsedPatch.inputValidation = {
'type': 'MD5',
'value': validMd5s
};
} else {
console.warn('Rom Patcher JS: invalid inputMd5 for embeded patch', embededPatchInfo.inputMd5);
}
}
if (typeof embededPatchInfo.inputCrc32 !== 'undefined') {
if (!parsedPatch.inputValidation) {
if (!Array.isArray(embededPatchInfo.inputCrc32))
embededPatchInfo.inputCrc32 = [embededPatchInfo.inputCrc32];
const validCrcs = embededPatchInfo.inputCrc32.reduce(function (acc, crc32) {
if (typeof crc32 === 'string' && /^(0x)?[0-9a-fA-F]{8}$/i.test(crc32.trim())) {
acc.push(parseInt(crc32.trim().replace('0x', ''), 16));
} else if (typeof crc32 === 'number') {
acc.push((crc32 >>> 0) & 0xffffffff);
} else {
console.warn('Rom Patcher JS: invalid inputCrc32 value');
}
return acc;
}, []);
if (validCrcs.length) {
parsedPatch.inputValidation = {
'type': 'CRC32',
'value': validCrcs
};
} else {
console.warn('Rom Patcher JS: invalid inputCrc32 for embeded patch', embededPatchInfo.inputCrc32);
}
} else {
console.warn('Rom Patcher JS: a valid inputMd5 was provided, inputCrc32 will be ignored', embededPatchInfo);
}
}
return parsedPatch;
}
var isFetching = false;
const _fetchPatchFile = function (embededPatchInfo) {
if (isFetching)
throw new Error('Rom Patcher JS: already fetching another file');
isFetching = true;
htmlElements.disableAll();
const spinnerHtml = '<span class="rom-patcher-spinner" id="rom-patcher-spinner-patch"></span>';
htmlElements.hide('select-patch');
htmlElements.empty('select-patch');
htmlElements.removeClass('select-patch', 'single');
htmlElements.removeClass('select-patch', 'multiple');
htmlElements.setEnabled('select-patch', false);
htmlElements.empty('container-optional-patches');
htmlElements.setText('span-loading-embeded-patch', _('Downloading...') + ' ' + spinnerHtml);
htmlElements.show('span-loading-embeded-patch');
fetch(decodeURI(embededPatchInfo.file))
.then(result => result.arrayBuffer()) // Gets the response and returns it as a blob
.then(arrayBuffer => {
const fetchedFile = new BinFile(arrayBuffer);
if (ZIPManager.isZipFile(fetchedFile)) {
if (typeof embededPatchInfo.patches === 'object') {
if (Array.isArray(embededPatchInfo.patches)) {
currentEmbededPatches = embededPatchInfo.patches.map((embededPatchInfo) => _parseEmbededPatchInfo(embededPatchInfo));
} else {
console.warn('Rom Patcher JS: Invalid patches object for embeded patch', embededPatchInfo);
}
} else {
currentEmbededPatches = [_parseEmbededPatchInfo(embededPatchInfo)];
}
htmlElements.setText('span-loading-embeded-patch', _('Unzipping...') + ' ' + spinnerHtml);
ZIPManager.unzipEmbededPatches(arrayBuffer, currentEmbededPatches);
} else {
const parsedPatch = _parseEmbededPatchInfo(embededPatchInfo);
currentEmbededPatches = [parsedPatch];
const option = document.createElement('option');
option.innerHTML = parsedPatch.name;
htmlElements.get('select-patch').appendChild(option);
htmlElements.setEnabled('select-patch', false);
htmlElements.addClass('select-patch', 'single');
htmlElements.hide('span-loading-embeded-patch');
htmlElements.show('select-patch');
fetchedFile.fileName = embededPatchInfo.file;
RomPatcherWeb.providePatchFile(fetchedFile);
}
isFetching = false;
})
.catch(function (evt) {
isFetching = false;
_setToastError((_('Error downloading %s') + '<br />' + evt.message).replace('%s', embededPatchInfo.file.replace(/^.*[\/\\]/g, '')));
});
};
const _getEmbededPatchInfo = function (fileName) {
if (currentEmbededPatches)
return currentEmbededPatches.find((embededPatchInfo) => embededPatchInfo.file === fileName);
return null;
}
const _padZeroes = function (intVal, nBytes) {
var hexString = intVal.toString(16);
while (hexString.length < nBytes * 2)
hexString = '0' + hexString;
return hexString
}
const _setElementsStatus = function (status, applyButtonStatus) {
htmlElements.setEnabled('input-file-rom', status);
htmlElements.setEnabled('input-file-patch', status);
if (htmlElements.get('select-patch')) {
htmlElements.setEnabled('select-patch', htmlElements.get('select-patch').children.length > 1 ? status : false);
}
htmlElements.setEnabled('checkbox-alter-header', status);
if (romFile && patch && status) {
if (settings.requireValidation && typeof applyButtonStatus !== 'undefined')
status = !!applyButtonStatus;
htmlElements.setEnabled('button-apply', status);
} else {
htmlElements.setEnabled('button-apply', false);
}
};
const _setInputFileSpinner = function (inputFileId, status) {
const elementId = currentEmbededPatches && inputFileId === 'patch' ? ('select-' + inputFileId) : ('input-file-' + inputFileId);
const spinnerId = 'spinner-' + inputFileId;
htmlElements.removeClass(elementId, 'empty');
if (status) {
const spinner = document.createElement('spinner');
spinner.id = 'rom-patcher-' + spinnerId;
spinner.className = 'rom-patcher-spinner';
const htmlInputFile = htmlElements.get(elementId);
if (htmlInputFile) {
if (elementId === 'select-patch') {
htmlInputFile.parentElement.insertBefore(spinner, htmlElements.get('span-loading-embeded-patch'));
} else {
htmlInputFile.parentElement.appendChild(spinner);
}
}
htmlElements.addClass(elementId, 'loading');
htmlElements.removeClass(elementId, 'valid');
htmlElements.removeClass(elementId, 'invalid');
return spinner;
} else {
const spinner = htmlElements.get(spinnerId);
if (spinner)
spinner.parentElement.removeChild(spinner);
htmlElements.removeClass(elementId, 'loading');
return spinner;
}
}
const _setRomInputSpinner = function (status) {
return _setInputFileSpinner('rom', status);
}
const _setPatchInputSpinner = function (status) {
return _setInputFileSpinner('patch', status);
}
const _setApplyButtonSpinner = function (status) {
if (status) {
htmlElements.setText('button-apply', '<span class="rom-patcher-spinner"></span> ' + _('Applying patch...'));
} else {
htmlElements.setText('button-apply', _('Apply patch'));
}
}
const _setToastError = function (errorMessage, className) {
const row = htmlElements.get('row-error-message');
const span = htmlElements.get('error-message');
if (row && span) {
if (errorMessage) {
htmlElements.addClass('row-error-message', 'show');
htmlElements.setText('error-message', errorMessage);
if (className === 'warning')
htmlElements.addClass('error-message', 'warning');
else
htmlElements.removeClass('error-message', 'warning');
} else {
htmlElements.removeClass('row-error-message', 'show');
htmlElements.setText('error-message', '');
}
} else {
console.error('Rom Patcher JS: ' + errorMessage);
}
}
const htmlElements = {
get: function (id) {
return document.getElementById('rom-patcher-' + id);
},
enableAll: function () {
_setElementsStatus(true);
},
disableAll: function () {
_setElementsStatus(false);
},
show: function (id) {
if (document.getElementById('rom-patcher-' + id))
document.getElementById('rom-patcher-' + id).style.display = 'block';
},
hide: function (id) {
if (document.getElementById('rom-patcher-' + id))
document.getElementById('rom-patcher-' + id).style.display = 'none';
},
setValue: function (id, val) {
if (document.getElementById('rom-patcher-' + id))
document.getElementById('rom-patcher-' + id).value = val;
},
getValue: function (id, val, fallback) {
if (document.getElementById('rom-patcher-' + id))
return document.getElementById('rom-patcher-' + id).value;
return fallback || 0;
},
setFakeFile: function (id, fileName) {
if (!isBrowserSafari && document.getElementById('rom-patcher-input-file-' + id)) { /* safari does not show fake file name: https://pqina.nl/blog/set-value-to-file-input/#but-safari */
try {
/* add a fake file to the input file, so it shows the chosen file name */
const fakeFile = new File(new Uint8Array(0), fileName);
const dataTransfer = new DataTransfer();
dataTransfer.items.add(fakeFile);
document.getElementById('rom-patcher-input-file-' + id).files = dataTransfer.files;
} catch (ex) {
console.warning('File API constructor is not supported');
}
}
},
setText: function (id, text) {
if (document.getElementById('rom-patcher-' + id))
document.getElementById('rom-patcher-' + id).innerHTML = text;
},
empty: function (id) {
if (document.getElementById('rom-patcher-' + id))
document.getElementById('rom-patcher-' + id).innerHTML = '';
},
addClass: function (id, className) {
if (document.getElementById('rom-patcher-' + id))
document.getElementById('rom-patcher-' + id).classList.add(className);
},
removeClass: function (id, className) {
if (document.getElementById('rom-patcher-' + id))
document.getElementById('rom-patcher-' + id).classList.remove(className);
},
setClass: function (id, className) {
if (document.getElementById('rom-patcher-' + id))
document.getElementById('rom-patcher-' + id).className = className;
},
setEnabled: function (id, enabled) {
if (document.getElementById('rom-patcher-' + id))
document.getElementById('rom-patcher-' + id).disabled = !enabled;
},
setChecked: function (id, checked) {
if (document.getElementById('rom-patcher-' + id))
document.getElementById('rom-patcher-' + id).checked = !!checked;
},
isChecked: function (id) {
if (document.getElementById('rom-patcher-' + id))
return document.getElementById('rom-patcher-' + id).checked;
return false;
},
setSpinner: function (inputFileId, status) {
if (inputFileId !== 'rom' && inputFileId !== 'patch')
throw new Error('RomPatcherWeb.htmlElements.setSpinner: only rom or patch input file ids are allowed');
return _setInputFileSpinner(inputFileId, status);
}
}
/* web workers */
const webWorkerApply = new Worker(ROM_PATCHER_JS_PATH + 'RomPatcher.webworker.apply.js');
webWorkerApply.onmessage = event => { // listen for events from the worker
//retrieve arraybuffers back from webworker
romFile._u8array = event.data.romFileU8Array;
patch._originalPatchFile._u8array = event.data.patchFileU8Array;
htmlElements.enableAll();
_setApplyButtonSpinner(false);
if (event.data.patchedRomU8Array && !event.data.errorMessage) {
var patchedRom = new BinFile(event.data.patchedRomU8Array.buffer);
patchedRom.fileName = event.data.patchedRomFileName;
if (currentEmbededPatches) {
const optionalPatches = currentEmbededPatches.filter((embededPatchInfo) => embededPatchInfo.optional);
if (optionalPatches.length) {
const originalFileName = patchedRom.fileName;
for (var i = 0; i < optionalPatches.length; i++) {
/* could be improved by using webWorkerApply to apply optional patches */
if (optionalPatches[i].checkbox.checked)
patchedRom = RomPatcher.applyPatch(patchedRom, optionalPatches[i].parsedPatch, { requireValidation: false, fixChecksum: true });
}
patchedRom.fileName = originalFileName;
}
}
if (typeof settings.onpatch === 'function')
settings.onpatch(patchedRom);
patchedRom.save();
_setToastError();
} else {
_setToastError(event.data.errorMessage);
}
};
webWorkerApply.onerror = event => { // listen for exceptions from the worker
htmlElements.enableAll();
_setApplyButtonSpinner(false);
_setToastError('webWorkerApply error: ' + event.message);
};
const webWorkerCrc = new Worker(ROM_PATCHER_JS_PATH + 'RomPatcher.webworker.crc.js');
webWorkerCrc.onmessage = event => { // listen for events from the worker
//console.log('received_crc');
htmlElements.setText('span-crc32', _padZeroes(event.data.crc32, 4));
htmlElements.setText('span-md5', _padZeroes(event.data.md5, 16));
romFile._u8array = event.data.u8array;
if (WEB_CRYPTO_AVAILABLE) {
romFile.hashSHA1().then(function (res) {
htmlElements.setText('span-sha1', res);
});
}
if (event.data.rom) {
htmlElements.setText('span-rom-info', event.data.rom);
htmlElements.addClass('row-info-rom', 'show');
}
validRom = RomPatcherWeb.validateCurrentRom(event.data.checksumStartOffset);
_setElementsStatus(true, validRom);
};
webWorkerCrc.onerror = event => { // listen for events from the worker
_setToastError('webWorkerCrc error: ' + event.message);
};
const _getChecksumStartOffset = function () {
if (romFile) {
const headerInfo = RomPatcher.isRomHeadered(romFile);
if (headerInfo) {
const htmlCheckboxAlterHeader = htmlElements.get('checkbox-alter-header');
if (htmlCheckboxAlterHeader && htmlCheckboxAlterHeader.checked)
return headerInfo.size;
}
}
return 0;
}
const _getScriptPath = function () {
const currentScripts = document.querySelectorAll('script');
var scriptPath;
if (document.currentScript) {
scriptPath = document.currentScript.src;
} else {
for (var i = 0; i < currentScripts.length; i++) {
if (currentScripts[i].src.indexOf('RomPatcher.webapp.js') !== -1) {
scriptPath = currentScripts[i].src;
break;
}
}
if (!scriptPath)
scriptPath = './rom-patcher-js/';
}
return scriptPath.substring(0, scriptPath.lastIndexOf('/') + 1);
}
const _getMissingDependencies = function () {
const currentScripts = document.querySelectorAll('script');
const scriptPath = _getScriptPath();
var missingDependencies = [];
for (var i = 0; i < SCRIPT_DEPENDENCIES.length && !isLoaded; i++) {
var isLoaded = false;
for (var j = 0; j < currentScripts.length; j++) {
if (currentScripts[j].src === scriptPath + SCRIPT_DEPENDENCIES[i])
isLoaded = true;
}
if (!isLoaded)
missingDependencies.push(scriptPath + SCRIPT_DEPENDENCIES[i]);
}
return missingDependencies;
}
const _checkEmbededPatchParameter = function (embededPatchInfo) {
if (embededPatchInfo) {
if (typeof embededPatchInfo === 'string')
embededPatchInfo = { file: embededPatchInfo };
if (typeof embededPatchInfo !== 'object')
throw new Error('Rom Patcher JS: invalid embeded patch parameter');
else if (typeof embededPatchInfo.file !== 'string')
throw new Error('Rom Patcher JS: embeded patch missing file property');
return embededPatchInfo;
}
return false;
}
const _dragEventContainsFiles = function (evt) {
if (evt.dataTransfer.types) {
for (var i = 0; i < evt.dataTransfer.types.length; i++) {
if (evt.dataTransfer.types[i] === 'Files')
return true;
}
}
return false;
}
const _initialize = function (newSettings, embededPatchInfo) {
/* embeded patches */
var validEmbededPatch = _checkEmbededPatchParameter(embededPatchInfo);
if (newSettings && typeof newSettings.file === 'string') {
console.warn('Rom Patcher JS: embeded patch info was provided in settings and will be ignored, must be passed as second parameter');
}
/* check if Rom Patcher JS core is available */
if (typeof RomPatcher !== 'object') {
throw new Error('Rom Patcher JS: core not found');
}
/* check if zip-js web worker is available */
if (typeof zip !== 'object' || typeof zip.useWebWorkers !== 'boolean') {
console.error('Rom Patcher JS: zip.js web worker not found');
throw new Error('Rom Patcher JS: zip.js web worker not found');
}
zip.useWebWorkers = true;
zip.workerScriptsPath = ROM_PATCHER_JS_PATH + 'modules/zip.js/';
/* check if all required HTML elements are in DOM */
const htmlInputFileRom = htmlElements.get('input-file-rom');
if (htmlInputFileRom && htmlInputFileRom.tagName === 'INPUT' && htmlInputFileRom.type === 'file') {
htmlInputFileRom.addEventListener('change', function (evt) {
if (this.files && this.files.length) {
htmlElements.disableAll();
new BinFile(this, RomPatcherWeb.provideRomFile);
} else if (romFile) {
/* Webkit browsers trigger the change event when user cancels file selection and resets the input file value */
/* since we keep a cached copy of ROM file as a BinFile, we do not lose data but the input text, so we try to set it back */
/* Firefox keeps the previously selected file and does not trigger the change event */
htmlElements.setFakeFile('rom', romFile.fileName);
}
});
if (!isBrowserSafari)
htmlInputFileRom.classList.add('no-file-selector-button');
} else {
console.error('Rom Patcher JS: input#rom-patcher-input-file-rom[type=file] not found');
throw new Error('Rom Patcher JS: input#rom-patcher-input-file-rom[type=file] not found');
}
if (validEmbededPatch) {
const htmlSelectPatch = htmlElements.get('select-patch');
if (htmlSelectPatch && htmlSelectPatch.tagName === 'SELECT') {
htmlSelectPatch.addEventListener('change', function (evt) {
const zippedEntryIndex = parseInt(this.value);
this._unzipSelectedPatch(zippedEntryIndex);
});
} else {
console.error('Rom Patcher JS: select#rom-patcher-select-patch not found');
throw new Error('Rom Patcher JS: select#rom-patcher-select-patch not found');
}
const loadingSpan = document.createElement('span');
loadingSpan.id = 'rom-patcher-span-loading-embeded-patch';
loadingSpan.style.display = 'none';
htmlSelectPatch.parentElement.appendChild(loadingSpan);
const containerOptionalPatches = document.createElement('div');
containerOptionalPatches.id = 'rom-patcher-container-optional-patches';
containerOptionalPatches.style.display = 'none';
htmlSelectPatch.parentElement.appendChild(containerOptionalPatches);
} else {
const htmlInputFilePatch = htmlElements.get('input-file-patch');
if (htmlInputFilePatch && htmlInputFilePatch.tagName === 'INPUT' && htmlInputFilePatch.type === 'file') {
htmlInputFilePatch.addEventListener('change', function (evt) {
if (this.files && this.files.length) {
htmlElements.disableAll();
new BinFile(this, RomPatcherWeb.providePatchFile);
} else if (patch && patch._originalPatchFile) {
/* Webkit browsers trigger the change event when user cancels file selection and resets the input file value */
/* since we keep a cached copy of patch file as a BinFile, we do not lose data but the input text, so we try to set it back */
/* Firefox keeps the previously selected file and does not trigger the change event */
htmlElements.setFakeFile('patch', patch._originalPatchFile.fileName);
}
});
if (!isBrowserSafari)
htmlInputFilePatch.classList.add('no-file-selector-button');
} else {
console.error('Rom Patcher JS: input#rom-patcher-input-file-patch[type=file] not found');
throw new Error('Rom Patcher JS: input#rom-patcher-input-file-patch[type=file] not found');
}
/* dirty fix for iOS Safari, which only supports mimetypes in <input> accept attribute */
/* accept attribute compatibility: https://caniuse.com/input-file-accept */
if (isBrowserSafari && isBrowserMobile) {
htmlInputFilePatch.accept = 'application/zip, application/octet-stream, application/x-zip-compressed, multipart/x-zip';
}
}
const htmlButtonApply = htmlElements.get('button-apply');
if (htmlButtonApply && htmlButtonApply.tagName === 'BUTTON') {
htmlButtonApply.addEventListener('click', RomPatcherWeb.applyPatch);
} else {
console.error('Rom Patcher JS: button#rom-patcher-button-apply not found');
throw new Error('Rom Patcher JS: button#rom-patcher-button-apply not found');
}
const htmlCheckboxAlterHeader = htmlElements.get('checkbox-alter-header');
if (htmlCheckboxAlterHeader && htmlCheckboxAlterHeader.tagName === 'INPUT' && htmlCheckboxAlterHeader.type === 'checkbox') {
htmlCheckboxAlterHeader.addEventListener('change', function (evt) {
if (!romFile)
return false;
const headerInfo = RomPatcher.isRomHeadered(romFile);
if (headerInfo) {
htmlElements.disableAll();
webWorkerCrc.postMessage({ u8array: romFile._u8array, fileName: romFile.fileName, checksumStartOffset: _getChecksumStartOffset() }, [romFile._u8array.buffer]);
}
});
}
/* set all default input status just in case HTML is wrong */
htmlElements.disableAll();
/* reset input files */
htmlElements.setValue('input-file-rom', '');
htmlElements.setValue('input-file-patch', '');
/* translatable elements */
const translatableElements = document.querySelectorAll('*[data-localize="yes"]');
for (var i = 0; i < translatableElements.length; i++) {
translatableElements[i].setAttribute('data-localize', translatableElements[i].innerHTML);
}
/* add drag and drop events */
if (newSettings && newSettings.allowDropFiles) {
window.addEventListener('dragover', function (evt) {
if (_dragEventContainsFiles(evt))
evt.preventDefault(); /* needed ! */
});
window.addEventListener('drop', function (evt) {
evt.preventDefault();
if (_dragEventContainsFiles(evt)) {
const droppedFiles = evt.dataTransfer.files;
if (droppedFiles && droppedFiles.length === 1) {
new BinFile(droppedFiles[0], function (binFile) {
if (RomPatcherWeb.getEmbededPatches()) {
RomPatcherWeb.provideRomFile(binFile, true);
} else if (ZIPManager.isZipFile(binFile)) {
ZIPManager.unzipAny(binFile._u8array.buffer);
} else if (RomPatcher.parsePatchFile(binFile)) {
RomPatcherWeb.providePatchFile(binFile, null, true);
} else {
RomPatcherWeb.provideRomFile(binFile, true);
}
});
}
}
});
htmlInputFileRom.addEventListener('drop', function (evt) {
evt.stopPropagation();
});
if (!validEmbededPatch) {
htmlElements.get('input-file-patch').addEventListener('drop', function (evt) {
evt.stopPropagation();
});
}
}
console.log('Rom Patcher JS initialized');
initialized = true;
/* initialize Rom Patcher */
RomPatcherWeb.setSettings(newSettings);
/* download embeded patch */
if (validEmbededPatch)
_fetchPatchFile(validEmbededPatch);
else
htmlElements.enableAll();
if (typeof settings.oninitialize === 'function')
settings.oninitialize(this);
}
/* localization */
const _ = function (str) { return ROM_PATCHER_LOCALE[settings.language] && ROM_PATCHER_LOCALE[settings.language][str] ? ROM_PATCHER_LOCALE[settings.language][str] : str };
var initialized = false;
var loading = 0;
return {
_: function (str) { /* public localization function for external usage purposes */
return _(str);
},
getHtmlElements: function () {
return htmlElements;
},
isInitialized: function () {
return initialized;
},
getEmbededPatches: function () {
return currentEmbededPatches;
},
provideRomFile: function (binFile, transferFakeFile) {
htmlElements.disableAll();
romFile = binFile;
const canRomGetHeader = RomPatcher.canRomGetHeader(romFile);
const isRomHeadered = RomPatcher.isRomHeadered(romFile);
RomPatcherWeb.getHtmlElements().setChecked('checkbox-alter-header', false);
if (canRomGetHeader) {
RomPatcherWeb.getHtmlElements().setText('span-alter-header', _('Add %s header').replace('%s', _(canRomGetHeader.name)));
RomPatcherWeb.getHtmlElements().addClass('row-alter-header', 'show');
} else if (isRomHeadered) {
RomPatcherWeb.getHtmlElements().setText('span-alter-header', _('Remove %s header').replace('%s', _(isRomHeadered.name)));
RomPatcherWeb.getHtmlElements().addClass('row-alter-header', 'show');
} else {
RomPatcherWeb.getHtmlElements().setText('span-alter-header', '');
RomPatcherWeb.getHtmlElements().removeClass('row-alter-header', 'show');
}
romFile.seek(0);
_setRomInputSpinner(false);
if (ZIPManager.isZipFile(romFile)) {
ZIPManager.unzipRoms(romFile._u8array.buffer);
} else {
if (typeof settings.onloadrom === 'function')
settings.onloadrom(romFile);
RomPatcherWeb.calculateCurrentRomChecksums();
}
if (transferFakeFile) {
htmlElements.setFakeFile('rom', romFile.fileName);
}
},
providePatchFile: function (binFile, transferFakeFile) {
htmlElements.disableAll();
patch = null;
if (binFile) {
if (ZIPManager.isZipFile(binFile)) {
ZIPManager.unzipPatches(binFile._u8array.buffer);
} else {
try {
const parsedPatch = RomPatcher.parsePatchFile(binFile);
if (parsedPatch) {
patch = parsedPatch;
_setPatchInputSpinner(false);
const embededPatchInfo = _getEmbededPatchInfo(binFile.fileName);
if (embededPatchInfo) {
/* custom input validation */
if (embededPatchInfo.inputValidation) {
if (embededPatchInfo.inputValidation.type === 'CRC32') {
patch.validateSource = function (romFile, headerSize) {
for (var i = 0; i < embededPatchInfo.inputValidation.value.length; i++) {
if (embededPatchInfo.inputValidation.value[i] === romFile.hashCRC32(headerSize))
return true;
}
return false;
}
} else if (embededPatchInfo.inputValidation.type === 'MD5') {
patch.validateSource = function (romFile, headerSize) {
for (var i = 0; i < embededPatchInfo.inputValidation.value.length; i++) {
if (embededPatchInfo.inputValidation.value[i] === romFile.hashMD5(headerSize))
return true;
}
return false;
}
} else {
throw new Error('Rom Patcher JS: Invalid inputValidation type');
}
patch.getValidationInfo = function () {
return embededPatchInfo.inputValidation
};
}
/* custom description */
if (embededPatchInfo.description) {
patch.getDescription = function () {
return embededPatchInfo.description;
}
}
}
/* toggle ROM requirements */
if (htmlElements.get('row-patch-requirements') && htmlElements.get('patch-requirements-value')) {
if (typeof patch.getValidationInfo === 'function' && patch.getValidationInfo()) {
var validationInfo = patch.getValidationInfo();
if (Array.isArray(validationInfo) || !validationInfo.type) {
validationInfo = {
type: 'ROM',
value: validationInfo
}
}
htmlElements.setText('patch-requirements-value', '');
htmlElements.setText('patch-requirements-type', validationInfo.type === 'ROM' ? _('Required ROM:') : _('Required %s:').replace('%s', validationInfo.type));
if (!Array.isArray(validationInfo.value))
validationInfo.value = [validationInfo.value];
validationInfo.value.forEach(function (value) {
var line = document.createElement('div');
if (typeof value !== 'string') {
if (validationInfo.type === 'CRC32') {
value = value.toString(16);
while (value.length < 8)
value = '0' + value;
} else {
value = value.toString();
}
}
/*
var a=document.createElement('a');
a.href='https://www.google.com/search?q=%22'+value+'%22';
a.target='_blank';
a.className='clickable';
a.innerHTML=value;
line.appendChild(a);
*/
line.innerHTML = value;
htmlElements.get('patch-requirements-value').appendChild(line);
});
htmlElements.addClass('row-patch-requirements', 'show');
} else {
htmlElements.setText('patch-requirements-value', '');
htmlElements.removeClass('row-patch-requirements', 'show');
}
}
/* toggle patch description */
if (typeof patch.getDescription === 'function' && patch.getDescription()) {
htmlElements.setText('patch-description', patch.getDescription()/* .replace(/\n/g, '<br/>') */);
//htmlElements.setTitle('patch-description', patch.getDescription());
htmlElements.addClass('row-patch-description', 'show');
} else {
htmlElements.setText('patch-description', '');
//htmlElements.setTitle('patch-description', '');
htmlElements.removeClass('row-patch-description', 'show');
}
RomPatcherWeb.validateCurrentRom(_getChecksumStartOffset());
if (typeof settings.onloadpatch === 'function') {
settings.onloadpatch(binFile, embededPatchInfo, parsedPatch);
}
if (transferFakeFile) {
htmlElements.setFakeFile('patch', binFile.fileName);
}
} else {
_setToastError(_('Invalid patch file'));
}
} catch (ex) {
_setToastError(ex.message);
}
}
}
if (patch) {
htmlElements.removeClass('input-file-patch', 'invalid');
} else {
htmlElements.addClass('input-file-patch', 'invalid');
}
htmlElements.enableAll();
},
refreshRomFileName: function () {
if (romFile)
htmlElements.setFakeFile('rom', romFile.fileName);
},
pickEmbededFile: function (fileName) {
if (!currentEmbededPatches)
throw new Error('No embeded patches available');
else if (typeof fileName !== 'string')
throw new Error('Invalid embeded patch file name');
const selectPatch = htmlElements.get('select-patch');
for (var i = 0; i < selectPatch.children.length; i++) {
if (selectPatch.children[i].patchFileName === fileName) {
if (selectPatch.value != selectPatch.children[i].value) {
selectPatch.value = selectPatch.children[i].value;
/* create and dispatch change event */
const evt = new Event('change');
selectPatch.dispatchEvent(evt);
}
break;
}
}
},
initialize: function (newSettings, embededPatchInfo) {
if (initialized)
throw new Error('Rom Patcher JS was already initialized');
else if (loading)
throw new Error('Rom Patcher JS is already loading or has failed to load');
/* check incompatible browsers */
if (
typeof window === 'undefined' ||
typeof Worker !== 'function' ||
typeof Array.isArray !== 'function' ||
typeof window.addEventListener !== 'function'
// !document.createElement('div').classList instanceof DOMTokenList
)
throw new Error('Rom Patcher JS: incompatible browser');
/* queue script dependencies */
const missingDependencies = _getMissingDependencies();
loading = missingDependencies.length;
const onLoadScript = function () {
loading--;
if (loading === 0) {
try {
_initialize(newSettings, embededPatchInfo);
} catch (ex) {
_setToastError(ex.message);
htmlElements.disableAll();
}
}
};
const onErrorScript = function () {
throw new Error('Rom Patcher JS: error loading script ' + script.src);
};
console.log('Rom Patcher JS: loading ' + missingDependencies.length + ' dependencies');
missingDependencies.forEach(function (path) {
var script = document.createElement('script');
script.onload = onLoadScript;
script.onerror = onErrorScript;
script.src = path;
document.head.appendChild(script);
});
},
setEmbededPatches: function (embededPatchInfo) {
if (!currentEmbededPatches)
throw new Error('Rom Patcher JS: not in embeded patch mode');
/* embeded patches */
var validEmbededPatch = _checkEmbededPatchParameter(embededPatchInfo);
if (!validEmbededPatch)
throw new Error('Rom Patcher JS: invalid embeded patch parameter');
_fetchPatchFile(validEmbededPatch);
},
applyPatch: function () {
if (romFile && patch) {
const romPatcherOptions = {
requireValidation: settings.requireValidation,
removeHeader: RomPatcher.isRomHeadered(romFile) && htmlElements.isChecked('checkbox-alter-header'),
addHeader: RomPatcher.canRomGetHeader(romFile) && htmlElements.isChecked('checkbox-alter-header'),
fixChecksum: settings.fixChecksum,
outputSuffix: settings.outputSuffix
};
htmlElements.disableAll();
_setApplyButtonSpinner(true);
const embededPatchInfo = _getEmbededPatchInfo(patch._originalPatchFile.fileName);
webWorkerApply.postMessage(
{
romFileU8Array: romFile._u8array,
patchFileU8Array: patch._originalPatchFile._u8array,
romFileName: romFile.fileName,
patchFileName: patch._originalPatchFile.fileName,
patchExtraInfo: embededPatchInfo,
//romFileType:romFile.fileType,
options: romPatcherOptions
},
[
romFile._u8array.buffer,
patch._originalPatchFile._u8array.buffer
]
);
} else if (!romFile) {
_setToastError(_('No ROM provided'));
} else if (!patch) {
_setToastError(_('No patch file provided'));
}
},
calculateCurrentRomChecksums: function (force) {
if (romFile.fileSize > 67108864 && !force) {
htmlElements.setText('span-crc32', _('File is too big.') + ' <span class=\"clickable\" onclick=\"RomPatcherWeb.calculateCurrentRomChecksums(true)\">' + _('Force calculate checksum') + '</span>');
htmlElements.setText('span-md5', '');
htmlElements.setText('span-sha1', '');
htmlElements.enableAll();
return false;
}
htmlElements.setText('span-crc32', _('Calculating...'));
htmlElements.setText('span-md5', _('Calculating...'));
if (WEB_CRYPTO_AVAILABLE)
htmlElements.setText('span-sha1', _('Calculating...'));
htmlElements.setText('span-rom-info', '');
htmlElements.removeClass('row-info-rom', 'show');
htmlElements.disableAll();
webWorkerCrc.postMessage({ u8array: romFile._u8array, fileName: romFile.fileName, checksumStartOffset: _getChecksumStartOffset() }, [romFile._u8array.buffer]);
},
validateCurrentRom: function (checksumStartOffset) {
if (romFile && patch && typeof patch.validateSource === 'function') {
const validRom = RomPatcher.validateRom(romFile, patch, checksumStartOffset ?? 0);
if (validRom) {
htmlElements.addClass('input-file-rom', 'valid');
htmlElements.removeClass('input-file-rom', 'invalid');
_setToastError();
} else {
htmlElements.addClass('input-file-rom', 'invalid');
htmlElements.removeClass('input-file-rom', 'valid');
_setToastError(_('Source ROM checksum mismatch'));
}
if (typeof settings.onvalidaterom === 'function')
settings.onvalidaterom(romFile, validRom);
return validRom;
} else {
htmlElements.removeClass('input-file-rom', 'valid');
htmlElements.removeClass('input-file-rom', 'invalid');
_setToastError();
if (romFile && patch && typeof settings.onvalidaterom === 'function')
settings.onvalidaterom(romFile, true);
return (romFile && patch);
}
},
enable: function () {
htmlElements.enableAll();
},
disable: function () {
htmlElements.disableAll();
},
setErrorMessage: function (message, className) {
_setToastError(message, className);
},
translateUI: function (newLanguage) {
if (typeof newLanguage === 'object' && typeof newLanguage.language === 'string')
newLanguage = newLanguage.language;
if (typeof newLanguage === 'string')
settings.language = newLanguage;
const translatableElements = document.querySelectorAll('*[data-localize]');
for (var i = 0; i < translatableElements.length; i++) {
translatableElements[i].innerHTML = _(translatableElements[i].getAttribute('data-localize'));
}
},
getCurrentLanguage: function () {
return settings.language;
},
setSettings: function (newSettings) {
if (newSettings && typeof newSettings === 'object') {
if (typeof newSettings.language === 'string')
settings.language = newSettings.language;
if (typeof newSettings.outputSuffix === 'boolean')
settings.outputSuffix = newSettings.outputSuffix;
if (typeof newSettings.fixChecksum === 'boolean')
settings.fixChecksum = newSettings.fixChecksum;
if (typeof newSettings.requireValidation === 'boolean')
settings.requireValidation = newSettings.requireValidation;
if (typeof newSettings.oninitialize === 'function')
settings.oninitialize = newSettings.oninitialize;
else if (typeof newSettings.oninitialize !== 'undefined')
settings.oninitialize = null;
if (typeof newSettings.onloadrom === 'function')
settings.onloadrom = newSettings.onloadrom;
else if (typeof newSettings.onloadrom !== 'undefined')
settings.onloadrom = null;
if (typeof newSettings.onvalidaterom === 'function')
settings.onvalidaterom = newSettings.onvalidaterom;
else if (typeof newSettings.onvalidaterom !== 'undefined')
settings.onvalidaterom = null;
if (typeof newSettings.onloadpatch === 'function')
settings.onloadpatch = newSettings.onloadpatch;
else if (typeof newSettings.onloadpatch !== 'undefined')
settings.onloadpatch = null;
if (typeof newSettings.onpatch === 'function')
settings.onpatch = newSettings.onpatch;
else if (typeof newSettings.onpatch !== 'undefined')
settings.onpatch = null;
}
RomPatcherWeb.translateUI();
}
}
}());
/* ZIP manager */
const ZIPManager = (function (romPatcherWeb) {
const _ = romPatcherWeb._;
const htmlElements = romPatcherWeb.getHtmlElements();
const _setRomInputSpinner = function (status) {
htmlElements.setSpinner('rom', status);
};
const _setPatchInputSpinner = function (status) {
htmlElements.setSpinner('patch', status);
};
const ZIP_MAGIC = '\x50\x4b\x03\x04';
const FILTER_PATCHES = /\.(ips|ups|bps|aps|rup|ppf|mod|xdelta|vcdiff)$/i;
//const FILTER_ROMS=/(?<!\.(txt|diz|rtf|docx?|xlsx?|html?|pdf|jpe?g|gif|png|bmp|webp|zip|rar|7z))$/i; //negative lookbehind is not compatible with Safari https://stackoverflow.com/a/51568859
const FILTER_NON_ROMS = /(\.(txt|diz|rtf|docx?|xlsx?|html?|pdf|jpe?g|gif|png|bmp|webp|zip|rar|7z))$/i;
const DIALOG_BACKDROP_FALLBACK_ID = 'rom-patcher-dialog-zip-backdrop';
const dialogZip = document.createElement('dialog');
dialogZip.id = 'rom-patcher-dialog-zip';
dialogZip.className = 'rom-patcher-dialog';
const dialogZipMessage = document.createElement('div');
dialogZipMessage.id = 'rom-patcher-dialog-zip-message';
dialogZip.appendChild(dialogZipMessage);
const dialogZipList = document.createElement('ul');
dialogZipList.id = 'rom-patcher-dialog-zip-file-list';
dialogZip.appendChild(dialogZipList);
const _filterEntriesRoms = function (zipEntries) {
/* get ROM files only by ignoring patches */
const filteredEntries = zipEntries.filter(function (elem) {
return !FILTER_NON_ROMS.test(elem.filename) && !FILTER_PATCHES.test(elem.filename);
});
_sortFileEntries(filteredEntries);
return filteredEntries;
}
const _filterEntriesPatches = function (zipEntries) {
/* get ROM files only by ignoring patches */
const filteredEntries = zipEntries.filter(function (elem) {
return FILTER_PATCHES.test(elem.filename);
});
_sortFileEntries(filteredEntries);
return filteredEntries;
}
const _sortFileEntries = function (entries) {
/* sort patch files by name and folder */
entries
.sort(function (file1, file2) {
return file1.filename.toLowerCase().replace(/\.\S+$/, '').localeCompare(file2.filename.toLowerCase().replace(/\.\S+$/, ''));
})
.sort(function (file1, file2) {
var file1Folder = file1.filename.indexOf('/') === -1 ? 0 : 1;
var file2Folder = file2.filename.indexOf('/') === -1 ? 0 : 1;
return file1Folder - file2Folder;
});
}
const _unzipEntry = function (zippedEntry, onUnzip) {
htmlElements.disableAll();
if (onUnzip === romPatcherWeb.provideRomFile) {
_setRomInputSpinner(true);
} else if (onUnzip === romPatcherWeb.providePatchFile) {
_setPatchInputSpinner(true);
} else {
throw new Error('ZIPManager._unzipEntry: invalid onUnzip callback');
}
zippedEntry.getData(new zip.BlobWriter(), function (blob) {
const fileReader = new FileReader();
fileReader.onload = function () {
const binFile = new BinFile(this.result);
binFile.fileName = zippedEntry.filename;
/* transfer files to input elements */
if (onUnzip === romPatcherWeb.provideRomFile) {
_setRomInputSpinner(false);
onUnzip(binFile, true);
} else {
_setPatchInputSpinner(false);
onUnzip(binFile, true);
}
};
fileReader.readAsArrayBuffer(blob);
});
};
const _showFilePicker = function (zipEntries, onUnzip) {
const _evtClickEntry = function (evt) {
if (typeof dialogZip.close === 'function') {
dialogZip.close();
} else {
document.getElementById(DIALOG_BACKDROP_FALLBACK_ID).style.display = 'none';
}
_unzipEntry(this.zipEntry, onUnzip);
}
if (onUnzip === romPatcherWeb.provideRomFile) {
dialogZipMessage.innerHTML = _('ROM file:');
} else if (onUnzip === romPatcherWeb.providePatchFile) {
dialogZipMessage.innerHTML = _('Patch file:');
} else {
throw new Error('ZIPManager._unzipEntry: invalid onUnzip callback');
}
dialogZipList.innerHTML = '';
for (var i = 0; i < zipEntries.length; i++) {
var li = document.createElement('li');
li.zipEntry = zipEntries[i];
li.innerHTML = zipEntries[i].filename;
li.title = zipEntries[i].filename;
li.addEventListener('click', _evtClickEntry);
dialogZipList.appendChild(li);
}
if (typeof dialogZip.showModal === 'function') {
if (!document.getElementById(dialogZip.id))
document.body.appendChild(dialogZip);
dialogZip.showModal();
} else {
/* fallback for incompatible browsers */
if (!document.getElementById(DIALOG_BACKDROP_FALLBACK_ID)) {
const dialogBackdrop = document.createElement('div');
dialogBackdrop.id = DIALOG_BACKDROP_FALLBACK_ID;
dialogBackdrop.className = 'rom-patcher-dialog-backdrop';
dialogBackdrop.style.position = 'fixed';
dialogBackdrop.style.top = '0';
dialogBackdrop.style.left = '0';
dialogBackdrop.style.width = '100%';
dialogBackdrop.style.height = '100%';
dialogBackdrop.style.display = 'none';
dialogBackdrop.style.alignContent = 'center';
dialogBackdrop.style.justifyContent = 'center';
dialogBackdrop.style.zIndex = 1000;
dialogBackdrop.appendChild(dialogZip);
document.body.appendChild(dialogBackdrop);
}
document.getElementById(DIALOG_BACKDROP_FALLBACK_ID).style.display = 'flex';
dialogZip.style.display = 'block';
}
}
const _unzipError = function (zipReader) {
if (zipReader.message)
console.error('zip.js: ' + zipReader.message);
romPatcherWeb.enable();
romPatcherWeb.setErrorMessage(_('Error unzipping file'), 'error');
};
return {
isZipFile: function (binFile) {
binFile.seek(0);
return binFile.getExtension() !== 'jar' && binFile.readString(4).startsWith(ZIP_MAGIC);
},
unzipRoms: function (arrayBuffer) {
zip.createReader(
new zip.BlobReader(new Blob([arrayBuffer])),
/* success */
function (zipReader) {
zipReader.getEntries(function (zipEntries) {
const filteredEntries = _filterEntriesRoms(zipEntries);
if (filteredEntries.length === 1) {
_unzipEntry(filteredEntries[0], romPatcherWeb.provideRomFile);
} else if (filteredEntries.length > 1) {
_showFilePicker(filteredEntries, romPatcherWeb.provideRomFile);
romPatcherWeb.enable();
} else {
/* no possible patchable files found in zip, treat zip file as ROM file */
romPatcherWeb.calculateCurrentRomChecksums();
}
});
},
/* failed */
_unzipError
);
},
unzipPatches: function (arrayBuffer) {
zip.createReader(
new zip.BlobReader(new Blob([arrayBuffer])),
/* success */
function (zipReader) {
zipReader.getEntries(function (zipEntries) {
const filteredEntries = _filterEntriesPatches(zipEntries);
if (filteredEntries.length === 1) {
_unzipEntry(filteredEntries[0], romPatcherWeb.providePatchFile);
} else if (filteredEntries.length > 1) {
_showFilePicker(filteredEntries, romPatcherWeb.providePatchFile);
} else {
romPatcherWeb.providePatchFile(null);
}
});
},
/* failed */
_unzipError
);
},
unzipAny: function (arrayBuffer) {
zip.createReader(
new zip.BlobReader(new Blob([arrayBuffer])),
/* success */
function (zipReader) {
zipReader.getEntries(function (zipEntries) {
const filteredEntriesRoms = _filterEntriesRoms(zipEntries);
const filteredEntriesPatches = _filterEntriesPatches(zipEntries);
if (filteredEntriesRoms.length && filteredEntriesPatches.length === 0) {
if (filteredEntriesRoms.length === 1) {
_unzipEntry(filteredEntriesRoms[0], romPatcherWeb.provideRomFile);
} else {
_showFilePicker(filteredEntriesRoms, romPatcherWeb.provideRomFile);
romPatcherWeb.enable();
}
} else if (filteredEntriesPatches.length && filteredEntriesRoms.length === 0) {
if (filteredEntriesPatches.length === 1) {
_unzipEntry(filteredEntriesPatches[0], romPatcherWeb.providePatchFile);
} else {
_showFilePicker(filteredEntriesPatches, romPatcherWeb.providePatchFile);
}
} else {
console.warn('ZIPManager.unzipAny: zip file contains both ROMs and patches, cannot guess');
}
});
},
/* failed */
_unzipError
);
},
unzipEmbededPatches: function (arrayBuffer, embededPatchesInfo) {
zip.createReader(
new zip.BlobReader(new Blob([arrayBuffer])),
/* success */
function (zipReader) {
zipReader.getEntries(function (zipEntries) {
const filteredEntries = _filterEntriesPatches(zipEntries);
if (filteredEntries.length) {
const selectablePatches = [];
const optionalPatches = [];
for (var i = 0; i < filteredEntries.length; i++) {
const embededPatchInfo = embededPatchesInfo.find((embededPatchInfo) => embededPatchInfo.file === filteredEntries[i].filename);
if (embededPatchInfo && embededPatchInfo.optional)
optionalPatches.push(filteredEntries[i]);
else
selectablePatches.push(filteredEntries[i]);
}
if (!selectablePatches.length) {
romPatcherWeb.setErrorMessage(_('No valid non-optional patches found in ZIP'), 'error');
romPatcherWeb.disable();
throw new Error('No valid non-optional patches found in ZIP');
}
if (embededPatchesInfo.length && embededPatchesInfo.length === 1 && selectablePatches.length === 1)
embededPatchesInfo[0].file = selectablePatches[0].filename;
for (var i = 0; i < selectablePatches.length; i++) {
const embededPatchInfo = embededPatchesInfo.find((embededPatchInfo) => embededPatchInfo.file === selectablePatches[i].filename);
const option = document.createElement('option');
option.innerHTML = embededPatchInfo && embededPatchInfo.name ? embededPatchInfo.name : selectablePatches[i].filename;
option.value = i;
option.patchFileName = selectablePatches[i].filename;
htmlElements.get('select-patch').appendChild(option);
}
htmlElements.get('select-patch')._unzipSelectedPatch = function (fileIndex) {
_unzipEntry(selectablePatches[fileIndex], romPatcherWeb.providePatchFile);
};
for (var i = 0; i < optionalPatches.length; i++) {
const embededPatchInfo = embededPatchesInfo.find((embededPatchInfo) => embededPatchInfo.file === optionalPatches[i].filename);
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.value = i;
checkbox.checked = false;
checkbox.disabled = true;
embededPatchInfo.checkbox = checkbox;
const label = document.createElement('label');
label.className = 'rom-patcher-checkbox-optional-patch';
label.appendChild(checkbox);
label.appendChild(document.createTextNode(embededPatchInfo.name || embededPatchInfo.file));
if (embededPatchInfo.description)
label.title = embededPatchInfo.description;
htmlElements.get('container-optional-patches').appendChild(label);
optionalPatches[i].getData(new zip.BlobWriter(), function (blob) {
const fileReader = new FileReader();
fileReader.onload = function () {
const binFile = new BinFile(this.result);
binFile.fileName = 'optional_patch.unk';
embededPatchInfo.parsedPatch = RomPatcher.parsePatchFile(binFile);
checkbox.disabled = false;
};
fileReader.readAsArrayBuffer(blob);
});
}
if (optionalPatches.length === 1)
htmlElements.show('container-optional-patches');
if (selectablePatches.length === 1)
htmlElements.addClass('select-patch', 'single');
else
htmlElements.addClass('select-patch', 'multiple');
htmlElements.setEnabled('select-patch', false);
htmlElements.hide('span-loading-embeded-patch');
htmlElements.show('select-patch');
_unzipEntry(selectablePatches[0], romPatcherWeb.providePatchFile);
} else {
romPatcherWeb.setErrorMessage(_('No valid patches found in ZIP'), 'error');
romPatcherWeb.disable();
}
});
},
/* failed */
_unzipError
);
}
}
})(RomPatcherWeb);
/* Patch Builder */
const PatchBuilderWeb = (function (romPatcherWeb) {
var originalRom, modifiedRom;
const isBrowserSafari = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent); /* Safari userAgent does not include word Chrome, Chrome includes both! */
/* localization */
const _ = function (str) {
const language = romPatcherWeb.getCurrentLanguage();
return ROM_PATCHER_LOCALE[language] && ROM_PATCHER_LOCALE[language][str] ? ROM_PATCHER_LOCALE[language][str] : str
};
const _setCreateButtonSpinner = function (status) {
if (status) {
document.getElementById('patch-builder-button-create').innerHTML = '<span class="rom-patcher-spinner"></span> ' + _('Creating patch...');
} else {
document.getElementById('patch-builder-button-create').innerHTML = _('Create patch');
}
}
const _setToastError = function (errorMessage, className) {
const row = document.getElementById('patch-builder-row-error-message');
const span = document.getElementById('patch-builder-error-message');
if (row && span) {
if (errorMessage) {
row.classList.add('show');
span.innerHTML = errorMessage;
} else {
row.classList.remove('show');
span.innerHTML = '';
}
if (className === 'warning')
span.classList.add('warning');
else
span.classList.remove('warning');
} else {
if (className === 'warning')
console.warn('Patch Builder JS: ' + errorMessage);
else
console.error('Patch Builder JS: ' + errorMessage);
}
}
const _setElementsStatus = function (status) {
document.getElementById('patch-builder-input-file-original').disabled = !status;
document.getElementById('patch-builder-input-file-modified').disabled = !status;
document.getElementById('patch-builder-select-patch-type').disabled = !status;
if (originalRom && modifiedRom && status) {
document.getElementById('patch-builder-button-create').disabled = !status;
} else {
document.getElementById('patch-builder-button-create').disabled = true
}
};
var webWorkerCreate;
var initialized = false;
return {
isInitialized: function () {
return initialized;
},
initialize: function () {
if (initialized)
throw new Error('Patch Builder JS was already initialized');
else if (!romPatcherWeb.isInitialized())
throw new Error('Rom Patcher JS must be initialized before Patch Builder JS');
if (!document.getElementById('patch-builder-button-create') || document.getElementById('patch-builder-button-create').tagName !== 'BUTTON') {
console.error('Patch Builder JS: button#patch-builder-button-create not found');
throw new Error('Patch Builder JS: button#patch-builder-button-create not found');
}
if (!document.getElementById('patch-builder-select-patch-type') || document.getElementById('patch-builder-select-patch-type').tagName !== 'SELECT') {
console.error('Patch Builder JS: select#patch-builder-select-patch-type not found');
throw new Error('Patch Builder JS: select#patch-builder-select-patch-type not found');
}
if (!document.getElementById('patch-builder-input-file-original') || document.getElementById('patch-builder-input-file-original').tagName !== 'INPUT' || document.getElementById('patch-builder-input-file-original').type !== 'file') {
console.error('Patch Builder JS: input[type=file]#patch-builder-input-file-original not found');
throw new Error('Patch Builder JS: input[type=file]#patch-builder-input-file-original not found');
}
if (!document.getElementById('patch-builder-input-file-modified') || document.getElementById('patch-builder-input-file-modified').tagName !== 'INPUT' || document.getElementById('patch-builder-input-file-modified').type !== 'file') {
console.error('Patch Builder JS: input[type=file]#patch-builder-input-file-modified not found');
throw new Error('Patch Builder JS: input[type=file]#patch-builder-input-file-modified not found');
}
if (!isBrowserSafari) {
document.getElementById('patch-builder-input-file-original').classList.add('no-file-selector-button');
document.getElementById('patch-builder-input-file-modified').classList.add('no-file-selector-button');
}
webWorkerCreate = new Worker(ROM_PATCHER_JS_PATH + 'RomPatcher.webworker.create.js');
webWorkerCreate.onmessage = event => { // listen for events from the worker
//retrieve arraybuffers back from webworker
originalRom._u8array = event.data.originalRomU8Array;
modifiedRom._u8array = event.data.modifiedRomU8Array;
_setElementsStatus(true);
_setCreateButtonSpinner(false);
const patchFile = new BinFile(event.data.patchFileU8Array.buffer);
patchFile.fileName = modifiedRom.getName() + '.' + document.getElementById('patch-builder-select-patch-type').value;
patchFile.save();
_setToastError();
};
webWorkerCreate.onerror = event => { // listen for events from the worker
_setElementsStatus(true);
_setCreateButtonSpinner(false);
_setToastError('webWorkerCreate error: ' + event.message);
};
document.getElementById('patch-builder-button-create').disabled = true;
document.getElementById('patch-builder-input-file-original').addEventListener('change', function () {
if (this.files && this.files.length) {
_setElementsStatus(false);
this.classList.remove('empty');
originalRom = new BinFile(this.files[0], function (evt) {
_setElementsStatus(true);
if (RomPatcher.isRomTooBig(originalRom))
_setToastError(_('Using big files is not recommended'), 'warning');
else if (ZIPManager.isZipFile(originalRom))
_setToastError(_('Patch creation is not compatible with zipped ROMs'), 'warning');
});
}
});
document.getElementById('patch-builder-input-file-modified').addEventListener('change', function () {
_setElementsStatus(false);
this.classList.remove('empty');
modifiedRom = new BinFile(this.files[0], function (evt) {
_setElementsStatus(true);
if (RomPatcher.isRomTooBig(modifiedRom))
_setToastError(_('Using big files is not recommended'), 'warning');
else if (ZIPManager.isZipFile(modifiedRom))
_setToastError(_('Patch creation is not compatible with zipped ROMs'), 'warning');
});
});
document.getElementById('patch-builder-button-create').addEventListener('click', function () {
_setElementsStatus(false);
_setCreateButtonSpinner(true);
webWorkerCreate.postMessage(
{
originalRomU8Array: originalRom._u8array,
modifiedRomU8Array: modifiedRom._u8array,
format: document.getElementById('patch-builder-select-patch-type').value
}, [
originalRom._u8array.buffer,
modifiedRom._u8array.buffer
]
);
});
console.log('Patch Builder JS initialized');
initialized = true;
_setElementsStatus(true);
}
}
}(RomPatcherWeb));
const ROM_PATCHER_LOCALE = {
'fr': {
'Creator mode': 'Mode créateur',
'Settings': 'Configurations',
'Use patch name for output': 'Utiliser le nom du patch pour renommer la ROM une fois patchée',
'Light theme': 'Thème Clair',
'Apply patch': 'Appliquer le patch',
'ROM file:': 'Fichier ROM:',
'Patch file:': 'Fichier patch:',
'Remove %s header': 'Supprimer l\'en-tête %s',
//'Add %s header': 'Add %s header',
'Compatible formats:': 'Formats compatibles:',
//'Description:': 'Description:',
//'Required ROM:': 'Required ROM:',
//'Required %s:': 'Required %s:',
'Applying patch...': 'Application du patch...',
'Downloading...': 'Téléchargement...',
'Unzipping...': 'Décompresser...',
'Create patch': 'Créer le patch',
'Original ROM:': 'ROM originale:',
'Modified ROM:': 'ROM modifiée:',
'Patch type:': 'Type de patch:',
'Creating patch...': 'Création du patch...',
'Source ROM checksum mismatch': 'Non-concordance de la somme de contrôle de la ROM source',
'Target ROM checksum mismatch': 'Non-concordance de la somme de contrôle de la ROM cible',
'Patch checksum mismatch': 'Non-concordance de la somme de contrôle du patch',
'Error downloading %s': 'Erreur lors du téléchargement du patch',
'Error unzipping file': 'Erreur lors de la décompression du fichier',
'Invalid patch file': 'Fichier patch invalide',
'Using big files is not recommended': 'L\'utilisation de gros fichiers n\'est pas recommandée'
},
'de': {
'Creator mode': 'Erstellmodus',
'Settings': 'Einstellungen',
'Use patch name for output': 'Output ist Name vom Patch',
'Fix ROM header checksum': 'Prüfsumme im ROM Header korrigieren',
'Light theme': 'Helles Design',
'Apply patch': 'Patch anwenden',
'ROM file:': 'ROM-Datei:',
'Patch file:': 'Patch-Datei:',
'Remove %s header': 'Header entfernen %s',
//'Add %s header': 'Add %s header',
'Compatible formats:': 'Unterstützte Formate:',
//'Description:': 'Description:',
//'Required ROM:': 'Required ROM:',
//'Required %s:': 'Required %s:',
'Applying patch...': 'Patch wird angewandt...',
'Downloading...': 'Herunterladen...',
'Unzipping...': 'Entpacken...',
'Create patch': 'Patch erstellen',
'Original ROM:': 'Originale ROM:',
'Modified ROM:': 'Veränderte ROM:',
'Patch type:': 'Patch-Format:',
'Creating patch...': 'Patch wird erstellt...',
'Source ROM checksum mismatch': 'Prüfsumme der Input-ROM stimmt nicht überein',
'Target ROM checksum mismatch': 'Prüfsumme der Output-ROM stimmt nicht überein',
'Patch checksum mismatch': 'Prüfsumme vom Patch stimmt nicht überein',
'Error downloading %s': 'Fehler beim Herunterladen vom %s',
'Error unzipping file': 'Fehler beim Entpacken',
'Invalid patch file': 'Ungültiger Patch',
'Using big files is not recommended': 'Große Dateien zu verwenden ist nicht empfohlen'
},
'es': {
'Creator mode': 'Modo creador',
'Settings': 'Configuración',
'Use patch name for output': 'Guardar con nombre del parche',
'Fix ROM header checksum': 'Corregir checksum cabecera ROM',
'Light theme': 'Tema claro',
'Apply patch': 'Aplicar parche',
'ROM file:': 'Archivo ROM:',
'Patch file:': 'Archivo parche:',
'Remove %s header': 'Quitar cabecera %s',
'Add %s header': 'Añadir cabecera %s',
'Compatible formats:': 'Formatos compatibles:',
'Description:': 'Descripción:',
'Required ROM:': 'ROM requerida:',
'Required %s:': '%s requerido:',
'Applying patch...': 'Aplicando parche...',
'Downloading...': 'Descargando...',
'Unzipping...': 'Descomprimiendo...',
'Calculating...': 'Calculando...',
'Force calculate checksum': 'Forzar cálculo de checksum',
'Create patch': 'Crear parche',
'Original ROM:': 'ROM original:',
'Modified ROM:': 'ROM modificada:',
'Patch type:': 'Tipo de parche:',
'Creating patch...': 'Creando parche...',
'Source ROM checksum mismatch': 'Checksum de ROM original no válida',
'Target ROM checksum mismatch': 'Checksum de ROM creada no válida',
'Patch checksum mismatch': 'Checksum de parche no válida',
'Error downloading %s': 'Error descargando %s',
'Error unzipping file': 'Error descomprimiendo archivo',
'Invalid patch file': 'Archivo de parche no válido',
'Using big files is not recommended': 'No es recomendable usar archivos muy grandes',
'SNES copier': 'copión SNES'
},
'it': {
'Creator mode': 'Modalità creatore',
'Settings': 'Impostazioni',
'Use patch name for output': 'Usa il nome della patch per uscita',
'Light theme': 'Tema chiaro',
'Apply patch': 'Applica patch',
'ROM file:': 'File ROM:',
'Patch file:': 'File patch:',
'Remove %s header': 'Rimuovi header %s',
//'Add %s header': 'Add %s header',
'Compatible formats:': 'Formati:',
//'Description:': 'Description:',
//'Required ROM:': 'Required ROM:',
//'Required %s:': 'Required %s:',
'Applying patch...': 'Applica patch...',
'Downloading...': 'Scaricamento...',
'Unzipping...': 'Estrazione...',
'Create patch': 'Crea patch',
'Original ROM:': 'ROM originale:',
'Modified ROM:': 'ROM modificata:',
'Patch type:': 'Tipologia patch:',
'Creating patch...': 'Creazione patch...',
'Source ROM checksum mismatch': 'Checksum della ROM sorgente non valido',
'Target ROM checksum mismatch': 'Checksum della ROM destinataria non valido',
'Patch checksum mismatch': 'Checksum della patch non valido',
'Error downloading %s': 'Errore di scaricamento %s',
'Error unzipping file': 'Errore estrazione file',
'Invalid patch file': 'File della patch non valido',
'Using big files is not recommended': 'Non è raccomandato usare file di grandi dimensioni'
},
'nl': {
'Creator mode': 'Creator-modus',
'Settings': 'Settings',
'Use patch name for output': 'Use patch name for output',
'Light theme': 'Light theme',
'Apply patch': 'Pas patch toe',
'ROM file:': 'ROM bestand:',
'Patch file:': 'Patch bestand:',
'Remove %s header': 'Verwijder rubriek %s',
//'Add %s header': 'Add %s header',
'Compatible formats:': 'Compatibele formaten:',
//'Description:': 'Description:',
//'Required ROM:': 'Required ROM:',
//'Required %s:': 'Required %s:',
'Applying patch...': 'Patch toepassen...',
'Downloading...': 'Downloaden...',
'Unzipping...': 'Uitpakken...',
'Create patch': 'Maak patch',
'Original ROM:': 'Originale ROM:',
'Modified ROM:': 'Aangepaste ROM:',
'Patch type:': 'Type patch:',
'Creating patch...': 'Patch maken...',
'Source ROM checksum mismatch': 'Controlesom van bron-ROM komt niet overeen',
'Target ROM checksum mismatch': 'Controlesom van doel-ROM komt niet overeen',
'Patch checksum mismatch': 'Controlesom van patch komt niet overeen',
'Error downloading %s': 'Fout bij downloaden van patch',
'Error unzipping file': 'Fout bij uitpakken van bestand',
'Invalid patch file': 'Ongeldig patchbestand',
'Using big files is not recommended': 'Het gebruik van grote bestanden wordt niet aanbevolen'
},
'sv': {
'Creator mode': 'Skaparläge',
'Settings': 'Settings',
'Use patch name for output': 'Use patch name for output',
'Light theme': 'Light theme',
'Apply patch': 'Tillämpa korrigeringsfil',
'ROM file:': 'ROM-fil:',
'Patch file:': 'Korrigeringsfil:',
'Remove %s header': 'Ta bort rubrik %s',
//'Add %s header': 'Add %s header',
'Compatible formats:': 'Kompatibla format:',
//'Description:': 'Description:',
//'Required ROM:': 'Required ROM:',
//'Required %s:': 'Required %s:',
'Applying patch...': 'Tillämpar korrigeringsfil...',
'Downloading...': 'Ladda ner...',
'Unzipping...': 'Packa upp...',
'Create patch': 'Skapa korrigeringsfil',
'Original ROM:': 'Original-ROM:',
'Modified ROM:': 'Modifierad ROM:',
'Patch type:': 'Korrigeringsfil-typ:',
'Creating patch...': 'Skapa korrigeringsfil...',
'Source ROM checksum mismatch': 'ROM-källans kontrollsumman matchar inte',
'Target ROM checksum mismatch': 'ROM-målets kontrollsumman matchar inte',
'Patch checksum mismatch': 'korrigeringsfilens kontrollsumman matchar inte',
'Error downloading %s': 'Fel vid nedladdning av korrigeringsfilen',
'Error unzipping file': 'Det gick inte att packa upp filen',
'Invalid patch file': 'Ogiltig korrigeringsfil',
'Using big files is not recommended': 'Användning av stora filer rekommenderas inte'
},
'ca': {
'Creator mode': 'Mode creador',
'Settings': 'Configuració',
'Use patch name for output': 'Desar amb nom del pedaç',
'Light theme': 'Tema clar',
'Apply patch': 'Aplicar pedaç',
'ROM file:': 'Arxiu ROM:',
'Patch file:': 'Arxiu pedaç:',
'Remove %s header': 'Treure capçalera %s',
'Add %s header': 'Afegir capçalera %s',
'Compatible formats:': 'Formats compatibles:',
'Description:': 'Descripció:',
'Required ROM:': 'ROM requerida:',
'Required %s:': '%s requerit:',
'Applying patch...': 'Aplicant pedaç...',
'Downloading...': 'Descarregant...',
'Unzipping...': 'Descomprimint...',
'Create patch': 'Crear pedaç',
'Original ROM:': 'ROM original:',
'Modified ROM:': 'ROM modificada:',
'Patch type:': 'Tipus de pedaç:',
'Creating patch...': 'Creant pedaç...',
'Source ROM checksum mismatch': 'Checksum de ROM original no vàlida',
'Target ROM checksum mismatch': 'Checksum de ROM creada no vàlida',
'Patch checksum mismatch': 'Checksum de pedaç no vàlida',
'Error downloading %s': 'Error descarregant %s',
'Error unzipping file': 'Error descomprimint arxiu',
'Invalid patch file': 'Arxiu de pedaç no vàlid',
'Using big files is not recommended': 'No és recomanable usar arxius molt grans'
},
'ca-va': {
'Creator mode': 'Mode creador',
'Settings': 'Configuració',
'Use patch name for output': 'Guardar amb nom del pedaç',
'Light theme': 'Tema clar',
'Apply patch': 'Aplicar pedaç',
'ROM file:': 'Arxiu ROM:',
'Patch file:': 'Arxiu pedaç:',
'Remove %s header': 'Llevar capçalera %s',
'Add %s header': 'Afegir capçalera %s',
'Compatible formats:': 'Formats compatibles:',
'Description:': 'Descripció:',
'Required ROM:': 'ROM requerida:',
'Required %s:': '%s requerit:',
'Applying patch...': 'Aplicant pedaç...',
'Downloading...': 'Descarregant...',
'Unzipping...': 'Descomprimint...',
'Create patch': 'Crear pedaç',
'Original ROM:': 'ROM original:',
'Modified ROM:': 'ROM modificada:',
'Patch type:': 'Tipus de pedaç:',
'Creating patch...': 'Creant pedaç...',
'Source ROM checksum mismatch': 'Checksum de ROM original incorrecta',
'Target ROM checksum mismatch': 'Checksum de ROM creada incorrecta',
'Patch checksum mismatch': 'Checksum de pedaç incorrecte',
'Error downloading %s': 'Error descarregant %s',
'Error unzipping file': 'Error descomprimint arxiu',
'Invalid patch file': 'Arxiu de pedaç incorrecte',
'Using big files is not recommended': 'No és recomanable utilitzar arxius molt grans'
},
'ru': {
'Creator mode': 'Режим создания',
'Settings': 'Settings',
'Use patch name for output': 'Use patch name for output',
'Light theme': 'Light theme',
'Apply patch': 'Применить патч',
'ROM file:': 'Файл ROM:',
'Patch file:': 'Файл патча:',
'Remove %s header': 'Удалить заголовок перед применением',
//'Add %s header': 'Add %s header',
'Compatible formats:': 'Совместимые форматы:',
//'Description:': 'Description:',
//'Required ROM:': 'Required ROM:',
//'Required %s:': 'Required %s:',
'Applying patch...': 'Применяется патч...',
'Downloading...': 'Загрузка...',
'Unzipping...': 'Unzipping...',
'Create patch': 'Создать патч',
'Original ROM:': 'Оригинальный ROM:',
'Modified ROM:': 'Изменённый ROM:',
'Patch type:': 'Тип патча:',
'Creating patch...': 'Патч создаётся...',
'Source ROM checksum mismatch': 'Неправильная контрольная сумма входного ROM',
'Target ROM checksum mismatch': 'Неправильная контрольная сумма выходного ROM',
'Patch checksum mismatch': 'Неправильная контрольная сумма патча',
'Error downloading %s': 'Ошибка при скачивании патча',
'Error unzipping file': 'Error unzipping file',
'Invalid patch file': 'Неправильный файл патча',
'Using big files is not recommended': 'Не рекомендуется использовать большие файлы'
},
'pt-br': {
'Creator mode': 'Modo criador',
'Settings': 'Configurações',
'Use patch name for output': 'Usar o nome do patch na saída',
'Fix ROM header checksum': 'Consertar o checksum do cabeçalho da ROM',
'Light theme': 'Tema leve',
'Apply patch': 'Aplicar patch',
'ROM file:': 'Arquivo da ROM:',
'Patch file:': 'Arquivo do patch:',
'Remove %s header': 'Remover cabeçalho %s',
//'Add %s header': 'Add %s header',
'Compatible formats:': 'Formatos compatíveis:',
//'Description:': 'Description:',
//'Required ROM:': 'Required ROM:',
//'Required %s:': 'Required %s:',
'Applying patch...': 'Aplicando patch...',
'Downloading...': 'Baixando...',
'Unzipping...': 'Descompactando...',
'Create patch': 'Criar patch',
'Original ROM:': 'ROM original:',
'Modified ROM:': 'ROM modificada:',
'Patch type:': 'Tipo de patch:',
'Creating patch...': 'Criando o patch...',
'Source ROM checksum mismatch': 'O checksum da ROM original é inválido',
'Target ROM checksum mismatch': 'O checksum da ROM alvo é inválido',
'Patch checksum mismatch': 'O checksum do patch é inválido',
'Error downloading %s': 'Erro ao baixar o %s',
'Error unzipping file': 'Erro ao descompactar o arquivo',
'Invalid patch file': 'Arquivo do patch inválido',
'Using big files is not recommended': 'O uso de arquivos grandes não é recomendado'
},
'ja': {
'Creator mode': '作成モード',
'Settings': '設定',
'Use patch name for output': 'パッチと同じ名前で出力',
'Light theme': 'ライトテーマ',
'Apply patch': 'パッチを当て',
'ROM file:': 'ROMファィル',
'Patch file:': 'パッチファイル:',
'Remove %s header': 'ヘッダーを削除',
//'Add %s header': 'Add %s header',
'Compatible formats:': '互換性のあるフォーマット:',
//'Description:': 'Description:',
//'Required ROM:': 'Required ROM:',
//'Required %s:': 'Required %s:',
'Applying patch...': 'パッチを当ている…',
'Downloading...': 'ダウンロードしている…',
'Unzipping...': '解凍している…',
'Create patch': 'パッチを作成',
'Original ROM:': '元のROM',
'Modified ROM:': '変更されたROM',
'Patch type:': 'パッチのタイプ:',
'Creating patch...': 'パッチを作成している…',
'Source ROM checksum mismatch': 'ソースROMチェックサムの不一致',
'Target ROM checksum mismatch': 'ターゲットROMチェクサムの不一致',
'Patch checksum mismatch': 'バッチチェックサムの不一致',
'Error downloading %s': 'パッチのダウンロードエラー',
'Error unzipping file': 'パッチの解凍エラー',
'Invalid patch file': '無効なパッチエラー',
'Using big files is not recommended': '大きなファイルの使いはおすすめしない。'
},
'zh-cn': {
'Creator mode': '创建模式',
'Settings': '设置',
'Use patch name for output': '修改后ROM文件名和补丁保持一致',
'Light theme': '浅色主题',
'Apply patch': '打补丁',
'ROM file:': 'ROM文件',
'Patch file:': '补丁文件:',
'Remove %s header': '删除文件头',
//'Add %s header': 'Add %s header',
'Compatible formats:': '兼容补丁格式:',
//'Description:': 'Description:',
//'Required ROM:': 'Required ROM:',
//'Required %s:': 'Required %s:',
'Applying patch...': '正在打补丁……',
'Downloading...': '正在下载……',
'Unzipping...': '正在解压……',
'Create patch': '创建补丁',
'Original ROM:': '原始ROM',
'Modified ROM:': '修改后ROM',
'Patch type:': '补丁类型:',
'Creating patch...': '正在创建补丁……',
'Source ROM checksum mismatch': '原始ROM校验和不匹配',
'Target ROM checksum mismatch': '目标ROM校验和不匹配',
'Patch checksum mismatch': '补丁文件校验和不匹配',
'Error downloading %s': '下载出错:%s',
'Error unzipping file': '解压出错',
'Invalid patch file': '无效补丁',
'Using big files is not recommended': '不推荐使用大文件。'
},
'zh-tw': {
'Creator mode': '創作者模式',
'Settings': '設定',
'Use patch name for output': '修改後ROM檔名和patch保持一致',
'Fix ROM header checksum': '修正ROM檔頭校驗碼',
'Light theme': '淺色主題',
'Apply patch': '套用patch',
'ROM file:': 'ROM檔',
'Patch file:': 'patch檔',
'Remove %s header': '刪除檔頭',
//'Add %s header': 'Add %s header',
'Compatible formats:': '相容格式:',
//'Description:': 'Description:',
//'Required ROM:': 'Required ROM:',
//'Required %s:': 'Required %s:',
'Applying patch...': '套用patch中……',
'Downloading...': '下載中……',
'Unzipping...': '解壓中……',
'Create patch': '創建patch',
'Original ROM:': '原始ROM',
'Modified ROM:': '修改後ROM',
'Patch type:': 'patch類型',
'Creating patch...': '正在創建patch……',
'Source ROM checksum mismatch': '原始ROM校驗碼不匹配',
'Target ROM checksum mismatch': '目標ROM校驗碼不匹配',
'Patch checksum mismatch': 'patch檔校驗碼不匹配',
'Error downloading %s': '下載出錯:%s',
'Error unzipping file': '解壓出錯',
'Invalid patch file': '無效的patch檔',
'Using big files is not recommended': '不建議使用大檔。'
}
};