Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding command line support #61

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*.gba
*.sav
*.sgm
*.iml
.idea
node_modules
12 changes: 12 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#! /usr/bin/env node
const { program } = require('commander')
const {applyPatch} = require('./js/cmd')
program
.command('patch')
.description('List all the TODO tasks')
.argument('<file>', 'the patch to apply')
.argument('<file>','the rom file getting the patch')
.option('-v, --validate-checksum','should validate checksum')
.action((patch, romFile, options) => applyPatch(romFile, patch, options));

program.parse()
12 changes: 12 additions & 0 deletions js/File.js
Original file line number Diff line number Diff line change
@@ -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);
}
82 changes: 49 additions & 33 deletions js/MarcFile.js
Original file line number Diff line number Diff line change
@@ -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];

Expand All @@ -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;

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);



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);

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;
Expand Down Expand Up @@ -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);
}


Expand Down
193 changes: 193 additions & 0 deletions js/cmd.js
Original file line number Diff line number Diff line change
@@ -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}
4 changes: 4 additions & 0 deletions js/crc.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,7 @@ function crc16(marcFile, offset, len){

return crc & 0xffff;
}

if(typeof module !== "undefined" && module.exports){
module.exports = {md5};
}
4 changes: 3 additions & 1 deletion js/formats/aps_gba.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 3 additions & 1 deletion js/formats/aps_n64.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 3 additions & 1 deletion js/formats/bps.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 6 additions & 2 deletions js/formats/ips.js
Original file line number Diff line number Diff line change
@@ -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;
Expand Down