mirror of
https://github.com/marcrobledo/RomPatcher.js.git
synced 2025-06-27 16:25:54 +00:00
224 lines
No EOL
5.2 KiB
JavaScript
224 lines
No EOL
5.2 KiB
JavaScript
/* UPS module for Rom Patcher JS v20240721 - Marc Robledo 2017-2024 - http://www.marcrobledo.com/license */
|
|
/* File format specification: http://www.romhacking.net/documents/392/ */
|
|
|
|
const UPS_MAGIC='UPS1';
|
|
if(typeof module !== "undefined" && module.exports){
|
|
module.exports = UPS;
|
|
}
|
|
function UPS(){
|
|
this.records=[];
|
|
this.sizeInput=0;
|
|
this.sizeOutput=0;
|
|
this.checksumInput=0;
|
|
this.checksumOutput=0;
|
|
}
|
|
UPS.prototype.addRecord=function(relativeOffset, d){
|
|
this.records.push({offset:relativeOffset, XORdata:d})
|
|
}
|
|
UPS.prototype.toString=function(){
|
|
var s='Records: '+this.records.length;
|
|
s+='\nInput file size: '+this.sizeInput;
|
|
s+='\nOutput file size: '+this.sizeOutput;
|
|
s+='\nInput file checksum: '+this.checksumInput.toString(16);
|
|
s+='\nOutput file checksum: '+this.checksumOutput.toString(16);
|
|
return s
|
|
}
|
|
UPS.prototype.export=function(fileName){
|
|
var patchFileSize=UPS_MAGIC.length;//UPS1 string
|
|
patchFileSize+=UPS_getVLVLength(this.sizeInput); //input file size
|
|
patchFileSize+=UPS_getVLVLength(this.sizeOutput); //output file size
|
|
for(var i=0; i<this.records.length; i++){
|
|
patchFileSize+=UPS_getVLVLength(this.records[i].offset);
|
|
patchFileSize+=this.records[i].XORdata.length+1;
|
|
}
|
|
patchFileSize+=12; //input/output/patch checksums
|
|
|
|
tempFile=new BinFile(patchFileSize);
|
|
tempFile.writeVLV=UPS_writeVLV;
|
|
tempFile.fileName=fileName+'.ups';
|
|
tempFile.writeString(UPS_MAGIC);
|
|
|
|
tempFile.writeVLV(this.sizeInput);
|
|
tempFile.writeVLV(this.sizeOutput);
|
|
|
|
for(var i=0; i<this.records.length; i++){
|
|
tempFile.writeVLV(this.records[i].offset);
|
|
tempFile.writeBytes(this.records[i].XORdata);
|
|
tempFile.writeU8(0x00);
|
|
}
|
|
tempFile.littleEndian=true;
|
|
tempFile.writeU32(this.checksumInput);
|
|
tempFile.writeU32(this.checksumOutput);
|
|
tempFile.writeU32(tempFile.hashCRC32(0, tempFile.fileSize - 4));
|
|
|
|
return tempFile
|
|
}
|
|
UPS.prototype.validateSource=function(romFile,headerSize){return romFile.hashCRC32(headerSize)===this.checksumInput}
|
|
UPS.prototype.getValidationInfo=function(){
|
|
return {
|
|
'type':'CRC32',
|
|
'value':this.checksumInput
|
|
};
|
|
}
|
|
UPS.prototype.apply=function(romFile, validate){
|
|
if(validate && !this.validateSource(romFile)){
|
|
throw new Error('Source ROM checksum mismatch');
|
|
}
|
|
|
|
/* fix the glitch that cut the end of the file if it's larger than the changed file patch was originally created with */
|
|
/* more info: https://github.com/marcrobledo/RomPatcher.js/pull/40#issuecomment-1069087423 */
|
|
sizeOutput = this.sizeOutput;
|
|
sizeInput = this.sizeInput;
|
|
if(!validate && sizeInput < romFile.fileSize){
|
|
sizeInput = romFile.fileSize;
|
|
if(sizeOutput < sizeInput){
|
|
sizeOutput = sizeInput;
|
|
}
|
|
}
|
|
|
|
/* copy original file */
|
|
tempFile=new BinFile(sizeOutput);
|
|
romFile.copyTo(tempFile, 0, sizeInput);
|
|
|
|
romFile.seek(0);
|
|
|
|
|
|
var nextOffset=0;
|
|
for(var i=0; i<this.records.length; i++){
|
|
var record=this.records[i];
|
|
tempFile.skip(record.offset);
|
|
romFile.skip(record.offset);
|
|
|
|
for(var j=0; j<record.XORdata.length; j++){
|
|
tempFile.writeU8((romFile.isEOF()?0x00:romFile.readU8()) ^ record.XORdata[j]);
|
|
}
|
|
tempFile.skip(1);
|
|
romFile.skip(1);
|
|
}
|
|
|
|
if(validate && tempFile.hashCRC32()!==this.checksumOutput){
|
|
throw new Error('Target ROM checksum mismatch');
|
|
}
|
|
|
|
return tempFile
|
|
}
|
|
|
|
UPS.MAGIC=UPS_MAGIC;
|
|
|
|
|
|
/* encode/decode variable length values, used by UPS file structure */
|
|
function UPS_writeVLV(data){
|
|
while(1){
|
|
var x=data & 0x7f;
|
|
data=data>>7;
|
|
if(data===0){
|
|
this.writeU8(0x80 | x);
|
|
break;
|
|
}
|
|
this.writeU8(x);
|
|
data=data-1;
|
|
}
|
|
}
|
|
function UPS_readVLV(){
|
|
var data=0;
|
|
|
|
var shift=1;
|
|
while(1){
|
|
var x=this.readU8();
|
|
|
|
if(x==-1)
|
|
throw new Error('Can\'t read UPS VLV at 0x'+(this.offset-1).toString(16));
|
|
|
|
data+=(x&0x7f)*shift;
|
|
if((x&0x80)!==0)
|
|
break;
|
|
shift=shift<<7;
|
|
data+=shift;
|
|
}
|
|
return data
|
|
}
|
|
function UPS_getVLVLength(data){
|
|
var len=0;
|
|
while(1){
|
|
var x=data & 0x7f;
|
|
data=data>>7;
|
|
len++;
|
|
if(data===0){
|
|
break;
|
|
}
|
|
data=data-1;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
|
|
UPS.fromFile=function(file){
|
|
var patch=new UPS();
|
|
file.readVLV=UPS_readVLV;
|
|
|
|
file.seek(UPS_MAGIC.length);
|
|
|
|
patch.sizeInput=file.readVLV();
|
|
patch.sizeOutput=file.readVLV();
|
|
|
|
|
|
var nextOffset=0;
|
|
while(file.offset<(file.fileSize-12)){
|
|
var relativeOffset=file.readVLV();
|
|
|
|
|
|
var XORdifferences=[];
|
|
while(file.readU8()){
|
|
XORdifferences.push(file._lastRead);
|
|
}
|
|
patch.addRecord(relativeOffset, XORdifferences);
|
|
}
|
|
|
|
file.littleEndian=true;
|
|
patch.checksumInput=file.readU32();
|
|
patch.checksumOutput=file.readU32();
|
|
|
|
if(file.readU32()!==file.hashCRC32(0, file.fileSize - 4)){
|
|
throw new Error('Patch checksum mismatch');
|
|
}
|
|
|
|
file.littleEndian=false;
|
|
return patch;
|
|
}
|
|
|
|
|
|
|
|
UPS.buildFromRoms=function(original, modified){
|
|
var patch=new UPS();
|
|
patch.sizeInput=original.fileSize;
|
|
patch.sizeOutput=modified.fileSize;
|
|
|
|
|
|
var previousSeek=1;
|
|
while(!modified.isEOF()){
|
|
var b1=original.isEOF()?0x00:original.readU8();
|
|
var b2=modified.readU8();
|
|
|
|
if(b1!==b2){
|
|
var currentSeek=modified.offset;
|
|
var XORdata=[];
|
|
|
|
while(b1!==b2){
|
|
XORdata.push(b1 ^ b2);
|
|
|
|
if(modified.isEOF())
|
|
break;
|
|
b1=original.isEOF()?0x00:original.readU8();
|
|
b2=modified.readU8();
|
|
}
|
|
|
|
patch.addRecord(currentSeek-previousSeek, XORdata);
|
|
previousSeek=currentSeek+XORdata.length+1;
|
|
}
|
|
}
|
|
|
|
|
|
patch.checksumInput=original.hashCRC32();
|
|
patch.checksumOutput=modified.hashCRC32();
|
|
return patch
|
|
} |