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

added support for APS (GBA), fixing #58

This commit is contained in:
Marc Robledo 2023-03-31 19:08:27 +02:00
parent 0769fae972
commit 1ea536ad27
9 changed files with 147 additions and 133 deletions

View file

@ -5,7 +5,7 @@ A ROM patcher made in HTML5.
* Supported formats: * Supported formats:
* IPS * IPS
* UPS * UPS
* APS * APS (N64/GBA)
* BPS * BPS
* RUP * RUP
* PPF * PPF

View file

@ -14,7 +14,7 @@
*/ */
var PRECACHE_ID='rom-patcher-js'; var PRECACHE_ID='rom-patcher-js';
var PRECACHE_VERSION='v27b'; var PRECACHE_VERSION='v28';
var PRECACHE_URLS=[ var PRECACHE_URLS=[
'/RomPatcher.js/','/RomPatcher.js/index.html', '/RomPatcher.js/','/RomPatcher.js/index.html',
'/RomPatcher.js/manifest.json', '/RomPatcher.js/manifest.json',
@ -41,7 +41,8 @@ var PRECACHE_URLS=[
'/RomPatcher.js/js/zip.js/inflate.js', '/RomPatcher.js/js/zip.js/inflate.js',
'/RomPatcher.js/js/formats/ips.js', '/RomPatcher.js/js/formats/ips.js',
'/RomPatcher.js/js/formats/ups.js', '/RomPatcher.js/js/formats/ups.js',
'/RomPatcher.js/js/formats/aps.js', '/RomPatcher.js/js/formats/aps_n64.js',
'/RomPatcher.js/js/formats/aps_gba.js',
'/RomPatcher.js/js/formats/bps.js', '/RomPatcher.js/js/formats/bps.js',
'/RomPatcher.js/js/formats/rup.js', '/RomPatcher.js/js/formats/rup.js',
'/RomPatcher.js/js/formats/ppf.js', '/RomPatcher.js/js/formats/ppf.js',

View file

@ -40,7 +40,8 @@
<script type="text/javascript" src="./js/formats/zip.js"></script> <script type="text/javascript" src="./js/formats/zip.js"></script>
<script type="text/javascript" src="./js/formats/ips.js"></script> <script type="text/javascript" src="./js/formats/ips.js"></script>
<script type="text/javascript" src="./js/formats/ups.js"></script> <script type="text/javascript" src="./js/formats/ups.js"></script>
<script type="text/javascript" src="./js/formats/aps.js"></script> <script type="text/javascript" src="./js/formats/aps_n64.js"></script>
<script type="text/javascript" src="./js/formats/aps_gba.js"></script>
<script type="text/javascript" src="./js/formats/bps.js"></script> <script type="text/javascript" src="./js/formats/bps.js"></script>
<script type="text/javascript" src="./js/formats/rup.js"></script> <script type="text/javascript" src="./js/formats/rup.js"></script>
<script type="text/javascript" src="./js/formats/ppf.js"></script> <script type="text/javascript" src="./js/formats/ppf.js"></script>
@ -179,7 +180,7 @@
<button id="button-settings" class="button-outer"><img src="style/icon_settings.svg" class="icon settings" /> <span data-localize="settings">Settings</span></button> <button id="button-settings" class="button-outer"><img src="style/icon_settings.svg" class="icon settings" /> <span data-localize="settings">Settings</span></button>
</div> </div>
Rom Patcher JS <small>v2.7.1</small> by <a href="/">Marc Robledo</a> Rom Patcher JS <small>v2.8</small> by <a href="/">Marc Robledo</a>
<br /> <br />
<img src="style/icon_github.svg" class="icon github" /> <a href="https://github.com/marcrobledo/RomPatcher.js/" target="_blank">See on GitHub</a> <img src="style/icon_github.svg" class="icon github" /> <a href="https://github.com/marcrobledo/RomPatcher.js/" target="_blank">See on GitHub</a>
<img src="style/icon_heart.svg" class="icon heart" /> <a href="https://www.paypal.me/marcrobledo/5" target="_blank" rel="nofollow">Donate</a> <img src="style/icon_heart.svg" class="icon heart" /> <a href="https://www.paypal.me/marcrobledo/5" target="_blank" rel="nofollow">Donate</a>

View file

@ -1,4 +1,4 @@
/* Rom Patcher JS v20230202 - Marc Robledo 2016-2023 - http://www.marcrobledo.com/license */ /* Rom Patcher JS v20230331 - Marc Robledo 2016-2023 - http://www.marcrobledo.com/license */
const TOO_BIG_ROM_SIZE=67108863; const TOO_BIG_ROM_SIZE=67108863;
const HEADERS_INFO=[ const HEADERS_INFO=[
@ -663,8 +663,10 @@ function _readPatchFile(){
patch=parseIPSFile(patchFile); patch=parseIPSFile(patchFile);
}else if(header.startsWith(UPS_MAGIC)){ }else if(header.startsWith(UPS_MAGIC)){
patch=parseUPSFile(patchFile); patch=parseUPSFile(patchFile);
}else if(header.startsWith(APS_MAGIC)){ }else if(header.startsWith(APS_N64_MAGIC)){
patch=parseAPSFile(patchFile); patch=parseAPSFile(patchFile);
}else if(header.startsWith(APS_GBA_MAGIC)){
patch=APSGBA.fromFile(patchFile);
}else if(header.startsWith(BPS_MAGIC)){ }else if(header.startsWith(BPS_MAGIC)){
patch=parseBPSFile(patchFile); patch=parseBPSFile(patchFile);
}else if(header.startsWith(RUP_MAGIC)){ }else if(header.startsWith(RUP_MAGIC)){

133
js/crc.js
View file

@ -108,126 +108,19 @@ function adler32(marcFile, offset, len){
/* CRC16 */ /* CRC16/CCITT-FALSE */
/* function crc16(marcFile, offset, len){
const CRC16_TABLE=(function(){ var crc=0xffff;
var c,crcTable=[];
for(var n=0;n<256;n++){ offset=offset? offset : 0;
c=n; len=len && len>0? len : marcFile.fileSize;
for(var k=0;k<8;k++)
c=((c&1)?(0x8408^(c>>>1)):(c>>>1)); for(var i=0; i<len; i++){
crcTable[n]=c; crc ^= marcFile._u8array[offset++] << 8;
for (j=0; j<8; ++j) {
crc = (crc & 0x8000) >>> 0 ? (crc << 1) ^ 0x1021 : crc << 1;
}
} }
return crcTable;
}());
function crc16(marcFile){
var crc=0^(-1);
for(var i=0;i<marcFile._u8array.length;i++) return crc & 0xffff;
crc=((crc>>>8)&0x0ff)^CRC16_TABLE[(crc^marcFile._u8array[i])&0xff];
return ((crc^(-1))>>>0) & 0xffff;
}
*/
/* specific ROM checksums */
/* this is unused code, might be used in a future so ROM checksums can be fixed after patching */
const CONSOLES=[
{
title:'Sega Mega Drive/Genesis',
MEGADRIVE_LOGO:[0x53, 0x45, 0x47, 0x41, 0x20, 0x4d, 0x45, 0x47, 0x41, 0x20, 0x44, 0x52],
GENESIS_LOGO:[0x53, 0x45, 0x47, 0x41, 0x20, 0x47, 0x45, 0x4e, 0x45, 0x53, 0x49, 0x53],
checkHeader:function(marcFile){
var megadrive=true;
var genesis=true;
for(var i=0; i<12 && (megadrive || genesis); i++){
if(marcFile._u8array[0x100+i]!==this.MEGADRIVE_LOGO[i])
megadrive=false;
if(marcFile._u8array[0x100+i]!==this.GENESIS_LOGO[i])
genesis=false;
}
return megadrive || genesis;
},
getChecksum:function(marcFile){
return (marcFile._u8array[0x018e]<<8) + marcFile._u8array[0x018f];
},
recalculateChecksum:function(marcFile){
var checksum=0;
for(var i=0x200; i<marcFile.fileSize; i+=2)
checksum=(checksum + (((marcFile._u8array[i]<<8) + marcFile._u8array[i+1])>>>0)) & 0xffff;
return checksum
},
updateChecksum:function(marcFile, newChecksum){
marcFile._u8array[0x18e]=newChecksum>>8;
marcFile._u8array[0x18f]=newChecksum & 0xff;
}
},{
title:'Game Boy',
NINTENDO_LOGO:[0xce, 0xed, 0x66, 0x66, 0xcc, 0x0d, 0x00, 0x0b, 0x03, 0x73, 0x00, 0x83, 0x00, 0x0c, 0x00, 0x0d],
checkHeader:function(marcFile){
for(var i=0; i<this.NINTENDO_LOGO.length; i++){
if(marcFile._u8array[0x104+i]!==this.NINTENDO_LOGO[i])
return false;
}
return true;
},
getChecksum:function(marcFile){
return marcFile._u8array[0x14d]
},
recalculateChecksum:function(marcFile){
var checksum=0;
for(var i=0x134; i<0x014d; i++){
checksum=(checksum - marcFile._u8array[i] - 1) & 0xff;
}
return checksum
},
updateChecksum:function(marcFile, newChecksum){
marcFile._u8array[0x014d]=newChecksum;
/* global checksum isn't checked by real hw, but fix it anyway */
var globalChecksumOld=(marcFile._u8array[0x014e]<<8) + marcFile._u8array[0x014f];
var globalChecksumNew=0;
for(var i=0x0000; i<0x014e; i++)
globalChecksumNew=((globalChecksumNew + marcFile._u8array[i]) >>> 0) & 0xffff;
for(i=0x0150;i<marcFile.fileSize; i++)
globalChecksumNew=((globalChecksumNew + marcFile._u8array[i]) >>> 0) & 0xffff;
if(globalChecksumOld!==globalChecksumNew){
marcFile._u8array[0x014e]=globalChecksumNew>>8;
marcFile._u8array[0x014f]=globalChecksumNew & 0xff;
}
}
}
];
function checkConsole(marcFile){
return false;
}
function fixConsoleChecksum(marcFile){
var system=false;
for(var i=0; i<CONSOLES.length && !system; i++)
if(CONSOLES[i].checkHeader(marcFile))
system=CONSOLES[i];
if(!system)
return false;
var oldChecksum=console.getChecksum(marcFile);
var newChecksum=console.recalculateChecksum(marcFile);
if(oldChecksum!==newChecksum)
if(alert('Fix '+console.title+' checksum?')){
console.updateChecksum(marcFile, newChecksum);
return true;
}
return false;
} }

114
js/formats/aps_gba.js Normal file
View file

@ -0,0 +1,114 @@
/* APS (GBA) module for Rom Patcher JS v20230331 - Marc Robledo 2017-2023 - http://www.marcrobledo.com/license */
/* File format specification: https://github.com/btimofeev/UniPatcher/wiki/APS-(GBA) */
const APS_GBA_MAGIC='APS1';
const APS_GBA_BLOCK_SIZE=0x010000; //64Kb
const APS_GBA_RECORD_SIZE=4 + 2 + 2 + APS_GBA_BLOCK_SIZE;
function APSGBA(){
this.sourceSize=0;
this.targetSize=0;
this.records=[];
}
APSGBA.prototype.addRecord=function(offset, sourceCrc16, targetCrc16, xorBytes){
this.records.push({
offset:offset,
sourceCrc16:sourceCrc16,
targetCrc16:targetCrc16,
xorBytes:xorBytes}
);
}
APSGBA.prototype.toString=function(){
var s='Total records: '+this.records.length;
s+='\nInput file size: '+this.sourceSize;
s+='\nOutput file size: '+this.targetSize;
return s
}
APSGBA.prototype.validateSource=function(sourceFile){
if(sourceFile.fileSize!==this.sourceSize)
return false;
var originalEndianness=sourceFile.littleEndian;
sourceFile.littleEndian=true;
for(var i=0; i<this.records.length; i++){
sourceFile.seek(this.records[i].offset);
var bytes=sourceFile.readBytes(APS_GBA_BLOCK_SIZE);
if(crc16(sourceFile, this.records[i].offset, APS_GBA_BLOCK_SIZE) !== this.records[i].sourceCrc16)
return false
}
sourceFile.littleEndian=originalEndianness;
return true
}
APSGBA.prototype.export=function(fileName){
var patchFileSize=12 + (this.records.length * APS_GBA_RECORD_SIZE);
tempFile=new MarcFile(patchFileSize);
tempFile.littleEndian=true;
tempFile.fileName=fileName+'.aps';
tempFile.writeString(APS_GBA_MAGIC, APS_GBA_MAGIC.length);
tempFile.writeU32(this.sourceSize);
tempFile.writeU32(this.targetSize);
for(var i=0; i<this.records.length; i++){
tempFile.writeU32(this.records[i].offset);
tempFile.writeU16(this.records[i].sourceCrc16);
tempFile.writeU16(this.records[i].targetCrc16);
tempFile.writeBytes(this.records[i].xorBytes);
}
return tempFile
}
APSGBA.prototype.apply=function(romFile, validate){
if(validate && !this.validateSource(romFile)){
throw new Error('error_crc_input');
}
tempFile=new MarcFile(this.targetSize);
romFile.copyToFile(tempFile, 0, romFile.fileSize);
for(var i=0; i<this.records.length; i++){
romFile.seek(this.records[i].offset);
tempFile.seek(this.records[i].offset);
for(var j=0; j<APS_GBA_RECORD_SIZE; j++){
tempFile.writeU8(romFile.readU8() ^ this.records[i].xorBytes[j]);
}
if(validate && crc16(tempFile, this.records[i].offset, APS_GBA_BLOCK_SIZE)!==this.records[i].targetCrc16){
throw new Error('error_crc_output');
}
}
return tempFile
}
APSGBA.fromFile=function(patchFile){
patchFile.seek(0);
patchFile.littleEndian=true;
if(
patchFile.readString(APS_GBA_MAGIC.length)!==APS_GBA_MAGIC ||
patchFile.fileSize < (12 + APS_GBA_RECORD_SIZE) ||
(patchFile.fileSize-12)%APS_GBA_RECORD_SIZE!==0
)
return null;
var patch=new APSGBA();
patch.sourceSize=patchFile.readU32();
patch.targetSize=patchFile.readU32();
while(!patchFile.isEOF()){
var offset=patchFile.readU32();
var sourceCrc16=patchFile.readU16();
var targetCrc16=patchFile.readU16();
var xorBytes=patchFile.readBytes(APS_GBA_BLOCK_SIZE);
patch.addRecord(offset, sourceCrc16, targetCrc16, xorBytes);
}
return patch;
}

View file

@ -1,7 +1,7 @@
/* APS (N64) module for Rom Patcher JS v20180930 - Marc Robledo 2017-2018 - http://www.marcrobledo.com/license */ /* APS (N64) module for Rom Patcher JS v20180930 - Marc Robledo 2017-2018 - http://www.marcrobledo.com/license */
/* File format specification: https://github.com/btimofeev/UniPatcher/wiki/APS-(N64) */ /* File format specification: https://github.com/btimofeev/UniPatcher/wiki/APS-(N64) */
const APS_MAGIC='APS10'; const APS_N64_MAGIC='APS10';
const APS_RECORD_RLE=0x0000; const APS_RECORD_RLE=0x0000;
const APS_RECORD_SIMPLE=0x01; const APS_RECORD_SIMPLE=0x01;
const APS_N64_MODE=0x01; const APS_N64_MODE=0x01;
@ -61,7 +61,7 @@ APS.prototype.export=function(fileName){
tempFile=new MarcFile(patchFileSize); tempFile=new MarcFile(patchFileSize);
tempFile.littleEndian=true; tempFile.littleEndian=true;
tempFile.fileName=fileName+'.aps'; tempFile.fileName=fileName+'.aps';
tempFile.writeString(APS_MAGIC, APS_MAGIC.length); tempFile.writeString(APS_N64_MAGIC, APS_N64_MAGIC.length);
tempFile.writeU8(this.headerType); tempFile.writeU8(this.headerType);
tempFile.writeU8(this.encodingMethod); tempFile.writeU8(this.encodingMethod);
tempFile.writeString(this.description, 50); tempFile.writeString(this.description, 50);

View file

@ -1,10 +1,11 @@
/* Rom Patcher JS v20200502 - Marc Robledo 2016-2020 - http://www.marcrobledo.com/license */ /* Rom Patcher JS v20230331 - Marc Robledo 2016-2023 - http://www.marcrobledo.com/license */
self.importScripts( self.importScripts(
'./MarcFile.js', './MarcFile.js',
'./crc.js', './crc.js',
'./formats/ips.js', './formats/ips.js',
'./formats/aps.js', './formats/aps_n64.js',
'./formats/aps_gba.js',
'./formats/ups.js', './formats/ups.js',
'./formats/bps.js', './formats/bps.js',
'./formats/rup.js', './formats/rup.js',
@ -26,8 +27,10 @@ self.onmessage = event => { // listen for messages from the main thread
patch=parseIPSFile(patchFile); patch=parseIPSFile(patchFile);
}else if(header.startsWith(UPS_MAGIC)){ }else if(header.startsWith(UPS_MAGIC)){
patch=parseUPSFile(patchFile); patch=parseUPSFile(patchFile);
}else if(header.startsWith(APS_MAGIC)){ }else if(header.startsWith(APS_N64_MAGIC)){
patch=parseAPSFile(patchFile); patch=parseAPSFile(patchFile);
}else if(header.startsWith(APS_GBA_MAGIC)){
patch=APSGBA.fromFile(patchFile);
}else if(header.startsWith(BPS_MAGIC)){ }else if(header.startsWith(BPS_MAGIC)){
patch=parseBPSFile(patchFile); patch=parseBPSFile(patchFile);
}else if(header.startsWith(RUP_MAGIC)){ }else if(header.startsWith(RUP_MAGIC)){

View file

@ -4,7 +4,7 @@ self.importScripts(
'./MarcFile.js', './MarcFile.js',
'./crc.js', './crc.js',
'./formats/ips.js', './formats/ips.js',
'./formats/aps.js', './formats/aps_n64.js',
'./formats/ups.js', './formats/ups.js',
'./formats/bps.js', './formats/bps.js',
'./formats/ppf.js', './formats/ppf.js',