2018-04-28 15:57:54 +02:00
|
|
|
/* BPS module for RomPatcher.js v20180428 - Marc Robledo 2016-2018 - http://www.marcrobledo.com/license */
|
2017-11-03 09:43:29 +01:00
|
|
|
/* File format specification: https://www.romhacking.net/documents/746/ */
|
|
|
|
|
|
|
|
var BPS_MAGIC='BPS1';
|
|
|
|
var BPS_ACTION_SOURCE_READ=0;
|
|
|
|
var BPS_ACTION_TARGET_READ=1;
|
|
|
|
var BPS_ACTION_SOURCE_COPY=2;
|
|
|
|
var BPS_ACTION_TARGET_COPY=3;
|
|
|
|
|
|
|
|
|
|
|
|
function BPS(){
|
|
|
|
this.sourceSize=0;
|
|
|
|
this.targetSize=0;
|
|
|
|
this.metaData='';
|
|
|
|
this.actionsOffset=0;
|
|
|
|
this.file=null;
|
|
|
|
this.sourceChecksum=0;
|
|
|
|
this.targetChecksum=0;
|
|
|
|
this.patchChecksum=0;
|
|
|
|
}
|
|
|
|
BPS.prototype.toString=function(){
|
|
|
|
var s='Source size: '+this.sourceSize;
|
|
|
|
s+='\Target size: '+this.targetSize;
|
|
|
|
s+='\nMetadata: '+this.metaData;
|
|
|
|
s+='\nActions offset: '+this.actionsOffset;
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
/*BPS.prototype.export=function(){
|
|
|
|
|
|
|
|
}*/
|
2018-04-28 15:57:54 +02:00
|
|
|
BPS.prototype.validateSource=function(romFile){return this.sourceChecksum===crc32(romFile,false)}
|
2017-11-03 09:43:29 +01:00
|
|
|
BPS.prototype.apply=function(romFile){
|
2018-04-28 15:57:54 +02:00
|
|
|
if(!this.validateSource(romFile)){
|
2017-11-12 16:29:14 +01:00
|
|
|
MarcDialogs.alert('Error: invalid source ROM checksum');
|
|
|
|
return false;
|
2017-11-03 09:43:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// first we determine target file size
|
|
|
|
var newFileSize=0;
|
|
|
|
var seek=this.actionsOffset;
|
|
|
|
while(seek<(this.file.fileSize-12)){
|
|
|
|
var data=decodeBPS(this.file, seek);
|
|
|
|
var action={type: data.number & 3, length: (data.number >> 2)+1};
|
|
|
|
seek+=data.length;
|
|
|
|
|
|
|
|
newFileSize+=action.length;
|
|
|
|
if(action.type===BPS_ACTION_TARGET_READ){
|
|
|
|
seek+=action.length;
|
|
|
|
}else if(action.type===BPS_ACTION_SOURCE_COPY || action.type===BPS_ACTION_TARGET_COPY){
|
|
|
|
seek+=decodeBPS(this.file, seek).length;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
tempFile=new MarcBinFile(newFileSize);
|
|
|
|
|
|
|
|
|
|
|
|
//patch
|
|
|
|
var outputOffset=0;
|
|
|
|
var sourceRelativeOffset=0;
|
|
|
|
var targetRelativeOffset=0;
|
|
|
|
seek=this.actionsOffset;
|
|
|
|
while(seek<(this.file.fileSize-12)){
|
|
|
|
var data=decodeBPS(this.file, seek);
|
|
|
|
var action={type: data.number & 3, length: (data.number >> 2)+1};
|
|
|
|
//console.log('0x'+seek.toString(16)+' - action: '+action.type+':'+action.length);
|
|
|
|
seek+=data.length;
|
|
|
|
|
|
|
|
if(action.type===BPS_ACTION_SOURCE_READ){
|
|
|
|
tempFile.writeBytes(outputOffset, romFile.readBytes(outputOffset, action.length));
|
|
|
|
outputOffset+=action.length;
|
|
|
|
|
|
|
|
}else if(action.type===BPS_ACTION_TARGET_READ){
|
|
|
|
tempFile.writeBytes(outputOffset, this.file.readBytes(seek, action.length));
|
|
|
|
outputOffset+=action.length;
|
|
|
|
seek+=action.length;
|
|
|
|
|
|
|
|
}else if(action.type===BPS_ACTION_SOURCE_COPY){
|
|
|
|
var data2=decodeBPS(this.file, seek);
|
|
|
|
seek+=data2.length;
|
|
|
|
sourceRelativeOffset+=(data2.number & 1 ? -1 : +1) * (data2.number >> 1);
|
|
|
|
while(action.length--){
|
|
|
|
tempFile.writeByte(outputOffset, romFile.readByte(sourceRelativeOffset));
|
|
|
|
outputOffset++;
|
|
|
|
sourceRelativeOffset++;
|
|
|
|
}
|
|
|
|
}else if(action.type===BPS_ACTION_TARGET_COPY){
|
|
|
|
var data2=decodeBPS(this.file, seek);
|
|
|
|
seek+=data2.length;
|
|
|
|
targetRelativeOffset += (data2.number & 1 ? -1 : +1) * (data2.number >> 1);
|
|
|
|
while(action.length--) {
|
|
|
|
tempFile.writeByte(outputOffset, tempFile.readByte(targetRelativeOffset));
|
|
|
|
outputOffset++;
|
|
|
|
targetRelativeOffset++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(this.targetChecksum!==crc32(tempFile,false)){
|
|
|
|
MarcDialogs.alert('Warning: invalid target ROM checksum');
|
|
|
|
}
|
|
|
|
|
|
|
|
return tempFile
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function readBPSFile(file){
|
|
|
|
file.littleEndian=true;
|
|
|
|
var patchFile=new BPS();
|
|
|
|
|
|
|
|
var seek=4; //skip BPS1
|
|
|
|
var decodedSourceSize=decodeBPS(file, seek);
|
|
|
|
patchFile.sourceSize=decodedSourceSize.number;
|
|
|
|
seek+=decodedSourceSize.length;
|
|
|
|
var decodedTargetSize=decodeBPS(file, seek);
|
|
|
|
patchFile.targetSize=decodedTargetSize.number;
|
|
|
|
seek+=decodedTargetSize.length;
|
|
|
|
|
|
|
|
var decodedMetaDataLength=decodeBPS(file, seek);
|
|
|
|
seek+=decodedMetaDataLength.length;
|
|
|
|
patchFile.metaData=file.readString(seek, decodedMetaDataLength.length);
|
|
|
|
seek+=patchFile.metaData.length;
|
|
|
|
|
|
|
|
patchFile.actionsOffset=seek;
|
|
|
|
patchFile.file=file;
|
|
|
|
|
|
|
|
patchFile.sourceChecksum=file.readInt(file.fileSize-12);
|
|
|
|
patchFile.targetChecksum=file.readInt(file.fileSize-8);
|
|
|
|
patchFile.patchChecksum=file.readInt(file.fileSize-4);
|
|
|
|
|
|
|
|
if(patchFile.patchChecksum!==crc32(file,true)){
|
|
|
|
MarcDialogs.alert('Warning: invalid patch checksum');
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return patchFile;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*function createBPSFromFiles(original, modified){
|
|
|
|
|
|
|
|
}*/
|
|
|
|
|
|
|
|
|
|
|
|
/*function encodeBPS(number){
|
|
|
|
number=number>>>0;
|
|
|
|
var dataBytes=[];
|
|
|
|
while(true){
|
|
|
|
var x = number & 0x7f;
|
|
|
|
number >>= 7;
|
|
|
|
if(number == 0){
|
|
|
|
dataBytes.push(0x80 | x);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
dataBytes.push(x);
|
|
|
|
number--;
|
|
|
|
}
|
|
|
|
return dataBytes;
|
|
|
|
}*/
|
|
|
|
function decodeBPS(dataBytes, i){
|
|
|
|
var number = 0, shift = 1;
|
|
|
|
var len=0;
|
|
|
|
while(true){
|
|
|
|
var x = dataBytes.readByte(i);
|
|
|
|
i++;
|
|
|
|
len++;
|
|
|
|
number += (x & 0x7f) * shift;
|
|
|
|
if(x & 0x80)
|
|
|
|
break;
|
|
|
|
shift <<= 7;
|
|
|
|
number += shift;
|
|
|
|
}
|
|
|
|
return {number:number,length:len};
|
|
|
|
}
|