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

Merge branch 'marcrobledo:master' into main

This commit is contained in:
xenophile 2024-08-28 12:24:44 -07:00 committed by GitHub
commit 146231dde7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 217 additions and 193 deletions

View file

@ -6,7 +6,7 @@
*/
var PRECACHE_ID = 'rom-patcher-js';
var PRECACHE_VERSION = 'v30rc2b';
var PRECACHE_VERSION = 'v30rc4';
var PRECACHE_URLS = [
'/RomPatcher.js/', '/RomPatcher.js/index.html',
'/RomPatcher.js/manifest.json',

View file

@ -151,7 +151,7 @@
<button id="button-settings" class="btn-transparent"><img src="./webapp/icon_settings.svg" loading="lazy" class="icon settings" /> <span data-localize="yes">Settings</span></button>
</div>
Rom Patcher JS <small>v3.0 <a style="color:rgb(255, 197, 7)" href="legacy/" rel="nofollow">RC2</a></small> by <a href="/">Marc Robledo</a>
Rom Patcher JS <small>v3.0 <a style="color:rgb(255, 197, 7)" href="legacy/" rel="nofollow">RC4</a></small> by <a href="/">Marc Robledo</a>
<br />
<img src="./webapp/icon_github.svg" loading="lazy" class="icon github" /> <a href="https://github.com/marcrobledo/RomPatcher.js/" target="_blank">See on GitHub</a>
<img src="./webapp/icon_heart.svg" loading="lazy" class="icon heart" /> <a href="https://www.paypal.me/marcrobledo/5" target="_blank" rel="nofollow">Donate</a>

View file

@ -318,7 +318,8 @@ var RomPatcherWeb = (function () {
return fallback || 0;
},
setFakeFile: function (id, fileName) {
if (document.getElementById('rom-patcher-input-file-' + id)) {
const isBrowserSafari = /Safari/i.test(navigator.userAgent); /* safari does not show fake file name: https://pqina.nl/blog/set-value-to-file-input/#but-safari */
if (!isBrowserSafari && document.getElementById('rom-patcher-input-file-' + id)) {
try {
/* add a fake file to the input file, so it shows the chosen file name */
const fakeFile = new File(new Uint8Array(0), fileName);
@ -508,6 +509,15 @@ var RomPatcherWeb = (function () {
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);
@ -727,106 +737,110 @@ var RomPatcherWeb = (function () {
if (ZIPManager.isZipFile(binFile)) {
ZIPManager.unzipPatches(binFile._u8array.buffer);
} else {
const parsedPatch = RomPatcher.parsePatchFile(binFile);
if (parsedPatch) {
patch = parsedPatch;
_setPatchInputSpinner(false);
const embededPatchInfo = _getEmbededPatchInfo(binFile.fileName);
if (embededPatchInfo) {
/* custom crc32s validation */
if (embededPatchInfo.inputCrc32) {
patch.validateSource = function (romFile, headerSize) {
for (var i = 0; i < embededPatchInfo.inputCrc32.length; i++) {
if (embededPatchInfo.inputCrc32[i] === romFile.hashCRC32(headerSize))
return true;
try{
const parsedPatch = RomPatcher.parsePatchFile(binFile);
if (parsedPatch) {
patch = parsedPatch;
_setPatchInputSpinner(false);
const embededPatchInfo = _getEmbededPatchInfo(binFile.fileName);
if (embededPatchInfo) {
/* custom crc32s validation */
if (embededPatchInfo.inputCrc32) {
patch.validateSource = function (romFile, headerSize) {
for (var i = 0; i < embededPatchInfo.inputCrc32.length; i++) {
if (embededPatchInfo.inputCrc32[i] === romFile.hashCRC32(headerSize))
return true;
}
return false;
}
return false;
patch.getValidationInfo = function () {
return {
'type': 'CRC32',
'value': embededPatchInfo.inputCrc32
}
};
}
patch.getValidationInfo = function () {
return {
'type': 'CRC32',
'value': embededPatchInfo.inputCrc32
/* custom description */
if (embededPatchInfo.description) {
patch.getDescription = function () {
return embededPatchInfo.description;
}
};
}
/* 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();
/* 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
}
}
/*
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');
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);
}
}
/* 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');
_setToastError(_('Invalid patch file'));
}
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);
}
}
}

View file

@ -1,5 +1,5 @@
/*
* BinFile.js (last update: 2024-02-27)
* BinFile.js (last update: 2024-08-21)
* by Marc Robledo, https://www.marcrobledo.com
*
* a JS class for reading/writing sequentially binary data from/to a file
@ -188,7 +188,7 @@ BinFile.prototype.slice = function (offset, len, doNotClone) {
else if (len === 0)
throw new Error('zero length provided for slicing');
else
offset = Math.floor(offset);
len = Math.floor(len);
if (offset === 0 && len === this.fileSize && doNotClone)
return this;

View file

@ -204,7 +204,6 @@ APS.buildFromRoms=function(original, modified){
}else{
patch.addRecord(offset, differentBytes);
}
//NO se puede comentar??? why????
}
}

View file

@ -1,4 +1,4 @@
/* BPS module for Rom Patcher JS v20240721 - Marc Robledo 2016-2024 - http://www.marcrobledo.com/license */
/* BPS module for Rom Patcher JS v20240821 - Marc Robledo 2016-2024 - http://www.marcrobledo.com/license */
/* File format specification: https://www.romhacking.net/documents/746/ */
const BPS_MAGIC='BPS1';
@ -26,6 +26,10 @@ BPS.prototype.toString=function(){
s+='\n#Actions: '+this.actions.length;
return s
}
BPS.prototype.calculateFileChecksum = function () {
var patchFile = this.export();
return patchFile.hashCRC32(0, patchFile.fileSize - 4);
}
BPS.prototype.validateSource=function(romFile,headerSize){return this.sourceChecksum===romFile.hashCRC32(headerSize)}
BPS.prototype.getValidationInfo=function(){
return {
@ -121,7 +125,7 @@ BPS.fromFile=function(file){
patch.targetChecksum=file.readU32();
patch.patchChecksum=file.readU32();
if(patch.patchChecksum!==file.hashCRC32(0, file.fileSize - 4)){
if (patch.patchChecksum !== patch.calculateFileChecksum()) {
throw new Error('Patch checksum mismatch');
}
@ -238,10 +242,9 @@ BPS.buildFromRoms=function(original, modified, deltaMode){
patch.actions=createBPSFromFilesLinear(original, modified);
}
var patchFile=patch.export();
patch.sourceChecksum=original.hashCRC32();
patch.targetChecksum=modified.hashCRC32();
patch.patchChecksum=patchFile.hashCRC32(0, patchFile.fileSize - 4);
patch.patchChecksum = patch.calculateFileChecksum();
return patch;
}

194
test.js
View file

@ -29,8 +29,8 @@
- ROM: New Super Mario Bros. (USA, Australia).nds [CRC32=0197576a]
*/
const chalk=require('chalk');
const { existsSync }=require('fs');
const chalk = require('chalk');
const { existsSync } = require('fs');
const BinFile = require('./rom-patcher-js/modules/BinFile');
const HashCalculator = require('./rom-patcher-js/modules/HashCalculator');
@ -38,68 +38,68 @@ const RomPatcher = require('./rom-patcher-js/RomPatcher');
const TEST_PATH='_test_files/';
const TEST_PATCHES=[
const TEST_PATH = '_test_files/';
const TEST_PATCHES = [
{
title:'IPS - Super Mario Land 2 DX',
romFile:'Super Mario Land 2 - 6 Golden Coins (USA, Europe).gb',
romCrc32:0xd5ec24e4,
patchFile:'SML2DXv181.ips',
patchCrc32:0x0b742316,
patchDownload:'https://www.romhacking.net/hacks/3784/',
outputCrc32:0xf0799017
},{
title:'BPS - Samurai Kid translation',
romFile:'Samurai Kid (Japan).gbc',
romCrc32:0x44a9ddfb,
patchFile:'samurai_kid_en_v1.bps',
patchCrc32:0x2144df1c,
patchDownload:'https://www.romhacking.net/translations/6297/',
outputCrc32:0xed238edb
},{
title:'UPS - Mother 3 translation',
romFile:'Mother 3 (Japan).gba',
romCrc32:0x42ac9cb9,
patchFile:'mother3.ups',
patchCrc32:0x2144df1c,
patchDownload:'https://mother3.fobby.net/',
outputCrc32:0x8a3bc5a8
},{
title:'APS - Zelda OoT spanish translation',
romFile:'Legend of Zelda, The - Ocarina of Time (USA).z64',
romCrc32:0xcd16c529,
patchFile:'ZELDA64.APS',
patchCrc32:0x7b70119d,
patchDownload:'http://dorando.emuverse.com/projects/eduardo_a2j/zelda-ocarina-of-time.html',
outputCrc32:0x7866f1ca
},{
title:'Tekkaman Blade translation',
romFile:'Uchuu no Kishi - Tekkaman Blade (Japan).sfc',
romCrc32:0x7e107c35,
patchFile:'Tekkaman Blade v1.0.rup',
patchCrc32:0x621ab323,
patchDownload:'https://www.romhacking.net/hacks/4633/',
outputCrc32:0xe83e9b0a
},{
title:'NSMB Hack Domain Infusion',
romFile:'New Super Mario Bros. (USA, Australia).nds',
romCrc32:0x0197576a,
patchFile:'nsmb_infusion10a.xdelta',
patchCrc32:0xa211f97c,
patchDownload:'https://www.romhacking.net/hacks/2871/',
outputCrc32:0x9cecd976
title: 'IPS - Super Mario Land 2 DX',
romFile: 'Super Mario Land 2 - 6 Golden Coins (USA, Europe).gb',
romCrc32: 0xd5ec24e4,
patchFile: 'SML2DXv181.ips',
patchCrc32: 0x0b742316,
patchDownload: 'https://www.romhacking.net/hacks/3784/',
outputCrc32: 0xf0799017
}, {
title: 'BPS - Samurai Kid translation',
romFile: 'Samurai Kid (Japan).gbc',
romCrc32: 0x44a9ddfb,
patchFile: 'samurai_kid_en_v1.bps',
patchCrc32: 0x2144df1c,
patchDownload: 'https://www.romhacking.net/translations/6297/',
outputCrc32: 0xed238edb
}, {
title: 'UPS - Mother 3 translation',
romFile: 'Mother 3 (Japan).gba',
romCrc32: 0x42ac9cb9,
patchFile: 'mother3.ups',
patchCrc32: 0x2144df1c,
patchDownload: 'https://mother3.fobby.net/',
outputCrc32: 0x8a3bc5a8
}, {
title: 'APS - Zelda OoT spanish translation',
romFile: 'Legend of Zelda, The - Ocarina of Time (USA).z64',
romCrc32: 0xcd16c529,
patchFile: 'ZELDA64.APS',
patchCrc32: 0x7b70119d,
patchDownload: 'http://dorando.emuverse.com/projects/eduardo_a2j/zelda-ocarina-of-time.html',
outputCrc32: 0x7866f1ca
}, {
title: 'Tekkaman Blade translation',
romFile: 'Uchuu no Kishi - Tekkaman Blade (Japan).sfc',
romCrc32: 0x7e107c35,
patchFile: 'Tekkaman Blade v1.0.rup',
patchCrc32: 0x621ab323,
patchDownload: 'https://www.romhacking.net/hacks/4633/',
outputCrc32: 0xe83e9b0a
}, {
title: 'NSMB Hack Domain Infusion',
romFile: 'New Super Mario Bros. (USA, Australia).nds',
romCrc32: 0x0197576a,
patchFile: 'nsmb_infusion10a.xdelta',
patchCrc32: 0xa211f97c,
patchDownload: 'https://www.romhacking.net/hacks/2871/',
outputCrc32: 0x9cecd976
}
];
const _test=function(title, testFunction){
try{
const startTime=(new Date()).getTime();
const result=testFunction.call();
const executionTime=((new Date()).getTime() - startTime) / 1000;
console.log(chalk.greenBright('√ '+title + ' ('+executionTime+'s)'));
}catch(err){
console.log(chalk.redBright('× '+title + ' - failed with error: '+err.message));
const _test = function (title, testFunction) {
try {
const startTime = (new Date()).getTime();
const result = testFunction.call();
const executionTime = ((new Date()).getTime() - startTime) / 1000;
console.log(chalk.greenBright('√ ' + title + ' (' + executionTime + 's)'));
} catch (err) {
console.log(chalk.redBright('× ' + title + ' - failed with error: ' + err.message));
}
};
@ -111,14 +111,14 @@ const TEST_DATA = (new Uint8Array([
_test('HashCalculator integrity', function(){
if(HashCalculator.md5(TEST_DATA) !== '55c76e7e683fd7cd63c673c5df3efa6e')
_test('HashCalculator integrity', function () {
if (HashCalculator.md5(TEST_DATA) !== '55c76e7e683fd7cd63c673c5df3efa6e')
throw new Error('invalid MD5');
if(HashCalculator.crc32(TEST_DATA).toString(16) !== '903a031b')
if (HashCalculator.crc32(TEST_DATA) !== 0x903a031b)
throw new Error('invalid CRC32');
if(HashCalculator.adler32(TEST_DATA).toString(16) !== 'ef984205')
if (HashCalculator.adler32(TEST_DATA) !== 0xef984205)
throw new Error('invalid ADLER32');
if(HashCalculator.crc16(TEST_DATA).toString(16) !== '96e4')
if (HashCalculator.crc16(TEST_DATA) !== 0x96e4)
throw new Error('invalid SHA1');
});
@ -126,13 +126,21 @@ _test('HashCalculator integrity', function(){
const MODIFIED_TEST_DATA = (new Uint8Array([
98, 91, 64, 8, 35, 53, 122, 167, 52, 253, 222, 156, 247, 82, 227, 213, 22, 221, 17, 247, 107, 102, 164, 254, 221, 8, 207, 63, 117, 164, 223, 10, 1, 77, 87, 123, 48, 9, 111, 64, 233, 118, 1, 36, 1, 60, 208, 245, 136, 126, 29, 231, 168, 18, 125, 172, 11, 184, 81, 20, 16, 30, 154, 16, 236, 21, 5, 74, 255, 112, 171, 198, 185, 89, 2, 98, 45, 164, 214, 55, 103, 15, 217, 95, 212, 133, 184, 21, 67, 144, 198, 163, 76, 35, 248, 229, 163, 37, 103, 33, 193, 96, 77, 255, 117, 89, 193, 61, 64, 253, 119, 82, 49, 187, 195, 165, 205, 140, 222, 134, 249, 68, 224, 248, 144, 207, 18, 126
])).buffer;
['ips','bps','ppf','ups','aps','rup'].forEach(function(patchFormat){
_test('create and apply '+patchFormat.toUpperCase(), function(){
const originalFile=new BinFile(TEST_DATA);
const modifiedFile=new BinFile(MODIFIED_TEST_DATA);
const patch=RomPatcher.createPatch(originalFile, modifiedFile, patchFormat);
const patchedFile=RomPatcher.applyPatch(originalFile, patch, {requireValidation:true});
if(patchedFile.hashCRC32() !== modifiedFile.hashCRC32())
['ips', 'bps', 'ppf', 'ups', 'aps', 'rup'].forEach(function (patchFormat) {
_test('create and apply ' + patchFormat.toUpperCase(), function () {
const originalFile = new BinFile(TEST_DATA);
const modifiedFile = new BinFile(MODIFIED_TEST_DATA);
const patch = RomPatcher.createPatch(originalFile, modifiedFile, patchFormat);
const patchedFile = RomPatcher.applyPatch(originalFile, patch, { requireValidation: true });
if (patchFormat === 'bps') {
if (patch.patchChecksum !== patch.calculateFileChecksum())
throw new Error('invalid patch checksum');
else if (patch.export().hashCRC32() !== 0x2144df1c)
throw new Error('invalid BPS crc32');
}
if (patchedFile.hashCRC32() !== modifiedFile.hashCRC32())
throw new Error('modified and patched files\' crc32 do not match');
})
});
@ -140,40 +148,40 @@ const MODIFIED_TEST_DATA = (new Uint8Array([
TEST_PATCHES.forEach(function(patchInfo){
const patchPath=TEST_PATH+'patches/'+patchInfo.patchFile;
if(!existsSync(patchPath)){
console.log(chalk.yellow('! skipping patch '+patchInfo.title));
console.log(chalk.yellow(' patch file not found: '+patchInfo.patchFile));
console.log(chalk.yellow(' download patch at '+patchInfo.patchDownload));
TEST_PATCHES.forEach(function (patchInfo) {
const patchPath = TEST_PATH + 'patches/' + patchInfo.patchFile;
if (!existsSync(patchPath)) {
console.log(chalk.yellow('! skipping patch ' + patchInfo.title));
console.log(chalk.yellow(' patch file not found: ' + patchInfo.patchFile));
console.log(chalk.yellow(' download patch at ' + patchInfo.patchDownload));
return false;
}
const patchFile=new BinFile(patchPath);
if(patchFile.hashCRC32() !== patchInfo.patchCrc32){
const patchFile = new BinFile(patchPath);
if (patchFile.hashCRC32() !== patchInfo.patchCrc32) {
console.log(patchFile.hashCRC32().toString(16));
console.log(chalk.yellow('! skipping '+patchInfo.title+' test: invalid patch crc32'));
console.log(chalk.yellow(' download correct patch at '+patchInfo.patchDownload));
console.log(chalk.yellow('! skipping ' + patchInfo.title + ' test: invalid patch crc32'));
console.log(chalk.yellow(' download correct patch at ' + patchInfo.patchDownload));
return false;
}
const romPath=TEST_PATH+'roms/'+patchInfo.romFile;
if(!existsSync(romPath)){
console.log(chalk.yellow('! skipping patch '+patchInfo.title));
console.log(chalk.yellow(' ROM file not found: '+patchInfo.romFile));
const romPath = TEST_PATH + 'roms/' + patchInfo.romFile;
if (!existsSync(romPath)) {
console.log(chalk.yellow('! skipping patch ' + patchInfo.title));
console.log(chalk.yellow(' ROM file not found: ' + patchInfo.romFile));
return false;
}
const romFile=new BinFile(romPath);
if(romFile.hashCRC32() !== patchInfo.romCrc32){
console.log(chalk.yellow('! skipping '+patchInfo.title+' test: invalid ROM crc32'));
const romFile = new BinFile(romPath);
if (romFile.hashCRC32() !== patchInfo.romCrc32) {
console.log(chalk.yellow('! skipping ' + patchInfo.title + ' test: invalid ROM crc32'));
return false;
}
_test('patch '+patchInfo.title, function(){
const patch=RomPatcher.parsePatchFile(patchFile);
const patchedRom=RomPatcher.applyPatch(romFile, patch, {requireValidation:true});
if(patchedRom.hashCRC32() !== patchInfo.outputCrc32)
_test('patch ' + patchInfo.title, function () {
const patch = RomPatcher.parsePatchFile(patchFile);
const patchedRom = RomPatcher.applyPatch(romFile, patch, { requireValidation: true });
if (patchedRom.hashCRC32() !== patchInfo.outputCrc32)
throw new Error('invalid patched file crc32');
});
});