diff --git a/MarcFile.js b/MarcFile.js new file mode 100644 index 0000000..f2e4e9a --- /dev/null +++ b/MarcFile.js @@ -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>> 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; i0;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 { // listen for events from the worker + //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(patch.file){ + // patch.file._u8array=patchFile._u8array; + // patch.file._dataView=patchFile._dataView; + //} + + preparePatchedRom(romFile, new MarcFile(event.data.patchedRomU8Array.buffer), headerSize); + + setTabApplyEnabled(true); + }; + webWorkerApply.onerror = event => { // listen for events from the worker + setMessage('apply', _(event.message.replace('Error: ','')), 'error'); + setTabApplyEnabled(true); + }; + + + + + + webWorkerCreate=new Worker('./worker_create.js'); + webWorkerCreate.onmessage = event => { // listen for events from the worker + //console.log('received_create'); + //romFile1._u8array=event.data.sourceFileU8Array; + //romFile1._dataView=new DataView(romFile1._u8array.buffer); + //romFile2._u8array=event.data.modifiedFileU8Array; + //romFile2._dataView=new DataView(romFile2._u8array.buffer); + + + 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 + setMessage('create', _(event.message.replace('Error: ','')), 'error'); + setTabCreateEnabled(true); + }; + + + + + + webWorkerCrc=new Worker('./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 selectFetchedPatch(i){ + patchFile=fetchedPatches[i].slice(0); + _readPatchFile(); +} +function fetchPredefinedPatch(i, doNotDisable){ + if(!doNotDisable) + setTabApplyEnabled(false); + + var patchFromUrl=PREDEFINED_PATCHES[i].patch; + setMessage('apply', _('downloading'), 'loading'); + + + if(typeof window.fetch==='function'){ + fetch(patchFromUrl) + .then(result => result.arrayBuffer()) // Gets the response and returns it as a blob + .then(arrayBuffer => { + fetchedPatches[i]=new MarcFile(arrayBuffer); + fetchedPatches[i].fileName=decodeURIComponent(patchFromUrl); + + if(i===el('input-file-patch').selectedIndex) + selectFetchedPatch(i); + }) + .catch(function(evt){ + setMessage('apply', _('error_downloading'), 'error'); + //setMessage('apply', evt.message, 'error'); + }); + }else{ + var xhr=new XMLHttpRequest(); + xhr.open('GET', patchFromUrl, true); + xhr.responseType='arraybuffer'; + + xhr.onload=function(evt){ + if(this.status===200){ + fetchedPatches[i]=new MarcFile(xhr.response); + fetchedPatches[i].fileName=decodeURIComponent(patchFromUrl); + + if(i===el('input-file-patch').selectedIndex) + selectFetchedPatch(i); + }else{ + setMessage('apply', _('error_downloading')+' ('+this.status+')', 'error'); + } + }; + + xhr.onerror=function(evt){ + setMessage('apply', _('error_downloading'), 'error'); + }; + + xhr.send(null); + } +} /* initialize app */ addEvent(window,'load',function(){ /* service worker */ - if(location.protocol==='http:') + if(FORCE_HTTPS && location.protocol==='http:') location.href=window.location.href.replace('http:','https:'); - if('serviceWorker' in navigator) + else if(location.protocol==='https:' && 'serviceWorker' in navigator) navigator.serviceWorker.register('_cache_service_worker.js'); - + /* 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=0)} -function isSNESHeadered(romFile){return isSNESExtension(romFile) && (isPowerOfTwo(romFile.fileSize-512) || MORE_SNES_SIZES_HEADERLESS.indexOf(romFile.fileSize-512)>=0)} -function updateChecksums(file){ - //el('rom-info').style.display='flex'; - sha1(file); +function canHaveFakeHeader(romFile){ + if(romFile.fileSize<=0x600000){ + for(var i=0; i33554432 && !force){ + el('crc32').innerHTML='File is too big. Force calculate checksum'; + 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'){ - el('crc32').className=patch.validateSource(romFile)?'valid':'invalid'; + 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 if(patch && romFile && typeof PREDEFINED_PATCHES!=='undefined' && PREDEFINED_PATCHES[el('input-file-patch').selectedIndex].crc){ + if(PREDEFINED_PATCHES[el('input-file-patch').selectedIndex].crc===crc32(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(){ - tempFile.littleEndian=false; + setTabApplyEnabled(false); + patchFile.littleEndian=false; - if(tempFile.readString(0,5)===IPS_MAGIC){ - patch=readIPSFile(tempFile); - }else if(tempFile.readString(0,4)===UPS_MAGIC){ - patch=readUPSFile(tempFile); - }else if(tempFile.readString(0,5)===APS_MAGIC){ - patch=readAPSFile(tempFile); - }else if(tempFile.readString(0,4)===BPS_MAGIC){ - patch=readBPSFile(tempFile); - }/*else if(tempFile.readInt(0)===XDELTA_MAGIC){ - patch=readXDeltaFile(tempFile); - }*/else { - MarcDialogs.alert('Invalid IPS/UPS/APS/BPS file'); - } - validateSource(); -} -function openPatchFile(f){tempFile=new MarcBinFile(f, _readPatchFile)} -function applyPatchFile(p,r){ - if(p && r){ - var patchedROM=p.apply(r); - patchedROM.fileName=r.fileName.replace(/\.(.*?)$/, ' (patched).$1'); - if(el('checkbox-addheader').checked){ - var unheaderedPatchedROM=new MarcBinFile(patchedROM.fileSize-512); - unheaderedPatchedROM.fileName=patchedROM.fileName; - unheaderedPatchedROM.writeBytes(0, patchedROM.readBytes(512, patchedROM.fileSize-512)); - unheaderedPatchedROM.save(); - - if(DEBUG_CREATE_UNHEADERED_PATCH){ - romFile1=unheaderedRomFile; - romFile2=unheaderedPatchedROM; - unheaderedPatchedROM.fileName=unheaderedPatchedROM.fileName.replace(' (patched)',''); - createPatchFile(); - } - }else{ - patchedROM.save(); - } + 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(VCDIFF_MAGIC)){ + patch=parseVCDIFF(patchFile); }else{ - MarcDialogs.alert('No ROM/patch selected'); + patch=null; + setMessage('apply', _('error_invalid_patch'), 'error'); } + + + validateSource(); + setTabApplyEnabled(true); } -function createPatchFile(){ - var mode=el('patch-type').value; - if(!romFile1 || !romFile2){ - MarcDialogs.alert('No original/modified ROM file specified'); - return false; - }else if(mode==='ips' && (romFile1.fileSize>MAX_IPS_SIZE || romFile2.fileSize>MAX_IPS_SIZE)){ - MarcDialogs.alert('Files are too big for IPS format'); - return false; + + +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); + + } } - - var newPatch; - if(mode==='ips'){ - newPatch=createIPSFromFiles(romFile1, romFile2); - }else if(mode==='ups'){ - newPatch=createUPSFromFiles(romFile1, romFile2); - }else if(mode==='aps'){ - newPatch=createAPSFromFiles(romFile1, romFile2, false); - }else if(mode==='apsn64'){ - newPatch=createAPSFromFiles(romFile1, romFile2, true); - }/*else if(el('radio-apsgba').checked){ - newPatch=createAPSGBAFromFiles(romFile1, romFile2); - }else if(el('radio-bps').checked){ - newPatch=createBPSFromFiles(romFile1, romFile2); + setMessage('apply'); + patchedRom.save(); + + //debug: create unheadered patch + /*if(headerSize && el('checkbox-addheader').checked){ + createPatch(romFile, patchedRom); }*/ - newPatch.export(romFile2.fileName.replace(/\.[^\.]+$/,'')).save(); +} + + +/*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 + ] + ); + + + romFile=new MarcFile(el('input-file-rom')); + if(typeof PREDEFINED_PATCHES==='undefined'){ + patchFile=new MarcFile(el('input-file-patch')); + }else{ + patchFile=fetchedPatches[el('input-file-patch').selectedIndex].slice(0); + } + if(patch.file) //VCDiff does not parse patch file, it just keeps a copy of original patch, so we retrieve arraybuffer again + patch.file=patchFile; + + }else{ + setMessage('apply', _('applying_patch'), 'loading'); + + try{ + preparePatchedRom(r, p.apply(r, validateChecksums), headerSize); + + }catch(e){ + setMessage('apply', 'Error: '+_(e.message), 'error'); + } + } + + }else{ + setMessage('apply', 'No ROM/patch selected', 'error'); + } } -function setTab(tab){ - for(var i=0; i<2; i++){ - if(i===tab){ - el('tab'+i).style.display='block'; - el('tabs').children[i].className='selected'; - }else{ - el('tab'+i).style.display=i===tab?'block':'none'; - el('tabs').children[i].className='clickable' + + +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){ + setMessage('create', _('creating_patch'), 'loading'); + + setTabCreateEnabled(false); + + 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'); } } } @@ -214,97 +560,87 @@ function setTab(tab){ -/* CRC32/MD5/SHA-1 calculators */ -var HEX_CHR='0123456789abcdef'.split(''); -var CRC_TABLE=false; -var CAN_USE_CRYPTO_API=(window.crypto&&window.crypto.subtle&&window.crypto.subtle.digest); -/* SHA-1 using WebCryptoAPI */ -function sha1(file){ - if(CAN_USE_CRYPTO_API && file.fileSize<=MAX_ROM_SIZE){ - - window.crypto.subtle.digest('SHA-1', file.fileReader.dataView.buffer).then(function(hash){ - //console.log('SHA-1:'+); - var bytes=new Uint8Array(hash); - var hexString=''; - for(var i=0;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<>>(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(file){ - var data=new Uint8Array(file.fileReader.dataView.buffer); - 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>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; +function setElementEnabled(element,status){ + if(status){ + el(element).className='enabled'; + }else{ + el(element).className='disabled'; } - tail[14]=n*8; - _md5cycle(state,tail); - - for(var i=0;i>(j*8+4))&0x0f]+HEX_CHR[(state[i]>>(j*8))&0x0f]; - state[i]=s; - } - return state.join('') + el(element).disabled=!status; } -/* CRC32 - from Alex - https://stackoverflow.com/a/18639999 */ -function _makeCRCTable(){ - 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; +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); } - return crcTable; } -function crc32(file,ignoreLast4Bytes){ - var data=new Uint8Array(file.fileReader.dataView.buffer); - if(!CRC_TABLE) - CRC_TABLE=_makeCRCTable(); - - var crc=0^(-1); - - var len=ignoreLast4Bytes?data.length-4:data.length; - for(var i=0;i>>8)^CRC_TABLE[(crc^data[i])&0xff]; - - return ((crc^(-1))>>>0); +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' + } } -/* MarcDialogs.js */ -MarcDialogs=function(){function e(e,t,n){a?e.attachEvent("on"+t,n):e.addEventListener(t,n,!1)}function t(){s&&(o?history.go(-1):(c.className="dialog-overlay",s.className=s.className.replace(/ active/g,""),s=null))}function n(e){for(var t=0;t0;e++)d+=String.fromCharCode(c[e]);return d},MarcBinFile.prototype.writeByte=function(a,b){this.fileReader.dataView.setUint8(a,b,this.littleEndian)},MarcBinFile.prototype.writeByteSigned=function(a,b){this.fileReader.dataView.setInt8(a,b,this.littleEndian)},MarcBinFile.prototype.writeBytes=function(a,b){for(var c=0;c> 16, (val & 0x00ff00) >> 8, (val & 0x0000ff)])} -/* FileSaver.min.js: https://github.com/eligrey/FileSaver.js/blob/master/FileSaver.min.js */ -var saveAs=saveAs||function(view){"use strict";if(typeof navigator!=="undefined"&&/MSIE [1-9]\./.test(navigator.userAgent)){return}var doc=view.document,get_URL=function(){return view.URL||view.webkitURL||view},save_link=doc.createElementNS("http://www.w3.org/1999/xhtml","a"),can_use_save_link="download"in save_link,click=function(node){var event=new MouseEvent("click");node.dispatchEvent(event)},is_safari=/Version\/[\d\.]+.*Safari/.test(navigator.userAgent),webkit_req_fs=view.webkitRequestFileSystem,req_fs=view.requestFileSystem||webkit_req_fs||view.mozRequestFileSystem,throw_outside=function(ex){(view.setImmediate||view.setTimeout)(function(){throw ex},0)},force_saveable_type="application/octet-stream",fs_min_size=0,arbitrary_revoke_timeout=500,revoke=function(file){var revoker=function(){if(typeof file==="string"){get_URL().revokeObjectURL(file)}else{file.remove()}};if(view.chrome){revoker()}else{setTimeout(revoker,arbitrary_revoke_timeout)}},dispatch=function(filesaver,event_types,event){event_types=[].concat(event_types);var i=event_types.length;while(i--){var listener=filesaver["on"+event_types[i]];if(typeof listener==="function"){try{listener.call(filesaver,event||filesaver)}catch(ex){throw_outside(ex)}}}},auto_bom=function(blob){if(/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)){return new Blob(["\ufeff",blob],{type:blob.type})}return blob},FileSaver=function(blob,name,no_auto_bom){if(!no_auto_bom){blob=auto_bom(blob)}var filesaver=this,type=blob.type,blob_changed=false,object_url,target_view,dispatch_all=function(){dispatch(filesaver,"writestart progress write writeend".split(" "))},fs_error=function(){if(target_view&&is_safari&&typeof FileReader!=="undefined"){var reader=new FileReader;reader.onloadend=function(){var base64Data=reader.result;target_view.location.href="data:attachment/file"+base64Data.slice(base64Data.search(/[,;]/));filesaver.readyState=filesaver.DONE;dispatch_all()};reader.readAsDataURL(blob);filesaver.readyState=filesaver.INIT;return}if(blob_changed||!object_url){object_url=get_URL().createObjectURL(blob)}if(target_view){target_view.location.href=object_url}else{var new_tab=view.open(object_url,"_blank");if(new_tab==undefined&&is_safari){view.location.href=object_url}}filesaver.readyState=filesaver.DONE;dispatch_all();revoke(object_url)},abortable=function(func){return function(){if(filesaver.readyState!==filesaver.DONE){return func.apply(this,arguments)}}},create_if_not_found={create:true,exclusive:false},slice;filesaver.readyState=filesaver.INIT;if(!name){name="download"}if(can_use_save_link){object_url=get_URL().createObjectURL(blob);setTimeout(function(){save_link.href=object_url;save_link.download=name;click(save_link);dispatch_all();revoke(object_url);filesaver.readyState=filesaver.DONE});return}if(view.chrome&&type&&type!==force_saveable_type){slice=blob.slice||blob.webkitSlice;blob=slice.call(blob,0,blob.size,force_saveable_type);blob_changed=true}if(webkit_req_fs&&name!=="download"){name+=".download"}if(type===force_saveable_type||webkit_req_fs){target_view=view}if(!req_fs){fs_error();return}fs_min_size+=blob.size;req_fs(view.TEMPORARY,fs_min_size,abortable(function(fs){fs.root.getDirectory("saved",create_if_not_found,abortable(function(dir){var save=function(){dir.getFile(name,create_if_not_found,abortable(function(file){file.createWriter(abortable(function(writer){writer.onwriteend=function(event){target_view.location.href=file.toURL();filesaver.readyState=filesaver.DONE;dispatch(filesaver,"writeend",event);revoke(file)};writer.onerror=function(){var error=writer.error;if(error.code!==error.ABORT_ERR){fs_error()}};"writestart progress write abort".split(" ").forEach(function(event){writer["on"+event]=filesaver["on"+event]});writer.write(blob);filesaver.abort=function(){writer.abort();filesaver.readyState=filesaver.DONE};filesaver.readyState=filesaver.WRITING}),fs_error)}),fs_error)};dir.getFile(name,{create:false},abortable(function(file){file.remove();save()}),abortable(function(ex){if(ex.code===ex.NOT_FOUND_ERR){save()}else{fs_error()}}))}),fs_error)}),fs_error)},FS_proto=FileSaver.prototype,saveAs=function(blob,name,no_auto_bom){return new FileSaver(blob,name,no_auto_bom)};if(typeof navigator!=="undefined"&&navigator.msSaveOrOpenBlob){return function(blob,name,no_auto_bom){if(!no_auto_bom){blob=auto_bom(blob)}return navigator.msSaveOrOpenBlob(blob,name||"download")}}FS_proto.abort=function(){var filesaver=this;filesaver.readyState=filesaver.DONE;dispatch(filesaver,"abort")};FS_proto.readyState=FS_proto.INIT=0;FS_proto.WRITING=1;FS_proto.DONE=2;FS_proto.error=FS_proto.onwritestart=FS_proto.onprogress=FS_proto.onwrite=FS_proto.onabort=FS_proto.onerror=FS_proto.onwriteend=null;return saveAs}(typeof self!=="undefined"&&self||typeof window!=="undefined"&&window||this.content);if(typeof module!=="undefined"&&module.exports){module.exports.saveAs=saveAs}else if(typeof define!=="undefined"&&define!==null&&define.amd!=null){define([],function(){return saveAs})} + + + + + +/* 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); \ No newline at end of file diff --git a/_cache_service_worker.js b/_cache_service_worker.js index ee1317a..810e019 100644 --- a/_cache_service_worker.js +++ b/_cache_service_worker.js @@ -1,29 +1,42 @@ /* +original: https://github.com/GoogleChrome/samples/blob/gh-pages/service-worker/basic/service-worker.js + Copyright 2016 Google Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. -You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ---- -mod by marcrobledo, original from: https://github.com/GoogleChrome/samples/blob/gh-pages/service-worker/basic/service-worker.js */ -const PRECACHE_ID='v20180926'; -const PRECACHE_FILES=[ -'index.html','./', -'RomPatcher.css', -'RomPatcher.js', -'favicon.png', -'logo192.png', -'ips.js', -'ups.js', -'aps.js', -'bps.js' + +const PRECACHE = 'precache-v2'; +const RUNTIME = 'runtime'; +const PRECACHE_URLS = [ + 'index.html','./', + 'manifest.json', + 'favicon.png', + 'logo192.png', + 'RomPatcher.css', + 'RomPatcher.js', + 'locale.js', + 'worker_apply.js', + 'worker_create.js', + 'worker_crc.js', + 'MarcFile.js', + 'crc.js', + 'ips.js', + 'ups.js', + 'aps.js', + 'bps.js', + 'rup.js', + 'ppf.js', + 'vcdiff.js' ]; -self.addEventListener('install',event=>{event.waitUntil(caches.open(PRECACHE_ID).then(cache=>cache.addAll(PRECACHE_FILES)).then(self.skipWaiting()))});self.addEventListener('activate',event=>{const currentCaches=[PRECACHE_ID,'runtime'];event.waitUntil(caches.keys().then(cacheNames=>{return cacheNames.filter(cacheName=>!currentCaches.includes(cacheName));}).then(cachesToDelete=>{return Promise.all(cachesToDelete.map(cacheToDelete=>{return caches.delete(cacheToDelete);}))}).then(()=>self.clients.claim()))});self.addEventListener('fetch',event=>{if(event.request.url.startsWith(self.location.origin))event.respondWith(caches.match(event.request).then(cachedResponse=>{if(cachedResponse)return cachedResponse;return caches.open('runtime').then(cache=>{return fetch(event.request).then(response=>{return cache.put(event.request,response.clone()).then(()=>{return response})})})}))}) \ No newline at end of file +self.addEventListener('install', event => {event.waitUntil(caches.open(PRECACHE).then(cache => cache.addAll(PRECACHE_URLS)).then(self.skipWaiting()));});self.addEventListener('activate', event => {const currentCaches = [PRECACHE, RUNTIME];event.waitUntil(caches.keys().then(cacheNames => {return cacheNames.filter(cacheName => !currentCaches.includes(cacheName));}).then(cachesToDelete => {return Promise.all(cachesToDelete.map(cacheToDelete => {return caches.delete(cacheToDelete);}));}).then(() => self.clients.claim()));});self.addEventListener('fetch', event => {if (event.request.url.startsWith(self.location.origin)) {event.respondWith(caches.match(event.request).then(cachedResponse => {if (cachedResponse) {return cachedResponse;}return caches.open(RUNTIME).then(cache => {return fetch(event.request).then(response => {return cache.put(event.request, response.clone()).then(() => {return response;});});});}));}}); \ No newline at end of file diff --git a/aps.js b/aps.js index 5b2113f..c7f837a 100644 --- a/aps.js +++ b/aps.js @@ -1,9 +1,10 @@ -/* APS (N64) module for RomPatcher.js v20180428 - Marc Robledo 2017-2018 - http://www.marcrobledo.com/license */ +/* 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) */ -var RECORD_RLE=0x0000; -var RECORD_SIMPLE=1; -var APS_MAGIC='APS10'; +const APS_MAGIC='APS10'; +const APS_RECORD_RLE=0x0000; +const APS_RECORD_SIMPLE=0x01; +const APS_N64_MODE=0x01; function APS(){ this.records=[]; @@ -14,110 +15,96 @@ function APS(){ this.header={}; } APS.prototype.addRecord=function(o, d){ - this.records.push({offset:o, type:RECORD_SIMPLE, data:d}) + this.records.push({offset:o, type:APS_RECORD_SIMPLE, data:d}) } -APS.prototype.addRLERecord=function(o, l, b){ - this.records.push({offset:o, type:RECORD_RLE, length:l, byte:b}) +APS.prototype.addRLERecord=function(o, b, l){ + this.records.push({offset:o, type:APS_RECORD_RLE, length:l, byte:b}) } APS.prototype.toString=function(){ - nSimpleRecords=0; - nRLERecords=0; - for(var i=0; i=original.fileSize?0x00:original.readByte(seek); - var b2=modified.readByte(seek); + 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=seek; + var offset=modified.offset-1; - while(b1!==b2 && differentBytes.length<255){ + while(b1!==b2 && differentBytes.length<0xff){ differentBytes.push(b2); if(b2!==differentBytes[0]) RLERecord=false; - seek++; - if(seek===modified.fileSize) + + if(modified.isEOF() || differentBytes.length===0xff) break; - b1=seek>=original.fileSize?0x00:original.readByte(seek); - b2=modified.readByte(seek); + + b1=original.isEOF()?0x00:original.readU8(); + b2=modified.readU8(); } if(RLERecord && differentBytes.length>2){ - tempFile.addRLERecord(offset, differentBytes.length, differentBytes[0]); + patch.addRLERecord(offset, differentBytes[0], differentBytes.length); }else{ - tempFile.addRecord(offset, differentBytes); + patch.addRecord(offset, differentBytes); } - //seek++; - }else{ - seek++; + //NO se puede comentar??? why???? } } - return tempFile + + return patch } \ No newline at end of file diff --git a/bps.js b/bps.js index e8d7c32..839839d 100644 --- a/bps.js +++ b/bps.js @@ -1,106 +1,71 @@ -/* BPS module for RomPatcher.js v20180926 - Marc Robledo 2016-2018 - http://www.marcrobledo.com/license */ +/* 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/ */ -var BPS_MAGIC='BPS1'; -var BPS_ACTION_SOURCE_READ=0; -var BPS_ACTION_TARGET_READ=1; -var BPS_ACTION_SOURCE_COPY=2; -var BPS_ACTION_TARGET_COPY=3; +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.actionsOffset=0; - this.file=null; + this.actions=[]; this.sourceChecksum=0; this.targetChecksum=0; this.patchChecksum=0; } BPS.prototype.toString=function(){ var s='Source size: '+this.sourceSize; - s+='\Target size: '+this.targetSize; + s+='\nTarget size: '+this.targetSize; s+='\nMetadata: '+this.metaData; - s+='\nActions offset: '+this.actionsOffset; + s+='\n#Actions: '+this.actions.length; return s } -/*BPS.prototype.export=function(){ - -}*/ -BPS.prototype.validateSource=function(romFile){return this.sourceChecksum===crc32(romFile,false)} -BPS.prototype.apply=function(romFile){ - if(!this.validateSource(romFile)){ - MarcDialogs.alert('Error: invalid source ROM checksum'); - return false; +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'); } - - // first we determine target file size - var newFileSize=0; - var seek=this.actionsOffset; - while(seek<(this.file.fileSize-12)){ - var data=decodeBPS(this.file, seek); - var action={type: data.number & 3, length: (data.number >> 2)+1}; - seek+=data.length; - newFileSize+=action.length; - if(action.type===BPS_ACTION_TARGET_READ){ - seek+=action.length; - }else if(action.type===BPS_ACTION_SOURCE_COPY || action.type===BPS_ACTION_TARGET_COPY){ - seek+=decodeBPS(this.file, seek).length; - }else{ - //console.log(action.type) - } - } - tempFile=new MarcBinFile(newFileSize); - //alert(newFileSize); + tempFile=new MarcFile(this.targetSize); //patch - var outputOffset=0; var sourceRelativeOffset=0; var targetRelativeOffset=0; - seek=this.actionsOffset; - while(seek<(this.file.fileSize-12)){ - var data=decodeBPS(this.file, seek); - var action={type: data.number & 3, length: (data.number >> 2)+1}; - //console.log('0x'+seek.toString(16)+' - action: '+action.type+':'+action.length); - seek+=data.length; + for(var i=0; i> 1); - while(action.length--){ - tempFile.writeByte(outputOffset, romFile.readByte(sourceRelativeOffset)); - outputOffset++; + sourceRelativeOffset+=action.relativeOffset; + var actionLength=action.length; + while(actionLength--){ + tempFile.writeU8(romFile._u8array[sourceRelativeOffset]); sourceRelativeOffset++; } }else if(action.type===BPS_ACTION_TARGET_COPY){ - var data2=decodeBPS(this.file, seek); - seek+=data2.length; - targetRelativeOffset += (data2.number & 1 ? -1 : +1) * (data2.number >> 1); - while(action.length--) { - tempFile.writeByte(outputOffset, tempFile.readByte(targetRelativeOffset)); - outputOffset++; + targetRelativeOffset+=action.relativeOffset; + var actionLength=action.length; + while(actionLength--) { + tempFile.writeU8(tempFile._u8array[targetRelativeOffset]); targetRelativeOffset++; } } } - if(this.targetChecksum!==crc32(tempFile,false)){ - MarcDialogs.alert('Warning: invalid target ROM checksum'); + if(validate && this.targetChecksum!==crc32(tempFile)){ + throw new Error('error_crc_output'); } return tempFile @@ -108,73 +73,381 @@ BPS.prototype.apply=function(romFile){ -function readBPSFile(file){ +function parseBPSFile(file){ + file.readVLV=BPS_readVLV; + file.littleEndian=true; - var patchFile=new BPS(); + var patch=new BPS(); - var seek=4; //skip BPS1 - var decodedSourceSize=decodeBPS(file, seek); - patchFile.sourceSize=decodedSourceSize.number; - seek+=decodedSourceSize.length; - var decodedTargetSize=decodeBPS(file, seek); - patchFile.targetSize=decodedTargetSize.number; - seek+=decodedTargetSize.length; + + file.seek(4); //skip BPS1 + + patch.sourceSize=file.readVLV(); + patch.targetSize=file.readVLV(); - var decodedMetaDataLength=decodeBPS(file, seek); - seek+=decodedMetaDataLength.length; - if(decodedMetaDataLength.number){ - patchFile.metaData=file.readString(seek, decodedMetaDataLength.number); - seek+=patchFile.metaData.number; + var metaDataLength=file.readVLV(); + if(metaDataLength){ + patch.metaData=file.readString(metaDataLength); } - patchFile.actionsOffset=seek; - patchFile.file=file; - patchFile.sourceChecksum=file.readInt(file.fileSize-12); - patchFile.targetChecksum=file.readInt(file.fileSize-8); - patchFile.patchChecksum=file.readInt(file.fileSize-4); + var endActionsOffset=file.fileSize-12; + while(file.offset> 2)+1}; - if(patchFile.patchChecksum!==crc32(file,true)){ - MarcDialogs.alert('Warning: invalid patch checksum'); + 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>>0; - var dataBytes=[]; - while(true){ - var x = number & 0x7f; - number >>= 7; - if(number == 0){ - dataBytes.push(0x80 | x); - break; +/* 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; } - dataBytes.push(x); - number--; } - return dataBytes; -}*/ -function decodeBPS(dataBytes, i){ - var number = 0, shift = 1; - var len=0; - while(true){ - var x = dataBytes.readByte(i); - i++; - len++; - number += (x & 0x7f) * shift; - if(x & 0x80) - break; - shift <<= 7; - number += shift; + + 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; } - return {number:number,length:len}; + + + + //source tree creation + for(var offset=0; offset 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; } \ No newline at end of file diff --git a/crc.js b/crc.js new file mode 100644 index 0000000..785db84 --- /dev/null +++ b/crc.js @@ -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>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<>>(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>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>(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>>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>>0; +} \ No newline at end of file diff --git a/index.html b/index.html index 837d8ea..117ec3b 100644 --- a/index.html +++ b/index.html @@ -3,36 +3,48 @@ Rom Patcher JS - - + + + + + - + + + +
-

RomPatcher.js

+

Rom Patcher JS

-
Apply patch
Create patch
+
Creator mode
-
+
- +
@@ -41,27 +53,28 @@
SHA-1:
-
-
+
+
- +
- + +
@@ -69,41 +82,47 @@
-
+
-
+
-
Patch type:
+
Patch type:
- + + - - +
- + +
+
+ + +