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

refactor and added EBP description showing in frontend

This commit is contained in:
Marc Robledo 2025-04-30 01:11:55 +02:00
parent 198c046424
commit 844cfcb5a6
5 changed files with 88 additions and 55 deletions

View file

@ -242,8 +242,8 @@ const RomPatcher = (function () {
format = 'ips';
var patch;
if (format === 'ips' || format === 'ebp') {
patch = IPS.buildFromRoms(originalFile, modifiedFile, format === 'ebp');
if (format === 'ips') {
patch = IPS.buildFromRoms(originalFile, modifiedFile);
} else if (format === 'bps') {
patch = BPS.buildFromRoms(originalFile, modifiedFile, (originalFile.fileSize <= 4194304));
} else if (format === 'ppf') {
@ -254,6 +254,8 @@ const RomPatcher = (function () {
patch = APS.buildFromRoms(originalFile, modifiedFile);
} else if (format === 'rup') {
patch = RUP.buildFromRoms(originalFile, modifiedFile);
} else if (format === 'ebp') {
patch = IPS.buildFromRoms(originalFile, modifiedFile, true);
} else {
throw new Error('Invalid patch format');
}

View file

@ -1175,7 +1175,7 @@ const ZIPManager = (function (romPatcherWeb) {
const ZIP_MAGIC = '\x50\x4b\x03\x04';
const FILTER_PATCHES = /\.(ips|ebp|ups|bps|aps|rup|ppf|mod|xdelta|vcdiff)$/i;
const FILTER_PATCHES = /\.(ips|ups|bps|aps|rup|ppf|mod|ebp|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;

View file

@ -1,21 +1,14 @@
/* IPS module for Rom Patcher JS v20230924 - Marc Robledo 2016-2023 - http://www.marcrobledo.com/license */
/* IPS module for Rom Patcher JS v20250430 - Marc Robledo 2016-2025 - http://www.marcrobledo.com/license */
/* File format specification: http://www.smwiki.net/wiki/IPS_file_format */
/* This file also acts as EBP (EarthBound Patch) module */
/* EBP is actually just IPS with some JSON metadata stuck on the end (implementation: https://github.com/Lyrositor/EBPatcher) */
const IPS_MAGIC='PATCH';
const IPS_MAX_ROM_SIZE=0x1000000; //16 megabytes
const IPS_RECORD_RLE=0x0000;
const IPS_RECORD_SIMPLE=0x01;
/* There is also support for the EBP (EarthBound Patch) format here. */
/* EBP has no real specification, but an implementation is here: https://github.com/Lyrositor/EBPatcher */
/* EBP is actually just IPS with some JSON metadata stuck on the end. */
/* We can safely ignore this data when applying patches. */
/* When creating patches, insert this data after everything else. */
/* Finally, EBP doesn't seem to support truncation metadata. */
const EBP_MAGIC_META_OPENER = 0x7B //UTF-8 '{'
/* EBPatcher (linked above) expects the "patcher" field to be EBPatcher to read the metadata. Can't imagine why... */
/* CoilSnake (EB modding tool) inserts this manually too. */
const EBP_META_DEFAULT={"patcher": "EBPatcher", "author": "Unknown", "title": "Untitled", "description": "No description"}
if(typeof module !== "undefined" && module.exports){
module.exports = IPS;
@ -25,8 +18,7 @@ if(typeof module !== "undefined" && module.exports){
function IPS(){
this.records=[];
this.truncate=false;
this.isEBP=false;
this.EBPmetadata=JSON.stringify(EBP_META_DEFAULT)
this.EBPmetadata=null;
}
IPS.prototype.addSimpleRecord=function(o, d){
this.records.push({offset:o, type:IPS_RECORD_SIMPLE, length:d.length, data:d})
@ -34,9 +26,31 @@ IPS.prototype.addSimpleRecord=function(o, d){
IPS.prototype.addRLERecord=function(o, l, b){
this.records.push({offset:o, type:IPS_RECORD_RLE, length:l, byte:b})
}
IPS.prototype.addEBPMetadata=function(author, title, description){
/* currently not used - no frontend support */
this.EBPmetadata=JSON.stringify({"patcher": "EBPatcher", "author": author, "title": title, "description": description})
IPS.prototype.setEBPMetadata=function(metadataObject){
if(typeof metadataObject !== 'object')
throw new TypeError('metadataObject must be an object');
for(var key in metadataObject){
if(typeof metadataObject[key] !== 'string')
throw new TypeError('metadataObject values must be strings');
}
/* EBPatcher (linked above) expects the "patcher" field to be EBPatcher to read the metadata */
/* CoilSnake (EB modding tool) inserts this manually too */
/* So we also add it here for compatibility purposes */
this.EBPmetadata={patcher:'EBPatcher', ...metadataObject};
}
IPS.prototype.getDescription=function(){
if(this.EBPmetadata){
var description='';
for(var key in this.EBPmetadata){
if(key!=='patcher'){
const keyPretty=key.charAt(0).toUpperCase() + key.slice(1);
description+=keyPretty+': '+this.EBPmetadata[key]+'\n';
}
}
return description.trim();
}
return null;
}
IPS.prototype.toString=function(){
nSimpleRecords=0;
@ -50,9 +64,10 @@ IPS.prototype.toString=function(){
var s='Simple records: '+nSimpleRecords;
s+='\nRLE records: '+nRLERecords;
s+='\nTotal records: '+this.records.length;
if(this.truncate && !this.isEBP)
if(this.truncate && !this.EBPmetadata)
s+='\nTruncate at: 0x'+this.truncate.toString(16);
s+='\nIs EBP: '+this.isEBP
else if(this.EBPmetadata)
s+='\nEBP Metadata: '+JSON.stringify(this.EBPmetadata);
return s
}
IPS.prototype.export=function(fileName){
@ -64,13 +79,13 @@ IPS.prototype.export=function(fileName){
patchFileSize+=(3+2+this.records[i].data.length); //offset+length+data
}
patchFileSize+=3; //EOF string
if(this.truncate && !this.isEBP)
if(this.truncate && !this.EBPmetadata)
patchFileSize+=3; //truncate
if(this.isEBP)
patchFileSize+=this.EBPmetadata.length
else if(this.EBPmetadata)
patchFileSize+=JSON.stringify(this.EBPmetadata);
tempFile=new BinFile(patchFileSize);
tempFile.fileName=fileName+('.ebp' ? this.isEBP : '.ips');
tempFile.fileName=fileName+(this.EBPmetadata? '.ebp' : '.ips');
tempFile.writeString(IPS_MAGIC);
for(var i=0; i<this.records.length; i++){
var rec=this.records[i];
@ -86,17 +101,15 @@ IPS.prototype.export=function(fileName){
}
tempFile.writeString('EOF');
if(this.truncate && !this.isEBP)
if(this.truncate && !this.EBPmetadata)
tempFile.writeU24(this.truncate);
if(this.isEBP) {
tempFile.writeString(this.EBPmetadata)
}
else if(this.EBPmetadata)
tempFile.writeString(JSON.stringify(this.EBPmetadata));
return tempFile
}
IPS.prototype.apply=function(romFile){
if(this.truncate && !this.isEBP){
if(this.truncate && !this.EBPmetadata){
if(this.truncate>romFile.fileSize){ //expand (discussed here: https://github.com/marcrobledo/RomPatcher.js/pull/46)
tempFile=new BinFile(this.truncate);
romFile.copyTo(tempFile, 0, romFile.fileSize, 0);
@ -159,7 +172,9 @@ IPS.fromFile=function(file){
}else if((file.offset+3)===file.fileSize){
patchFile.truncate=file.readU24();
break;
}else if (file.readU8()===EBP_MAGIC_META_OPENER) {
}else if (file.readU8()==='{'.charCodeAt(0)) {
file.skip(-1);
patchFile.setEBPMetadata(JSON.parse(file.readString(file.fileSize-file.offset)));
break;
}
}
@ -179,10 +194,14 @@ IPS.fromFile=function(file){
IPS.buildFromRoms=function(original, modified, asEBP=false){
var patch=new IPS();
patch.isEBP=asEBP
if(modified.fileSize<original.fileSize && !patch.isEBP){
if(!asEBP && modified.fileSize<original.fileSize){
patch.truncate=modified.fileSize;
}else if(asEBP){
patch.setEBPMetadata(typeof asEBP==='object'? asEBP : {
'Author':'Unknown',
'Title':'Untitled',
'Description':'No description',
});
}
//solucion: guardar startOffset y endOffset (ir mirando de 6 en 6 hacia atrás)
@ -231,7 +250,7 @@ IPS.buildFromRoms=function(original, modified, asEBP=false){
}
}else{
if(startOffset>=IPS_MAX_ROM_SIZE){
throw new Error(`Files are too big for ${'EBP' ? patch.isEBP : 'IPS'} format`);
throw new Error(`Files are too big for ${patch.EBPmetadata? 'EBP' : 'IPS'} format`);
return null;
}