mirror of
https://github.com/marcrobledo/RomPatcher.js.git
synced 2025-06-27 16:25:54 +00:00
patch creation can now create IPS patches if offsets are below format limit, rearranged files internally
This commit is contained in:
parent
e5bc46d725
commit
8146f4860a
27 changed files with 190 additions and 171 deletions
266
js/MarcFile.js
Normal file
266
js/MarcFile.js
Normal file
|
@ -0,0 +1,266 @@
|
|||
/* MODDED VERSION OF MarcFile.js v20181020 - Marc Robledo 2014-2018 - http://www.marcrobledo.com/license */
|
||||
|
||||
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];
|
||||
|
||||
this.littleEndian=false;
|
||||
this.offset=0;
|
||||
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);
|
||||
|
||||
|
||||
|
||||
}else if(typeof source==='object' && typeof source.fileName==='string' && typeof source.littleEndian==='boolean'){ /* source is MarcFile */
|
||||
this.fileName=source.fileName;
|
||||
this.fileType=source.fileType;
|
||||
this.fileSize=source.fileSize;
|
||||
|
||||
var ab=new ArrayBuffer(source);
|
||||
this._u8array=new Uint8Array(this.fileType);
|
||||
this._dataView=new DataView(this.fileType);
|
||||
|
||||
source.copyToFile(this, 0);
|
||||
if(onLoad)
|
||||
onLoad.call();
|
||||
|
||||
|
||||
|
||||
}else if(typeof source==='object' && typeof source.byteLength==='number'){ /* source is ArrayBuffer or TypedArray */
|
||||
this.fileName='file.bin';
|
||||
this.fileType='application/octet-stream';
|
||||
this.fileSize=source.byteLength;
|
||||
|
||||
if(typeof source.buffer !== 'undefined')
|
||||
source=source.buffer;
|
||||
this._u8array=new Uint8Array(source);
|
||||
this._dataView=new DataView(source);
|
||||
|
||||
if(onLoad)
|
||||
onLoad.call();
|
||||
|
||||
|
||||
|
||||
}else if(typeof source==='number'){ /* source is integer (new empty file) */
|
||||
this.fileName='file.bin';
|
||||
this.fileType='application/octet-stream';
|
||||
this.fileSize=source;
|
||||
|
||||
var ab=new ArrayBuffer(source);
|
||||
this._u8array=new Uint8Array(ab);
|
||||
this._dataView=new DataView(ab);
|
||||
|
||||
if(onLoad)
|
||||
onLoad.call();
|
||||
}else{
|
||||
throw new Error('Invalid source');
|
||||
}
|
||||
}
|
||||
MarcFile.IS_MACHINE_LITTLE_ENDIAN=(function(){ /* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView#Endianness */
|
||||
var buffer=new ArrayBuffer(2);
|
||||
new DataView(buffer).setInt16(0, 256, true /* littleEndian */);
|
||||
// Int16Array uses the platform's endianness.
|
||||
return new Int16Array(buffer)[0] === 256;
|
||||
})();
|
||||
|
||||
|
||||
|
||||
MarcFile.prototype.seek=function(offset){
|
||||
this.offset=offset;
|
||||
}
|
||||
MarcFile.prototype.skip=function(nBytes){
|
||||
this.offset+=nBytes;
|
||||
}
|
||||
MarcFile.prototype.isEOF=function(){
|
||||
return !(this.offset<this.fileSize)
|
||||
}
|
||||
|
||||
MarcFile.prototype.slice=function(offset, len){
|
||||
len=len || (this.fileSize-offset);
|
||||
|
||||
var newFile;
|
||||
|
||||
if(typeof this._u8array.buffer.slice!=='undefined'){
|
||||
newFile=new MarcFile(0);
|
||||
newFile.fileSize=len;
|
||||
newFile._u8array=new Uint8Array(this._u8array.buffer.slice(offset, offset+len));
|
||||
}else{
|
||||
newFile=new MarcFile(len);
|
||||
this.copyToFile(newFile, offset, len, 0);
|
||||
}
|
||||
newFile.fileName=this.fileName;
|
||||
newFile.fileType=this.fileType;
|
||||
newFile.littleEndian=this.littleEndian;
|
||||
return newFile;
|
||||
}
|
||||
|
||||
|
||||
MarcFile.prototype.copyToFile=function(target, offsetSource, len, offsetTarget){
|
||||
if(typeof offsetTarget==='undefined')
|
||||
offsetTarget=offsetSource;
|
||||
|
||||
len=len || (this.fileSize-offsetSource);
|
||||
|
||||
for(var i=0; i<len; i++){
|
||||
target._u8array[offsetTarget+i]=this._u8array[offsetSource+i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
saveAs(blob,this.fileName);
|
||||
}
|
||||
|
||||
|
||||
MarcFile.prototype.readU8=function(){
|
||||
this._lastRead=this._u8array[this.offset];
|
||||
|
||||
this.offset++;
|
||||
return this._lastRead
|
||||
}
|
||||
MarcFile.prototype.readU16=function(){
|
||||
if(this.littleEndian)
|
||||
this._lastRead=this._u8array[this.offset] + (this._u8array[this.offset+1] << 8);
|
||||
else
|
||||
this._lastRead=(this._u8array[this.offset] << 8) + this._u8array[this.offset+1];
|
||||
|
||||
this.offset+=2;
|
||||
return this._lastRead >>> 0
|
||||
}
|
||||
MarcFile.prototype.readU24=function(){
|
||||
if(this.littleEndian)
|
||||
this._lastRead=this._u8array[this.offset] + (this._u8array[this.offset+1] << 8) + (this._u8array[this.offset+2] << 16);
|
||||
else
|
||||
this._lastRead=(this._u8array[this.offset] << 16) + (this._u8array[this.offset+1] << 8) + this._u8array[this.offset+2];
|
||||
|
||||
this.offset+=3;
|
||||
return this._lastRead >>> 0
|
||||
}
|
||||
MarcFile.prototype.readU32=function(){
|
||||
if(this.littleEndian)
|
||||
this._lastRead=this._u8array[this.offset] + (this._u8array[this.offset+1] << 8) + (this._u8array[this.offset+2] << 16) + (this._u8array[this.offset+3] << 24);
|
||||
else
|
||||
this._lastRead=(this._u8array[this.offset] << 24) + (this._u8array[this.offset+1] << 16) + (this._u8array[this.offset+2] << 8) + this._u8array[this.offset+3];
|
||||
|
||||
this.offset+=4;
|
||||
return this._lastRead >>> 0
|
||||
}
|
||||
|
||||
|
||||
|
||||
MarcFile.prototype.readBytes=function(len){
|
||||
this._lastRead=new Array(len);
|
||||
for(var i=0; i<len; i++){
|
||||
this._lastRead[i]=this._u8array[this.offset+i];
|
||||
}
|
||||
|
||||
this.offset+=len;
|
||||
return this._lastRead
|
||||
}
|
||||
|
||||
MarcFile.prototype.readString=function(len){
|
||||
this._lastRead='';
|
||||
for(var i=0;i<len && (this.offset+i)<this.fileSize && this._u8array[this.offset+i]>0;i++)
|
||||
this._lastRead=this._lastRead+String.fromCharCode(this._u8array[this.offset+i]);
|
||||
|
||||
this.offset+=len;
|
||||
return this._lastRead
|
||||
}
|
||||
|
||||
MarcFile.prototype.writeU8=function(u8){
|
||||
this._u8array[this.offset]=u8;
|
||||
|
||||
this.offset++;
|
||||
}
|
||||
MarcFile.prototype.writeU16=function(u16){
|
||||
if(this.littleEndian){
|
||||
this._u8array[this.offset]=u16 & 0xff;
|
||||
this._u8array[this.offset+1]=u16 >> 8;
|
||||
}else{
|
||||
this._u8array[this.offset]=u16 >> 8;
|
||||
this._u8array[this.offset+1]=u16 & 0xff;
|
||||
}
|
||||
|
||||
this.offset+=2;
|
||||
}
|
||||
MarcFile.prototype.writeU24=function(u24){
|
||||
if(this.littleEndian){
|
||||
this._u8array[this.offset]=u24 & 0x0000ff;
|
||||
this._u8array[this.offset+1]=(u24 & 0x00ff00) >> 8;
|
||||
this._u8array[this.offset+2]=(u24 & 0xff0000) >> 16;
|
||||
}else{
|
||||
this._u8array[this.offset]=(u24 & 0xff0000) >> 16;
|
||||
this._u8array[this.offset+1]=(u24 & 0x00ff00) >> 8;
|
||||
this._u8array[this.offset+2]=u24 & 0x0000ff;
|
||||
}
|
||||
|
||||
this.offset+=3;
|
||||
}
|
||||
MarcFile.prototype.writeU32=function(u32){
|
||||
if(this.littleEndian){
|
||||
this._u8array[this.offset]=u32 & 0x000000ff;
|
||||
this._u8array[this.offset+1]=(u32 & 0x0000ff00) >> 8;
|
||||
this._u8array[this.offset+2]=(u32 & 0x00ff0000) >> 16;
|
||||
this._u8array[this.offset+3]=(u32 & 0xff000000) >> 24;
|
||||
}else{
|
||||
this._u8array[this.offset]=(u32 & 0xff000000) >> 24;
|
||||
this._u8array[this.offset+1]=(u32 & 0x00ff0000) >> 16;
|
||||
this._u8array[this.offset+2]=(u32 & 0x0000ff00) >> 8;
|
||||
this._u8array[this.offset+3]=u32 & 0x000000ff;
|
||||
}
|
||||
|
||||
this.offset+=4;
|
||||
}
|
||||
|
||||
|
||||
MarcFile.prototype.writeBytes=function(a){
|
||||
for(var i=0;i<a.length;i++)
|
||||
this._u8array[this.offset+i]=a[i]
|
||||
|
||||
this.offset+=a.length;
|
||||
}
|
||||
|
||||
MarcFile.prototype.writeString=function(str,len){
|
||||
len=len || str.length;
|
||||
for(var i=0;i<str.length && i<len;i++)
|
||||
this._u8array[this.offset+i]=str.charCodeAt(i);
|
||||
|
||||
for(;i<len;i++)
|
||||
this._u8array[this.offset+i]=0x00;
|
||||
|
||||
this.offset+=len;
|
||||
}
|
658
js/RomPatcher.js
Normal file
658
js/RomPatcher.js
Normal file
|
@ -0,0 +1,658 @@
|
|||
/* Rom Patcher JS v20200502 - Marc Robledo 2016-2020 - http://www.marcrobledo.com/license */
|
||||
|
||||
const TOO_BIG_ROM_SIZE=67108863;
|
||||
const HEADERS_INFO=[
|
||||
[/\.nes$/, 16, 1024], //interNES
|
||||
[/\.fds$/, 16, 65500], //fwNES
|
||||
[/\.lnx$/, 64, 1024],
|
||||
//[/\.rom$/, 8192, 1024], //jaguar
|
||||
[/\.(pce|nes|gbc?|smc|sfc|fig|swc)$/, 512, 1024]
|
||||
];
|
||||
|
||||
|
||||
|
||||
/* service worker */
|
||||
const FORCE_HTTPS=true;
|
||||
if(FORCE_HTTPS && location.protocol==='http:')
|
||||
location.href=window.location.href.replace('http:','https:');
|
||||
else if(location.protocol==='https:' && 'serviceWorker' in navigator)
|
||||
navigator.serviceWorker.register('/RomPatcher.js/_cache_service_worker.js', {scope: '/RomPatcher.js/'});
|
||||
|
||||
|
||||
|
||||
var romFile, patchFile, patch, romFile1, romFile2, tempFile, headerSize, oldHeader;
|
||||
var fetchedPatches;
|
||||
var userLanguage;
|
||||
|
||||
var CAN_USE_WEB_WORKERS=true;
|
||||
var webWorkerApply,webWorkerCreate,webWorkerCrc;
|
||||
try{
|
||||
webWorkerApply=new Worker('./js/worker_apply.js');
|
||||
webWorkerApply.onmessage = event => { // listen for events from the worker
|
||||
//retrieve arraybuffers back from webworker
|
||||
if(!el('checkbox-removeheader').checked && !el('checkbox-addheader').checked){ //when adding/removing header we don't need the arraybuffer back since we made a copy previously
|
||||
romFile._u8array=event.data.romFileU8Array;
|
||||
romFile._dataView=new DataView(romFile._u8array.buffer);
|
||||
}
|
||||
patchFile._u8array=event.data.patchFileU8Array;
|
||||
patchFile._dataView=new DataView(patchFile._u8array.buffer);
|
||||
|
||||
if(event.data.patchedRomU8Array)
|
||||
preparePatchedRom(romFile, new MarcFile(event.data.patchedRomU8Array.buffer), headerSize);
|
||||
|
||||
setTabApplyEnabled(true);
|
||||
if(event.data.errorMessage)
|
||||
setMessage('apply', _(event.data.errorMessage.replace('Error: ','')), 'error');
|
||||
else
|
||||
setMessage('apply');
|
||||
};
|
||||
webWorkerApply.onerror = event => { // listen for events from the worker
|
||||
setTabApplyEnabled(true);
|
||||
setMessage('apply', _(event.message.replace('Error: ','')), 'error');
|
||||
};
|
||||
|
||||
|
||||
|
||||
webWorkerCreate=new Worker('./js/worker_create.js');
|
||||
webWorkerCreate.onmessage = event => { // listen for events from the worker
|
||||
var newPatchFile=new MarcFile(event.data.patchFileU8Array);
|
||||
newPatchFile.fileName=romFile2.fileName.replace(/\.[^\.]+$/,'')+'.'+el('select-patch-type').value;
|
||||
newPatchFile.save();
|
||||
|
||||
setMessage('create');
|
||||
setTabCreateEnabled(true);
|
||||
};
|
||||
webWorkerCreate.onerror = event => { // listen for events from the worker
|
||||
setTabCreateEnabled(true);
|
||||
setMessage('create', _(event.message.replace('Error: ','')), 'error');
|
||||
};
|
||||
|
||||
|
||||
|
||||
webWorkerCrc=new Worker('./js/worker_crc.js');
|
||||
webWorkerCrc.onmessage = event => { // listen for events from the worker
|
||||
//console.log('received_crc');
|
||||
el('crc32').innerHTML=padZeroes(event.data.crc32, 4);
|
||||
el('md5').innerHTML=padZeroes(event.data.md5, 16);
|
||||
romFile._u8array=event.data.u8array;
|
||||
romFile._dataView=new DataView(event.data.u8array.buffer);
|
||||
|
||||
if(window.crypto&&window.crypto.subtle&&window.crypto.subtle.digest){
|
||||
sha1(romFile);
|
||||
}
|
||||
|
||||
validateSource();
|
||||
setTabApplyEnabled(true);
|
||||
};
|
||||
webWorkerCrc.onerror = event => { // listen for events from the worker
|
||||
setMessage('apply', event.message.replace('Error: ',''), 'error');
|
||||
};
|
||||
}catch(e){
|
||||
CAN_USE_WEB_WORKERS=false;
|
||||
}
|
||||
|
||||
|
||||
/* Shortcuts */
|
||||
function addEvent(e,ev,f){e.addEventListener(ev,f,false)}
|
||||
function el(e){return document.getElementById(e)}
|
||||
function _(str){return userLanguage[str] || str}
|
||||
|
||||
|
||||
|
||||
function fetchPatch(uri){
|
||||
setTabApplyEnabled(false);
|
||||
setMessage('apply', _('downloading'), 'loading');
|
||||
|
||||
|
||||
var isCompressed=/\#/.test(uri);
|
||||
var patchURI=decodeURI(uri.replace(/\#.*?$/, ''));
|
||||
//console.log(patchURI);
|
||||
var compressedName=uri.replace(/^.*?\#/,'');
|
||||
//console.log(compressedName);
|
||||
|
||||
|
||||
if(typeof window.fetch==='function'){
|
||||
fetch(patchURI)
|
||||
.then(result => result.arrayBuffer()) // Gets the response and returns it as a blob
|
||||
.then(arrayBuffer => {
|
||||
fetchedPatches[patchURI]=patchFile=new MarcFile(arrayBuffer);
|
||||
fetchedPatches[patchURI].fileName=patchURI.replace(/^(.*?\/)+/g, '');
|
||||
_readPatchFile();
|
||||
})
|
||||
.catch(function(evt){
|
||||
setMessage('apply', _('error_downloading'), 'error');
|
||||
//setMessage('apply', evt.message, 'error');
|
||||
});
|
||||
}else{
|
||||
var xhr=new XMLHttpRequest();
|
||||
xhr.open('GET', patchURI, true);
|
||||
xhr.responseType='arraybuffer';
|
||||
|
||||
xhr.onload=function(evt){
|
||||
if(this.status===200){
|
||||
fetchedPatches[patchURI]=patchFile=new MarcFile(xhr.response);
|
||||
fetchedPatches[patchURI].fileName=patchURI.replace(/^(.*?\/)+/g, '');
|
||||
_readPatchFile();
|
||||
}else{
|
||||
setMessage('apply', _('error_downloading')+' ('+this.status+')', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror=function(evt){
|
||||
setMessage('apply', _('error_downloading'), 'error');
|
||||
};
|
||||
|
||||
xhr.send(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function _parseROM(){
|
||||
el('checkbox-addheader').checked=false;
|
||||
el('checkbox-removeheader').checked=false;
|
||||
|
||||
if(romFile.readString(4).startsWith(ZIP_MAGIC)){
|
||||
parseZIPFile(romFile);
|
||||
setTabApplyEnabled(false);
|
||||
}else{
|
||||
if(headerSize=canHaveFakeHeader(romFile)){
|
||||
el('row-addheader').style.display='flex';
|
||||
if(headerSize<1024){
|
||||
el('headersize').innerHTML=headerSize+'b';
|
||||
}else{
|
||||
el('headersize').innerHTML=parseInt(headerSize/1024)+'kb';
|
||||
}
|
||||
el('row-removeheader').style.display='none';
|
||||
}else if(headerSize=hasHeader(romFile)){
|
||||
el('row-addheader').style.display='none';
|
||||
el('row-removeheader').style.display='flex';
|
||||
}else{
|
||||
el('row-addheader').style.display='none';
|
||||
el('row-removeheader').style.display='none';
|
||||
}
|
||||
|
||||
updateChecksums(romFile, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* initialize app */
|
||||
addEvent(window,'load',function(){
|
||||
/* zip-js web worker */
|
||||
if(CAN_USE_WEB_WORKERS){
|
||||
zip.useWebWorkers=true;
|
||||
zip.workerScriptsPath='./js/zip.js/';
|
||||
}else{
|
||||
zip.useWebWorkers=false;
|
||||
|
||||
var script=document.createElement('script');
|
||||
script.src='./js/zip.js/inflate.js';
|
||||
document.getElementsByTagName('head')[0].appendChild(script);
|
||||
}
|
||||
|
||||
/* language */
|
||||
var langCode=(navigator.language || navigator.userLanguage).substr(0,2);
|
||||
if(typeof LOCALIZATION[langCode]==='object'){
|
||||
userLanguage=LOCALIZATION[langCode];
|
||||
}else{
|
||||
userLanguage=LOCALIZATION.en;
|
||||
}
|
||||
var translatableElements=document.querySelectorAll('*[data-localize]');
|
||||
for(var i=0; i<translatableElements.length; i++){
|
||||
translatableElements[i].innerHTML=_(translatableElements[i].dataset.localize);
|
||||
}
|
||||
|
||||
el('row-file-patch').title=_('compatible_formats')+' IPS, UPS, APS, BPS, RUP, PPF, MOD (Paper Mario Star Rod), xdelta';
|
||||
|
||||
el('input-file-rom').value='';
|
||||
el('input-file-patch').value='';
|
||||
setTabApplyEnabled(true);
|
||||
|
||||
addEvent(el('input-file-rom'), 'change', function(){
|
||||
setTabApplyEnabled(false);
|
||||
romFile=new MarcFile(this, _parseROM);
|
||||
});
|
||||
|
||||
|
||||
|
||||
/* predefined patches */
|
||||
if(typeof PREDEFINED_PATCHES!=='undefined'){
|
||||
fetchedPatches={};
|
||||
|
||||
var container=el('input-file-patch').parentElement;
|
||||
container.removeChild(el('input-file-patch'));
|
||||
|
||||
var select=document.createElement('select');
|
||||
select.id='input-file-patch';
|
||||
for(var i=0; i<PREDEFINED_PATCHES.length; i++){
|
||||
var option=document.createElement('option');
|
||||
option.value=PREDEFINED_PATCHES[i].patch;
|
||||
option.innerHTML=PREDEFINED_PATCHES[i].name;
|
||||
select.appendChild(option);
|
||||
}
|
||||
container.appendChild(select)
|
||||
container.parentElement.title='';
|
||||
|
||||
|
||||
addEvent(select,'change',function(){
|
||||
if(fetchedPatches[this.value.replace(/\#.*?$/, '')]){
|
||||
patchFile=fetchedPatches[this.value.replace(/\#.*?$/, '')];
|
||||
patchFile.seek(0);
|
||||
_readPatchFile();
|
||||
}else{
|
||||
patch=null;
|
||||
patchFile=null;
|
||||
fetchPatch(this.value);
|
||||
}
|
||||
});
|
||||
fetchPatch(select.value);
|
||||
}else{
|
||||
setTabCreateEnabled(true);
|
||||
el('input-file-rom1').value='';
|
||||
el('input-file-rom2').value='';
|
||||
|
||||
el('switch-container').style.visibility='visible';
|
||||
|
||||
addEvent(el('input-file-patch'), 'change', function(){
|
||||
setTabApplyEnabled(false);
|
||||
patchFile=new MarcFile(this, _readPatchFile)
|
||||
});
|
||||
addEvent(el('input-file-rom1'), 'change', function(){
|
||||
setTabCreateEnabled(false);
|
||||
romFile1=new MarcFile(this, function(){setTabCreateEnabled(true)});
|
||||
});
|
||||
addEvent(el('input-file-rom2'), 'change', function(){
|
||||
setTabCreateEnabled(false);
|
||||
romFile2=new MarcFile(this, function(){setTabCreateEnabled(true)});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
addEvent(el('checkbox-removeheader'), 'change', function(){
|
||||
if(this.checked)
|
||||
updateChecksums(romFile, headerSize);
|
||||
else
|
||||
updateChecksums(romFile, 0);
|
||||
});
|
||||
|
||||
//setCreatorMode(true);
|
||||
});
|
||||
|
||||
|
||||
|
||||
function canHaveFakeHeader(romFile){
|
||||
if(romFile.fileSize<=0x600000){
|
||||
for(var 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 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 updateChecksums(file, startOffset, force){
|
||||
if(file===romFile && file.fileSize>33554432 && !force){
|
||||
el('crc32').innerHTML='File is too big. <span onclick=\"updateChecksums(romFile,'+startOffset+',true)\">Force calculate checksum</span>';
|
||||
el('md5').innerHTML='';
|
||||
el('sha1').innerHTML='';
|
||||
setTabApplyEnabled(true);
|
||||
return false;
|
||||
}
|
||||
el('crc32').innerHTML='Calculating...';
|
||||
el('md5').innerHTML='Calculating...';
|
||||
|
||||
if(CAN_USE_WEB_WORKERS){
|
||||
setTabApplyEnabled(false);
|
||||
webWorkerCrc.postMessage({u8array:file._u8array, startOffset:startOffset}, [file._u8array.buffer]);
|
||||
|
||||
if(window.crypto&&window.crypto.subtle&&window.crypto.subtle.digest){
|
||||
el('sha1').innerHTML='Calculating...';
|
||||
}
|
||||
}else{
|
||||
window.setTimeout(function(){
|
||||
el('crc32').innerHTML=padZeroes(crc32(file, startOffset), 4);
|
||||
el('md5').innerHTML=padZeroes(md5(file, startOffset), 16);
|
||||
|
||||
validateSource();
|
||||
setTabApplyEnabled(true);
|
||||
}, 30);
|
||||
|
||||
if(window.crypto&&window.crypto.subtle&&window.crypto.subtle.digest){
|
||||
el('sha1').innerHTML='Calculating...';
|
||||
sha1(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validateSource(){
|
||||
if(patch && romFile && typeof patch.validateSource !== 'undefined'){
|
||||
if(patch.validateSource(romFile, el('checkbox-removeheader').checked && hasHeader(romFile))){
|
||||
el('crc32').className='valid';
|
||||
setMessage('apply');
|
||||
}else{
|
||||
el('crc32').className='invalid';
|
||||
setMessage('apply', _('error_crc_input'), 'warning');
|
||||
}
|
||||
}else{
|
||||
el('crc32').className='';
|
||||
setMessage('apply');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function _readPatchFile(){
|
||||
setTabApplyEnabled(false);
|
||||
patchFile.littleEndian=false;
|
||||
|
||||
var header=patchFile.readString(6);
|
||||
if(header.startsWith(ZIP_MAGIC)){
|
||||
if(typeof PREDEFINED_PATCHES !== 'undefined' && /\#/.test(el('input-file-patch').value)){
|
||||
parseZIPFile(patchFile, el('input-file-patch').value.replace(/^.*?\#/, ''));
|
||||
}else{
|
||||
parseZIPFile(patchFile);
|
||||
}
|
||||
patch=false;
|
||||
validateSource();
|
||||
setTabApplyEnabled(false);
|
||||
}else{
|
||||
if(header.startsWith(IPS_MAGIC)){
|
||||
patch=parseIPSFile(patchFile);
|
||||
}else if(header.startsWith(UPS_MAGIC)){
|
||||
patch=parseUPSFile(patchFile);
|
||||
}else if(header.startsWith(APS_MAGIC)){
|
||||
patch=parseAPSFile(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;
|
||||
setMessage('apply', _('error_invalid_patch'), 'error');
|
||||
}
|
||||
|
||||
if(patch && typeof PREDEFINED_PATCHES!=='undefined' && PREDEFINED_PATCHES[el('input-file-patch').selectedIndex].crc){
|
||||
patch.validateSource=function(romFile, headerSize){
|
||||
return PREDEFINED_PATCHES[el('input-file-patch').selectedIndex].crc===crc32(romFile, headerSize)
|
||||
}
|
||||
}
|
||||
|
||||
validateSource();
|
||||
setTabApplyEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function preparePatchedRom(originalRom, patchedRom, headerSize){
|
||||
patchedRom.fileName=originalRom.fileName.replace(/\.([^\.]*?)$/, ' (patched).$1');
|
||||
patchedRom.fileType=originalRom.fileType;
|
||||
if(headerSize){
|
||||
if(el('checkbox-removeheader').checked){
|
||||
var 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);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
setMessage('apply');
|
||||
patchedRom.save();
|
||||
|
||||
//debug: create unheadered patch
|
||||
/*if(headerSize && el('checkbox-addheader').checked){
|
||||
createPatch(romFile, patchedRom);
|
||||
}*/
|
||||
}
|
||||
|
||||
|
||||
/*function removeHeader(romFile){
|
||||
//r._dataView=new DataView(r._dataView.buffer, headerSize);
|
||||
oldHeader=romFile.slice(0,headerSize);
|
||||
r=r.slice(headerSize);
|
||||
}*/
|
||||
function applyPatch(p,r,validateChecksums){
|
||||
if(p && r){
|
||||
if(headerSize){
|
||||
if(el('checkbox-removeheader').checked){
|
||||
//r._dataView=new DataView(r._dataView.buffer, headerSize);
|
||||
oldHeader=r.slice(0,headerSize);
|
||||
r=r.slice(headerSize);
|
||||
}else if(el('checkbox-addheader').checked){
|
||||
var romWithFakeHeader=new MarcFile(headerSize+r.fileSize);
|
||||
romWithFakeHeader.fileName=r.fileName;
|
||||
romWithFakeHeader.fileType=r.fileType;
|
||||
r.copyToFile(romWithFakeHeader, 0, r.fileSize, headerSize);
|
||||
|
||||
//add FDS header
|
||||
if(/\.fds$/.test(r.FileName) && r.fileSize%65500===0){
|
||||
//romWithFakeHeader.seek(0);
|
||||
romWithFakeHeader.writeBytes([0x46, 0x44, 0x53, 0x1a, r.fileSize/65500]);
|
||||
}
|
||||
|
||||
r=romWithFakeHeader;
|
||||
}
|
||||
}
|
||||
|
||||
if(CAN_USE_WEB_WORKERS){
|
||||
setMessage('apply', _('applying_patch'), 'loading');
|
||||
setTabApplyEnabled(false);
|
||||
|
||||
webWorkerApply.postMessage(
|
||||
{
|
||||
romFileU8Array:r._u8array,
|
||||
patchFileU8Array:patchFile._u8array,
|
||||
validateChecksums:validateChecksums
|
||||
},[
|
||||
r._u8array.buffer,
|
||||
patchFile._u8array.buffer
|
||||
]
|
||||
);
|
||||
|
||||
}else{
|
||||
setMessage('apply', _('applying_patch'), 'loading');
|
||||
|
||||
try{
|
||||
p.apply(r, validateChecksums);
|
||||
preparePatchedRom(r, p.apply(r, validateChecksums), headerSize);
|
||||
|
||||
}catch(e){
|
||||
setMessage('apply', 'Error: '+_(e.message), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
}else{
|
||||
setMessage('apply', 'No ROM/patch selected', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function createPatch(sourceFile, modifiedFile, mode){
|
||||
if(!sourceFile){
|
||||
setMessage('create', 'No source ROM file specified.', 'error');
|
||||
return false;
|
||||
}else if(!modifiedFile){
|
||||
setMessage('create', 'No modified ROM file specified.', 'error');
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if(CAN_USE_WEB_WORKERS){
|
||||
setTabCreateEnabled(false);
|
||||
|
||||
setMessage('create', _('creating_patch'), 'loading');
|
||||
|
||||
webWorkerCreate.postMessage(
|
||||
{
|
||||
sourceFileU8Array:sourceFile._u8array,
|
||||
modifiedFileU8Array:modifiedFile._u8array,
|
||||
modifiedFileName:modifiedFile.fileName,
|
||||
patchMode:mode
|
||||
},[
|
||||
sourceFile._u8array.buffer,
|
||||
modifiedFile._u8array.buffer
|
||||
]
|
||||
);
|
||||
|
||||
romFile1=new MarcFile(el('input-file-rom1'));
|
||||
romFile2=new MarcFile(el('input-file-rom2'));
|
||||
}else{
|
||||
try{
|
||||
sourceFile.seek(0);
|
||||
modifiedFile.seek(0);
|
||||
|
||||
var newPatch;
|
||||
if(mode==='ips'){
|
||||
newPatch=createIPSFromFiles(sourceFile, modifiedFile);
|
||||
}else if(mode==='bps'){
|
||||
newPatch=createBPSFromFiles(sourceFile, modifiedFile, (sourceFile.fileSize<=4194304));
|
||||
}else if(mode==='ups'){
|
||||
newPatch=createUPSFromFiles(sourceFile, modifiedFile);
|
||||
}else if(mode==='aps'){
|
||||
newPatch=createAPSFromFiles(sourceFile, modifiedFile);
|
||||
}else if(mode==='rup'){
|
||||
newPatch=createRUPFromFiles(sourceFile, modifiedFile);
|
||||
}else{
|
||||
setMessage('create', _('error_invalid_patch'), 'error');
|
||||
}
|
||||
|
||||
|
||||
if(crc32(modifiedFile)===crc32(newPatch.apply(sourceFile))){
|
||||
newPatch.export(modifiedFile.fileName.replace(/\.[^\.]+$/,'')).save();
|
||||
}else{
|
||||
setMessage('create', 'Unexpected error: verification failed. Patched file and modified file mismatch. Please report this bug.', 'error');
|
||||
}
|
||||
|
||||
}catch(e){
|
||||
setMessage('create', 'Error: '+_(e.message), 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* GUI functions */
|
||||
function setMessage(tab, msg, className){
|
||||
var messageBox=el('message-'+tab);
|
||||
if(msg){
|
||||
if(className==='loading'){
|
||||
messageBox.className='message';
|
||||
messageBox.innerHTML='<span class="loading"></span> '+msg;
|
||||
}else{
|
||||
messageBox.className='message '+className;
|
||||
if(className==='warning')
|
||||
messageBox.innerHTML='⚠ '+msg;
|
||||
else if(className==='error')
|
||||
messageBox.innerHTML='✗ '+msg;
|
||||
else
|
||||
messageBox.innerHTML=msg;
|
||||
}
|
||||
messageBox.style.display='inline';
|
||||
}else{
|
||||
messageBox.style.display='none';
|
||||
}
|
||||
}
|
||||
|
||||
function setElementEnabled(element,status){
|
||||
if(status){
|
||||
el(element).className='enabled';
|
||||
}else{
|
||||
el(element).className='disabled';
|
||||
}
|
||||
el(element).disabled=!status;
|
||||
}
|
||||
function setTabCreateEnabled(status){
|
||||
if(
|
||||
(romFile1 && romFile1.fileSize>TOO_BIG_ROM_SIZE) ||
|
||||
(romFile2 && romFile2.fileSize>TOO_BIG_ROM_SIZE)
|
||||
){
|
||||
setMessage('create',_('warning_too_big'),'warning');
|
||||
}
|
||||
setElementEnabled('input-file-rom1', status);
|
||||
setElementEnabled('input-file-rom2', status);
|
||||
setElementEnabled('select-patch-type', status);
|
||||
if(romFile1 && romFile2 && status){
|
||||
setElementEnabled('button-create', status);
|
||||
}else{
|
||||
setElementEnabled('button-create', false);
|
||||
}
|
||||
}
|
||||
function setTabApplyEnabled(status){
|
||||
setElementEnabled('input-file-rom', status);
|
||||
setElementEnabled('input-file-patch', status);
|
||||
if(romFile && status && (patch || typeof PREDEFINED_PATCHES!=='undefined')){
|
||||
setElementEnabled('button-apply', status);
|
||||
}else{
|
||||
setElementEnabled('button-apply', false);
|
||||
}
|
||||
}
|
||||
function setCreatorMode(creatorMode){
|
||||
if(creatorMode){
|
||||
el('tab0').style.display='none';
|
||||
el('tab1').style.display='block';
|
||||
el('switch-create').className='switch enabled'
|
||||
}else{
|
||||
el('tab0').style.display='block';
|
||||
el('tab1').style.display='none';
|
||||
el('switch-create').className='switch disabled'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* FileSaver.js (source: http://purl.eligrey.com/github/FileSaver.js/blob/master/src/FileSaver.js)
|
||||
* A saveAs() FileSaver implementation.
|
||||
* 1.3.8
|
||||
* 2018-03-22 14:03:47
|
||||
*
|
||||
* By Eli Grey, https://eligrey.com
|
||||
* License: MIT
|
||||
* See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
|
||||
*/
|
||||
var saveAs=saveAs||function(c){"use strict";if(!(void 0===c||"undefined"!=typeof navigator&&/MSIE [1-9]\./.test(navigator.userAgent))){var t=c.document,f=function(){return c.URL||c.webkitURL||c},s=t.createElementNS("http://www.w3.org/1999/xhtml","a"),d="download"in s,u=/constructor/i.test(c.HTMLElement)||c.safari,l=/CriOS\/[\d]+/.test(navigator.userAgent),p=c.setImmediate||c.setTimeout,v=function(t){p(function(){throw t},0)},w=function(t){setTimeout(function(){"string"==typeof t?f().revokeObjectURL(t):t.remove()},4e4)},m=function(t){return/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(t.type)?new Blob([String.fromCharCode(65279),t],{type:t.type}):t},r=function(t,n,e){e||(t=m(t));var r,o=this,a="application/octet-stream"===t.type,i=function(){!function(t,e,n){for(var r=(e=[].concat(e)).length;r--;){var o=t["on"+e[r]];if("function"==typeof o)try{o.call(t,n||t)}catch(t){v(t)}}}(o,"writestart progress write writeend".split(" "))};if(o.readyState=o.INIT,d)return r=f().createObjectURL(t),void p(function(){var t,e;s.href=r,s.download=n,t=s,e=new MouseEvent("click"),t.dispatchEvent(e),i(),w(r),o.readyState=o.DONE},0);!function(){if((l||a&&u)&&c.FileReader){var e=new FileReader;return e.onloadend=function(){var t=l?e.result:e.result.replace(/^data:[^;]*;/,"data:attachment/file;");c.open(t,"_blank")||(c.location.href=t),t=void 0,o.readyState=o.DONE,i()},e.readAsDataURL(t),o.readyState=o.INIT}r||(r=f().createObjectURL(t)),a?c.location.href=r:c.open(r,"_blank")||(c.location.href=r);o.readyState=o.DONE,i(),w(r)}()},e=r.prototype;return"undefined"!=typeof navigator&&navigator.msSaveOrOpenBlob?function(t,e,n){return e=e||t.name||"download",n||(t=m(t)),navigator.msSaveOrOpenBlob(t,e)}:(e.abort=function(){},e.readyState=e.INIT=0,e.WRITING=1,e.DONE=2,e.error=e.onwritestart=e.onprogress=e.onwrite=e.onabort=e.onerror=e.onwriteend=null,function(t,e,n){return new r(t,e||t.name||"download",n)})}}("undefined"!=typeof self&&self||"undefined"!=typeof window&&window||this);
|
105
js/crc.js
Normal file
105
js/crc.js
Normal file
|
@ -0,0 +1,105 @@
|
|||
/* Rom Patcher JS - CRC32/MD5/SHA-1 calculators v20181017 - Marc Robledo 2016-2018 - http://www.marcrobledo.com/license */
|
||||
|
||||
function padZeroes(intVal, nBytes){
|
||||
var hexString=intVal.toString(16);
|
||||
while(hexString.length<nBytes*2)
|
||||
hexString='0'+hexString;
|
||||
return hexString
|
||||
}
|
||||
|
||||
|
||||
/* SHA-1 using WebCryptoAPI */
|
||||
function _sha1_promise(hash){
|
||||
var bytes=new Uint8Array(hash);
|
||||
var hexString='';
|
||||
for(var i=0;i<bytes.length;i++)
|
||||
hexString+=padZeroes(bytes[i], 1);
|
||||
el('sha1').innerHTML=hexString;
|
||||
}
|
||||
function sha1(marcFile){
|
||||
window.crypto.subtle.digest('SHA-1', marcFile._u8array.buffer)
|
||||
.then(_sha1_promise)
|
||||
.catch(function(error){
|
||||
el('sha1').innerHTML='Error';
|
||||
})
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* MD5 - from Joseph's Myers - http://www.myersdaily.org/joseph/javascript/md5.js */
|
||||
const HEX_CHR='0123456789abcdef'.split('');
|
||||
function _add32(a,b){return (a+b)&0xffffffff}
|
||||
function _md5cycle(x,k){var a=x[0],b=x[1],c=x[2],d=x[3];a=ff(a,b,c,d,k[0],7,-680876936);d=ff(d,a,b,c,k[1],12,-389564586);c=ff(c,d,a,b,k[2],17,606105819);b=ff(b,c,d,a,k[3],22,-1044525330);a=ff(a,b,c,d,k[4],7,-176418897);d=ff(d,a,b,c,k[5],12,1200080426);c=ff(c,d,a,b,k[6],17,-1473231341);b=ff(b,c,d,a,k[7],22,-45705983);a=ff(a,b,c,d,k[8],7,1770035416);d=ff(d,a,b,c,k[9],12,-1958414417);c=ff(c,d,a,b,k[10],17,-42063);b=ff(b,c,d,a,k[11],22,-1990404162);a=ff(a,b,c,d,k[12],7,1804603682);d=ff(d,a,b,c,k[13],12,-40341101);c=ff(c,d,a,b,k[14],17,-1502002290);b=ff(b,c,d,a,k[15],22,1236535329);a=gg(a,b,c,d,k[1],5,-165796510);d=gg(d,a,b,c,k[6],9,-1069501632);c=gg(c,d,a,b,k[11],14,643717713);b=gg(b,c,d,a,k[0],20,-373897302);a=gg(a,b,c,d,k[5],5,-701558691);d=gg(d,a,b,c,k[10],9,38016083);c=gg(c,d,a,b,k[15],14,-660478335);b=gg(b,c,d,a,k[4],20,-405537848);a=gg(a,b,c,d,k[9],5,568446438);d=gg(d,a,b,c,k[14],9,-1019803690);c=gg(c,d,a,b,k[3],14,-187363961);b=gg(b,c,d,a,k[8],20,1163531501);a=gg(a,b,c,d,k[13],5,-1444681467);d=gg(d,a,b,c,k[2],9,-51403784);c=gg(c,d,a,b,k[7],14,1735328473);b=gg(b,c,d,a,k[12],20,-1926607734);a=hh(a,b,c,d,k[5],4,-378558);d=hh(d,a,b,c,k[8],11,-2022574463);c=hh(c,d,a,b,k[11],16,1839030562);b=hh(b,c,d,a,k[14],23,-35309556);a=hh(a,b,c,d,k[1],4,-1530992060);d=hh(d,a,b,c,k[4],11,1272893353);c=hh(c,d,a,b,k[7],16,-155497632);b=hh(b,c,d,a,k[10],23,-1094730640);a=hh(a,b,c,d,k[13],4,681279174);d=hh(d,a,b,c,k[0],11,-358537222);c=hh(c,d,a,b,k[3],16,-722521979);b=hh(b,c,d,a,k[6],23,76029189);a=hh(a,b,c,d,k[9],4,-640364487);d=hh(d,a,b,c,k[12],11,-421815835);c=hh(c,d,a,b,k[15],16,530742520);b=hh(b,c,d,a,k[2],23,-995338651);a=ii(a,b,c,d,k[0],6,-198630844);d=ii(d,a,b,c,k[7],10,1126891415);c=ii(c,d,a,b,k[14],15,-1416354905);b=ii(b,c,d,a,k[5],21,-57434055);a=ii(a,b,c,d,k[12],6,1700485571);d=ii(d,a,b,c,k[3],10,-1894986606);c=ii(c,d,a,b,k[10],15,-1051523);b=ii(b,c,d,a,k[1],21,-2054922799);a=ii(a,b,c,d,k[8],6,1873313359);d=ii(d,a,b,c,k[15],10,-30611744);c=ii(c,d,a,b,k[6],15,-1560198380);b=ii(b,c,d,a,k[13],21,1309151649);a=ii(a,b,c,d,k[4],6,-145523070);d=ii(d,a,b,c,k[11],10,-1120210379);c=ii(c,d,a,b,k[2],15,718787259);b=ii(b,c,d,a,k[9],21,-343485551);x[0]=_add32(a,x[0]);x[1]=_add32(b,x[1]);x[2]=_add32(c,x[2]);x[3]=_add32(d,x[3])}
|
||||
function _md5blk(d){var md5blks=[],i;for(i=0;i<64;i+=4)md5blks[i>>2]=d[i]+(d[i+1]<<8)+(d[i+2]<<16)+(d[i+3]<<24);return md5blks}
|
||||
function _cmn(q,a,b,x,s,t){a=_add32(_add32(a,q),_add32(x,t));return _add32((a<<s)|(a>>>(32-s)),b)}
|
||||
function ff(a,b,c,d,x,s,t){return _cmn((b&c)|((~b)&d),a,b,x,s,t)}
|
||||
function gg(a,b,c,d,x,s,t){return _cmn((b&d)|(c&(~d)),a,b,x,s,t)}
|
||||
function hh(a,b,c,d,x,s,t){return _cmn(b^c^d,a,b,x,s,t)}
|
||||
function ii(a,b,c,d,x,s,t){return _cmn(c^(b|(~d)),a,b,x,s,t)}
|
||||
function md5(marcFile, headerSize){
|
||||
var data=headerSize? new Uint8Array(marcFile._u8array.buffer, headerSize):marcFile._u8array;
|
||||
|
||||
var n=data.length,state=[1732584193,-271733879,-1732584194,271733878],i;
|
||||
for(i=64;i<=data.length;i+=64)
|
||||
_md5cycle(state,_md5blk(data.slice(i-64,i)));
|
||||
data=data.slice(i-64);
|
||||
var tail=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
|
||||
for(i=0;i<data.length;i++)
|
||||
tail[i>>2]|=data[i]<<((i%4)<<3);
|
||||
tail[i>>2]|=0x80<<((i%4)<<3);
|
||||
if(i>55){
|
||||
_md5cycle(state,tail);
|
||||
for(i=0;i<16;i++)tail[i]=0;
|
||||
}
|
||||
tail[14]=n*8;
|
||||
_md5cycle(state,tail);
|
||||
|
||||
for(var i=0;i<state.length;i++){
|
||||
var s='',j=0;
|
||||
for(;j<4;j++)
|
||||
s+=HEX_CHR[(state[i]>>(j*8+4))&0x0f]+HEX_CHR[(state[i]>>(j*8))&0x0f];
|
||||
state[i]=s;
|
||||
}
|
||||
return state.join('')
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Adler-32 - https://en.wikipedia.org/wiki/Adler-32#Example_implementation */
|
||||
const ADLER32_MOD=0xfff1;
|
||||
function adler32(marcFile, offset, len){
|
||||
var a=1, b=0;
|
||||
|
||||
for(var i=0; i<len; i++){
|
||||
a=(a+marcFile._u8array[i+offset])%ADLER32_MOD;
|
||||
b=(b+a)%ADLER32_MOD;
|
||||
}
|
||||
|
||||
return ((b<<16) | a)>>>0;
|
||||
}
|
198
js/formats/aps.js
Normal file
198
js/formats/aps.js
Normal file
|
@ -0,0 +1,198 @@
|
|||
/* APS (N64) module for Rom Patcher JS v20180930 - Marc Robledo 2017-2018 - http://www.marcrobledo.com/license */
|
||||
/* File format specification: https://github.com/btimofeev/UniPatcher/wiki/APS-(N64) */
|
||||
|
||||
const APS_MAGIC='APS10';
|
||||
const APS_RECORD_RLE=0x0000;
|
||||
const APS_RECORD_SIMPLE=0x01;
|
||||
const APS_N64_MODE=0x01;
|
||||
|
||||
function APS(){
|
||||
this.records=[];
|
||||
this.headerType=0;
|
||||
this.encodingMethod=0;
|
||||
this.description='no description';
|
||||
|
||||
this.header={};
|
||||
}
|
||||
APS.prototype.addRecord=function(o, d){
|
||||
this.records.push({offset:o, type:APS_RECORD_SIMPLE, data:d})
|
||||
}
|
||||
APS.prototype.addRLERecord=function(o, b, l){
|
||||
this.records.push({offset:o, type:APS_RECORD_RLE, length:l, byte:b})
|
||||
}
|
||||
APS.prototype.toString=function(){
|
||||
var s='Total records: '+this.records.length;
|
||||
s+='\nHeader type: '+this.headerType;
|
||||
if(this.headerType===APS_N64_MODE){
|
||||
s+=' (N64)';
|
||||
}
|
||||
s+='\nEncoding method: '+this.encodingMethod;
|
||||
s+='\nDescription: '+this.description;
|
||||
s+='\nHeader: '+JSON.stringify(this.header);
|
||||
return s
|
||||
}
|
||||
APS.prototype.validateSource=function(sourceFile){
|
||||
if(this.headerType===APS_N64_MODE){
|
||||
sourceFile.seek(0x3c);
|
||||
if(sourceFile.readString(3)!==this.header.cartId)
|
||||
return false;
|
||||
|
||||
sourceFile.seek(0x10);
|
||||
var crc=sourceFile.readBytes(8);
|
||||
for(var i=0; i<8; i++){
|
||||
if(crc[i]!==this.header.crc[i])
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
APS.prototype.export=function(fileName){
|
||||
var patchFileSize=61;
|
||||
if(this.headerType===APS_N64_MODE)
|
||||
patchFileSize+=17;
|
||||
|
||||
for(var i=0; i<this.records.length; i++){
|
||||
if(this.records[i].type===APS_RECORD_RLE)
|
||||
patchFileSize+=7;
|
||||
else
|
||||
patchFileSize+=5+this.records[i].data.length; //offset+length+data
|
||||
}
|
||||
|
||||
tempFile=new MarcFile(patchFileSize);
|
||||
tempFile.littleEndian=true;
|
||||
tempFile.fileName=fileName+'.aps';
|
||||
tempFile.writeString(APS_MAGIC, APS_MAGIC.length);
|
||||
tempFile.writeU8(this.headerType);
|
||||
tempFile.writeU8(this.encodingMethod);
|
||||
tempFile.writeString(this.description, 50);
|
||||
|
||||
if(this.headerType===APS_N64_MODE){
|
||||
tempFile.writeU8(this.header.originalN64Format);
|
||||
tempFile.writeString(this.header.cartId, 3);
|
||||
tempFile.writeBytes(this.header.crc);
|
||||
tempFile.writeBytes(this.header.pad);
|
||||
}
|
||||
tempFile.writeU32(this.header.sizeOutput);
|
||||
|
||||
for(var i=0; i<this.records.length; i++){
|
||||
var rec=this.records[i];
|
||||
tempFile.writeU32(rec.offset);
|
||||
if(rec.type===APS_RECORD_RLE){
|
||||
tempFile.writeU8(0x00);
|
||||
tempFile.writeU8(rec.byte);
|
||||
tempFile.writeU8(rec.length);
|
||||
}else{
|
||||
tempFile.writeU8(rec.data.length);
|
||||
tempFile.writeBytes(rec.data);
|
||||
}
|
||||
}
|
||||
|
||||
return tempFile
|
||||
}
|
||||
|
||||
APS.prototype.apply=function(romFile, validate){
|
||||
if(validate && !this.validateSource(romFile)){
|
||||
throw new Error('error_crc_input');
|
||||
}
|
||||
|
||||
tempFile=new MarcFile(this.header.sizeOutput);
|
||||
romFile.copyToFile(tempFile, 0, tempFile.fileSize);
|
||||
|
||||
for(var i=0; i<this.records.length; i++){
|
||||
tempFile.seek(this.records[i].offset);
|
||||
if(this.records[i].type===APS_RECORD_RLE){
|
||||
for(var j=0; j<this.records[i].length; j++)
|
||||
tempFile.writeU8(this.records[i].byte);
|
||||
}else{
|
||||
tempFile.writeBytes(this.records[i].data);
|
||||
}
|
||||
}
|
||||
|
||||
return tempFile
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function parseAPSFile(patchFile){
|
||||
var patch=new APS();
|
||||
patchFile.littleEndian=true;
|
||||
|
||||
patchFile.seek(5);
|
||||
patch.headerType=patchFile.readU8();
|
||||
patch.encodingMethod=patchFile.readU8();
|
||||
patch.description=patchFile.readString(50);
|
||||
|
||||
var seek;
|
||||
if(patch.headerType===APS_N64_MODE){
|
||||
patch.header.originalN64Format=patchFile.readU8();
|
||||
patch.header.cartId=patchFile.readString(3);
|
||||
patch.header.crc=patchFile.readBytes(8);
|
||||
patch.header.pad=patchFile.readBytes(5);
|
||||
}
|
||||
patch.header.sizeOutput=patchFile.readU32();
|
||||
|
||||
while(!patchFile.isEOF()){
|
||||
var offset=patchFile.readU32();
|
||||
var length=patchFile.readU8();
|
||||
|
||||
if(length===APS_RECORD_RLE)
|
||||
patch.addRLERecord(offset, patchFile.readU8(seek), patchFile.readU8(seek+1));
|
||||
else
|
||||
patch.addRecord(offset, patchFile.readBytes(length));
|
||||
}
|
||||
return patch;
|
||||
}
|
||||
|
||||
function createAPSFromFiles(original, modified){
|
||||
var patch=new APS();
|
||||
|
||||
|
||||
|
||||
if(original.readU32()===0x80371240){ //is N64 ROM
|
||||
patch.headerType=APS_N64_MODE;
|
||||
|
||||
patch.header.originalN64Format=/\.v64$/i.test(original.fileName)?0:1;
|
||||
original.seek(0x3c);
|
||||
patch.header.cartId=original.readString(3);
|
||||
original.seek(0x10);
|
||||
patch.header.crc=original.readBytes(8);
|
||||
patch.header.pad=[0,0,0,0,0];
|
||||
}
|
||||
patch.header.sizeOutput=modified.fileSize;
|
||||
|
||||
original.seek(0);
|
||||
modified.seek(0);
|
||||
|
||||
while(!modified.isEOF()){
|
||||
var b1=original.isEOF()?0x00:original.readU8();
|
||||
var b2=modified.readU8();
|
||||
|
||||
if(b1!==b2){
|
||||
var RLERecord=true;
|
||||
var differentBytes=[];
|
||||
var offset=modified.offset-1;
|
||||
|
||||
while(b1!==b2 && differentBytes.length<0xff){
|
||||
differentBytes.push(b2);
|
||||
if(b2!==differentBytes[0])
|
||||
RLERecord=false;
|
||||
|
||||
if(modified.isEOF() || differentBytes.length===0xff)
|
||||
break;
|
||||
|
||||
b1=original.isEOF()?0x00:original.readU8();
|
||||
b2=modified.readU8();
|
||||
}
|
||||
|
||||
if(RLERecord && differentBytes.length>2){
|
||||
patch.addRLERecord(offset, differentBytes[0], differentBytes.length);
|
||||
}else{
|
||||
patch.addRecord(offset, differentBytes);
|
||||
}
|
||||
//NO se puede comentar??? why????
|
||||
}
|
||||
}
|
||||
|
||||
return patch
|
||||
}
|
453
js/formats/bps.js
Normal file
453
js/formats/bps.js
Normal file
|
@ -0,0 +1,453 @@
|
|||
/* BPS module for Rom Patcher JS v20180930 - Marc Robledo 2016-2018 - http://www.marcrobledo.com/license */
|
||||
/* File format specification: https://www.romhacking.net/documents/746/ */
|
||||
|
||||
const BPS_MAGIC='BPS1';
|
||||
const BPS_ACTION_SOURCE_READ=0;
|
||||
const BPS_ACTION_TARGET_READ=1;
|
||||
const BPS_ACTION_SOURCE_COPY=2;
|
||||
const BPS_ACTION_TARGET_COPY=3;
|
||||
|
||||
|
||||
function BPS(){
|
||||
this.sourceSize=0;
|
||||
this.targetSize=0;
|
||||
this.metaData='';
|
||||
this.actions=[];
|
||||
this.sourceChecksum=0;
|
||||
this.targetChecksum=0;
|
||||
this.patchChecksum=0;
|
||||
}
|
||||
BPS.prototype.toString=function(){
|
||||
var s='Source size: '+this.sourceSize;
|
||||
s+='\nTarget size: '+this.targetSize;
|
||||
s+='\nMetadata: '+this.metaData;
|
||||
s+='\n#Actions: '+this.actions.length;
|
||||
return s
|
||||
}
|
||||
BPS.prototype.validateSource=function(romFile,headerSize){return this.sourceChecksum===crc32(romFile, headerSize)}
|
||||
BPS.prototype.apply=function(romFile, validate){
|
||||
if(validate && !this.validateSource(romFile)){
|
||||
throw new Error('error_crc_input');
|
||||
}
|
||||
|
||||
|
||||
tempFile=new MarcFile(this.targetSize);
|
||||
|
||||
|
||||
//patch
|
||||
var sourceRelativeOffset=0;
|
||||
var targetRelativeOffset=0;
|
||||
for(var i=0; i<this.actions.length; i++){
|
||||
var action=this.actions[i];
|
||||
|
||||
if(action.type===BPS_ACTION_SOURCE_READ){
|
||||
romFile.copyToFile(tempFile, tempFile.offset, action.length);
|
||||
tempFile.skip(action.length);
|
||||
|
||||
}else if(action.type===BPS_ACTION_TARGET_READ){
|
||||
tempFile.writeBytes(action.bytes);
|
||||
|
||||
}else if(action.type===BPS_ACTION_SOURCE_COPY){
|
||||
sourceRelativeOffset+=action.relativeOffset;
|
||||
var actionLength=action.length;
|
||||
while(actionLength--){
|
||||
tempFile.writeU8(romFile._u8array[sourceRelativeOffset]);
|
||||
sourceRelativeOffset++;
|
||||
}
|
||||
}else if(action.type===BPS_ACTION_TARGET_COPY){
|
||||
targetRelativeOffset+=action.relativeOffset;
|
||||
var actionLength=action.length;
|
||||
while(actionLength--) {
|
||||
tempFile.writeU8(tempFile._u8array[targetRelativeOffset]);
|
||||
targetRelativeOffset++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(validate && this.targetChecksum!==crc32(tempFile)){
|
||||
throw new Error('error_crc_output');
|
||||
}
|
||||
|
||||
return tempFile
|
||||
}
|
||||
|
||||
|
||||
|
||||
function parseBPSFile(file){
|
||||
file.readVLV=BPS_readVLV;
|
||||
|
||||
file.littleEndian=true;
|
||||
var patch=new BPS();
|
||||
|
||||
|
||||
file.seek(4); //skip BPS1
|
||||
|
||||
patch.sourceSize=file.readVLV();
|
||||
patch.targetSize=file.readVLV();
|
||||
|
||||
var metaDataLength=file.readVLV();
|
||||
if(metaDataLength){
|
||||
patch.metaData=file.readString(metaDataLength);
|
||||
}
|
||||
|
||||
|
||||
var endActionsOffset=file.fileSize-12;
|
||||
while(file.offset<endActionsOffset){
|
||||
var data=file.readVLV();
|
||||
var action={type: data & 3, length: (data >> 2)+1};
|
||||
|
||||
if(action.type===BPS_ACTION_TARGET_READ){
|
||||
action.bytes=file.readBytes(action.length);
|
||||
|
||||
}else if(action.type===BPS_ACTION_SOURCE_COPY || action.type===BPS_ACTION_TARGET_COPY){
|
||||
var relativeOffset=file.readVLV();
|
||||
action.relativeOffset=(relativeOffset & 1? -1 : +1) * (relativeOffset >> 1)
|
||||
}
|
||||
|
||||
patch.actions.push(action);
|
||||
}
|
||||
|
||||
//file.seek(endActionsOffset);
|
||||
patch.sourceChecksum=file.readU32();
|
||||
patch.targetChecksum=file.readU32();
|
||||
patch.patchChecksum=file.readU32();
|
||||
|
||||
if(patch.patchChecksum!==crc32(file, 0, true)){
|
||||
throw new Error('error_crc_patch');
|
||||
}
|
||||
|
||||
|
||||
return patch;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function BPS_readVLV(){
|
||||
var data=0, shift=1;
|
||||
while(true){
|
||||
var x = this.readU8();
|
||||
data += (x & 0x7f) * shift;
|
||||
if(x & 0x80)
|
||||
break;
|
||||
shift <<= 7;
|
||||
data += shift;
|
||||
}
|
||||
|
||||
this._lastRead=data;
|
||||
return data;
|
||||
}
|
||||
function BPS_writeVLV(data){
|
||||
while(true){
|
||||
var x = data & 0x7f;
|
||||
data >>= 7;
|
||||
if(data === 0){
|
||||
this.writeU8(0x80 | x);
|
||||
break;
|
||||
}
|
||||
this.writeU8(x);
|
||||
data--;
|
||||
}
|
||||
}
|
||||
function BPS_getVLVLen(data){
|
||||
var len=0;
|
||||
while(true){
|
||||
var x = data & 0x7f;
|
||||
data >>= 7;
|
||||
if(data === 0){
|
||||
len++;
|
||||
break;
|
||||
}
|
||||
len++;
|
||||
data--;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
BPS.prototype.export=function(fileName){
|
||||
var patchFileSize=BPS_MAGIC.length;
|
||||
patchFileSize+=BPS_getVLVLen(this.sourceSize);
|
||||
patchFileSize+=BPS_getVLVLen(this.targetSize);
|
||||
patchFileSize+=BPS_getVLVLen(this.metaData.length);
|
||||
patchFileSize+=this.metaData.length;
|
||||
for(var i=0; i<this.actions.length; i++){
|
||||
var action=this.actions[i];
|
||||
patchFileSize+=BPS_getVLVLen(((action.length-1)<<2) + action.type);
|
||||
|
||||
if(action.type===BPS_ACTION_TARGET_READ){
|
||||
patchFileSize+=action.length;
|
||||
}else if(action.type===BPS_ACTION_SOURCE_COPY || action.type===BPS_ACTION_TARGET_COPY){
|
||||
patchFileSize+=BPS_getVLVLen((Math.abs(action.relativeOffset)<<1)+(action.relativeOffset<0?1:0));
|
||||
}
|
||||
}
|
||||
patchFileSize+=12;
|
||||
|
||||
var patchFile=new MarcFile(patchFileSize);
|
||||
patchFile.fileName=fileName+'.bps';
|
||||
patchFile.littleEndian=true;
|
||||
patchFile.writeVLV=BPS_writeVLV;
|
||||
|
||||
patchFile.writeString(BPS_MAGIC);
|
||||
patchFile.writeVLV(this.sourceSize);
|
||||
patchFile.writeVLV(this.targetSize);
|
||||
patchFile.writeVLV(this.metaData.length);
|
||||
patchFile.writeString(this.metaData, this.metaData.length);
|
||||
|
||||
for(var i=0; i<this.actions.length; i++){
|
||||
var action=this.actions[i];
|
||||
patchFile.writeVLV(((action.length-1)<<2) + action.type);
|
||||
|
||||
if(action.type===BPS_ACTION_TARGET_READ){
|
||||
patchFile.writeBytes(action.bytes);
|
||||
}else if(action.type===BPS_ACTION_SOURCE_COPY || action.type===BPS_ACTION_TARGET_COPY){
|
||||
patchFile.writeVLV((Math.abs(action.relativeOffset)<<1)+(action.relativeOffset<0?1:0));
|
||||
}
|
||||
}
|
||||
patchFile.writeU32(this.sourceChecksum);
|
||||
patchFile.writeU32(this.targetChecksum);
|
||||
patchFile.writeU32(this.patchChecksum);
|
||||
|
||||
return patchFile;
|
||||
}
|
||||
|
||||
function BPS_Node(){
|
||||
this.offset=0;
|
||||
this.next=null;
|
||||
};
|
||||
BPS_Node.prototype.delete=function(){
|
||||
if(this.next)
|
||||
delete this.next;
|
||||
}
|
||||
function createBPSFromFiles(original, modified, deltaMode){
|
||||
var patch=new BPS();
|
||||
patch.sourceSize=original.fileSize;
|
||||
patch.targetSize=modified.fileSize;
|
||||
|
||||
if(deltaMode){
|
||||
patch.actions=createBPSFromFilesDelta(original, modified);
|
||||
}else{
|
||||
patch.actions=createBPSFromFilesLinear(original, modified);
|
||||
}
|
||||
|
||||
patch.sourceChecksum=crc32(original);
|
||||
patch.targetChecksum=crc32(modified);
|
||||
patch.patchChecksum=crc32(patch.export(), 0, true);
|
||||
return patch;
|
||||
}
|
||||
|
||||
|
||||
/* delta implementation from https://github.com/chiya/beat/blob/master/nall/beat/linear.hpp */
|
||||
function createBPSFromFilesLinear(original, modified){
|
||||
var patchActions=[];
|
||||
|
||||
/* references to match original beat code */
|
||||
var sourceData=original._u8array;
|
||||
var targetData=modified._u8array;
|
||||
var sourceSize=original.fileSize;
|
||||
var targetSize=modified.fileSize;
|
||||
var Granularity=1;
|
||||
|
||||
|
||||
|
||||
var targetRelativeOffset=0;
|
||||
var outputOffset=0;
|
||||
var targetReadLength=0;
|
||||
|
||||
function targetReadFlush(){
|
||||
if(targetReadLength){
|
||||
//encode(TargetRead | ((targetReadLength - 1) << 2));
|
||||
var action={type:BPS_ACTION_TARGET_READ, length:targetReadLength, bytes:[]};
|
||||
patchActions.push(action);
|
||||
var offset = outputOffset - targetReadLength;
|
||||
while(targetReadLength){
|
||||
//write(targetData[offset++]);
|
||||
action.bytes.push(targetData[offset++]);
|
||||
targetReadLength--;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
while(outputOffset < targetSize) {
|
||||
var sourceLength = 0;
|
||||
for(var n = 0; outputOffset + n < Math.min(sourceSize, targetSize); n++) {
|
||||
if(sourceData[outputOffset + n] != targetData[outputOffset + n]) break;
|
||||
sourceLength++;
|
||||
}
|
||||
|
||||
var rleLength = 0;
|
||||
for(var n = 1; outputOffset + n < targetSize; n++) {
|
||||
if(targetData[outputOffset] != targetData[outputOffset + n]) break;
|
||||
rleLength++;
|
||||
}
|
||||
|
||||
if(rleLength >= 4) {
|
||||
//write byte to repeat
|
||||
targetReadLength++;
|
||||
outputOffset++;
|
||||
targetReadFlush();
|
||||
|
||||
//copy starting from repetition byte
|
||||
//encode(TargetCopy | ((rleLength - 1) << 2));
|
||||
var relativeOffset = (outputOffset - 1) - targetRelativeOffset;
|
||||
//encode(relativeOffset << 1);
|
||||
patchActions.push({type:BPS_ACTION_TARGET_COPY, length:rleLength, relativeOffset:relativeOffset});
|
||||
outputOffset += rleLength;
|
||||
targetRelativeOffset = outputOffset - 1;
|
||||
} else if(sourceLength >= 4) {
|
||||
targetReadFlush();
|
||||
//encode(SourceRead | ((sourceLength - 1) << 2));
|
||||
patchActions.push({type:BPS_ACTION_SOURCE_READ, length:sourceLength});
|
||||
outputOffset += sourceLength;
|
||||
} else {
|
||||
targetReadLength += Granularity;
|
||||
outputOffset += Granularity;
|
||||
}
|
||||
}
|
||||
|
||||
targetReadFlush();
|
||||
|
||||
|
||||
|
||||
return patchActions;
|
||||
}
|
||||
|
||||
/* delta implementation from https://github.com/chiya/beat/blob/master/nall/beat/delta.hpp */
|
||||
function createBPSFromFilesDelta(original, modified){
|
||||
var patchActions=[];
|
||||
|
||||
|
||||
/* references to match original beat code */
|
||||
var sourceData=original._u8array;
|
||||
var targetData=modified._u8array;
|
||||
var sourceSize=original.fileSize;
|
||||
var targetSize=modified.fileSize;
|
||||
var Granularity=1;
|
||||
|
||||
|
||||
|
||||
var sourceRelativeOffset=0;
|
||||
var targetRelativeOffset=0;
|
||||
var outputOffset=0;
|
||||
|
||||
|
||||
|
||||
var sourceTree=new Array(65536);
|
||||
var targetTree=new Array(65536);
|
||||
for(var n=0; n<65536; n++){
|
||||
sourceTree[n]=null;
|
||||
targetTree[n]=null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//source tree creation
|
||||
for(var offset=0; offset<sourceSize; offset++) {
|
||||
var symbol = sourceData[offset + 0];
|
||||
//sourceChecksum = crc32_adjust(sourceChecksum, symbol);
|
||||
if(offset < sourceSize - 1)
|
||||
symbol |= sourceData[offset + 1] << 8;
|
||||
var node=new BPS_Node();
|
||||
node.offset=offset;
|
||||
node.next=sourceTree[symbol];
|
||||
sourceTree[symbol] = node;
|
||||
}
|
||||
|
||||
var targetReadLength=0;
|
||||
|
||||
function targetReadFlush(){
|
||||
if(targetReadLength) {
|
||||
//encode(TargetRead | ((targetReadLength - 1) << 2));
|
||||
var action={type:BPS_ACTION_TARGET_READ, length:targetReadLength, bytes:[]};
|
||||
patchActions.push(action);
|
||||
var offset = outputOffset - targetReadLength;
|
||||
while(targetReadLength){
|
||||
//write(targetData[offset++]);
|
||||
action.bytes.push(targetData[offset++]);
|
||||
targetReadLength--;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
while(outputOffset<modified.fileSize){
|
||||
var maxLength = 0, maxOffset = 0, mode = BPS_ACTION_TARGET_READ;
|
||||
|
||||
var symbol = targetData[outputOffset + 0];
|
||||
if(outputOffset < targetSize - 1) symbol |= targetData[outputOffset + 1] << 8;
|
||||
|
||||
{ //source read
|
||||
var length = 0, offset = outputOffset;
|
||||
while(offset < sourceSize && offset < targetSize && sourceData[offset] == targetData[offset]) {
|
||||
length++;
|
||||
offset++;
|
||||
}
|
||||
if(length > maxLength) maxLength = length, mode = BPS_ACTION_SOURCE_READ;
|
||||
}
|
||||
|
||||
{ //source copy
|
||||
var node = sourceTree[symbol];
|
||||
while(node) {
|
||||
var length = 0, x = node.offset, y = outputOffset;
|
||||
while(x < sourceSize && y < targetSize && sourceData[x++] == targetData[y++]) length++;
|
||||
if(length > maxLength) maxLength = length, maxOffset = node.offset, mode = BPS_ACTION_SOURCE_COPY;
|
||||
node = node.next;
|
||||
}
|
||||
}
|
||||
|
||||
{ //target copy
|
||||
var node = targetTree[symbol];
|
||||
while(node) {
|
||||
var length = 0, x = node.offset, y = outputOffset;
|
||||
while(y < targetSize && targetData[x++] == targetData[y++]) length++;
|
||||
if(length > maxLength) maxLength = length, maxOffset = node.offset, mode = BPS_ACTION_TARGET_COPY;
|
||||
node = node.next;
|
||||
}
|
||||
|
||||
//target tree append
|
||||
node = new BPS_Node();
|
||||
node.offset = outputOffset;
|
||||
node.next = targetTree[symbol];
|
||||
targetTree[symbol] = node;
|
||||
}
|
||||
|
||||
{ //target read
|
||||
if(maxLength < 4) {
|
||||
maxLength = Math.min(Granularity, targetSize - outputOffset);
|
||||
mode = BPS_ACTION_TARGET_READ;
|
||||
}
|
||||
}
|
||||
|
||||
if(mode != BPS_ACTION_TARGET_READ) targetReadFlush();
|
||||
|
||||
switch(mode) {
|
||||
case BPS_ACTION_SOURCE_READ:
|
||||
//encode(BPS_ACTION_SOURCE_READ | ((maxLength - 1) << 2));
|
||||
patchActions.push({type:BPS_ACTION_SOURCE_READ, length:maxLength});
|
||||
break;
|
||||
case BPS_ACTION_TARGET_READ:
|
||||
//delay write to group sequential TargetRead commands into one
|
||||
targetReadLength += maxLength;
|
||||
break;
|
||||
case BPS_ACTION_SOURCE_COPY:
|
||||
case BPS_ACTION_TARGET_COPY:
|
||||
//encode(mode | ((maxLength - 1) << 2));
|
||||
var relativeOffset;
|
||||
if(mode == BPS_ACTION_SOURCE_COPY) {
|
||||
relativeOffset = maxOffset - sourceRelativeOffset;
|
||||
sourceRelativeOffset = maxOffset + maxLength;
|
||||
} else {
|
||||
relativeOffset = maxOffset - targetRelativeOffset;
|
||||
targetRelativeOffset = maxOffset + maxLength;
|
||||
}
|
||||
//encode((relativeOffset < 0) | (abs(relativeOffset) << 1));
|
||||
patchActions.push({type:mode, length:maxLength, relativeOffset:relativeOffset});
|
||||
break;
|
||||
}
|
||||
|
||||
outputOffset += maxLength;
|
||||
}
|
||||
|
||||
targetReadFlush();
|
||||
|
||||
|
||||
return patchActions;
|
||||
}
|
227
js/formats/ips.js
Normal file
227
js/formats/ips.js
Normal file
|
@ -0,0 +1,227 @@
|
|||
/* IPS module for Rom Patcher JS v20200502 - Marc Robledo 2016-2020 - 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;
|
||||
|
||||
function IPS(){
|
||||
this.records=[];
|
||||
this.truncate=false;
|
||||
}
|
||||
IPS.prototype.addSimpleRecord=function(o, d){
|
||||
this.records.push({offset:o, type:IPS_RECORD_SIMPLE, length:d.length, data:d})
|
||||
}
|
||||
IPS.prototype.addRLERecord=function(o, l, b){
|
||||
this.records.push({offset:o, type:IPS_RECORD_RLE, length:l, byte:b})
|
||||
}
|
||||
IPS.prototype.toString=function(){
|
||||
nSimpleRecords=0;
|
||||
nRLERecords=0;
|
||||
for(var i=0; i<this.records.length; i++){
|
||||
if(this.records[i].type===IPS_RECORD_RLE)
|
||||
nRLERecords++;
|
||||
else
|
||||
nSimpleRecords++;
|
||||
}
|
||||
var s='Simple records: '+nSimpleRecords;
|
||||
s+='\nRLE records: '+nRLERecords;
|
||||
s+='\nTotal records: '+this.records.length;
|
||||
if(this.truncate)
|
||||
s+='\nTruncate at: 0x'+this.truncate.toString(16);
|
||||
return s
|
||||
}
|
||||
IPS.prototype.export=function(fileName){
|
||||
var patchFileSize=5; //PATCH string
|
||||
for(var i=0; i<this.records.length; i++){
|
||||
if(this.records[i].type===IPS_RECORD_RLE)
|
||||
patchFileSize+=(3+2+2+1); //offset+0x0000+length+RLE byte to be written
|
||||
else
|
||||
patchFileSize+=(3+2+this.records[i].data.length); //offset+length+data
|
||||
}
|
||||
patchFileSize+=3; //EOF string
|
||||
if(this.truncate)
|
||||
patchFileSize+=3; //truncate
|
||||
|
||||
tempFile=new MarcFile(patchFileSize);
|
||||
tempFile.fileName=fileName+'.ips';
|
||||
tempFile.writeString(IPS_MAGIC);
|
||||
for(var i=0; i<this.records.length; i++){
|
||||
var rec=this.records[i];
|
||||
tempFile.writeU24(rec.offset);
|
||||
if(rec.type===IPS_RECORD_RLE){
|
||||
tempFile.writeU16(0x0000);
|
||||
tempFile.writeU16(rec.length);
|
||||
tempFile.writeU8(rec.byte);
|
||||
}else{
|
||||
tempFile.writeU16(rec.data.length);
|
||||
tempFile.writeBytes(rec.data);
|
||||
}
|
||||
}
|
||||
|
||||
tempFile.writeString('EOF');
|
||||
if(rec.truncate)
|
||||
tempFile.writeU24(rec.truncate);
|
||||
|
||||
|
||||
return tempFile
|
||||
}
|
||||
IPS.prototype.apply=function(romFile){
|
||||
if(this.truncate){
|
||||
tempFile=romFile.slice(0, this.truncate);
|
||||
}else{
|
||||
|
||||
var newFileSize=romFile.fileSize;
|
||||
for(var i=0; i<this.records.length; i++){
|
||||
var rec=this.records[i];
|
||||
if(rec.type===IPS_RECORD_RLE){
|
||||
if(rec.offset+rec.length>newFileSize){
|
||||
newFileSize=rec.offset+rec.length;
|
||||
}
|
||||
}else{
|
||||
if(rec.offset+rec.data.length>newFileSize){
|
||||
newFileSize=rec.offset+rec.data.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(newFileSize===romFile.fileSize){
|
||||
tempFile=romFile.slice(0, romFile.fileSize);
|
||||
}else{
|
||||
tempFile=new MarcFile(newFileSize);
|
||||
romFile.copyToFile(tempFile,0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
romFile.seek(0);
|
||||
|
||||
for(var i=0; i<this.records.length; i++){
|
||||
tempFile.seek(this.records[i].offset);
|
||||
if(this.records[i].type===IPS_RECORD_RLE){
|
||||
for(var j=0; j<this.records[i].length; j++)
|
||||
tempFile.writeU8(this.records[i].byte);
|
||||
}else{
|
||||
tempFile.writeBytes(this.records[i].data);
|
||||
}
|
||||
}
|
||||
|
||||
return tempFile
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function parseIPSFile(file){
|
||||
var patchFile=new IPS();
|
||||
file.seek(5);
|
||||
|
||||
while(!file.isEOF()){
|
||||
var offset=file.readU24();
|
||||
|
||||
if(offset===0x454f46){ /* EOF */
|
||||
if(file.isEOF()){
|
||||
break;
|
||||
}else if((file.offset+3)===file.fileSize){
|
||||
patchFile.truncate=file.readU24();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var length=file.readU16();
|
||||
|
||||
if(length===IPS_RECORD_RLE){
|
||||
patchFile.addRLERecord(offset, file.readU16(), file.readU8());
|
||||
}else{
|
||||
patchFile.addSimpleRecord(offset, file.readBytes(length));
|
||||
}
|
||||
}
|
||||
return patchFile;
|
||||
}
|
||||
|
||||
|
||||
function createIPSFromFiles(original, modified){
|
||||
var patch=new IPS();
|
||||
|
||||
if(modified.fileSize<original.fileSize){
|
||||
patch.truncate=modified.fileSize;
|
||||
}
|
||||
|
||||
//solucion: guardar startOffset y endOffset (ir mirando de 6 en 6 hacia atrás)
|
||||
var previousRecord={type:0xdeadbeef,startOffset:0,length:0};
|
||||
while(!modified.isEOF()){
|
||||
var b1=original.isEOF()?0x00:original.readU8();
|
||||
var b2=modified.readU8();
|
||||
|
||||
if(b1!==b2){
|
||||
var RLEmode=true;
|
||||
var differentData=[];
|
||||
var startOffset=modified.offset-1;
|
||||
|
||||
while(b1!==b2 && differentData.length<0xffff){
|
||||
differentData.push(b2);
|
||||
if(b2!==differentData[0])
|
||||
RLEmode=false;
|
||||
|
||||
if(modified.isEOF() || differentData.length===0xffff)
|
||||
break;
|
||||
|
||||
b1=original.isEOF()?0x00:original.readU8();
|
||||
b2=modified.readU8();
|
||||
}
|
||||
|
||||
|
||||
//check if this record is near the previous one
|
||||
var distance=startOffset-(previousRecord.offset+previousRecord.length);
|
||||
if(
|
||||
previousRecord.type===IPS_RECORD_SIMPLE &&
|
||||
distance<6 && (previousRecord.length+distance+differentData.length)<0xffff
|
||||
){
|
||||
if(RLEmode && differentData.length>6){
|
||||
// separate a potential RLE record
|
||||
original.seek(startOffset);
|
||||
modified.seek(startOffset);
|
||||
previousRecord={type:0xdeadbeef,startOffset:0,length:0};
|
||||
}else{
|
||||
// merge both records
|
||||
while(distance--){
|
||||
previousRecord.data.push(modified._u8array[previousRecord.offset+previousRecord.length]);
|
||||
previousRecord.length++;
|
||||
}
|
||||
previousRecord.data=previousRecord.data.concat(differentData);
|
||||
previousRecord.length=previousRecord.data.length;
|
||||
}
|
||||
}else{
|
||||
if(startOffset>=IPS_MAX_SIZE){
|
||||
throw new Error('files are too big for IPS format');
|
||||
return null;
|
||||
}
|
||||
|
||||
if(RLEmode && differentData.length>2){
|
||||
patch.addRLERecord(startOffset, differentData.length, differentData[0]);
|
||||
}else{
|
||||
patch.addSimpleRecord(startOffset, differentData);
|
||||
}
|
||||
previousRecord=patch.records[patch.records.length-1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if(modified.fileSize>original.fileSize){
|
||||
var lastRecord=patch.records[patch.records.length-1];
|
||||
var lastOffset=lastRecord.offset+lastRecord.length;
|
||||
|
||||
if(lastOffset<modified.fileSize){
|
||||
patch.addSimpleRecord(modified.fileSize-1, [0x00]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return patch
|
||||
}
|
87
js/formats/pmsr.js
Normal file
87
js/formats/pmsr.js
Normal file
|
@ -0,0 +1,87 @@
|
|||
/* PMSR (Paper Mario Star Rod) module for Rom Patcher JS v20200225 - Marc Robledo 2020 - http://www.marcrobledo.com/license */
|
||||
/* File format specification: http://origami64.net/attachment.php?aid=790 (dead link) */
|
||||
|
||||
const PMSR_MAGIC='PMSR';
|
||||
const YAY0_MAGIC='Yay0';
|
||||
const PAPER_MARIO_USA10_CRC32=0xa7f5cd7e;
|
||||
const PAPER_MARIO_USA10_FILE_SIZE=41943040;
|
||||
|
||||
|
||||
function PMSR(){
|
||||
this.targetSize=0;
|
||||
this.records=[];
|
||||
}
|
||||
PMSR.prototype.addRecord=function(offset, data){
|
||||
this.records.push({offset:offset, data:data})
|
||||
}
|
||||
PMSR.prototype.toString=function(){
|
||||
var s='Star Rod patch';
|
||||
s+='\nTarget file size: '+this.targetSize;
|
||||
s+='\n#Records: '+this.records.length;
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
PMSR.prototype.validateSource=function(romFile){
|
||||
return romFile.fileSize===PAPER_MARIO_USA10_FILE_SIZE && crc32(romFile)===PAPER_MARIO_USA10_CRC32;
|
||||
}
|
||||
PMSR.prototype.apply=function(romFile, validate){
|
||||
if(validate && !this.validateSource(romFile)){
|
||||
throw new Error('error_crc_input');
|
||||
}
|
||||
|
||||
console.log('a');
|
||||
if(this.targetSize===romFile.fileSize){
|
||||
tempFile=romFile.slice(0, romFile.fileSize);
|
||||
}else{
|
||||
tempFile=new MarcFile(this.targetSize);
|
||||
romFile.copyToFile(tempFile,0);
|
||||
}
|
||||
|
||||
console.log('b');
|
||||
for(var i=0; i<this.records.length; i++){
|
||||
tempFile.seek(this.records[i].offset);
|
||||
tempFile.writeBytes(this.records[i].data);
|
||||
}
|
||||
|
||||
return tempFile;
|
||||
}
|
||||
|
||||
function parseMODFile(file){
|
||||
var patch=new PMSR();
|
||||
|
||||
/*file.seek(0);
|
||||
if(file.readString(YAY0_MAGIC.length)===YAY0_MAGIC){
|
||||
file=PMSR.YAY0_decode(file);
|
||||
}*/
|
||||
|
||||
patch.targetSize=PAPER_MARIO_USA10_FILE_SIZE;
|
||||
|
||||
file.seek(4);
|
||||
var nRecords=file.readU32();
|
||||
|
||||
for(var i=0; i<nRecords; i++){
|
||||
var offset=file.readU32();
|
||||
var length=file.readU32();
|
||||
patch.addRecord(offset, file.readBytes(length));
|
||||
|
||||
if((offset+length)>patch.targetSize)
|
||||
patch.targetSize=offset+length;
|
||||
}
|
||||
|
||||
return patch;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* to-do */
|
||||
//MOD.prototype.export=function(fileName){return null}
|
||||
//function createMODFromFiles(original, modified){return null}
|
||||
|
||||
|
||||
/* https://github.com/pho/WindViewer/wiki/Yaz0-and-Yay0 */
|
||||
PMSR.YAY0_decode=function(file){
|
||||
/* to-do */
|
||||
}
|
266
js/formats/ppf.js
Normal file
266
js/formats/ppf.js
Normal file
|
@ -0,0 +1,266 @@
|
|||
/* PPF module for Rom Patcher JS v20200221 - Marc Robledo 2019-2020 - http://www.marcrobledo.com/license */
|
||||
/* 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
|
||||
|
||||
function PPF(){
|
||||
this.version=3;
|
||||
this.imageType=PPF_IMAGETYPE_BIN;
|
||||
this.blockCheck=false;
|
||||
this.undoData=false;
|
||||
this.records=[];
|
||||
}
|
||||
PPF.prototype.addRecord=function(offset, data, undoData){
|
||||
if(this.undoData){
|
||||
this.records.push({offset:offset, data:data, undoData:undoData});
|
||||
}else{
|
||||
this.records.push({offset:offset, data:data});
|
||||
}
|
||||
}
|
||||
PPF.prototype.toString=function(){
|
||||
var s=this.description;
|
||||
s+='\nPPF version: '+this.version;
|
||||
s+='\n#Records: '+this.records.length;
|
||||
s+='\nImage type: '+this.imageType;
|
||||
s+='\nBlock check: '+!!this.blockCheck;
|
||||
s+='\nUndo data: '+this.undoData;
|
||||
if(this.fileIdDiz)
|
||||
s+='\nFILE_ID.DIZ: '+this.fileIdDiz;
|
||||
return s
|
||||
}
|
||||
PPF.prototype.export=function(fileName){
|
||||
var patchFileSize=5+1+50; //PPFx0
|
||||
for(var i=0; i<this.records.length; i++){
|
||||
patchFileSize+=4+1+this.records[i].data.length;
|
||||
if(this.version===3)
|
||||
patchFileSize+=4; //offsets are u64
|
||||
}
|
||||
|
||||
if(this.version===3 || this.version===2){
|
||||
patchFileSize+=4;
|
||||
}
|
||||
if(this.blockCheck){
|
||||
patchFileSize+=1024;
|
||||
}
|
||||
if(this.fileIdDiz){
|
||||
patchFileSize+=18+this.fileIdDiz.length+16+4;
|
||||
}
|
||||
|
||||
tempFile=new MarcFile(patchFileSize);
|
||||
tempFile.fileName=fileName+'.ppf';
|
||||
tempFile.writeString(PPF_MAGIC);
|
||||
tempFile.writeString((this.version*10).toString());
|
||||
tempFile.writeU8(parseInt(this.version)-1);
|
||||
tempFile.writeString(this.description, 50);
|
||||
|
||||
|
||||
if(this.version===3){
|
||||
tempFile.writeU8(this.imageType);
|
||||
tempFile.writeU8(this.blockCheck?0x01:0x00);
|
||||
tempFile.writeU8(this.undoData?0x01:0x00);
|
||||
tempFile.writeU8(0x00); //dummy
|
||||
|
||||
}else if(this.version===2){
|
||||
tempFile.writeU32(this.inputFileSize);
|
||||
}
|
||||
|
||||
if(this.blockCheck){
|
||||
tempFile.writeBytes(this.blockCheck);
|
||||
}
|
||||
|
||||
|
||||
|
||||
tempFile.littleEndian=true;
|
||||
for(var i=0; i<this.records.length; i++){
|
||||
tempFile.writeU32(this.records[i].offset & 0xffffffff);
|
||||
|
||||
if(this.version===3){
|
||||
var offset2=this.records[i].offset;
|
||||
for(var j=0; j<32; j++)
|
||||
offset2=parseInt((offset2/2)>>>0);
|
||||
tempFile.writeU32(offset2);
|
||||
}
|
||||
tempFile.writeU8(this.records[i].data.length);
|
||||
tempFile.writeBytes(this.records[i].data);
|
||||
if(this.undoData)
|
||||
tempFile.writeBytes(this.records[i].undoData);
|
||||
}
|
||||
|
||||
if(this.fileIdDiz){
|
||||
tempFile.writeString('@BEGIN_FILE_ID.DIZ');
|
||||
tempFile.writeString(this.fileIdDiz);
|
||||
tempFile.writeString('@END_FILE_ID.DIZ');
|
||||
tempFile.writeU16(this.fileIdDiz.length);
|
||||
tempFile.writeU16(0x00);
|
||||
}
|
||||
|
||||
|
||||
|
||||
return tempFile
|
||||
}
|
||||
PPF.prototype.apply=function(romFile){
|
||||
var newFileSize=romFile.fileSize;
|
||||
for(var i=0; i<this.records.length; i++){
|
||||
if(this.records[i].offset+this.records[i].data.length>newFileSize)
|
||||
newFileSize=this.records[i].offset+this.records[i].data.length;
|
||||
}
|
||||
if(newFileSize===romFile.fileSize){
|
||||
tempFile=romFile.slice(0, romFile.fileSize);
|
||||
}else{
|
||||
tempFile=new MarcFile(newFileSize);
|
||||
romFile.copyToFile(tempFile,0);
|
||||
}
|
||||
|
||||
//check if undoing
|
||||
var undoingData=false;
|
||||
if(this.undoData){
|
||||
tempFile.seek(this.records[0].offset);
|
||||
var originalBytes=tempFile.readBytes(this.records[0].data.length);
|
||||
var foundDifferences=false;
|
||||
for(var i=0; i<originalBytes.length && !foundDifferences; i++){
|
||||
if(originalBytes[i]!==this.records[0].data[i]){
|
||||
foundDifferences=true;
|
||||
}
|
||||
}
|
||||
if(!foundDifferences){
|
||||
undoingData=true;
|
||||
}
|
||||
}
|
||||
|
||||
for(var i=0; i<this.records.length; i++){
|
||||
tempFile.seek(this.records[i].offset);
|
||||
|
||||
if(undoingData){
|
||||
tempFile.writeBytes(this.records[i].undoData);
|
||||
}else{
|
||||
tempFile.writeBytes(this.records[i].data);
|
||||
}
|
||||
}
|
||||
|
||||
return tempFile
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function parsePPFFile(patchFile){
|
||||
var patch=new PPF();
|
||||
|
||||
|
||||
patchFile.seek(3);
|
||||
var version1=parseInt(patchFile.readString(2))/10;
|
||||
var version2=patchFile.readU8()+1;
|
||||
if(version1!==version2 || version1>3){
|
||||
throw new Error('invalid PPF version');
|
||||
}
|
||||
|
||||
patch.version=version1;
|
||||
patch.description=patchFile.readString(50).replace(/ +$/,'');
|
||||
|
||||
|
||||
|
||||
|
||||
if(patch.version===3){
|
||||
patch.imageType=patchFile.readU8();
|
||||
if(patchFile.readU8())
|
||||
patch.blockCheck=true;
|
||||
if(patchFile.readU8())
|
||||
patch.undoData=true;
|
||||
|
||||
patchFile.skip(1);
|
||||
}else if(patch.version===2){
|
||||
patch.blockCheck=true;
|
||||
patch.inputFileSize=patchFile.readU32();
|
||||
}
|
||||
|
||||
if(patch.blockCheck){
|
||||
patch.blockCheck=patchFile.readBytes(1024);
|
||||
}
|
||||
|
||||
|
||||
|
||||
patchFile.littleEndian=true;
|
||||
while(!patchFile.isEOF()){
|
||||
|
||||
if(patchFile.readString(4)===PPF_BEGIN_FILE_ID_DIZ_MAGIC){
|
||||
patchFile.skip(14);
|
||||
//console.log('found file_id.diz begin');
|
||||
patch.fileIdDiz=patchFile.readString(3072);
|
||||
patch.fileIdDiz=patch.fileIdDiz.substr(0, patch.fileIdDiz.indexOf('@END_FILE_ID.DIZ'));
|
||||
break;
|
||||
}
|
||||
patchFile.skip(-4);
|
||||
|
||||
var offset;
|
||||
if(patch.version===3){
|
||||
var u64_1=patchFile.readU32();
|
||||
var u64_2=patchFile.readU32();
|
||||
offset=u64_1+(u64_2*0x100000000);
|
||||
}else
|
||||
offset=patchFile.readU32();
|
||||
|
||||
var len=patchFile.readU8();
|
||||
var data=patchFile.readBytes(len);
|
||||
|
||||
var undoData=false;
|
||||
if(patch.undoData){
|
||||
undoData=patchFile.readBytes(len);
|
||||
}
|
||||
|
||||
patch.addRecord(offset, data, undoData);
|
||||
}
|
||||
|
||||
|
||||
|
||||
return patch;
|
||||
}
|
||||
|
||||
|
||||
function createPPFFromFiles(original, modified){
|
||||
var patch=new PPF();
|
||||
|
||||
patch.description='Patch description';
|
||||
|
||||
if(original.fileSize>modified.fileSize){
|
||||
var expandedModified=new MarcFile(original.fileSize);
|
||||
modified.copyToFile(expandedModified,0);
|
||||
modified=expandedModified;
|
||||
}
|
||||
|
||||
original.seek(0);
|
||||
modified.seek(0);
|
||||
while(!modified.isEOF()){
|
||||
var b1=original.isEOF()?0x00:original.readU8();
|
||||
var b2=modified.readU8();
|
||||
|
||||
if(b1!==b2){
|
||||
var differentData=[];
|
||||
var offset=modified.offset-1;
|
||||
|
||||
while(b1!==b2 && differentData.length<0xff){
|
||||
differentData.push(b2);
|
||||
|
||||
if(modified.isEOF() || differentData.length===0xff)
|
||||
break;
|
||||
|
||||
b1=original.isEOF()?0x00:original.readU8();
|
||||
b2=modified.readU8();
|
||||
}
|
||||
|
||||
patch.addRecord(offset, differentData);
|
||||
}
|
||||
}
|
||||
|
||||
if(original.fileSize<modified.fileSize){
|
||||
modified.seek(modified.fileSize-1);
|
||||
if(modified.readU8()===0x00)
|
||||
patch.addRecord(modified.fileSize-1, [0x00]);
|
||||
}
|
||||
|
||||
return patch
|
||||
}
|
328
js/formats/rup.js
Normal file
328
js/formats/rup.js
Normal file
|
@ -0,0 +1,328 @@
|
|||
/* RUP module for Rom Patcher JS v20180930 - Marc Robledo 2018 - http://www.marcrobledo.com/license */
|
||||
/* File format specification: http://www.romhacking.net/documents/288/ */
|
||||
|
||||
const RUP_MAGIC='NINJA2';
|
||||
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'];
|
||||
|
||||
|
||||
function RUP(){
|
||||
this.author='';
|
||||
this.version='';
|
||||
this.title='';
|
||||
this.genre='';
|
||||
this.language='';
|
||||
this.date='';
|
||||
this.web='';
|
||||
this.description='';
|
||||
this.files=[];
|
||||
}
|
||||
RUP.prototype.toString=function(){
|
||||
var s='Author: '+this.author;
|
||||
s+='\nVersion: '+this.version;
|
||||
s+='\nTitle: '+this.title;
|
||||
s+='\nGenre: '+this.genre;
|
||||
s+='\nLanguage: '+this.language;
|
||||
s+='\nDate: '+this.date;
|
||||
s+='\nWeb: '+this.web;
|
||||
s+='\nDescription: '+this.description;
|
||||
for(var i=0; i<this.files.length; i++){
|
||||
var file=this.files[i];
|
||||
s+='\n---------------';
|
||||
s+='\nFile '+i+':';
|
||||
s+='\nFile name: '+file.fileName;
|
||||
s+='\nRom type: '+RUP_ROM_TYPES[file.romType];
|
||||
s+='\nSource file size: '+file.sourceFileSize;
|
||||
s+='\nTarget file size: '+file.targetFileSize;
|
||||
s+='\nSource MD5: '+file.sourceMD5;
|
||||
s+='\nTarget MD5: '+file.targetMD5;
|
||||
s+='\nOverflow text: '+file.overflowText;
|
||||
s+='\n#records: '+file.records.length;
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
|
||||
RUP.prototype.validateSource=function(romFile,headerSize){
|
||||
var md5string=md5(romFile, headerSize);
|
||||
for(var i=0; i<this.files.length; i++){
|
||||
if(this.files[i].sourceMD5===md5string){
|
||||
return this.files[i];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
RUP.prototype.apply=function(romFile, validate){
|
||||
var validFile;
|
||||
if(validate){
|
||||
validFile=this.validateSource(romFile);
|
||||
|
||||
if(!validFile)
|
||||
throw new Error('error_crc_input');
|
||||
}else{
|
||||
validFile=this.files[0];
|
||||
}
|
||||
|
||||
|
||||
|
||||
tempFile=new MarcFile(validFile.targetFileSize);
|
||||
/* copy original file */
|
||||
romFile.copyToFile(tempFile, 0);
|
||||
|
||||
|
||||
for(var i=0; i<validFile.records.length; i++){
|
||||
var offset=validFile.records[i].offset;
|
||||
romFile.seek(offset);
|
||||
tempFile.seek(offset);
|
||||
for(var j=0; j<validFile.records[i].xor.length; j++){
|
||||
tempFile.writeU8(
|
||||
(romFile.isEOF()?0x00:romFile.readU8()) ^ validFile.records[i].xor[j]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(validate && md5(tempFile)!==validFile.targetMD5){
|
||||
throw new Error('error_crc_output');
|
||||
}
|
||||
|
||||
return tempFile
|
||||
}
|
||||
|
||||
|
||||
function parseRUPFile(file){
|
||||
var patch=new RUP();
|
||||
file.readVLV=RUP_readVLV;
|
||||
|
||||
file.seek(RUP_MAGIC.length);
|
||||
|
||||
patch.textEncoding=file.readU8();
|
||||
patch.author=file.readString(84);
|
||||
patch.version=file.readString(11);
|
||||
patch.title=file.readString(256);
|
||||
patch.genre=file.readString(48);
|
||||
patch.language=file.readString(48);
|
||||
patch.date=file.readString(8);
|
||||
patch.web=file.readString(512);
|
||||
patch.description=file.readString(1074).replace(/\\n/g,'\n');
|
||||
|
||||
|
||||
file.seek(0x800);
|
||||
var nextFile;
|
||||
while(!file.isEOF()){
|
||||
var command=file.readU8();
|
||||
|
||||
if(command===RUP_COMMAND_OPEN_NEW_FILE){
|
||||
if(nextFile)
|
||||
patch.files.push(nextFile)
|
||||
|
||||
nextFile={
|
||||
records:[]
|
||||
};
|
||||
|
||||
|
||||
nextFile.fileName=file.readString(file.readVLV());
|
||||
nextFile.romType=file.readU8();
|
||||
nextFile.sourceFileSize=file.readVLV();
|
||||
nextFile.targetFileSize=file.readVLV();
|
||||
|
||||
nextFile.sourceMD5='';
|
||||
for(var i=0; i<16; i++)
|
||||
nextFile.sourceMD5+=padZeroes(file.readU8(),1);
|
||||
|
||||
nextFile.targetMD5='';
|
||||
for(var i=0; i<16; i++)
|
||||
nextFile.targetMD5+=padZeroes(file.readU8(),1);
|
||||
|
||||
|
||||
|
||||
if(nextFile.sourceFileSize!==nextFile.targetFileSize){
|
||||
file.skip(1); //skip 'M' (source>target) or 'A' (source<target)
|
||||
nextFile.overflowText=file.readString(file.readVLV());
|
||||
}
|
||||
|
||||
}else if(command===RUP_COMMAND_XOR_RECORD){
|
||||
nextFile.records.push({
|
||||
offset:file.readVLV(),
|
||||
xor:file.readBytes(file.readVLV())
|
||||
});
|
||||
}else if(command===RUP_COMMAND_END){
|
||||
if(nextFile)
|
||||
patch.files.push(nextFile);
|
||||
break;
|
||||
}else{
|
||||
throw new Error('invalid RUP command');
|
||||
}
|
||||
}
|
||||
return patch;
|
||||
}
|
||||
|
||||
|
||||
function RUP_readVLV(){
|
||||
var nBytes=this.readU8();
|
||||
var data=0;
|
||||
for(var i=0; i<nBytes; i++){
|
||||
data+=this.readU8() << i*8;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
function RUP_writeVLV(data){
|
||||
var len=RUP_getVLVLen(data)-1;
|
||||
|
||||
this.writeU8(len);
|
||||
|
||||
while(data){
|
||||
this.writeU8(data & 0xff);
|
||||
data>>=8;
|
||||
}
|
||||
}
|
||||
function RUP_getVLVLen(data){
|
||||
var ret=1;
|
||||
while(data){
|
||||
ret++;
|
||||
data>>=8;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
RUP.prototype.export=function(fileName){
|
||||
var patchFileSize=2048;
|
||||
for(var i=0; i<this.files.length; i++){
|
||||
var file=this.files[i];
|
||||
patchFileSize++; //command 0x01
|
||||
|
||||
patchFileSize+=RUP_getVLVLen(file.fileName.length);
|
||||
patchFileSize+=file.fileName.length;
|
||||
patchFileSize++; //rom type
|
||||
patchFileSize+=RUP_getVLVLen(file.sourceFileSize);
|
||||
patchFileSize+=RUP_getVLVLen(file.targetFileSize);
|
||||
patchFileSize+=32; //MD5s
|
||||
|
||||
if(file.sourceFileSize!==file.targetFileSize){
|
||||
patchFileSize++; // M or A
|
||||
patchFileSize+=RUP_getVLVLen(file.overflowText);
|
||||
patchFileSize+=file.overflowText;
|
||||
}
|
||||
for(var j=0; j<file.records.length; j++){
|
||||
patchFileSize++; //command 0x01
|
||||
patchFileSize+=RUP_getVLVLen(file.records[j].offset);
|
||||
patchFileSize+=RUP_getVLVLen(file.records[j].xor.length);
|
||||
patchFileSize+=file.records[j].xor.length;
|
||||
}
|
||||
}
|
||||
patchFileSize++; //command 0x00
|
||||
|
||||
|
||||
var patchFile=new MarcFile(patchFileSize);
|
||||
patchFile.fileName=fileName+'.rup';
|
||||
patchFile.writeVLV=RUP_writeVLV;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
patchFile.writeString(RUP_MAGIC);
|
||||
patchFile.writeU8(this.textEncoding);
|
||||
patchFile.writeString(this.author, 84);
|
||||
patchFile.writeString(this.version, 11);
|
||||
patchFile.writeString(this.title, 256);
|
||||
patchFile.writeString(this.genre, 48);
|
||||
patchFile.writeString(this.language, 48);
|
||||
patchFile.writeString(this.date, 8);
|
||||
patchFile.writeString(this.web, 512);
|
||||
patchFile.writeString(this.description.replace(/\n/g,'\\n'), 1074);
|
||||
|
||||
for(var i=0; i<this.files.length; i++){
|
||||
var file=this.files[i];
|
||||
patchFile.writeU8(RUP_COMMAND_OPEN_NEW_FILE);
|
||||
|
||||
patchFile.writeVLV(file.fileName);
|
||||
patchFile.writeU8(file.romType);
|
||||
patchFile.writeVLV(file.sourceFileSize);
|
||||
patchFile.writeVLV(file.targetFileSize);
|
||||
|
||||
for(var j=0; j<16; j++)
|
||||
patchFile.writeU8(parseInt(file.sourceMD5.substr(j*2,2), 16));
|
||||
for(var j=0; j<16; j++)
|
||||
patchFile.writeU8(parseInt(file.targetMD5.substr(j*2,2), 16));
|
||||
|
||||
|
||||
|
||||
if(file.sourceFileSize!==file.targetFileSize){
|
||||
patchFile.writeString(file.sourceFileSize>file.targetFileSize?'M':'A');
|
||||
patchFile.writeVLV(file.overflowText.length);
|
||||
patchFile.writeString(file.overflowText);
|
||||
}
|
||||
|
||||
for(var j=0; j<file.records.length; j++){
|
||||
patchFile.writeU8(RUP_COMMAND_XOR_RECORD);
|
||||
|
||||
patchFile.writeVLV(file.records[j].offset);
|
||||
patchFile.writeVLV(file.records[j].xor.length);
|
||||
patchFile.writeBytes(file.records[j].xor);
|
||||
}
|
||||
}
|
||||
|
||||
patchFile.writeU8(RUP_COMMAND_END);
|
||||
|
||||
|
||||
|
||||
return patchFile;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function createRUPFromFiles(original, modified){
|
||||
var patch=new RUP();
|
||||
|
||||
var today=new Date();
|
||||
patch.date=(today.getYear()+1900)+padZeroes(today.getMonth()+1, 1)+padZeroes(today.getDate(), 1);
|
||||
|
||||
var file={
|
||||
fileName:'',
|
||||
romType:0,
|
||||
sourceFileSize:original.fileSize,
|
||||
targetFileSize:modified.fileSize,
|
||||
sourceMD5:md5(original),
|
||||
targetMD5:md5(modified),
|
||||
overflowText:'',
|
||||
records:[]
|
||||
};
|
||||
|
||||
|
||||
original.seek(0);
|
||||
modified.seek(0);
|
||||
|
||||
while(!modified.isEOF()){
|
||||
var b1=original.isEOF()?0x00:original.readU8();
|
||||
var b2=modified.readU8();
|
||||
|
||||
if(b1!==b2){
|
||||
var originalOffset=modified.offset-1;
|
||||
var xorDifferences=[];
|
||||
|
||||
while(b1!==b2){
|
||||
xorDifferences.push(b1^b2);
|
||||
|
||||
if(modified.isEOF())
|
||||
break;
|
||||
|
||||
b1=original.isEOF()?0x00:original.readU8();
|
||||
b2=modified.readU8();
|
||||
}
|
||||
|
||||
file.records.push({offset:originalOffset, xor:xorDifferences});
|
||||
}
|
||||
}
|
||||
|
||||
patch.files.push(file);
|
||||
|
||||
return patch
|
||||
}
|
204
js/formats/ups.js
Normal file
204
js/formats/ups.js
Normal file
|
@ -0,0 +1,204 @@
|
|||
/* UPS module for Rom Patcher JS v20180930 - Marc Robledo 2017-2018 - http://www.marcrobledo.com/license */
|
||||
/* File format specification: http://www.romhacking.net/documents/392/ */
|
||||
|
||||
const UPS_MAGIC='UPS1';
|
||||
|
||||
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: '+padZeroes(this.checksumInput,4);
|
||||
s+='\nOutput file checksum: '+padZeroes(this.checksumOutput,4);
|
||||
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 MarcFile(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(crc32(tempFile, 0, true));
|
||||
|
||||
return tempFile
|
||||
}
|
||||
UPS.prototype.validateSource=function(romFile,headerSize){return crc32(romFile,headerSize)===this.checksumInput}
|
||||
UPS.prototype.apply=function(romFile, validate){
|
||||
if(validate && !this.validateSource(romFile)){
|
||||
throw new Error('error_crc_input');
|
||||
}
|
||||
|
||||
|
||||
/* copy original file */
|
||||
tempFile=new MarcFile(this.sizeOutput);
|
||||
romFile.copyToFile(tempFile, 0, this.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 && crc32(tempFile)!==this.checksumOutput){
|
||||
throw new Error('error_crc_output');
|
||||
}
|
||||
|
||||
return tempFile
|
||||
}
|
||||
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
|
||||
function parseUPSFile(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()!==crc32(file, 0, true)){
|
||||
throw new Error('error_crc_patch');
|
||||
}
|
||||
|
||||
file.littleEndian=false;
|
||||
return patch;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function createUPSFromFiles(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=crc32(original);
|
||||
patch.checksumOutput=crc32(modified);
|
||||
return patch
|
||||
}
|
374
js/formats/vcdiff.js
Normal file
374
js/formats/vcdiff.js
Normal file
|
@ -0,0 +1,374 @@
|
|||
/* VCDIFF module for RomPatcher.js v20181021 - Marc Robledo 2018 - http://www.marcrobledo.com/license */
|
||||
/* File format specification: https://tools.ietf.org/html/rfc3284 */
|
||||
/*
|
||||
Mostly based in:
|
||||
https://github.com/vic-alexiev/TelerikAcademy/tree/master/C%23%20Fundamentals%20II/Homework%20Assignments/3.%20Methods/000.%20MiscUtil/Compression/Vcdiff
|
||||
some code and ideas borrowed from:
|
||||
https://hack64.net/jscripts/libpatch.js?6
|
||||
*/
|
||||
|
||||
//const VCDIFF_MAGIC=0xd6c3c400;
|
||||
const VCDIFF_MAGIC='\xd6\xc3\xc4';
|
||||
/*
|
||||
const XDELTA_014_MAGIC='%XDELTA';
|
||||
const XDELTA_018_MAGIC='%XDZ000';
|
||||
const XDELTA_020_MAGIC='%XDZ001';
|
||||
const XDELTA_100_MAGIC='%XDZ002';
|
||||
const XDELTA_104_MAGIC='%XDZ003';
|
||||
const XDELTA_110_MAGIC='%XDZ004';
|
||||
*/
|
||||
|
||||
|
||||
function VCDIFF(patchFile){
|
||||
this.file=patchFile;
|
||||
}
|
||||
VCDIFF.prototype.toString=function(){
|
||||
return 'VCDIFF patch'
|
||||
}
|
||||
|
||||
VCDIFF.prototype.apply=function(romFile, validate){
|
||||
//romFile._u8array=new Uint8Array(romFile._dataView.buffer);
|
||||
|
||||
//var t0=performance.now();
|
||||
var parser=new VCDIFF_Parser(this.file);
|
||||
|
||||
//read header
|
||||
parser.seek(4);
|
||||
var headerIndicator=parser.readU8();
|
||||
|
||||
if(headerIndicator & VCD_DECOMPRESS){
|
||||
//has secondary decompressor, read its id
|
||||
var secondaryDecompressorId=parser.readU8();
|
||||
|
||||
if(secondaryDecompressorId!==0)
|
||||
throw new Error('not implemented: secondary decompressor');
|
||||
}
|
||||
|
||||
|
||||
if(headerIndicator & VCD_CODETABLE){
|
||||
var codeTableDataLength=parser.read7BitEncodedInt();
|
||||
|
||||
if(codeTableDataLength!==0)
|
||||
throw new Error('not implemented: custom code table'); // custom code table
|
||||
}
|
||||
|
||||
if(headerIndicator & VCD_APPHEADER){
|
||||
// ignore app header data
|
||||
var appDataLength=parser.read7BitEncodedInt();
|
||||
parser.skip(appDataLength);
|
||||
}
|
||||
var headerEndOffset=parser.offset;
|
||||
|
||||
//calculate target file size
|
||||
var newFileSize=0;
|
||||
while(!parser.isEOF()){
|
||||
var winHeader=parser.decodeWindowHeader();
|
||||
newFileSize+=winHeader.targetWindowLength;
|
||||
parser.skip(winHeader.addRunDataLength + winHeader.addressesLength + winHeader.instructionsLength);
|
||||
}
|
||||
tempFile=new MarcFile(newFileSize);
|
||||
|
||||
|
||||
|
||||
|
||||
parser.seek(headerEndOffset);
|
||||
|
||||
|
||||
|
||||
var cache = new VCD_AdressCache(4,3);
|
||||
var codeTable = VCD_DEFAULT_CODE_TABLE;
|
||||
|
||||
var targetWindowPosition = 0; //renombrar
|
||||
|
||||
while(!parser.isEOF()){
|
||||
var winHeader = parser.decodeWindowHeader();
|
||||
|
||||
var addRunDataStream = new VCDIFF_Parser(this.file, parser.offset);
|
||||
var instructionsStream = new VCDIFF_Parser(this.file, addRunDataStream.offset + winHeader.addRunDataLength);
|
||||
var addressesStream = new VCDIFF_Parser(this.file, instructionsStream.offset + winHeader.instructionsLength);
|
||||
|
||||
var addRunDataIndex = 0;
|
||||
|
||||
cache.reset(addressesStream);
|
||||
|
||||
var addressesStreamEndOffset = addressesStream.offset;
|
||||
while(instructionsStream.offset<addressesStreamEndOffset){
|
||||
/*
|
||||
var instructionIndex=instructionsStream.readS8();
|
||||
if(instructionIndex===-1){
|
||||
break;
|
||||
}
|
||||
*/
|
||||
var instructionIndex = instructionsStream.readU8();
|
||||
|
||||
|
||||
for(var i=0; i<2; i++){
|
||||
var instruction=codeTable[instructionIndex][i];
|
||||
var size=instruction.size;
|
||||
|
||||
if(size===0 && instruction.type!==VCD_NOOP){
|
||||
size=instructionsStream.read7BitEncodedInt()
|
||||
}
|
||||
|
||||
if(instruction.type===VCD_NOOP){
|
||||
continue;
|
||||
|
||||
}else if(instruction.type===VCD_ADD){
|
||||
addRunDataStream.copyToFile2(tempFile, addRunDataIndex+targetWindowPosition, size);
|
||||
addRunDataIndex += size;
|
||||
|
||||
}else if(instruction.type===VCD_COPY){
|
||||
var addr = cache.decodeAddress(addRunDataIndex+winHeader.sourceLength, instruction.mode);
|
||||
var absAddr = 0;
|
||||
|
||||
// source segment and target segment are treated as if they're concatenated
|
||||
var sourceData = null;
|
||||
if(addr < winHeader.sourceLength){
|
||||
absAddr = winHeader.sourcePosition + addr;
|
||||
if(winHeader.indicator & VCD_SOURCE){
|
||||
sourceData = romFile;
|
||||
}else if(winHeader.indicator & VCD_TARGET){
|
||||
sourceData = tempFile;
|
||||
}
|
||||
}else{
|
||||
absAddr = targetWindowPosition + (addr - winHeader.sourceLength);
|
||||
sourceData = tempFile;
|
||||
}
|
||||
|
||||
while(size--){
|
||||
tempFile._u8array[targetWindowPosition + addRunDataIndex++]=sourceData._u8array[absAddr++];
|
||||
//targetU8[targetWindowPosition + targetWindowOffs++] = copySourceU8[absAddr++];
|
||||
}
|
||||
//to-do: test
|
||||
//sourceData.copyToFile2(tempFile, absAddr, size, targetWindowPosition + addRunDataIndex);
|
||||
//addRunDataIndex += size;
|
||||
}else if(instruction.type===VCD_RUN){
|
||||
var runByte = addRunDataStream.readU8();
|
||||
var offset = targetWindowPosition + addRunDataIndex;
|
||||
for(var j=0; j<size; j++){
|
||||
tempFile._u8array[offset+j]=runByte;
|
||||
}
|
||||
|
||||
addRunDataIndex += size;
|
||||
}else{
|
||||
throw new Error('invalid instruction type found');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(validate && winHeader.adler32 && (winHeader.adler32 !== adler32(tempFile, targetWindowPosition, winHeader.targetWindowLength))){
|
||||
throw new Error('error_crc_output');
|
||||
}
|
||||
|
||||
parser.skip(winHeader.addRunDataLength + winHeader.addressesLength + winHeader.instructionsLength);
|
||||
targetWindowPosition += winHeader.targetWindowLength;
|
||||
}
|
||||
|
||||
//console.log((performance.now()-t0)/1000);
|
||||
return tempFile;
|
||||
}
|
||||
|
||||
|
||||
function parseVCDIFF(file){
|
||||
return new VCDIFF(file);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function VCDIFF_Parser(marcFile, offset)
|
||||
{
|
||||
MarcFile.call(this, 0);
|
||||
this.fileSize=marcFile.fileSize;
|
||||
this._u8array=marcFile._u8array;
|
||||
this._dataView=marcFile._dataView;
|
||||
this.offset=offset || 0;
|
||||
}
|
||||
|
||||
VCDIFF_Parser.prototype=Object.create(MarcFile.prototype);
|
||||
VCDIFF_Parser.prototype.read7BitEncodedInt=function(){
|
||||
var num=0, bits = 0;
|
||||
|
||||
do {
|
||||
bits = this.readU8();
|
||||
num = (num << 7) + (bits & 0x7f);
|
||||
} while(bits & 0x80);
|
||||
|
||||
return num;
|
||||
}
|
||||
VCDIFF_Parser.prototype.decodeWindowHeader=function(){
|
||||
var windowHeader={
|
||||
indicator:this.readU8(),
|
||||
sourceLength:0,
|
||||
sourcePosition:0,
|
||||
adler32:false
|
||||
};
|
||||
|
||||
|
||||
if(windowHeader.indicator & (VCD_SOURCE | VCD_TARGET)){
|
||||
windowHeader.sourceLength = this.read7BitEncodedInt();
|
||||
windowHeader.sourcePosition = this.read7BitEncodedInt();
|
||||
}
|
||||
|
||||
windowHeader.deltaLength = this.read7BitEncodedInt();
|
||||
windowHeader.targetWindowLength = this.read7BitEncodedInt();
|
||||
windowHeader.deltaIndicator = this.readU8(); // secondary compression: 1=VCD_DATACOMP,2=VCD_INSTCOMP,4=VCD_ADDRCOMP
|
||||
if(windowHeader.deltaIndicator!==0){
|
||||
throw new Error('unimplemented windowHeader.deltaIndicator:'+windowHeader.deltaIndicator);
|
||||
}
|
||||
|
||||
windowHeader.addRunDataLength = this.read7BitEncodedInt();
|
||||
windowHeader.instructionsLength = this.read7BitEncodedInt();
|
||||
windowHeader.addressesLength = this.read7BitEncodedInt();
|
||||
|
||||
if(windowHeader.indicator & VCD_ADLER32){
|
||||
windowHeader.adler32 = this.readU32();
|
||||
}
|
||||
|
||||
|
||||
return windowHeader;
|
||||
}
|
||||
|
||||
|
||||
VCDIFF_Parser.prototype.copyToFile2=function(target, targetOffset, len){
|
||||
for(var i=0; i<len; i++){
|
||||
target._u8array[targetOffset+i]=this._u8array[this.offset+i];
|
||||
}
|
||||
//this.file.copyToFile(target, this.offset, len, targetOffset);
|
||||
this.skip(len);
|
||||
}
|
||||
|
||||
//------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
// hdrIndicator
|
||||
const VCD_DECOMPRESS = 0x01;
|
||||
const VCD_CODETABLE = 0x02;
|
||||
const VCD_APPHEADER = 0x04; // nonstandard?
|
||||
|
||||
// winIndicator
|
||||
const VCD_SOURCE = 0x01;
|
||||
const VCD_TARGET = 0x02;
|
||||
const VCD_ADLER32 = 0x04;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function VCD_Instruction(instruction, size, mode){
|
||||
this.type=instruction;
|
||||
this.size=size;
|
||||
this.mode=mode;
|
||||
}
|
||||
|
||||
/*
|
||||
build the default code table (used to encode/decode instructions) specified in RFC 3284
|
||||
heavily based on
|
||||
https://github.com/vic-alexiev/TelerikAcademy/blob/master/C%23%20Fundamentals%20II/Homework%20Assignments/3.%20Methods/000.%20MiscUtil/Compression/Vcdiff/CodeTable.cs
|
||||
*/
|
||||
const VCD_NOOP=0;
|
||||
const VCD_ADD=1;
|
||||
const VCD_RUN=2;
|
||||
const VCD_COPY=3;
|
||||
const VCD_DEFAULT_CODE_TABLE=(function(){
|
||||
var entries=[];
|
||||
|
||||
var empty = {type: VCD_NOOP, size: 0, mode: 0};
|
||||
|
||||
// 0
|
||||
entries.push([{type: VCD_RUN, size: 0, mode: 0}, empty]);
|
||||
|
||||
// 1,18
|
||||
for(var size=0; size<18; size++){
|
||||
entries.push([{type: VCD_ADD, size: size, mode: 0}, empty]);
|
||||
}
|
||||
|
||||
// 19,162
|
||||
for(var mode=0; mode<9; mode++){
|
||||
entries.push([{type: VCD_COPY, size: 0, mode: mode}, empty]);
|
||||
|
||||
for(var size=4; size<19; size++){
|
||||
entries.push([{type: VCD_COPY, size: size, mode: mode}, empty]);
|
||||
}
|
||||
}
|
||||
|
||||
// 163,234
|
||||
for(var mode=0; mode<6; mode++){
|
||||
for(var addSize=1; addSize<5; addSize++){
|
||||
for(var copySize=4; copySize<7; copySize++){
|
||||
entries.push([{type: VCD_ADD, size: addSize, mode: 0},
|
||||
{type: VCD_COPY, size: copySize, mode: mode}]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 235,246
|
||||
for(var mode=6; mode<9; mode++){
|
||||
for(var addSize=1; addSize<5; addSize++){
|
||||
entries.push([{type: VCD_ADD, size: addSize, mode: 0},
|
||||
{type: VCD_COPY, size: 4, mode: mode}]);
|
||||
}
|
||||
}
|
||||
|
||||
// 247,255
|
||||
for(var mode=0; mode<9; mode++){
|
||||
entries.push([{type: VCD_COPY, size: 4, mode: mode},
|
||||
{type: VCD_ADD, size: 1, mode: 0}]);
|
||||
}
|
||||
|
||||
return entries;
|
||||
})();
|
||||
|
||||
|
||||
|
||||
/*
|
||||
ported from https://github.com/vic-alexiev/TelerikAcademy/tree/master/C%23%20Fundamentals%20II/Homework%20Assignments/3.%20Methods/000.%20MiscUtil/Compression/Vcdiff
|
||||
by Victor Alexiev (https://github.com/vic-alexiev)
|
||||
*/
|
||||
const VCD_MODE_SELF=0;
|
||||
const VCD_MODE_HERE=1;
|
||||
function VCD_AdressCache(nearSize, sameSize){
|
||||
this.nearSize=nearSize;
|
||||
this.sameSize=sameSize;
|
||||
|
||||
this.near=new Array(nearSize);
|
||||
this.same=new Array(sameSize*256);
|
||||
}
|
||||
VCD_AdressCache.prototype.reset=function(addressStream){
|
||||
this.nextNearSlot=0;
|
||||
this.near.fill(0);
|
||||
this.same.fill(0);
|
||||
|
||||
this.addressStream=addressStream;
|
||||
}
|
||||
VCD_AdressCache.prototype.decodeAddress=function(here, mode){
|
||||
var address=0;
|
||||
|
||||
if(mode===VCD_MODE_SELF){
|
||||
address=this.addressStream.read7BitEncodedInt();
|
||||
}else if(mode===VCD_MODE_HERE){
|
||||
address=here-this.addressStream.read7BitEncodedInt();
|
||||
}else if(mode-2<this.nearSize){ //near cache
|
||||
address=this.near[mode-2]+this.addressStream.read7BitEncodedInt();
|
||||
}else{ //same cache
|
||||
var m=mode-(2+this.nearSize);
|
||||
address=this.same[m*256+this.addressStream.readU8()];
|
||||
}
|
||||
|
||||
this.update(address);
|
||||
return address;
|
||||
}
|
||||
VCD_AdressCache.prototype.update=function(address){
|
||||
if(this.nearSize>0){
|
||||
this.near[this.nextNearSlot]=address;
|
||||
this.nextNearSlot=(this.nextNearSlot+1)%this.nearSize;
|
||||
}
|
||||
|
||||
if(this.sameSize>0){
|
||||
this.same[address%(this.sameSize*256)]=address;
|
||||
}
|
||||
}
|
83
js/formats/zip.js
Normal file
83
js/formats/zip.js
Normal file
|
@ -0,0 +1,83 @@
|
|||
/* ZIP module for Rom Patcher JS v20190531 - Marc Robledo 2016-2019 - http://www.marcrobledo.com/license */
|
||||
const ZIP_MAGIC='\x50\x4b\x03\x04';
|
||||
|
||||
function parseZIPFile(zipFile, unzipEntryName){
|
||||
var regex=(zipFile===patchFile)? /\.(ips|ups|bps|aps|rup|ppf|xdelta)$/i : /\.(\w+)$/i;
|
||||
setMessage('apply', _('unzipping'), 'loading');
|
||||
|
||||
var arrayBuffer=zipFile._u8array.buffer;
|
||||
zip.createReader(
|
||||
new zip.BlobReader(new Blob([arrayBuffer])),
|
||||
function(zipReader){
|
||||
zipReader.getEntries(function(zipEntries){
|
||||
var zippedFiles=[];
|
||||
for(var i=0; i<zipEntries.length; i++){
|
||||
if(typeof unzipEntryName==='string' && unzipEntryName === zipEntries[i].filename){
|
||||
zippedFiles=[zipEntries[i]];
|
||||
break;
|
||||
}else if(regex.test(zipEntries[i].filename)){
|
||||
zippedFiles.push(zipEntries[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if(zippedFiles.length>1){
|
||||
var zipOverlay=document.createElement('div');
|
||||
zipOverlay.className='zip-overlay';
|
||||
var zipDialog=document.createElement('div');
|
||||
zipDialog.className='zip-dialog';
|
||||
var zipList=document.createElement('ul');
|
||||
zipList.className='zipped-files'
|
||||
for(var i=0; i<zippedFiles.length; i++){
|
||||
var li=document.createElement('li');
|
||||
li.zipEntry=zippedFiles[i];
|
||||
li.zipEntry.originalZipFile=zipFile;
|
||||
li.innerHTML=zippedFiles[i].filename;
|
||||
addEvent(li, 'click', _evtClickZipEntry);
|
||||
zipList.appendChild(li);
|
||||
}
|
||||
zipDialog.innerHTML=_('patch_file');
|
||||
zipDialog.appendChild(zipList);
|
||||
zipOverlay.appendChild(zipDialog);
|
||||
document.body.appendChild(zipOverlay);
|
||||
}else if(zippedFiles.length===1){
|
||||
zippedFiles[0].originalZipFile=zipFile;
|
||||
unzipEntry(zippedFiles[0]);
|
||||
}else{
|
||||
if(zipFile===romFile){
|
||||
romFile=null;
|
||||
}else{
|
||||
patchFile=null;
|
||||
}
|
||||
}
|
||||
setTabApplyEnabled(true);
|
||||
});
|
||||
},
|
||||
function(zipReader){
|
||||
setTabApplyEnabled(true);
|
||||
setMessage('apply', _('error_unzipping'), 'error');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function unzipEntry(zipEntry){
|
||||
zipEntry.getData(new zip.BlobWriter(), function(blob){
|
||||
var fileReader=new FileReader();
|
||||
fileReader.onload=function(){
|
||||
var unzippedFile=new MarcFile(this.result);
|
||||
unzippedFile.fileName=zipEntry.filename;
|
||||
if(zipEntry.originalZipFile===romFile){
|
||||
romFile=unzippedFile;
|
||||
_parseROM();
|
||||
}else if(zipEntry.originalZipFile===patchFile){
|
||||
patchFile=unzippedFile;
|
||||
_readPatchFile();
|
||||
}
|
||||
};
|
||||
fileReader.readAsArrayBuffer(blob);
|
||||
});
|
||||
}
|
||||
|
||||
function _evtClickZipEntry(evt){
|
||||
document.body.removeChild(this.parentElement.parentElement.parentElement);
|
||||
unzipEntry(this.zipEntry);
|
||||
}
|
110
js/locale.js
Normal file
110
js/locale.js
Normal file
|
@ -0,0 +1,110 @@
|
|||
const LOCALIZATION={
|
||||
'en':{
|
||||
'creator_mode': 'Creator mode',
|
||||
|
||||
'apply_patch': 'Apply patch',
|
||||
'rom_file': 'ROM file:',
|
||||
'patch_file': 'Patch file:',
|
||||
'remove_header': 'Remove header',
|
||||
'add_header': 'Add temporary header',
|
||||
'compatible_formats': 'Compatible formats:',
|
||||
'applying_patch': 'Applying patch...',
|
||||
'downloading': 'Downloading...',
|
||||
'unzipping': 'Unzipping...',
|
||||
|
||||
'create_patch': 'Create patch:',
|
||||
'original_rom': 'Original ROM:',
|
||||
'modified_rom': 'Modified ROM:',
|
||||
'patch_type': 'Patch type:',
|
||||
'creating_patch': 'Creating patch...',
|
||||
|
||||
'error_crc_input': 'Source ROM checksum mismatch',
|
||||
'error_crc_output': 'Target ROM checksum mismatch',
|
||||
'error_crc_patch': 'Patch checksum mismatch',
|
||||
'error_downloading': 'Error downloading patch',
|
||||
'error_unzipping': 'Error unzipping file',
|
||||
'error_invalid_patch': 'Invalid patch file',
|
||||
'warning_too_big': 'Using big files is not recommended.'
|
||||
},
|
||||
'es':{
|
||||
'creator_mode': 'Modo creador',
|
||||
|
||||
'apply_patch': 'Aplicar parche',
|
||||
'rom_file': 'Archivo ROM:',
|
||||
'patch_file': 'Archivo parche:',
|
||||
'remove_header': 'Quitar cabecera',
|
||||
'add_header': 'Añadir cabecera temporal',
|
||||
'compatible_formats': 'Formatos compatibles:',
|
||||
'applying_patch': 'Aplicando parche...',
|
||||
'downloading': 'Descargando...',
|
||||
'unzipping': 'Descomprimiendo...',
|
||||
|
||||
'create_patch': 'Crear parche',
|
||||
'original_rom': 'ROM original:',
|
||||
'modified_rom': 'ROM modificada:',
|
||||
'patch_type': 'Tipo de parche:',
|
||||
'creating_patch': 'Creando parche...',
|
||||
|
||||
'error_crc_input': 'Checksum de ROM original no válida',
|
||||
'error_crc_output': 'Checksum de ROM creada no válida',
|
||||
'error_crc_patch': 'Checksum de parche no válida',
|
||||
'error_downloading': 'Error descargando parche',
|
||||
'error_unzipping': 'Error descomprimiendo archivo',
|
||||
'error_invalid_patch': 'Archivo de parche no válido',
|
||||
'warning_too_big': 'No se recomienda usar archivos muy grandes.'
|
||||
},
|
||||
'ca':{
|
||||
'creator_mode': 'Mode creador',
|
||||
|
||||
'apply_patch': 'Aplicar pedaç',
|
||||
'rom_file': 'Arxiu ROM:',
|
||||
'patch_file': 'Arxiu pedaç:',
|
||||
'remove_header': 'Treure capçalera',
|
||||
'add_header': 'Afegir capçalera temporal',
|
||||
'compatible_formats': 'Formats compatibles:',
|
||||
'applying_patch': 'Aplicant pedaç...',
|
||||
'downloading': 'Descarregant...',
|
||||
'unzipping': 'Descomprimint...',
|
||||
|
||||
'create_patch': 'Crear pedaç',
|
||||
'original_rom': 'ROM original:',
|
||||
'modified_rom': 'ROM modificada:',
|
||||
'patch_type': 'Tipus de pedaç:',
|
||||
'creating_patch': 'Creant pedaç...',
|
||||
|
||||
'error_crc_input': 'Checksum de ROM original no vàlida',
|
||||
'error_crc_output': 'Checksum de ROM creada no vàlida',
|
||||
'error_crc_patch': 'Checksum de pedaç no vàlida',
|
||||
'error_downloading': 'Error descarregant pedaç',
|
||||
'error_unzipping': 'Error descomprimint arxiu',
|
||||
'error_invalid_patch': 'Arxiu de pedaç no vàlid',
|
||||
'warning_too_big': 'No es recomana usar arxius molt grans.'
|
||||
},
|
||||
'ru':{
|
||||
'creator_mode': 'Режим создания',
|
||||
|
||||
'apply_patch': 'Применить патч',
|
||||
'rom_file': 'Файл ROM:',
|
||||
'patch_file': 'Файл патча:',
|
||||
'remove_header': 'Удалить заголовок перед применением',
|
||||
'add_header': 'Добавить временный заголовок',
|
||||
'compatible_formats': 'Совместимые форматы:',
|
||||
'applying_patch': 'Применяется патч...',
|
||||
'downloading': 'Загрузка...',
|
||||
'unzipping': 'Unzipping...',
|
||||
|
||||
'create_patch': 'Создать патч:',
|
||||
'original_rom': 'Оригинальный ROM:',
|
||||
'modified_rom': 'Изменённый ROM:',
|
||||
'patch_type': 'Тип патча:',
|
||||
'creating_patch': 'Патч создаётся...',
|
||||
|
||||
'error_crc_input': 'Неправильная контрольная сумма входного ROM',
|
||||
'error_crc_output': 'Неправильная контрольная сумма выходного ROM',
|
||||
'error_crc_patch': 'Неправильная контрольная сумма патча',
|
||||
'error_downloading': 'Ошибка при скачивании патча',
|
||||
'error_unzipping': 'Error unzipping file',
|
||||
'error_invalid_patch': 'Неправильный файл патча',
|
||||
'warning_too_big': 'Не рекомендуется использовать большие файлы.'
|
||||
}
|
||||
};
|
83
js/worker_apply.js
Normal file
83
js/worker_apply.js
Normal file
|
@ -0,0 +1,83 @@
|
|||
/* Rom Patcher JS v20200502 - Marc Robledo 2016-2020 - http://www.marcrobledo.com/license */
|
||||
|
||||
self.importScripts(
|
||||
'./MarcFile.js',
|
||||
'./crc.js',
|
||||
'./formats/ips.js',
|
||||
'./formats/aps.js',
|
||||
'./formats/ups.js',
|
||||
'./formats/bps.js',
|
||||
'./formats/rup.js',
|
||||
'./formats/ppf.js',
|
||||
'./formats/pmsr.js',
|
||||
'./formats/vcdiff.js'
|
||||
);
|
||||
|
||||
|
||||
self.onmessage = event => { // listen for messages from the main thread
|
||||
var romFile=new MarcFile(event.data.romFileU8Array);
|
||||
var patchFile=new MarcFile(event.data.patchFileU8Array);
|
||||
|
||||
var errorMessage=false;
|
||||
|
||||
var patch;
|
||||
var header=patchFile.readString(6);
|
||||
if(header.startsWith(IPS_MAGIC)){
|
||||
patch=parseIPSFile(patchFile);
|
||||
}else if(header.startsWith(UPS_MAGIC)){
|
||||
patch=parseUPSFile(patchFile);
|
||||
}else if(header.startsWith(APS_MAGIC)){
|
||||
patch=parseAPSFile(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{
|
||||
errorMessage='error_invalid_patch';
|
||||
}
|
||||
|
||||
//console.log('apply');
|
||||
var patchedRom;
|
||||
if(patch){
|
||||
try{
|
||||
patchedRom=patch.apply(romFile, event.data.validateChecksums);
|
||||
}catch(evt){
|
||||
errorMessage=evt.message;
|
||||
}
|
||||
}
|
||||
|
||||
//console.log('postMessage');
|
||||
if(patchedRom){
|
||||
self.postMessage(
|
||||
{
|
||||
romFileU8Array:event.data.romFileU8Array,
|
||||
patchFileU8Array:event.data.patchFileU8Array,
|
||||
patchedRomU8Array:patchedRom._u8array,
|
||||
errorMessage:errorMessage
|
||||
},
|
||||
[
|
||||
event.data.romFileU8Array.buffer,
|
||||
event.data.patchFileU8Array.buffer,
|
||||
patchedRom._u8array.buffer
|
||||
]
|
||||
);
|
||||
}else{
|
||||
self.postMessage(
|
||||
{
|
||||
romFileU8Array:event.data.romFileU8Array,
|
||||
patchFileU8Array:event.data.patchFileU8Array,
|
||||
errorMessage:errorMessage
|
||||
},
|
||||
[
|
||||
event.data.romFileU8Array.buffer,
|
||||
event.data.patchFileU8Array.buffer
|
||||
]
|
||||
);
|
||||
}
|
||||
};
|
23
js/worker_crc.js
Normal file
23
js/worker_crc.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
/* Rom Patcher JS v20200502 - Marc Robledo 2016-2020 - http://www.marcrobledo.com/license */
|
||||
|
||||
self.importScripts(
|
||||
'./MarcFile.js',
|
||||
'./crc.js'
|
||||
);
|
||||
|
||||
|
||||
|
||||
self.onmessage = event => { // listen for messages from the main thread
|
||||
var sourceFile=new MarcFile(event.data.u8array);
|
||||
|
||||
self.postMessage(
|
||||
{
|
||||
crc32:crc32(sourceFile, event.data.startOffset),
|
||||
md5:md5(sourceFile, event.data.startOffset),
|
||||
u8array:event.data.u8array
|
||||
},
|
||||
[
|
||||
event.data.u8array.buffer
|
||||
]
|
||||
);
|
||||
};
|
63
js/worker_create.js
Normal file
63
js/worker_create.js
Normal file
|
@ -0,0 +1,63 @@
|
|||
/* Rom Patcher JS v20200502 - Marc Robledo 2016-2020 - http://www.marcrobledo.com/license */
|
||||
|
||||
self.importScripts(
|
||||
'./MarcFile.js',
|
||||
'./crc.js',
|
||||
'./formats/ips.js',
|
||||
'./formats/aps.js',
|
||||
'./formats/ups.js',
|
||||
'./formats/bps.js',
|
||||
'./formats/ppf.js',
|
||||
'./formats/rup.js'
|
||||
);
|
||||
|
||||
|
||||
|
||||
self.onmessage = event => { // listen for messages from the main thread
|
||||
var sourceFile=new MarcFile(event.data.sourceFileU8Array);
|
||||
var modifiedFile=new MarcFile(event.data.modifiedFileU8Array);
|
||||
var mode=event.data.patchMode;
|
||||
|
||||
sourceFile.seek(0);
|
||||
modifiedFile.seek(0);
|
||||
|
||||
var patch;
|
||||
if(mode==='ips'){
|
||||
patch=createIPSFromFiles(sourceFile, modifiedFile);
|
||||
}else if(mode==='bps'){
|
||||
//use delta mode (slower, but smaller patch size) only with <4mb files
|
||||
patch=createBPSFromFiles(sourceFile, modifiedFile, (sourceFile.fileSize<=4194304));
|
||||
}else if(mode==='ups'){
|
||||
patch=createUPSFromFiles(sourceFile, modifiedFile);
|
||||
}else if(mode==='aps'){
|
||||
patch=createAPSFromFiles(sourceFile, modifiedFile);
|
||||
}else if(mode==='rup'){
|
||||
patch=createRUPFromFiles(sourceFile, modifiedFile);
|
||||
}else if(mode==='ppf'){
|
||||
patch=createPPFFromFiles(sourceFile, modifiedFile);
|
||||
}else{
|
||||
throw new Error('error_invalid_patch');
|
||||
}
|
||||
|
||||
|
||||
//special case: PPF+modified size>original size, skip verification
|
||||
if(!(mode==='ppf' && sourceFile.fileSize>modifiedFile.fileSize) && crc32(modifiedFile)!==crc32(patch.apply(sourceFile))){
|
||||
throw new Error('Unexpected error: verification failed. Patched file and modified file mismatch. Please report this bug.');
|
||||
}
|
||||
|
||||
var newPatchFile=patch.export('file');
|
||||
|
||||
//console.log('postMessage');
|
||||
self.postMessage(
|
||||
{
|
||||
//sourceFileU8Array:event.data.sourceFileU8Array,
|
||||
//modifiedFileU8Array:event.data.modifiedFileU8Array,
|
||||
patchFileU8Array:newPatchFile._u8array
|
||||
},
|
||||
[
|
||||
//event.data.sourceFileU8Array.buffer,
|
||||
//event.data.modifiedFileU8Array.buffer,
|
||||
newPatchFile._u8array.buffer
|
||||
]
|
||||
);
|
||||
};
|
36
js/zip.js/inflate.js
Normal file
36
js/zip.js/inflate.js
Normal file
File diff suppressed because one or more lines are too long
2
js/zip.js/z-worker.js
Normal file
2
js/zip.js/z-worker.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
/* jshint worker:true */
|
||||
!function(c){"use strict";if(c.zWorkerInitialized)throw new Error("z-worker.js should be run only once");c.zWorkerInitialized=!0,addEventListener("message",function(t){var e,r,c=t.data,n=c.type,s=c.sn,p=o[n];if(p)try{p(c)}catch(t){e={type:n,sn:s,error:(r=t,{message:r.message,stack:r.stack})},postMessage(e)}});var o={importScripts:function(t){t.scripts&&0<t.scripts.length&&importScripts.apply(void 0,t.scripts);postMessage({type:"importScripts"})},newTask:h,append:t,flush:t},f={};function h(t){var e=c[t.codecClass],r=t.sn;if(f[r])throw Error("duplicated sn");f[r]={codec:new e(t.options),crcInput:"input"===t.crcType,crcOutput:"output"===t.crcType,crc:new n},postMessage({type:"newTask",sn:r})}var l=c.performance?c.performance.now.bind(c.performance):Date.now;function t(t){var e=t.sn,r=t.type,c=t.data,n=f[e];!n&&t.codecClass&&(h(t),n=f[e]);var s,p="append"===r,o=l();if(p)try{s=n.codec.append(c,function(t){postMessage({type:"progress",sn:e,loaded:t})})}catch(t){throw delete f[e],t}else delete f[e],s=n.codec.flush();var a=l()-o;o=l(),c&&n.crcInput&&n.crc.append(c),s&&n.crcOutput&&n.crc.append(s);var i=l()-o,u={type:r,sn:e,codecTime:a,crcTime:i},d=[];s&&(u.data=s,d.push(s.buffer)),p||!n.crcInput&&!n.crcOutput||(u.crc=n.crc.get());try{postMessage(u,d)}catch(t){postMessage(u)}}function n(){this.crc=-1}function e(){}n.prototype.append=function(t){for(var e=0|this.crc,r=this.table,c=0,n=0|t.length;c<n;c++)e=e>>>8^r[255&(e^t[c])];this.crc=e},n.prototype.get=function(){return~this.crc},n.prototype.table=function(){var t,e,r,c=[];for(t=0;t<256;t++){for(r=t,e=0;e<8;e++)1&r?r=r>>>1^3988292384:r>>>=1;c[t]=r}return c}(),(c.NOOP=e).prototype.append=function(t,e){return t},e.prototype.flush=function(){}}(this);
|
28
js/zip.js/zip.js
Normal file
28
js/zip.js/zip.js
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue