1
0
Fork 0
mirror of https://github.com/marcrobledo/RomPatcher.js.git synced 2025-08-16 17:00:55 +00:00

adding command line support

This commit is contained in:
Jonathan Arndt 2023-06-29 11:29:54 -06:00
parent c87df560e9
commit faee7e6a43
18 changed files with 557 additions and 40 deletions

12
js/File.js Normal file
View file

@ -0,0 +1,12 @@
const path = require('path');
const fs = require("fs");
exports.File = File;
function File(_path) {
this.stat = fs.statSync(_path);
this.size = this.stat.size;
this.name = path.basename(_path);
this.type = this.stat.type;
this.data = fs.readFileSync(_path);
}

View file

@ -1,6 +1,11 @@
/* MODDED VERSION OF MarcFile.js v20230202 - Marc Robledo 2014-2023 - http://www.marcrobledo.com/license */
function MarcFile(source, onLoad){
if(typeof module !== "undefined" && module.exports){
exports.MarcFile = MarcFile;
// saveAs = (blob,fileName) => fs.writeFileSync(fileName, blob.arrayBuffer())
}
function MarcFile(source, onLoad){
if(typeof source==='object' && source.files) /* get first file only if source is input with multiple files */
source=source.files[0];
@ -9,27 +14,34 @@ function MarcFile(source, onLoad){
this._lastRead=null;
if(typeof source==='object' && source.name && source.size){ /* source is file */
if(typeof window.FileReader!=='function')
throw new Error('Incompatible Browser');
this.fileName=source.name;
this.fileType=source.type;
this.fileSize=source.size;
try {
if (typeof window.FileReader !== 'function')
throw new Error('Incompatible Browser');
this._fileReader=new FileReader();
this._fileReader.addEventListener('load',function(){
this.marcFile._u8array=new Uint8Array(this.result);
this.marcFile._dataView=new DataView(this.result);
this._fileReader=new FileReader();
this._fileReader.marcFile=this;
this._fileReader.addEventListener('load',function(){
this.marcFile._u8array=new Uint8Array(this.result);
this.marcFile._dataView=new DataView(this.result);
if(onLoad)
onLoad.call();
},false);
this._fileReader.readAsArrayBuffer(source);
if(onLoad)
onLoad.call();
},false);
this._fileReader.marcFile=this;
this._fileReader.readAsArrayBuffer(source);
}catch (e){
if(!(e instanceof ReferenceError))
throw e;
this._fileReader = {}
this._u8array = new Uint8Array(source.data.buffer);
this._dataView=new DataView(source.data.buffer);
this.source = source;
MarcFile.prototype.readString = () => source.data.toString();
}
}else if(typeof source==='object' && typeof source.fileName==='string' && typeof source.littleEndian==='boolean'){ /* source is MarcFile */
this.fileName=source.fileName;
this.fileType=source.fileType;
@ -127,22 +139,26 @@ MarcFile.prototype.copyToFile=function(target, offsetSource, len, offsetTarget){
MarcFile.prototype.save=function(){
var blob;
try{
blob=new Blob([this._u8array],{type:this.fileType});
}catch(e){
//old browser, use BlobBuilder
window.BlobBuilder=window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder;
if(e.name==='InvalidStateError' && window.BlobBuilder){
var bb=new BlobBuilder();
bb.append(this._u8array.buffer);
blob=bb.getBlob(this.fileType);
}else{
throw new Error('Incompatible Browser');
return false;
if(typeof module !== "undefined" && module.exports)
require('fs').writeFileSync(this.fileName, Buffer.from(this._u8array.buffer));
else {
var blob;
try {
blob = new Blob([this._u8array], {type: this.fileType});
} catch (e) {
//old browser, use BlobBuilder
window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder;
if (e.name === 'InvalidStateError' && window.BlobBuilder) {
var bb = new BlobBuilder();
bb.append(this._u8array.buffer);
blob = bb.getBlob(this.fileType);
} else {
throw new Error('Incompatible Browser');
return false;
}
}
saveAs(blob, this.fileName);
}
saveAs(blob,this.fileName);
}

193
js/cmd.js Normal file
View file

@ -0,0 +1,193 @@
const {MarcFile} = require("./MarcFile");
const {File} = require("./File")
const {IPS_MAGIC, parseIPSFile} = require("./formats/ips")
const {UPS_MAGIC, parseUPSFile} = require("./formats/ups")
const {APS_N64_MAGIC, parseAPSFile} = require("./formats/aps_n64")
const {APS_GBA_MAGIC, APSGBA} = require("./formats/aps_gba")
const {BPS_MAGIC, parseBPSFile} = require("./formats/bps")
const {RUP_MAGIC, parseRUPFile} = require("./formats/rup")
const {PPF_MAGIC, parsePPFFile} = require("./formats/ppf")
const {PMSR_MAGIC, parseMODFile} = require("./formats/pmsr")
const {VCDIFF_MAGIC, parseVCDIFF} = require("./formats/vcdiff")
const {ZIP_MAGIC, ZIPManager} = require("./formats/zip")
const {md5} = require("./crc")
function hasHeader(romFile){
if(romFile.fileSize<=0x600200){
if(romFile.fileSize%1024===0)
return 0;
for(var i=0; i<HEADERS_INFO.length; i++){
if(HEADERS_INFO[i][0].test(romFile.fileName) && (romFile.fileSize-HEADERS_INFO[i][1])%HEADERS_INFO[i][1]===0){
return HEADERS_INFO[i][1];
}
}
}
return 0;
}
function validateSource(patch, romFile){
if(patch && romFile && typeof patch.validateSource !== 'undefined'){
if(patch.validateSource(romFile, hasHeader(romFile))){
console.log('apply');
}else{
console.warn('apply'+ 'error_crc_input'+ 'warning');
}
}else{
console.log('valid source');
}
}
function _readPatchFile(patchFile, romFile){
patchFile.littleEndian=false;
let patch;
const header = patchFile.readString(6);
if(patchFile.getExtension()!=='jar' && header.startsWith(ZIP_MAGIC)){
patch=false;
validateSource();
ZIPManager.parseFile(patchFile);
}else{
if(header.startsWith(IPS_MAGIC)){
patch=parseIPSFile(patchFile);
}else if(header.startsWith(UPS_MAGIC)){
patch=parseUPSFile(patchFile);
}else if(header.startsWith(APS_N64_MAGIC)){
patch=parseAPSFile(patchFile);
}else if(header.startsWith(APS_GBA_MAGIC)){
patch=APSGBA.fromFile(patchFile);
}else if(header.startsWith(BPS_MAGIC)){
patch=parseBPSFile(patchFile);
}else if(header.startsWith(RUP_MAGIC)){
patch=parseRUPFile(patchFile);
}else if(header.startsWith(PPF_MAGIC)){
patch=parsePPFFile(patchFile);
}else if(header.startsWith(PMSR_MAGIC)){
patch=parseMODFile(patchFile);
}else if(header.startsWith(VCDIFF_MAGIC)){
patch=parseVCDIFF(patchFile);
}else{
patch=null;
console.log('apply'+ 'error_invalid_patch'+ 'error');
}
validateSource(patchFile, romFile);
}
return patch;
}
function preparePatchedRom(originalRom, patchedRom, headerSize) {
patchedRom.fileName = originalRom.fileName.replace(/\.([^\.]*?)$/, ' (patched).$1');
patchedRom.fileType = originalRom.fileType;
if (headerSize) {
if (el('checkbox-removeheader').checked) {
const patchedRomWithOldHeader = new MarcFile(headerSize + patchedRom.fileSize);
oldHeader.copyToFile(patchedRomWithOldHeader, 0);
patchedRom.copyToFile(patchedRomWithOldHeader, 0, patchedRom.fileSize, headerSize);
patchedRomWithOldHeader.fileName = patchedRom.fileName;
patchedRomWithOldHeader.fileType = patchedRom.fileType;
patchedRom = patchedRomWithOldHeader;
} else if (el('checkbox-addheader').checked) {
patchedRom = patchedRom.slice(headerSize);
}
}
/* fix checksum if needed */
// if (false) {
// var checksumInfo = _getHeaderChecksumInfo(patchedRom);
// if (checksumInfo && checksumInfo.current !== checksumInfo.calculated && confirm(_('fix_checksum_prompt') + ' (' + padZeroes(checksumInfo.current) + ' -> ' + padZeroes(checksumInfo.calculated) + ')')) {
// checksumInfo.fix(patchedRom);
// }
// }
console.log('apply');
patchedRom.save();
}
function padZeroes(intVal, nBytes){
var hexString=intVal.toString(16);
while(hexString.length<nBytes*2)
hexString='0'+hexString;
return hexString
}
/* CRC32 - from Alex - https://stackoverflow.com/a/18639999 */
const CRC32_TABLE=(function(){
var c,crcTable=[];
for(var n=0;n<256;n++){
c=n;
for(var k=0;k<8;k++)
c=((c&1)?(0xedb88320^(c>>>1)):(c>>>1));
crcTable[n]=c;
}
return crcTable;
}());
function crc32(marcFile, headerSize, ignoreLast4Bytes){
var data=headerSize? new Uint8Array(marcFile._u8array.buffer, headerSize):marcFile._u8array;
var crc=0^(-1);
var len=ignoreLast4Bytes?data.length-4:data.length;
for(var i=0;i<len;i++)
crc=(crc>>>8)^CRC32_TABLE[(crc^data[i])&0xff];
return ((crc^(-1))>>>0);
}
function updateChecksums(file, romFile, startOffset, force){
if(file===romFile && file.fileSize>33554432 && !force){
console.log('File is too big. Force calculate checksum? Add -f to command');
return false;
}
console.log('crc32='+padZeroes(crc32(file, startOffset), 4));
console.log('md5='+padZeroes(md5(file, startOffset), 16));
}
let headerSize;
function canHaveFakeHeader(romFile){
if(romFile.fileSize<=0x600000){
for(let i=0; i<HEADERS_INFO.length; i++){
if(HEADERS_INFO[i][0].test(romFile.fileName) && (romFile.fileSize%HEADERS_INFO[i][2]===0)){
return HEADERS_INFO[i][1];
}
}
}
return 0;
}
function _parseROM(romFile){
if(romFile.getExtension()!=='jar' && romFile.readString(4).startsWith(ZIP_MAGIC)){
ZIPManager.parseFile(romFile);
}else{
if(headerSize=canHaveFakeHeader(romFile)){
if(headerSize<1024){
console.log(headerSize+'b');
}else{
console.log(parseInt(headerSize/1024)+'kb');
}
}else if(headerSize=hasHeader(romFile)){
// do nothing
}else{
// do nothing
}
updateChecksums(romFile, 0);
}
}
const applyPatch = (patch, rom, validateChecksums) => {
rom = new MarcFile(new File(rom));
_parseROM(rom);
patch = new MarcFile(new File(patch));
patch = _readPatchFile(patch, rom);
if (!patch || !rom)
throw new Error('No ROM/patch selected');
console.log('apply'+ 'applying_patch'+ 'loading');
try {
preparePatchedRom(rom, patch.apply(rom, validateChecksums), headerSize);
} catch (e) {
// console.log('apply'+ 'Error: ' + (e.message)+ 'error');
throw e;
}
}
module.exports = {applyPatch}

View file

@ -124,3 +124,7 @@ function crc16(marcFile, offset, len){
return crc & 0xffff;
}
if(typeof module !== "undefined" && module.exports){
module.exports = {md5};
}

View file

@ -4,7 +4,9 @@
const APS_GBA_MAGIC='APS1';
const APS_GBA_BLOCK_SIZE=0x010000; //64Kb
const APS_GBA_RECORD_SIZE=4 + 2 + 2 + APS_GBA_BLOCK_SIZE;
if(typeof module !== "undefined" && module.exports){
module.exports = {APS_GBA_MAGIC, APSGBA};
}
function APSGBA(){
this.sourceSize=0;
this.targetSize=0;

View file

@ -5,7 +5,9 @@ const APS_N64_MAGIC='APS10';
const APS_RECORD_RLE=0x0000;
const APS_RECORD_SIMPLE=0x01;
const APS_N64_MODE=0x01;
if(typeof module !== "undefined" && module.exports){
module.exports = {APS_N64_MAGIC, parseAPSFile};
}
function APS(){
this.records=[];
this.headerType=0;

View file

@ -6,7 +6,9 @@ const BPS_ACTION_SOURCE_READ=0;
const BPS_ACTION_TARGET_READ=1;
const BPS_ACTION_SOURCE_COPY=2;
const BPS_ACTION_TARGET_COPY=3;
if(typeof module !== "undefined" && module.exports){
module.exports = {BPS_MAGIC, parseBPSFile};
}
function BPS(){
this.sourceSize=0;

View file

@ -1,13 +1,17 @@
/* IPS module for Rom Patcher JS v20220417 - Marc Robledo 2016-2022 - http://www.marcrobledo.com/license */
/* File format specification: http://www.smwiki.net/wiki/IPS_file_format */
const IPS_MAGIC='PATCH';
const IPS_MAX_SIZE=0x1000000; //16 megabytes
const IPS_RECORD_RLE=0x0000;
const IPS_RECORD_SIMPLE=0x01;
if(typeof module !== "undefined" && module.exports){
module.exports = {IPS_MAGIC, IPS_MAX_SIZE, IPS_RECORD_RLE, IPS_RECORD_SIMPLE, IPS, parseIPSFile};
}
function IPS(){
this.records=[];
this.truncate=false;

View file

@ -5,7 +5,9 @@ const PMSR_MAGIC='PMSR';
const YAY0_MAGIC='Yay0';
const PAPER_MARIO_USA10_CRC32=0xa7f5cd7e;
const PAPER_MARIO_USA10_FILE_SIZE=41943040;
if(typeof module !== "undefined" && module.exports){
module.exports = {PMSR_MAGIC, parseMODFile};
}
function PMSR(){
this.targetSize=0;

View file

@ -2,12 +2,14 @@
/* File format specification: https://www.romhacking.net/utilities/353/ */
const PPF_MAGIC='PPF';
const PPF_IMAGETYPE_BIN=0x00;
const PPF_IMAGETYPE_GI=0x01;
const PPF_BEGIN_FILE_ID_DIZ_MAGIC='@BEG';//@BEGIN_FILE_ID.DIZ
if(typeof module !== "undefined" && module.exports){
module.exports = {PPF_MAGIC, parsePPFFile};
}
function PPF(){
this.version=3;
this.imageType=PPF_IMAGETYPE_BIN;

View file

@ -6,7 +6,9 @@ const RUP_COMMAND_END=0x00;
const RUP_COMMAND_OPEN_NEW_FILE=0x01;
const RUP_COMMAND_XOR_RECORD=0x02;
const RUP_ROM_TYPES=['raw','nes','fds','snes','n64','gb','sms','mega','pce','lynx'];
if(typeof module !== "undefined" && module.exports){
module.exports = {RUP_MAGIC, parseRUPFile};
}
function RUP(){
this.author='';

View file

@ -2,7 +2,9 @@
/* File format specification: http://www.romhacking.net/documents/392/ */
const UPS_MAGIC='UPS1';
if(typeof module !== "undefined" && module.exports){
module.exports = {UPS_MAGIC, UPS, parseUPSFile};
}
function UPS(){
this.records=[];
this.sizeInput=0;

View file

@ -6,7 +6,6 @@
some code and ideas borrowed from:
https://hack64.net/jscripts/libpatch.js?6
*/
//const VCDIFF_MAGIC=0xd6c3c400;
const VCDIFF_MAGIC='\xd6\xc3\xc4';
/*
@ -17,7 +16,10 @@ const XDELTA_100_MAGIC='%XDZ002';
const XDELTA_104_MAGIC='%XDZ003';
const XDELTA_110_MAGIC='%XDZ004';
*/
if(typeof module !== "undefined" && module.exports){
module.exports = {VCDIFF_MAGIC, parseVCDIFF};
MarcFile = require("../MarcFile").MarcFile;
}
function VCDIFF(patchFile){
this.file=patchFile;

View file

@ -2,6 +2,9 @@
const ZIP_MAGIC='\x50\x4b\x03\x04';
if(typeof module !== "undefined" && module.exports){
module.exports = {ZIP_MAGIC, ZIPManager};
}
var ZIPManager=(function(){
const FILTER_PATCHES=/\.(ips|ups|bps|aps|rup|ppf|mod|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