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

added RUP/PPF/xdelta support, added webworkers for a huge performance boost, added localization, other small fixes

This commit is contained in:
Marc Robledo 2019-04-17 21:43:53 +02:00
parent 9d85db3baa
commit e7d2bccc47
19 changed files with 3016 additions and 1750 deletions

266
MarcFile.js Normal file
View 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;
}

View file

@ -7,8 +7,11 @@ A ROM patcher made in HTML5.
* UPS * UPS
* APS (N64) * APS (N64)
* BPS * BPS
* RUP
* VCDiff (xdelta)
* PPF
* can patch and create patches * can patch and create patches
* shows ROM CRC32, MD5 and SHA-1 before patching * shows ROM CRC32, MD5 and SHA-1 before patching
* can remove SNES headers before patching * can remove headers before patching
* made in Vanilla JS * made in Vanilla JS
* can be run in any modern web browser, including mobile * can be run in any modern web browser, including mobile

View file

@ -43,8 +43,8 @@ footer{padding: 50px 0 20px}
align-items:center; /* vertical align */ align-items:center; /* vertical align */
justify-content:space-between justify-content:space-between
} }
.leftcol{width:35%;text-align:right} .leftcol{width:28%;text-align:right}
.rightcol{width:63%} .rightcol{width:70%}
.leftcol,.rightcol{margin-bottom:8px} .leftcol,.rightcol{margin-bottom:8px}
@ -52,15 +52,7 @@ footer{padding: 50px 0 20px}
/* icons */ /* icons */
button.with-dot:before{
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAQAAABKmM6bAAAAWElEQVQI12P4zwCGIv/V/stC2CCC63/J/5f/f////n/nf3OIUMn/v/9h4M5/NZCWR/8R4N//FgagCd/+I4PVIFVPUFR1oZt1778Bso3fEDZC3CULxFwgNgDirn13m99yDAAAAABJRU5ErkJggg==');
background-size:100% 100%;
display:inline-block;
vertical-align:middle;
width:4px;height:4px;
content:"";
margin-right:10px;
}
footer .icon, #crc32.valid:after{ footer .icon, #crc32.valid:after{
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAQCAYAAABQrvyxAAACUElEQVRIx9XWzUtUURzG8atp04svtCiI3v6EQFIoInBREFgEQtC+VVCLWoQLMTKqRUEbU4uKaOht5a7IoDcoWkiLIKKVgqUUvkGMM2revid+V55u5+qdS5ANfEDvPXN4npl7zpkgDMMgpgFX8QEF8xG9aPKM/6f0n7W4ifkw+eXu5VG33Aq48G/C9K8B1EeTjLTsdrbjMQqYwn1sw2bkMYlp9GOHe89CiDNBuU5jAOujSe5KsFY8wVe8NaN4hsN4bWP7pIAL9B1hzGcMe667IjszFuhAaN65CfbIJ5tP8bVdl/F7rcBzT8ilvMxQ4JyEd9rdBHck0JUUBc7L+HtWoJShwEyswCasSghegUux8G2/7jHBoCzQDSkKuGd/1t4zZAVGMhQYlQLNmEA/1sTCV6JHgs/jxMJ9JihamEIZq3/M3lO0AmczFLggBYYloCuxWj753lj4Y78VZIJxeSQ2pgi/Dj9s/IQVqEZfGeEfIScFGjAmQZ+iDjfk2hyO/vF4McELKdCZokCbjH8l26gr8TBFeFc059lGXYlxCVyUv2dxxLs+mOC4hXkPt7AuYqsn+BYrOCcFTkkBpwq3Fwn/wIoGCedAIyZji3UGrYk7kx1ig1agXQLul/DNnsPsS3QiSwGnEt2e8LewQscmbKNN8k2UcGjRrdUm2YVp9OAgTqJKCuRi4UtWKvAUcCpwWcJ3WbEgRYHocRpCy5Jng4TcZ4vyk522NXJvpYSfwgF9vDwFIp1ut0m6/7d/zAW2C3XhG2rlerVdu2ZrYVn+Gv0v/QQllGUdmIg+uwAAAABJRU5ErkJggg=='); background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAQCAYAAABQrvyxAAACUElEQVRIx9XWzUtUURzG8atp04svtCiI3v6EQFIoInBREFgEQtC+VVCLWoQLMTKqRUEbU4uKaOht5a7IoDcoWkiLIKKVgqUUvkGMM2revid+V55u5+qdS5ANfEDvPXN4npl7zpkgDMMgpgFX8QEF8xG9aPKM/6f0n7W4ifkw+eXu5VG33Aq48G/C9K8B1EeTjLTsdrbjMQqYwn1sw2bkMYlp9GOHe89CiDNBuU5jAOujSe5KsFY8wVe8NaN4hsN4bWP7pIAL9B1hzGcMe667IjszFuhAaN65CfbIJ5tP8bVdl/F7rcBzT8ilvMxQ4JyEd9rdBHck0JUUBc7L+HtWoJShwEyswCasSghegUux8G2/7jHBoCzQDSkKuGd/1t4zZAVGMhQYlQLNmEA/1sTCV6JHgs/jxMJ9JihamEIZq3/M3lO0AmczFLggBYYloCuxWj753lj4Y78VZIJxeSQ2pgi/Dj9s/IQVqEZfGeEfIScFGjAmQZ+iDjfk2hyO/vF4McELKdCZokCbjH8l26gr8TBFeFc059lGXYlxCVyUv2dxxLs+mOC4hXkPt7AuYqsn+BYrOCcFTkkBpwq3Fwn/wIoGCedAIyZji3UGrYk7kx1ig1agXQLul/DNnsPsS3QiSwGnEt2e8LewQscmbKNN8k2UcGjRrdUm2YVp9OAgTqJKCuRi4UtWKvAUcCpwWcJ3WbEgRYHocRpCy5Jng4TcZ4vyk522NXJvpYSfwgF9vDwFIp1ut0m6/7d/zAW2C3XhG2rlerVdu2ZrYVn+Gv0v/QQllGUdmIg+uwAAAABJRU5ErkJggg==');
} }
@ -105,28 +97,55 @@ footer a:hover{
hr{border:none;border-top:1px dotted #bbb;margin:15px 0} hr{border:none;border-top:1px dotted #bbb;margin:15px 0}
/* tabs */ /* Switch mode */
#tabs div{ #switch-container{
width:50%; visibility:hidden;
text-align:right;
color:white;
margin-bottom:10px;
font-size:88%;
}
#switch-create-button{
border-radius:2px;
padding: 6px 8px;
transition:background-color .1s;
}
#switch-create-button:hover{
background-color:#2b2e33;
cursor:pointer;
}
.switch{
background-color:#474c56;
display:inline-block; display:inline-block;
text-align:center; vertical-align:middle;
box-sizing:border-box; width:30px;height:16px;
padding: 10px; border-radius:8px;
color:#646871; position:relative;
border-radius: 2px 2px 0 0; transition:background-color .2s;
transition: .15s all;
border-top: 3px solid transparent;
} }
#tabs div:hover{color:white;background-color:#2e3137} .switch:before{
#tabs div.selected{ position:absolute;
background-color:#f9fafa; background-color:white;
color:black; height:12px;width:12px;
content:" ";
border-radius:6px;
top:2px;
left:2px;
transition:left .2s;
} }
.tab{background-color:#f9fafa;padding:30px 15px} .switch.enabled:before{
#tab0{border-radius: 0px 2px 2px 2px} left:16px;
#tab1{border-radius: 2px 0px 2px 2px;display:none} }
#tabs div:first-child.selected{border-color: #e74c3c;} .switch.enabled{
#tabs div:last-child.selected{border-color: #25ba84;/*border-color: #3498db;*/} background-color:#00a5ff;
background-color:#41bdc7;
}
.tab{background-color:#f9fafa;padding:30px 15px;border-radius: 3px}
#tab1{display:none}
@ -154,7 +173,8 @@ select{
background-repeat:no-repeat; background-repeat:no-repeat;
} }
select::-ms-expand{display:none} select::-ms-expand{display:none}
input[type=file]:hover,select:hover{background-color:#dee1e1} input[type=file].enabled:hover,select.enabled:hover{background-color:#dee1e1}
input[type=file].disabled,select.disabled{background-color:transparent}
@ -167,22 +187,22 @@ button{
min-width:120px; min-width:120px;
border-radius:3px;border:0; border-radius:3px;border:0;
padding:8px 16px; padding:10px 20px;
margin:0 5px; margin:0 5px;
background-color:#4c4f53; background-color:#2a9ca5;
color:white; color:white;
transition:background-color .15s; transition:background-color .15s;
box-sizing:border-box box-sizing:border-box
} }
button:hover{ button.enabled:hover{
cursor:pointer; cursor:pointer;
background-color:#6e7177; background-color:#3aacb5;
} }
button:active{ button.enabled:active{
background-color:#47494f; background-color:#297b81;
transform:translateY(1px) transform:translateY(1px)
} }
button:disabled{opacity:.35 !important;cursor:not-allowed} button:disabled{opacity:.35 !important;cursor:not-allowed}
@ -206,6 +226,8 @@ button.no-text.with-icon:before{margin-right:0px}
#rom-info .rightcol{white-space:nowrap;overflow:hidden;text-overflow:ellipsis} #rom-info .rightcol{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
#crc32.valid{color:green} #crc32.valid{color:green}
#crc32.invalid{color: red} #crc32.invalid{color: red}
#crc32 span{text-decoration:underline}
#crc32 span:hover{cursor:pointer;color:black}
#crc32.valid:after{ #crc32.valid:after{
@ -220,6 +242,45 @@ button.no-text.with-icon:before{margin-right:0px}
} }
.message{padding: 3px 10px; border-radius:3px;display:inline}
.message.error{background-color:#de4205; color:white}
.message.warning{background-color:#ffec68; color:black}
/* loading circle */
@-moz-keyframes spin { 100% { -moz-transform: rotate(360deg); } }
@-webkit-keyframes spin { 100% { -webkit-transform: rotate(360deg); } }
@keyframes spin { 100% { -webkit-transform: rotate(360deg); transform:rotate(360deg); } }
.loading{
width:20px;
height:20px;
display:inline-block;
position:relative;
-webkit-animation:spin 1s linear infinite;
-moz-animation:spin 1s linear infinite;
animation:spin 1s ease-in-out infinite;
vertical-align:middle;
}
.loading:before{
width:6px;
height:6px;
background-color:#41bdc7;
border-radius:3px;
display:inline-block;
content:"";
position:absolute;
top:0;
left:50%;
margin-left:-3px;
}
#wrapper{ #wrapper{
box-sizing:border-box; box-sizing:border-box;
max-width:95%; max-width:95%;

File diff suppressed because one or more lines are too long

View file

@ -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. Copyright 2016 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. 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=[ const PRECACHE = 'precache-v2';
'index.html','./', const RUNTIME = 'runtime';
'RomPatcher.css', const PRECACHE_URLS = [
'RomPatcher.js', 'index.html','./',
'favicon.png', 'manifest.json',
'logo192.png', 'favicon.png',
'ips.js', 'logo192.png',
'ups.js', 'RomPatcher.css',
'aps.js', 'RomPatcher.js',
'bps.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})})})}))}) 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;});});});}));}});

240
aps.js
View file

@ -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) */ /* File format specification: https://github.com/btimofeev/UniPatcher/wiki/APS-(N64) */
var RECORD_RLE=0x0000; const APS_MAGIC='APS10';
var RECORD_SIMPLE=1; const APS_RECORD_RLE=0x0000;
var APS_MAGIC='APS10'; const APS_RECORD_SIMPLE=0x01;
const APS_N64_MODE=0x01;
function APS(){ function APS(){
this.records=[]; this.records=[];
@ -14,110 +15,96 @@ function APS(){
this.header={}; this.header={};
} }
APS.prototype.addRecord=function(o, d){ 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){ APS.prototype.addRLERecord=function(o, b, l){
this.records.push({offset:o, type:RECORD_RLE, length:l, byte:b}) this.records.push({offset:o, type:APS_RECORD_RLE, length:l, byte:b})
} }
APS.prototype.toString=function(){ APS.prototype.toString=function(){
nSimpleRecords=0; var s='Total records: '+this.records.length;
nRLERecords=0;
for(var i=0; i<this.records.length; i++){
if(this.records[i].type===RECORD_RLE)
nRLERecords++;
else
nSimpleRecords++;
}
var s='';
s+='\Simple records: '+nSimpleRecords;
s+='\nRLE records: '+nRLERecords;
s+='\nTotal records: '+this.records.length;
s+='\nHeader type: '+this.headerType; s+='\nHeader type: '+this.headerType;
if(this.headerType===APS_N64_MODE){
s+=' (N64)';
}
s+='\nEncoding method: '+this.encodingMethod; s+='\nEncoding method: '+this.encodingMethod;
s+='\nDescription: '+this.description; s+='\nDescription: '+this.description;
s+='\nHeader: '+JSON.stringify(this.header); s+='\nHeader: '+JSON.stringify(this.header);
return s 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){ APS.prototype.export=function(fileName){
var patchFileSize=(this.headerType===1)?78:61; var patchFileSize=61;
if(this.headerType===APS_N64_MODE)
patchFileSize+=17;
for(var i=0; i<this.records.length; i++){ for(var i=0; i<this.records.length; i++){
if(this.records[i].type===RECORD_RLE) if(this.records[i].type===APS_RECORD_RLE)
patchFileSize+=7; patchFileSize+=7;
else else
patchFileSize+=5+this.records[i].data.length; //offset+length+data patchFileSize+=5+this.records[i].data.length; //offset+length+data
} }
tempFile=new MarcBinFile(patchFileSize); tempFile=new MarcFile(patchFileSize);
tempFile.littleEndian=true; tempFile.littleEndian=true;
tempFile.fileName=fileName+'.aps'; tempFile.fileName=fileName+'.aps';
tempFile.writeString(0, APS_MAGIC, APS_MAGIC.length); tempFile.writeString(APS_MAGIC, APS_MAGIC.length);
tempFile.writeByte(5, this.headerType); tempFile.writeU8(this.headerType);
tempFile.writeByte(6, this.encodingMethod); tempFile.writeU8(this.encodingMethod);
tempFile.writeString(7, this.description, 50); tempFile.writeString(this.description, 50);
var seek; if(this.headerType===APS_N64_MODE){
if(this.headerType===1){ tempFile.writeU8(this.header.originalN64Format);
tempFile.writeByte(57, this.header.originalFileFormat); tempFile.writeString(this.header.cartId, 3);
tempFile.writeString(58, this.header.cartId, 3); tempFile.writeBytes(this.header.crc);
tempFile.writeBytes(61, this.header.crc); tempFile.writeBytes(this.header.pad);
tempFile.writeBytes(69, this.header.pad);
tempFile.writeInt(74, this.header.sizeOutput);
seek=78;
}else{
tempFile.writeInt(57, this.header.sizeOutput);
seek=61;
} }
tempFile.writeU32(this.header.sizeOutput);
for(var i=0; i<this.records.length; i++){ for(var i=0; i<this.records.length; i++){
var rec=this.records[i]; var rec=this.records[i];
if(rec.type===RECORD_RLE){ tempFile.writeU32(rec.offset);
tempFile.writeInt(seek, rec.offset); if(rec.type===APS_RECORD_RLE){
tempFile.writeByte(seek+4, 0x00); tempFile.writeU8(0x00);
tempFile.writeByte(seek+5, rec.byte); tempFile.writeU8(rec.byte);
tempFile.writeByte(seek+6, rec.length); tempFile.writeU8(rec.length);
seek+=7;
}else{ }else{
tempFile.writeInt(seek, rec.offset); tempFile.writeU8(rec.data.length);
tempFile.writeByte(seek+4, rec.data.length); tempFile.writeBytes(rec.data);
tempFile.writeBytes(seek+5, rec.data);
seek+=5+rec.data.length;
} }
} }
return tempFile return tempFile
} }
APS.prototype.apply=function(romFile){
if(this.headerType===1){ APS.prototype.apply=function(romFile, validate){
if(romFile.readString(0x3c, 3)!==this.header.cartId){ if(validate && !this.validateSource(romFile)){
MarcDialogs.alert('Error: invalid ROM cart id'); throw new Error('error_crc_input');
return false;
}
var crc=romFile.readBytes(0x10, 8);
var crcOk=true;
for(var i=0; i<8 && crcOk; i++){
if(crc[i]!==this.header.crc[i])
crcOk=false;
}
if(!crcOk){
MarcDialogs.alert('Error: invalid ROM checksum');
return false;
}
} }
tempFile=new MarcBinFile(this.header.sizeOutput); tempFile=new MarcFile(this.header.sizeOutput);
romFile.copyToFile(tempFile, 0, tempFile.fileSize);
for(var i=0; i<romFile.fileSize && i<this.header.sizeOutput; i++)
tempFile.writeByte(i, romFile.readByte(i));
for(var i=0; i<this.records.length; i++){ for(var i=0; i<this.records.length; i++){
var rec=this.records[i]; tempFile.seek(this.records[i].offset);
if(rec.type===RECORD_RLE){ if(this.records[i].type===APS_RECORD_RLE){
for(var j=0; j<rec.length; j++) for(var j=0; j<this.records[i].length; j++)
tempFile.writeByte(rec.offset+j, rec.byte); tempFile.writeU8(this.records[i].byte);
}else{ }else{
for(var j=0; j<rec.data.length; j++) tempFile.writeBytes(this.records[i].data);
tempFile.writeByte(rec.offset+j, rec.data[j]);
} }
} }
@ -127,88 +114,85 @@ APS.prototype.apply=function(romFile){
function readAPSFile(file){ function parseAPSFile(patchFile){
var patchFile=new APS(); var patch=new APS();
file.littleEndian=true; patchFile.littleEndian=true;
patchFile.headerType=file.readByte(5); patchFile.seek(5);
patchFile.encodingMethod=file.readByte(6); patch.headerType=patchFile.readU8();
patchFile.description=file.readString(7, 50); patch.encodingMethod=patchFile.readU8();
patch.description=patchFile.readString(50);
var seek; var seek;
if(patchFile.headerType===1){ if(patch.headerType===APS_N64_MODE){
patchFile.header.originalFileFormat=file.readByte(57); patch.header.originalN64Format=patchFile.readU8();
patchFile.header.cartId=file.readString(58, 3); patch.header.cartId=patchFile.readString(3);
patchFile.header.crc=file.readBytes(61, 8); patch.header.crc=patchFile.readBytes(8);
patchFile.header.pad=file.readBytes(69, 5); patch.header.pad=patchFile.readBytes(5);
patchFile.header.sizeOutput=file.readInt(74);
seek=78;
}else{
patchFile.header.sizeOutput=file.readInt(57);
seek=61;
} }
patch.header.sizeOutput=patchFile.readU32();
while(seek<file.fileSize){ while(!patchFile.isEOF()){
var offset=file.readInt(seek); var offset=patchFile.readU32();
seek+=4; var length=patchFile.readU8();
var length=file.readByte(seek); if(length===APS_RECORD_RLE)
seek+=1; patch.addRLERecord(offset, patchFile.readU8(seek), patchFile.readU8(seek+1));
else
if(length==RECORD_RLE){ patch.addRecord(offset, patchFile.readBytes(length));
patchFile.addRLERecord(offset, file.readByte(seek+1), file.readByte(seek));
seek+=2;
}else{
patchFile.addRecord(offset, file.readBytes(seek, length));
seek+=length;
}
} }
return patchFile; return patch;
} }
function createAPSFromFiles(original, modified, N64header){ function createAPSFromFiles(original, modified){
tempFile=new APS(); var patch=new APS();
if(N64header){
tempFile.headerType=1;
tempFile.header.originalFileFormat=0;
tempFile.header.cartId=original.readString(0x3c, 3); if(original.readU32()===0x80371240){ //is N64 ROM
tempFile.header.crc=original.readBytes(0x10, 8); patch.headerType=APS_N64_MODE;
tempFile.header.pad=[0,0,0,0,0];
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];
} }
tempFile.header.sizeOutput=modified.fileSize; patch.header.sizeOutput=modified.fileSize;
var seek=0; original.seek(0);
while(seek<modified.fileSize){ modified.seek(0);
var b1=seek>=original.fileSize?0x00:original.readByte(seek);
var b2=modified.readByte(seek); while(!modified.isEOF()){
var b1=original.isEOF()?0x00:original.readU8();
var b2=modified.readU8();
if(b1!==b2){ if(b1!==b2){
var RLERecord=true; var RLERecord=true;
var differentBytes=[]; 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); differentBytes.push(b2);
if(b2!==differentBytes[0]) if(b2!==differentBytes[0])
RLERecord=false; RLERecord=false;
seek++;
if(seek===modified.fileSize) if(modified.isEOF() || differentBytes.length===0xff)
break; 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){ if(RLERecord && differentBytes.length>2){
tempFile.addRLERecord(offset, differentBytes.length, differentBytes[0]); patch.addRLERecord(offset, differentBytes[0], differentBytes.length);
}else{ }else{
tempFile.addRecord(offset, differentBytes); patch.addRecord(offset, differentBytes);
} }
//seek++; //NO se puede comentar??? why????
}else{
seek++;
} }
} }
return tempFile
return patch
} }

513
bps.js
View file

@ -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/ */ /* File format specification: https://www.romhacking.net/documents/746/ */
var BPS_MAGIC='BPS1'; const BPS_MAGIC='BPS1';
var BPS_ACTION_SOURCE_READ=0; const BPS_ACTION_SOURCE_READ=0;
var BPS_ACTION_TARGET_READ=1; const BPS_ACTION_TARGET_READ=1;
var BPS_ACTION_SOURCE_COPY=2; const BPS_ACTION_SOURCE_COPY=2;
var BPS_ACTION_TARGET_COPY=3; const BPS_ACTION_TARGET_COPY=3;
function BPS(){ function BPS(){
this.sourceSize=0; this.sourceSize=0;
this.targetSize=0; this.targetSize=0;
this.metaData=''; this.metaData='';
this.actionsOffset=0; this.actions=[];
this.file=null;
this.sourceChecksum=0; this.sourceChecksum=0;
this.targetChecksum=0; this.targetChecksum=0;
this.patchChecksum=0; this.patchChecksum=0;
} }
BPS.prototype.toString=function(){ BPS.prototype.toString=function(){
var s='Source size: '+this.sourceSize; var s='Source size: '+this.sourceSize;
s+='\Target size: '+this.targetSize; s+='\nTarget size: '+this.targetSize;
s+='\nMetadata: '+this.metaData; s+='\nMetadata: '+this.metaData;
s+='\nActions offset: '+this.actionsOffset; s+='\n#Actions: '+this.actions.length;
return s return s
} }
/*BPS.prototype.export=function(){ BPS.prototype.validateSource=function(romFile,headerSize){return this.sourceChecksum===crc32(romFile, headerSize)}
BPS.prototype.apply=function(romFile, validate){
}*/ if(validate && !this.validateSource(romFile)){
BPS.prototype.validateSource=function(romFile){return this.sourceChecksum===crc32(romFile,false)} throw new Error('error_crc_input');
BPS.prototype.apply=function(romFile){
if(!this.validateSource(romFile)){
MarcDialogs.alert('Error: invalid source ROM checksum');
return false;
} }
// first we determine target file size tempFile=new MarcFile(this.targetSize);
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);
//patch //patch
var outputOffset=0;
var sourceRelativeOffset=0; var sourceRelativeOffset=0;
var targetRelativeOffset=0; var targetRelativeOffset=0;
seek=this.actionsOffset; for(var i=0; i<this.actions.length; i++){
while(seek<(this.file.fileSize-12)){ var action=this.actions[i];
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;
if(action.type===BPS_ACTION_SOURCE_READ){ if(action.type===BPS_ACTION_SOURCE_READ){
tempFile.writeBytes(outputOffset, romFile.readBytes(outputOffset, action.length)); romFile.copyToFile(tempFile, tempFile.offset, action.length);
outputOffset+=action.length; tempFile.skip(action.length);
//seek+=action.length;
}else if(action.type===BPS_ACTION_TARGET_READ){ }else if(action.type===BPS_ACTION_TARGET_READ){
tempFile.writeBytes(outputOffset, this.file.readBytes(seek, action.length)); tempFile.writeBytes(action.bytes);
outputOffset+=action.length;
seek+=action.length;
}else if(action.type===BPS_ACTION_SOURCE_COPY){ }else if(action.type===BPS_ACTION_SOURCE_COPY){
var data2=decodeBPS(this.file, seek); sourceRelativeOffset+=action.relativeOffset;
seek+=data2.length; var actionLength=action.length;
sourceRelativeOffset+=(data2.number & 1 ? -1 : +1) * (data2.number >> 1); while(actionLength--){
while(action.length--){ tempFile.writeU8(romFile._u8array[sourceRelativeOffset]);
tempFile.writeByte(outputOffset, romFile.readByte(sourceRelativeOffset));
outputOffset++;
sourceRelativeOffset++; sourceRelativeOffset++;
} }
}else if(action.type===BPS_ACTION_TARGET_COPY){ }else if(action.type===BPS_ACTION_TARGET_COPY){
var data2=decodeBPS(this.file, seek); targetRelativeOffset+=action.relativeOffset;
seek+=data2.length; var actionLength=action.length;
targetRelativeOffset += (data2.number & 1 ? -1 : +1) * (data2.number >> 1); while(actionLength--) {
while(action.length--) { tempFile.writeU8(tempFile._u8array[targetRelativeOffset]);
tempFile.writeByte(outputOffset, tempFile.readByte(targetRelativeOffset));
outputOffset++;
targetRelativeOffset++; targetRelativeOffset++;
} }
} }
} }
if(this.targetChecksum!==crc32(tempFile,false)){ if(validate && this.targetChecksum!==crc32(tempFile)){
MarcDialogs.alert('Warning: invalid target ROM checksum'); throw new Error('error_crc_output');
} }
return tempFile return tempFile
@ -108,73 +73,381 @@ BPS.prototype.apply=function(romFile){
function readBPSFile(file){ function parseBPSFile(file){
file.readVLV=BPS_readVLV;
file.littleEndian=true; 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;
var decodedMetaDataLength=decodeBPS(file, seek); file.seek(4); //skip BPS1
seek+=decodedMetaDataLength.length;
if(decodedMetaDataLength.number){ patch.sourceSize=file.readVLV();
patchFile.metaData=file.readString(seek, decodedMetaDataLength.number); patch.targetSize=file.readVLV();
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); var endActionsOffset=file.fileSize-12;
patchFile.targetChecksum=file.readInt(file.fileSize-8); while(file.offset<endActionsOffset){
patchFile.patchChecksum=file.readInt(file.fileSize-4); var data=file.readVLV();
var action={type: data & 3, length: (data >> 2)+1};
if(patchFile.patchChecksum!==crc32(file,true)){ if(action.type===BPS_ACTION_TARGET_READ){
MarcDialogs.alert('Warning: invalid patch checksum'); 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; return patchFile;
} }
function BPS_Node(){
/*function createBPSFromFiles(original, modified){ this.offset=0;
this.next=null;
}*/ };
BPS_Node.prototype.delete=function(){
if(this.next)
/*function encodeBPS(number){ delete this.next;
number=number>>>0; }
var dataBytes=[]; function createBPSFromFiles(original, modified, deltaMode){
while(true){ var patch=new BPS();
var x = number & 0x7f; patch.sourceSize=original.fileSize;
number >>= 7; patch.targetSize=modified.fileSize;
if(number == 0){
dataBytes.push(0x80 | x); if(deltaMode){
break; patch.actions=createBPSFromFilesDelta(original, modified);
} }else{
dataBytes.push(x); patch.actions=createBPSFromFilesLinear(original, modified);
number--; }
}
return dataBytes; patch.sourceChecksum=crc32(original);
}*/ patch.targetChecksum=crc32(modified);
function decodeBPS(dataBytes, i){ patch.patchChecksum=crc32(patch.export(), 0, true);
var number = 0, shift = 1; return patch;
var len=0; }
while(true){
var x = dataBytes.readByte(i);
i++; /* delta implementation from https://github.com/chiya/beat/blob/master/nall/beat/linear.hpp */
len++; function createBPSFromFilesLinear(original, modified){
number += (x & 0x7f) * shift; var patchActions=[];
if(x & 0x80)
break; /* references to match original beat code */
shift <<= 7; var sourceData=original._u8array;
number += shift; var targetData=modified._u8array;
} var sourceSize=original.fileSize;
return {number:number,length:len}; 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;
} }

105
crc.js Normal file
View 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;
}

View file

@ -3,36 +3,48 @@
<head> <head>
<title>Rom Patcher JS</title> <title>Rom Patcher JS</title>
<meta http-equiv="content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="content-Type" content="text/html; charset=UTF-8"/>
<meta name="description" content="A web-based IPS/UPS/APS/BPS ROM patcher."/> <meta name="description" content="An online web-based ROM patcher. Supported formats: IPS, BPS, UPS, APS, RUP, PPF and xdelta."/>
<meta name="keywords" content="ips,ups,aps,bps,patcher,online,html5,web,online,rom,patch,hack,translation"/> <meta name="keywords" content="ips,ups,aps,bps,rup,ninja,ppf,xdelta,patcher,online,html5,web,rom,patch,hack,translation"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
<link rel="manifest" href="./manifest.json"/> <link rel="manifest" href="./manifest.json"/>
<link rel="shortcut icon" href="./favicon.png" type="image/png" sizes="16x16"/> <link rel="shortcut icon" href="./favicon.png" type="image/png" sizes="16x16"/>
<link rel="shortcut icon" href="./logo192.png" type="image/png" sizes="192x192"/> <link rel="shortcut icon" href="./logo192.png" type="image/png" sizes="192x192"/>
<!-- <link rel="apple-touch-icon" href="./logo192.png" sizes="192x192" /> --> <!-- <link rel="apple-touch-icon" href="./logo192.png" sizes="192x192" /> -->
<link type="text/css" rel="stylesheet" href="./RomPatcher.css" media="all"/> <link type="text/css" rel="stylesheet" href="./RomPatcher.css" media="all"/>
<script type="text/javascript" src="./locale.js"></script>
<script type="text/javascript" src="./RomPatcher.js"></script> <script type="text/javascript" src="./RomPatcher.js"></script>
<script type="text/javascript" src="./MarcFile.js"></script>
<script type="text/javascript" src="./crc.js"></script>
<script type="text/javascript" src="./ips.js"></script> <script type="text/javascript" src="./ips.js"></script>
<script type="text/javascript" src="./ups.js"></script> <script type="text/javascript" src="./ups.js"></script>
<script type="text/javascript" src="./aps.js"></script> <script type="text/javascript" src="./aps.js"></script>
<script type="text/javascript" src="./bps.js"></script> <script type="text/javascript" src="./bps.js"></script>
<!-- <script type="text/javascript" src="./xdelta.js"></script> --> <script type="text/javascript" src="./rup.js"></script>
<script type="text/javascript" src="./ppf.js"></script>
<script type="text/javascript" src="./vcdiff.js"></script>
<script type="text/javascript"><!--
//uncomment the following lines to enable predefined patches, example should be self explanatory
/*var PREDEFINED_PATCHES=[
{patch:'./_example/SONICDX.xdelta',name:'Sonic 3D Blast Director\'s Cut v1.1',crc:0x44a2ca44},
{patch:'./_example/SML2DXv181.ips',name:'Super Mario Land 2 DX v1.81',crc:0xd5ec24e4}
];*/
--></script>
</head> </head>
<body><div id="column"> <body><div id="column">
<!-- HEADER --> <!-- HEADER -->
<header><img src="logo192.png" /><h1>RomPatcher.js</h1></header> <header><img src="logo192.png" /><h1>Rom Patcher JS</h1></header>
<!-- APP --> <!-- APP -->
<div id="wrapper"> <div id="wrapper">
<div id="tabs"><div class="selected clickable" onclick="setTab(0)">Apply patch</div><div class="clickable" onclick="setTab(1)">Create patch</div></div> <div id="switch-container"><span id="switch-create-button" onclick="setCreatorMode(!/enabled/.test(el('switch-create').className));"><span data-localize="creator_mode">Creator mode</span> <span id="switch-create" class="switch disabled"></span></span></div>
<div id="tab0" class="tab"> <div id="tab0" class="tab">
<div class="row"> <div class="row">
<div class="leftcol"><label for="input-file-rom">ROM file:</label></div> <div class="leftcol"><label for="input-file-rom" data-localize="rom_file">ROM file:</label></div>
<div class="rightcol"> <div class="rightcol">
<input type="file" id="input-file-rom" /> <input type="file" id="input-file-rom" class="enabled" />
</div> </div>
</div> </div>
<div class="row" id="rom-info"> <div class="row" id="rom-info">
@ -41,27 +53,28 @@
<div class="leftcol">SHA-1:</div><div class="rightcol"><span id="sha1"></span></div> <div class="leftcol">SHA-1:</div><div class="rightcol"><span id="sha1"></span></div>
</div> </div>
<div class="row" id="row-removeheader" style="display:none"> <div class="row" id="row-removeheader" style="display:none">
<div class="leftcol"><label for="checkbox-removeheader">Remove header before patching:</label></div> <div class="leftcol"><label for="checkbox-removeheader" data-localize="remove_header">Remove header before patching:</label></div>
<div class="rightcol"> <div class="rightcol">
<input type="checkbox" id="checkbox-removeheader" /> <input type="checkbox" id="checkbox-removeheader" />
</div> </div>
</div> </div>
<div class="row" id="row-addheader" style="display:none"> <div class="row" id="row-addheader" style="display:none">
<div class="leftcol"><label for="checkbox-addheader">Patch needs a headered ROM:</label></div> <div class="leftcol"><label for="checkbox-addheader" data-localize="add_header">Add temporary header:</label></div>
<div class="rightcol"> <div class="rightcol">
<input type="checkbox" id="checkbox-addheader" /> <input type="checkbox" id="checkbox-addheader" /> <label id="headersize" for="checkbox-addheader"></label>
</div> </div>
</div> </div>
<div class="row" title="Compatible formats: IPS, UPS, APS, and BPS"> <div class="row" id="row-file-patch">
<div class="leftcol"><label for="input-file-patch">Patch file:</label></div> <div class="leftcol"><label for="input-file-patch" data-localize="patch_file">Patch file:</label></div>
<div class="rightcol"> <div class="rightcol">
<input type="file" id="input-file-patch" accept=".ips,.ups,.bps,.aps"/> <input type="file" id="input-file-patch" accept=".ips,.ups,.bps,.aps,.rup,.ppf,.xdelta"/>
</div> </div>
</div> </div>
<div class="buttons"> <div class="buttons">
<button class="with-dot" onclick="applyPatchFile(patch, romFile)">Apply patch</button> <span id="message-apply" class="message"></span>
<button id="button-apply" data-localize="apply_patch" class="disabled" disabled onclick="applyPatch(patch, romFile, !/error/.test(el('message-apply').className))">Apply patch</button>
</div> </div>
</div> </div>
@ -69,41 +82,47 @@
<div id="tab1" class="tab"> <div id="tab1" class="tab">
<div class="row"> <div class="row">
<div class="leftcol"><label for="input-file-rom1">Original ROM:</label></div> <div class="leftcol"><label for="input-file-rom1" data-localize="original_rom" >Original ROM:</label></div>
<div class="rightcol"> <div class="rightcol">
<input type="file" id="input-file-rom1" /> <input type="file" id="input-file-rom1" />
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="leftcol"><label for="input-file-rom2">Modified ROM:</label></div> <div class="leftcol"><label for="input-file-rom2" data-localize="modified_rom">Modified ROM:</label></div>
<div class="rightcol"> <div class="rightcol">
<input type="file" id="input-file-rom2" /> <input type="file" id="input-file-rom2" />
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="leftcol">Patch type:</div> <div class="leftcol" data-localize="patch_type">Patch type:</div>
<div class="rightcol"> <div class="rightcol">
<select id="patch-type"> <select id="select-patch-type">
<option value="ips">IPS</option> <option value="ips">IPS</option>
<option value="bps">BPS</option>
<option value="ppf">PPF</option>
<option value="ups">UPS</option> <option value="ups">UPS</option>
<option value="aps">APS</option> <option value="aps">APS</option>
<option value="apsn64">APS (N64)</option> <option value="rup">RUP</option>
<!-- <option value="bps">BPS</option> -->
</select> </select>
</div> </div>
</div> </div>
<div class="buttons"> <div class="buttons">
<button class="with-dot" onclick="createPatchFile()">Create patch</button> <span id="message-create" class="message"></span>
<button id="button-create" class="disabled" disabled onclick="createPatch(romFile1, romFile2, el('select-patch-type').value)" data-localize="create_patch">Create patch</button>
</div> </div>
</div> </div>
</div> </div>
<div id="snackbar" class="closed"></div>
<!-- FOOTER --> <!-- FOOTER -->
<footer> <footer>
Rom Patcher JS <small>rev20180926</small> by <a href="/">Marc Robledo</a> Rom Patcher JS <small>v2.0 RC1</small> by <a href="/">Marc Robledo</a>
<br /> <br />
<i class="icon github"></i> <a href="https://github.com/marcrobledo/RomPatcher.js/" target="_blank">See on GitHub</a> <i class="icon github"></i> <a href="https://github.com/marcrobledo/RomPatcher.js/" target="_blank">See on GitHub</a>
<i class="icon heart"></i> <a href="https://www.paypal.me/marcrobledo/5" target="_blank" rel="nofollow">Donate</a> <i class="icon heart"></i> <a href="https://www.paypal.me/marcrobledo/5" target="_blank" rel="nofollow">Donate</a>

261
ips.js
View file

@ -1,32 +1,33 @@
/* IPS module for RomPatcher.js v20180925 - Marc Robledo 2016-2018 - http://www.marcrobledo.com/license */ /* IPS module for Rom Patcher JS v20180930 - Marc Robledo 2016-2018 - http://www.marcrobledo.com/license */
/* File format specification: http://www.smwiki.net/wiki/IPS_file_format */ /* File format specification: http://www.smwiki.net/wiki/IPS_file_format */
var MAX_IPS_SIZE=16777216;
var RECORD_RLE=0x0000;
var RECORD_SIMPLE=1; const IPS_MAGIC='PATCH';
var IPS_MAGIC='PATCH'; const IPS_MAX_SIZE=0x1000000; //16 megabytes
const IPS_RECORD_RLE=0x0000;
const IPS_RECORD_SIMPLE=0x01;
function IPS(){ function IPS(){
this.records=[]; this.records=[];
this.truncate=false; this.truncate=false;
} }
IPS.prototype.addSimpleRecord=function(o, d){ IPS.prototype.addSimpleRecord=function(o, d){
this.records.push({offset:o, type:RECORD_SIMPLE, data:d}) this.records.push({offset:o, type:IPS_RECORD_SIMPLE, length:d.length, data:d})
} }
IPS.prototype.addRLERecord=function(o, l, b){ IPS.prototype.addRLERecord=function(o, l, b){
this.records.push({offset:o, type:RECORD_RLE, length:l, byte:b}) this.records.push({offset:o, type:IPS_RECORD_RLE, length:l, byte:b})
} }
IPS.prototype.toString=function(){ IPS.prototype.toString=function(){
nSimpleRecords=0; nSimpleRecords=0;
nRLERecords=0; nRLERecords=0;
for(var i=0; i<this.records.length; i++){ for(var i=0; i<this.records.length; i++){
if(this.records[i].type===RECORD_RLE) if(this.records[i].type===IPS_RECORD_RLE)
nRLERecords++; nRLERecords++;
else else
nSimpleRecords++; nSimpleRecords++;
} }
var s=''; var s='Simple records: '+nSimpleRecords;
s+='\Simple records: '+nSimpleRecords;
s+='\nRLE records: '+nRLERecords; s+='\nRLE records: '+nRLERecords;
s+='\nTotal records: '+this.records.length; s+='\nTotal records: '+this.records.length;
if(this.truncate) if(this.truncate)
@ -34,77 +35,77 @@ IPS.prototype.toString=function(){
return s return s
} }
IPS.prototype.export=function(fileName){ IPS.prototype.export=function(fileName){
var binFileSize=0; var patchFileSize=5; //PATCH string
binFileSize+=5; //PATCH string
for(var i=0; i<this.records.length; i++){ for(var i=0; i<this.records.length; i++){
if(this.records[i].type===RECORD_RLE) if(this.records[i].type===IPS_RECORD_RLE)
binFileSize+=(3+2+2+1); //offset+0x0000+length+RLE byte to be written patchFileSize+=(3+2+2+1); //offset+0x0000+length+RLE byte to be written
else else
binFileSize+=(3+2+this.records[i].data.length); //offset+length+data patchFileSize+=(3+2+this.records[i].data.length); //offset+length+data
} }
binFileSize+=3; //EOF string patchFileSize+=3; //EOF string
if(this.truncate) if(this.truncate)
binFileSize+=3; //truncate patchFileSize+=3; //truncate
tempFile=new MarcBinFile(binFileSize); tempFile=new MarcFile(patchFileSize);
tempFile.littleEndian=false;
tempFile.fileName=fileName+'.ips'; tempFile.fileName=fileName+'.ips';
tempFile.writeString(0, 'PATCH', 5); tempFile.writeString(IPS_MAGIC);
var seek=5;
for(var i=0; i<this.records.length; i++){ for(var i=0; i<this.records.length; i++){
var rec=this.records[i]; var rec=this.records[i];
if(rec.type===RECORD_RLE){ tempFile.writeU24(rec.offset);
tempFile.writeThreeBytes(seek, rec.offset); if(rec.type===IPS_RECORD_RLE){
tempFile.writeShort(seek+3, 0x0000); tempFile.writeU16(0x0000);
tempFile.writeShort(seek+5, rec.length); tempFile.writeU16(rec.length);
tempFile.writeByte(seek+7, rec.byte); tempFile.writeU8(rec.byte);
seek+=3+2+2+1;
}else{ }else{
tempFile.writeThreeBytes(seek, rec.offset); tempFile.writeU16(rec.data.length);
tempFile.writeShort(seek+3, rec.data.length); tempFile.writeBytes(rec.data);
tempFile.writeBytes(seek+5, rec.data);
seek+=3+2+rec.data.length;
} }
} }
tempFile.writeString(seek, 'EOF', 3); tempFile.writeString('EOF');
seek+=3; if(rec.truncate)
if(rec.truncate){ tempFile.writeU24(rec.truncate);
tempFile.writeThreeBytes(seek, rec.truncate);
}
return tempFile return tempFile
} }
IPS.prototype.validateInput=function(romFile){return '?'}
IPS.prototype.apply=function(romFile){ IPS.prototype.apply=function(romFile){
var newFileSize=romFile.fileSize; if(this.truncate){
for(var i=0; i<this.records.length; i++){ tempFile=romFile.slice(0, this.truncate);
var rec=this.records[i]; }else{
if(rec.type===RECORD_RLE){
if(rec.offset+rec.length>newFileSize){ var newFileSize=romFile.fileSize;
newFileSize=rec.offset+rec.length; 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{ }else{
if(rec.offset+rec.data.length>newFileSize){ tempFile=new MarcFile(newFileSize);
newFileSize=rec.offset+rec.data.length; romFile.copyToFile(tempFile,0);
}
} }
} }
tempFile=new MarcBinFile(newFileSize);
var clonedFileSize=this.truncate || romFile.fileSize; romFile.seek(0);
for(var i=0; i<romFile.fileSize; i++)
tempFile.writeByte(i, romFile.readByte(i));
for(var i=0; i<this.records.length; i++){ for(var i=0; i<this.records.length; i++){
var rec=this.records[i]; tempFile.seek(this.records[i].offset);
if(rec.type===RECORD_RLE){ if(this.records[i].type===IPS_RECORD_RLE){
for(var j=0; j<rec.length; j++) for(var j=0; j<this.records[i].length; j++)
tempFile.writeByte(rec.offset+j, rec.byte); tempFile.writeU8(this.records[i].byte);
}else{ }else{
for(var j=0; j<rec.data.length; j++) tempFile.writeBytes(this.records[i].data);
tempFile.writeByte(rec.offset+j, rec.data[j]);
} }
} }
@ -114,101 +115,113 @@ IPS.prototype.apply=function(romFile){
function readIPSFile(file){ function parseIPSFile(file){
var patchFile=new IPS(); var patchFile=new IPS();
var EOF=false; file.seek(5);
var seek=5;
while(seek<file.fileSize){ while(!file.isEOF()){
var address=file.readThreeBytes(seek); var offset=file.readU24();
seek+=3;
if(!EOF && address===0x454f46){ /* EOF */ if(offset===0x454f46){ /* EOF */
EOF=true; if(file.isEOF()){
}else if(EOF){ break;
patchFile.truncate=address; }else if((file.offset+3)===file.fileSize){
}else{ patchFile.truncate=file.readU24();
var length=file.readShort(seek); break;
seek+=2;
if(length==RECORD_RLE){
patchFile.addRLERecord(address, file.readShort(seek), file.readByte(seek+2));
seek+=3;
}else{
patchFile.addSimpleRecord(address, file.readBytes(seek, length));
seek+=length;
} }
} }
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; return patchFile;
} }
function createIPSFromFiles(original, modified){ function createIPSFromFiles(original, modified){
tempFile=new IPS(); if(original.fileSize>IPS_MAX_SIZE || modified.fileSize>IPS_MAX_SIZE){
throw new Error('files are too big for IPS format')
}
var patch=new IPS();
if(modified.fileSize<original.fileSize){ if(modified.fileSize<original.fileSize){
tempFile.truncate=modified.fileSize; patch.truncate=modified.fileSize;
}else if(modified.fileSize>original.fileSize){
var originalTemp=new MarcBinFile(modified.fileSize);
originalTemp.writeBytes(0, original.readBytes(0, original.fileSize));
original=originalTemp;
} }
var seek=0; //solucion: guardar startOffset y endOffset (ir mirando de 6 en 6 hacia atrás)
while(seek<modified.fileSize){ var previousRecord={type:0xdeadbeef,startOffset:0,length:0};
var b1=original.readByte(seek); while(!modified.isEOF()){
var b2=modified.readByte(seek); var b1=original.isEOF()?0x00:original.readU8();
var b2=modified.readU8();
if(b1!==b2){ if(b1!==b2){
var RLERecord=true; var RLEmode=true;
var originalSeek=seek; var differentData=[];
var length=1; var startOffset=modified.offset-1;
/* find difference in next 6 bytes (in order to save space) */ while(b1!==b2 && differentData.length<0xffff){
/* force length to be 0xffff-6 bytes to keep IPS standard */ differentData.push(b2);
var nearbyDifference=true; if(b2!==differentData[0])
while(nearbyDifference && length<(0xffff-6)){ RLEmode=false;
if((seek+6)>=modified.fileSize){
var finalSeek=modified.fileSize-seek-1; if(modified.isEOF() || differentData.length===0xffff)
length+=finalSeek;
seek+=finalSeek;
break; break;
}
for(var i=6;i>0 && nearbyDifference;i--){ b1=original.isEOF()?0x00:original.readU8();
if(original.readByte(seek+i)!==modified.readByte(seek+i)){ b2=modified.readU8();
length+=i;
seek+=i;
break;
}else if(i==1){
nearbyDifference=false;
}
}
} }
var data=modified.readBytes(originalSeek, length);
/* check RLE record */
for(var i=1; i<length && RLERecord; i++){
if(data[i]!==data[0])
RLERecord=false;
}
if(RLERecord){ //check if this record is near the previous one
if(length<3){ var distance=startOffset-(previousRecord.offset+previousRecord.length);
tempFile.addSimpleRecord(originalSeek, data); 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{ }else{
tempFile.addRLERecord(originalSeek, length, data[0]); // 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;
} }
//tempFile.addRLERecord(originalSeek, length, data[0]);
}else{ }else{
tempFile.addSimpleRecord(originalSeek, data); if(RLEmode && differentData.length>2){
patch.addRLERecord(startOffset, differentData.length, differentData[0]);
}else{
patch.addSimpleRecord(startOffset, differentData);
}
previousRecord=patch.records[patch.records.length-1];
} }
seek=originalSeek+length;
}else{
seek++;
} }
} }
return tempFile
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
} }

77
locale.js Normal file
View file

@ -0,0 +1,77 @@
const LOCALIZATION={
'en':{
'creator_mode': 'Creator mode',
'apply_patch': 'Apply patch',
'rom_file': 'ROM file:',
'patch_file': 'Patch file:',
'remove_header': 'Remove header before patching:',
'add_header': 'Add temporary header:',
'compatible_formats': 'Compatible formats:',
'applying_patch': 'Applying patch...',
'downloading': 'Downloading...',
'create_patch': 'Create patch:',
'original_rom': 'Original ROM',
'modified_rom': 'Modified ROM:',
'patch_type': 'Patch type:',
'creating_patch': 'Creating patch...',
'error_crc_input': 'Invalid input ROM checksum',
'error_crc_output': 'Invalid output ROM checksum',
'error_crc_patch': 'Invalid patch checksum',
'error_downloading': 'Error downloading patch',
'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 antes de aplicar:',
'add_header': 'Añadir cabecera temporal:',
'compatible_formats': 'Formatos compatibles:',
'applying_patch': 'Aplicando parche...',
'downloading': 'Descargando...',
'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_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 pegat',
'rom_file': 'Arxiu ROM:',
'patch_file': 'Arxiu pegat:',
'remove_header': 'El·liminar capçalera abans d\'aplicar:',
'add_header': 'Afegir capçalera temporal:',
'compatible_formats': 'Formats compatibles:',
'applying_patch': 'Aplicant pegat...',
'downloading': 'Descarregant...',
'create_patch': 'Crear pegat',
'original_rom': 'ROM original:',
'modified_rom': 'ROM modificada:',
'patch_type': 'Tipus de pegat:',
'creating_patch': 'Creant pegat...',
'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 pegat no vàlida',
'error_downloading': 'Error descarregant pegat',
'error_invalid_patch': 'Arxiu de pegat no vàlid',
'warning_too_big': 'No es recomana usar arxius molt grans.'
}
};

245
ppf.js Normal file
View file

@ -0,0 +1,245 @@
/* PPF module for Rom Patcher JS v20190401 - Marc Robledo 2019 - 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;
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;
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;
}
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){
//unknown data?
tempFile.writeU8(0x00);
tempFile.writeU8(0x00);
tempFile.writeU8(0x00);
tempFile.writeU8(0x00);
}
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);
//tempFile.writeU32(0x00000000); //to-do: limited to 4GB right now
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);
}
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;
patchFile.skip(4);
}
if(patch.blockCheck){
patchFile.blockCheck=patchFile.readBytes(1024);
}
patchFile.littleEndian=true;
while(!patchFile.isEOF()){
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
}

324
rup.js Normal file
View file

@ -0,0 +1,324 @@
/* 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=this.validateSource(romFile);
if(validate && !validFile){
throw new Error('error_crc_input');
}
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
}

226
ups.js
View file

@ -1,7 +1,7 @@
/* UPS module for RomPatcher.js v20180428 - Marc Robledo 2017-2018 - http://www.marcrobledo.com/license */ /* 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/ */ /* File format specification: http://www.romhacking.net/documents/392/ */
var UPS_MAGIC='UPS1'; const UPS_MAGIC='UPS1';
function UPS(){ function UPS(){
this.records=[]; this.records=[];
@ -10,81 +10,76 @@ function UPS(){
this.checksumInput=0; this.checksumInput=0;
this.checksumOutput=0; this.checksumOutput=0;
} }
UPS.prototype.addRecord=function(o, d){ UPS.prototype.addRecord=function(relativeOffset, d){
this.records.push({offset:o, XORdata:d}) this.records.push({offset:relativeOffset, XORdata:d})
} }
UPS.prototype.toString=function(){ UPS.prototype.toString=function(){
var s='Records: '+this.records.length; var s='Records: '+this.records.length;
s+='\nInput file size: '+this.sizeInput; s+='\nInput file size: '+this.sizeInput;
s+='\nOutput file size: '+this.sizeOutput; s+='\nOutput file size: '+this.sizeOutput;
s+='\nInput file checksum: '+this.checksumInput; s+='\nInput file checksum: '+padZeroes(this.checksumInput,4);
s+='\nOutput file checksum: '+this.checksumOutput; s+='\nOutput file checksum: '+padZeroes(this.checksumOutput,4);
return s return s
} }
UPS.prototype.export=function(fileName){ UPS.prototype.export=function(fileName){
var encodedSizeInput=encodeVLV(this.sizeInput); var patchFileSize=UPS_MAGIC.length;//UPS1 string
var encodedSizeOutput=encodeVLV(this.sizeOutput); patchFileSize+=UPS_getVLVLength(this.sizeInput); //input file size
var encodedRecords=[]; patchFileSize+=UPS_getVLVLength(this.sizeOutput); //output file size
var binFileSize=0;
binFileSize+=UPS_MAGIC.length; //UPS1 string
binFileSize+=encodedSizeInput.length; //input file size
binFileSize+=encodedSizeOutput.length; //output file size
for(var i=0; i<this.records.length; i++){ for(var i=0; i<this.records.length; i++){
encodedRecords.push(encodeVLV(this.records[i].offset)); patchFileSize+=UPS_getVLVLength(this.records[i].offset);
binFileSize+=encodedRecords[i].length; patchFileSize+=this.records[i].XORdata.length+1;
binFileSize+=this.records[i].XORdata.length+1;
} }
binFileSize+=12; //input/output/patch checksums patchFileSize+=12; //input/output/patch checksums
tempFile=new MarcBinFile(binFileSize); tempFile=new MarcFile(patchFileSize);
tempFile.littleEndian=false; tempFile.writeVLV=UPS_writeVLV;
tempFile.fileName=fileName+'.ups'; tempFile.fileName=fileName+'.ups';
tempFile.writeString(0, UPS_MAGIC, UPS_MAGIC.length); tempFile.writeString(UPS_MAGIC);
tempFile.writeBytes(4, encodedSizeInput); tempFile.writeVLV(this.sizeInput);
tempFile.writeBytes(4+encodedSizeInput.length, encodedSizeOutput); tempFile.writeVLV(this.sizeOutput);
var seek=4+encodedSizeInput.length+encodedSizeOutput.length;
for(var i=0; i<this.records.length; i++){ for(var i=0; i<this.records.length; i++){
tempFile.writeBytes(seek, encodedRecords[i]); tempFile.writeVLV(this.records[i].offset);
seek+=encodedRecords[i].length; tempFile.writeBytes(this.records[i].XORdata);
tempFile.writeBytes(seek, this.records[i].XORdata); tempFile.writeU8(0x00);
seek+=this.records[i].XORdata.length;
tempFile.writeByte(seek, 0);
seek+=1;
} }
tempFile.littleEndian=true; tempFile.littleEndian=true;
tempFile.writeInt(seek, this.checksumInput); tempFile.writeU32(this.checksumInput);
tempFile.writeInt(seek+4, this.checksumOutput); tempFile.writeU32(this.checksumOutput);
tempFile.writeInt(seek+8, crc32(tempFile, true)); tempFile.writeU32(crc32(tempFile, 0, true));
return tempFile return tempFile
} }
UPS.prototype.validateSource=function(romFile){return crc32(romFile)===this.checksumInput} UPS.prototype.validateSource=function(romFile,headerSize){return crc32(romFile,headerSize)===this.checksumInput}
UPS.prototype.apply=function(romFile){ UPS.prototype.apply=function(romFile, validate){
if(!this.validateSource(romFile)){ if(validate && !this.validateSource(romFile)){
MarcDialogs.alert('Error: invalid input ROM checksum'); throw new Error('error_crc_input');
return false;
} }
tempFile=new MarcBinFile(this.sizeOutput);
/* copy original file */ /* copy original file */
for(var i=0; i<romFile.fileSize; i++) tempFile=new MarcFile(this.sizeOutput);
tempFile.writeByte(i, romFile.readByte(i)); romFile.copyToFile(tempFile, 0, this.sizeInput);
romFile.seek(0);
var nextOffset=0; var nextOffset=0;
for(var i=0; i<this.records.length; i++){ for(var i=0; i<this.records.length; i++){
var nextDifference=this.records[i]; var record=this.records[i];
nextOffset+=nextDifference.offset; tempFile.skip(record.offset);
for(var j=0; j<nextDifference.XORdata.length; j++){ romFile.skip(record.offset);
tempFile.writeByte(nextOffset+j, ((nextOffset+j)<romFile.fileSize?romFile.readByte(nextOffset+j):0x00) ^ nextDifference.XORdata[j]);
for(var j=0; j<record.XORdata.length; j++){
tempFile.writeU8((romFile.isEOF()?0x00:romFile.readU8()) ^ record.XORdata[j]);
} }
nextOffset+=nextDifference.XORdata.length+1; tempFile.skip(1);
romFile.skip(1);
} }
if(crc32(tempFile)!==this.checksumOutput){ if(validate && crc32(tempFile)!==this.checksumOutput){
MarcDialogs.alert('Warning: invalid output ROM checksum'); throw new Error('error_crc_output');
} }
return tempFile return tempFile
@ -92,115 +87,118 @@ UPS.prototype.apply=function(romFile){
/* encode/decode variable length values, used by UPS file structure */ /* encode/decode variable length values, used by UPS file structure */
function encodeVLV(offset){ function UPS_writeVLV(data){
var bytes=[];
while(1){ while(1){
var x=offset & 0x7f; var x=data & 0x7f;
offset=offset>>7; data=data>>7;
if(offset===0){ if(data===0){
bytes.push(0x80 | x); this.writeU8(0x80 | x);
break; break;
} }
bytes.push(x); this.writeU8(x);
offset=offset-1; data=data-1;
} }
return bytes;
} }
function decodeVLV(file, pos){ function UPS_readVLV(){
var offset=0; var data=0;
var size=0;
var shift=1; var shift=1;
while(1){ while(1){
var x=file.readByte(pos); var x=this.readU8();
pos++;
if(x==-1) if(x==-1)
console.error('corrupted UPS file?'); throw new Error('Can\'t read UPS VLV at 0x'+(this.offset-1).toString(16));
size++;
offset+=(x&0x7f)*shift; data+=(x&0x7f)*shift;
if((x&0x80)!==0) if((x&0x80)!==0)
break; break;
shift=shift<<7; shift=shift<<7;
offset+=shift; data+=shift;
} }
return {offset:offset, size:size} 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 readUPSFile(file){ function parseUPSFile(file){
var patchFile=new UPS(); var patch=new UPS();
file.readVLV=UPS_readVLV;
var decodedInputFilesize=decodeVLV(tempFile,4); file.seek(UPS_MAGIC.length);
patchFile.sizeInput=decodedInputFilesize.offset;
patch.sizeInput=file.readVLV();
patch.sizeOutput=file.readVLV();
var decodedOutputFilesize=decodeVLV(tempFile,4+decodedInputFilesize.size);
patchFile.sizeOutput=decodedOutputFilesize.offset;
var seek=4+decodedInputFilesize.size+decodedOutputFilesize.size;
var nextOffset=0; var nextOffset=0;
while(seek<(tempFile.fileSize-12)){ while(file.offset<(file.fileSize-12)){
var decodedOffset=decodeVLV(tempFile, seek); var relativeOffset=file.readVLV();
seek+=decodedOffset.size;
nextOffset+=decodedOffset.offset;
var bytes=[]; var XORdifferences=[];
var lastByte; while(file.readU8()){
while(lastByte=tempFile.readByte(seek)){ XORdifferences.push(file._lastRead);
bytes.push(lastByte);
seek++;
} }
seek++; patch.addRecord(relativeOffset, XORdifferences);
patchFile.addRecord(decodedOffset.offset, bytes);
} }
file.littleEndian=true; file.littleEndian=true;
patchFile.checksumInput=tempFile.readInt(seek); patch.checksumInput=file.readU32();
patchFile.checksumOutput=tempFile.readInt(seek+4); patch.checksumOutput=file.readU32();
if(tempFile.readInt(seek+8)!==crc32(file, true)){ if(file.readU32()!==crc32(file, 0, true)){
MarcDialogs.alert('Warning: invalid patch checksum'); throw new Error('error_crc_patch');
} }
return patchFile;
file.littleEndian=false;
return patch;
} }
function createUPSFromFiles(original, modified){ function createUPSFromFiles(original, modified){
tempFile=new UPS(); var patch=new UPS();
tempFile.sizeInput=original.fileSize; patch.sizeInput=original.fileSize;
tempFile.sizeOutput=modified.fileSize; patch.sizeOutput=modified.fileSize;
var seek=0;
var previousSeek=0; var previousSeek=1;
while(seek<modified.fileSize){ while(!modified.isEOF()){
var b1=seek>=original.fileSize?0x00:original.readByte(seek); var b1=original.isEOF()?0x00:original.readU8();
var b2=modified.readByte(seek); var b2=modified.readU8();
if(b1!==b2){ if(b1!==b2){
var currentSeek=seek; var currentSeek=modified.offset;
var differentBytes=[]; var XORdata=[];
while(b1!==b2){ while(b1!==b2){
differentBytes.push(b1 ^ b2); XORdata.push(b1 ^ b2);
seek++;
if(seek===modified.fileSize) if(modified.isEOF())
break; break;
b1=seek>=original.fileSize?0x00:original.readByte(seek); b1=original.isEOF()?0x00:original.readU8();
b2=modified.readByte(seek); b2=modified.readU8();
} }
var nextDifference=currentSeek-previousSeek; patch.addRecord(currentSeek-previousSeek, XORdata);
tempFile.addRecord(nextDifference, differentBytes); previousSeek=currentSeek+XORdata.length+1;
previousSeek=currentSeek+differentBytes.length+1;
seek++;
}else{
seek++;
} }
} }
tempFile.checksumInput=crc32(original); patch.checksumInput=crc32(original);
tempFile.checksumOutput=crc32(modified); patch.checksumOutput=crc32(modified);
return tempFile return patch
} }

374
vcdiff.js Normal file
View 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;
}
}

62
worker_apply.js Normal file
View file

@ -0,0 +1,62 @@
/* Rom Patcher JS v20181018 - Marc Robledo 2016-2018 - http://www.marcrobledo.com/license */
self.importScripts(
'./MarcFile.js',
'./crc.js',
'./ips.js',
'./aps.js',
'./ups.js',
'./bps.js',
'./rup.js',
'./ppf.js',
'./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 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(VCDIFF_MAGIC)){
patch=parseVCDIFF(patchFile);
}else{
throw new Error('error_invalid_patch');
}
//console.log('apply');
var patchedRom;
try{
patchedRom=patch.apply(romFile, event.data.validateChecksums);
}catch(e){
throw e;
}
//console.log('postMessage');
self.postMessage(
{
//romFileU8Array:event.data.romFileU8Array,
//patchFileU8Array:event.data.patchFileU8Array,
patchedRomU8Array:patchedRom._u8array
},
[
//event.data.romFileU8Array.buffer,
//event.data.patchFileU8Array.buffer,
patchedRom._u8array.buffer
]
);
};

23
worker_crc.js Normal file
View file

@ -0,0 +1,23 @@
/* Rom Patcher JS v20181018 - Marc Robledo 2016-2018 - 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
worker_create.js Normal file
View file

@ -0,0 +1,63 @@
/* Rom Patcher JS v20181018 - Marc Robledo 2016-2018 - http://www.marcrobledo.com/license */
self.importScripts(
'./MarcFile.js',
'./crc.js',
'./ips.js',
'./aps.js',
'./ups.js',
'./bps.js',
'./ppf.js',
'./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
]
);
};

973
xdelta.js
View file

@ -1,973 +0,0 @@
/* xdelta3 module for RomPatcher.js v20180918 - Marc Robledo 2016-2018 - http://www.marcrobledo.com/license */
var XDELTA_MAGIC=0xd6c3c400;
function XDelta(patchData){
this.patchData=patchData;
}
XDelta.prototype.toString=function(){
return ''
}
/* code borrowed from https://github.com/google/xdelta3-decoder-js/blob/master/tests/testA.html */
XDelta.prototype.apply=function(romFile){
var target;
try{
target=XDelta3Decoder.decode(this.patchData, new Uint8Array(romFile.fileReader.result));
}catch(e){
alert('EXCEPTION: ' + e.message);
return false;
}
var tempFile=new MarcBinFile(target.byteLength);
var targetUint8Array=new Uint8Array(target);
for(var i=0; i<target.byteLength; i++){
tempFile.writeByte(i, targetUint8Array[i]);
}
return tempFile
}
function readXDeltaFile(file){
return new XDelta(new Uint8Array(file.fileReader.result));
}
/**
* xdelta3 - delta compression tools and library
* Copyright 2016 Joshua MacDonald
*
* Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
* 2011, 2012, 2013, 2014, 2015 josh.macdonald@gmail.com
*
* 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
*
* 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.
*/
/**
* @fileoverview This file implements XDelta3 decoding.
* Note: the subroutine, method, field, and variable names do not follow
* Javascript style guide but reflect the names in the XDelta3 C++ files. This
* makes is to make it easier to keep this code in synch with the C++ code.
*
* The C++ code is very casual about initializing and accessing data structures.
* This code is a port and follows that code style.
*/
(function() {
// Check for namespace collision.
if ((typeof window['XDelta3Decoder'] != 'undefined')
|| (typeof window.XDelta3Decoder != 'undefined')) {
throw new Error('XDelta3Decoder already defined.');
}
/**
* The public class.
*/
window.XDelta3Decoder = function(debugOutput) { //
};
var XDelta3Decoder = window.XDelta3Decoder;
/**
* The public API to decode a delta possibly with a source.
* @param {!Uint8Array} delta The Xdelta delta file.
* @param {Uint8Array=} opt_source The source file (optional).
* @return {!ArrayBuffer}
*/
XDelta3Decoder.decode = function(delta, opt_source) {
if (typeof opt_source != 'object') {
opt_source = null;
}
var xdelta3 = new _XDelta3Decoder(delta, opt_source);
var uint8Bytes = xdelta3.xd3_decode_input();
return uint8Bytes.buffer;
}
/**
* The public API to disable debug printf code.
*/
// Define debug fallback routines if needed.
/**
* The XDelta3 data commands.
*/
/** @type {number} */
var XD3_NOOP = 0;
/** @type {number} */
var XD3_ADD = 1;
/** @type {number} */
var XD3_RUN = 2;
/** @type {number} */
var XD3_CPY = 3;
/** @type {number} */
var MIN_MATCH = 4;
/**
* Header indicator bits.
*/
/** @type {number} */
var VCD_SECONDARY = 1;
/** @type {number} */
var VCD_CODETABLE = 2;
/** @type {number} */
var VCD_APPHEADER = 4;
/** @type {number} */
var VCD_INVHDR = ~(VCD_SECONDARY | VCD_CODETABLE | VCD_APPHEADER);
var VCD_SOURCE = 0x01;
var VCD_TARGET = 0x02;
var VCD_ADLER32 = 0x04;
/**
* Declares the main decode class.
* @param {!Uint8Array} delta The Xdelta3 delta file.
* @param {Uint8Array=} opt_source The source file (optional).
* @constructor
*/
function _XDelta3Decoder(delta, opt_source) {
/** @type {!Uint8Array} */
this.delta = delta;
var source = opt_source || new Uint8Array(1);
/** @type {!DataObject} */
this.source = new DataObject(source);
/** @type {!xd3_source} */
this.src = new xd3_source();
/** @type {number} */
this.position = 0;
/** @type {number} */
this.dec_window_count = 0;
/** @type {number} */
this.dec_winstart = 0;
/**
* The length of the target window.
* @type {number}
*/
this.dec_tgtlen = 0;
/**
* The Alder32 checksum. This is used to verify the decoded bytes checksum
* matches the checksum of the original.
*/
this.dec_adler32 = 0;
/**
* First half instruction.
* @type {!xd3_hinst}
*/
this.dec_current1 = new xd3_hinst();
/**
* Second half instruction.
* @type {!xd3_hinst}
*/
this.dec_current2 = new xd3_hinst();
/** @type {!xd3_desect} */
this.data_sect = new xd3_desect();
/** @type {!xd3_desect} */
this.inst_sect = new xd3_desect();
/** @type {!xd3_desect} */
this.addr_sect = new xd3_desect();
/**
* The address cache.
* @type {!xd3_addr_cache}
*/
this.acache = new xd3_addr_cache(
__rfc3284_code_table_desc.near_modes,
__rfc3284_code_table_desc.same_modes);
}
/**
* Allocates the address caches.
*/
_XDelta3Decoder.prototype.xd3_alloc_cache = function() {
this.acache.near_array = null; // not sure this is needed
this.acache.same_array = null; // not sure this is needed
if (this.acache.s_near > 0) {
this.acache.near_array = allocArray(this.acache.s_near, 0);
}
if (this.acache.s_same > 0) {
this.acache.same_array = allocArray(this.acache.s_same * 256, 0);
}
};
/**
* Parses the delta file data and produces the targetWindow data.
* @return {!Uint8Array}
*/
_XDelta3Decoder.prototype.xd3_decode_input = function() {
if (this.delta[0] != 0xD6 || // 'V' with MSB set
this.delta[1] != 0xC3 || // 'C' with MSB set
this.delta[2] != 0xC4 || // 'D' with MSB set
this.delta[3] != 0) { // unused but be set to zero
throw new Error('XD3_INVALID_INPUT invalid magic');
}
this.position = 4;
this.dec_hdr_ind = this.delta[this.position++];
if (this.dec_hdr_ind & VCD_INVHDR) {
throw new Error('VCD_INVHDR unrecognized header indicator bits set');
}
if (this.dec_hdr_ind & VCD_SECONDARY) {
throw new Error('VCD_SECONDARY not implemented');
}
if (this.dec_hdr_ind & VCD_CODETABLE) {
throw new Error('VCD_CODETABLE support was removed');
} else {
/* Use the default table. */
this.acache.s_near = __rfc3284_code_table_desc.near_modes;
this.acache.s_same = __rfc3284_code_table_desc.same_modes;
this.code_table = xd3_rfc3284_code_table();
}
this.xd3_alloc_cache();
if (this.dec_hdr_ind & VCD_APPHEADER) {
this.dec_appheadsz = this.getInteger();
// Note: appHeader does not have a 0-termination.
this.dec_apphead = this.xd3_alloc(this.dec_appheadsz + 1);
this.xd3_decode_bytes(this.dec_apphead, 0, this.dec_appheadsz);
this.dec_apphead[this.dec_appheadsz + 1] = 0;
}
//var targetLength = 0;
while (true) {
if (this.position >= this.delta.length) {
break;
}
//targetLength +=
this.handleWindow();
}
return this.dec_buffer.bytes;
};
_XDelta3Decoder.prototype.xd3_decode_init_window = function() {
this.dec_cpylen = 0;
this.dec_cpyoff = 0;
// this.dec_cksumbytes = 0;
xd3_init_cache(this.acache);
}
_XDelta3Decoder.prototype.handleWindow = function() {
this.dec_win_ind = this.delta[this.position++]; // DEC_WININD
if (this.dec_win_ind & ~7) {
throw new Error('VCD_INVWIN unexpected bits set');
}
this.current_window = this.dec_window_count;
this.dec_winstart += this.dec_tgtlen;
this.xd3_decode_init_window();
var SRCORTGT = VCD_SOURCE | VCD_TARGET;
var srcortgt = SRCORTGT & this.dec_win_ind;
// If using a source or target data segment: read the lenght and position
// integers.
if (srcortgt) {
this.dec_cpylen = this.getInteger(); // DEC_CPYLEN
}
this.dec_position = this.dec_cpylen;
if (srcortgt) {
var sourcePosition = this.getInteger(); // DEC_CPYOFF
this.dec_cpyoff = sourcePosition;
}
this.dec_enclen = this.getInteger(); // DEC_ENCLEN
// Calculate the position if the delta was actually read.
// var positionAfterDelta = this.position + this.dec_enclen;
// Get the target window length.
this.dec_tgtlen = this.getInteger(); // DEC_TGTLEN
this.dec_del_ind = this.getByte(); // DEC_DELIND
this.data_sect.size = this.getInteger(); // DEC_DATALEN
this.inst_sect.size = this.getInteger(); // DEC_INSTLEN
this.addr_sect.size = this.getInteger(); // DEC_ADDRLEN
if (this.dec_win_ind & VCD_ADLER32) { // DEC_CKSUM
this.dec_cksum = this.xd3_decode_allocate(4);
for (var i = 0; i < 4; i += 1) {
this.dec_adler32 = (this.dec_adler32 << 8) | this.dec_cksum[i];
}
}
this.xd3_decode_sections();
/* In the C++ code:
* To speed VCD_SOURCE block-address calculations, the source
* cpyoff_blocks and cpyoff_blkoff are pre-computed.
* However, in this Javascript code there is no 'blocks'.
*/
if (this.dec_win_ind & VCD_SOURCE) {
this.src.cpyoff_blkoff = this.dec_cpyoff;
}
this.xd3_decode_emit();
return this.dec_tgtlen;
};
/**
* This function only has code if the preprocessor statement
* "#if SECONDARY_ANY" is set. SECONDARY_ANY does not seem to be set.
*/
_XDelta3Decoder.prototype.xd3_decode_secondary_sections = function() { //
};
/**
* @param {!xd3_desect} sect
*/
_XDelta3Decoder.prototype.xd3_decode_section = function(sect) {
// It is possible to just point into the buffer but perhaps that can be done
// later.
sect.bytes = this.xd3_decode_allocate(sect.size);
};
_XDelta3Decoder.prototype.xd3_decode_sections = function() {
this.xd3_decode_section(this.data_sect);
this.xd3_decode_section(this.inst_sect);
this.xd3_decode_section(this.addr_sect);
this.xd3_decode_secondary_sections();
this.xd3_decode_setup_buffers();
};
_XDelta3Decoder.prototype.xd3_decode_setup_buffers = function() {
this.dec_buffer = new DataObject(new Uint8Array(this.dec_tgtlen));
};
var VCD_SELF = 0;
var VCD_HERE = 1;
/**
* xd3_decode_address
* @param {number} here
* @param {number} mode
* @param {!xd3_desect} sect
*/
_XDelta3Decoder.prototype.xd3_decode_address = function(here, mode, sect) {
var val;
var same_start = 2 + this.acache.s_near;
if (mode < same_start) {
val = sect.getInteger();
switch (mode) {
case VCD_SELF:
break;
case VCD_HERE:
// var old_val = val;
val = here - val;
break;
default:
val += this.acache.near_array[mode - 2];
}
} else {
mode -= same_start;
var offset = sect.getByte();
val = this.acache.same_array[mode * 256 + offset];
}
this.xd3_update_cache(this.acache, val);
return val;
};
/**
* @param {!xd3_addr_cache} acache
* @param {number} addr
*/
_XDelta3Decoder.prototype.xd3_update_cache = function(acache, addr) {
if (acache.s_near > 0) {
acache.near_array[acache.next_slot] = addr;
acache.next_slot = (acache.next_slot + 1) % acache.s_near;
}
if (acache.s_same > 0) {
acache.same_array[addr % (acache.s_same * 256)] = addr;
}
};
/**
* @param {!xd3_hinst} inst
*/
_XDelta3Decoder.prototype.xd3_decode_output_halfinst = function(inst) {
var take = inst.size;
var blkoff;
switch (inst.type) {
case XD3_RUN:
var val = this.data_sect.getByte();
this.dec_buffer.fill(val, take);
break;
case XD3_ADD:
this.dec_buffer.copySect(this.data_sect, take);
break;
default:
var overlap;
var overlap_pos;
if (inst.addr < this.dec_cpylen) {
overlap = 0;
if (this.dec_win_ind & VCD_TARGET) {
throw new Error('VCD_TARGET not supported');
} else {
blkoff = this.src.cpyoff_blkoff;
blkoff = this.dec_cpyoff + inst.addr;
}
} else {
overlap = 1;
overlap_pos = inst.addr - this.dec_cpylen;
}
if (overlap) {
this.dec_buffer.copyBytes(this.dec_buffer.bytes, overlap_pos, take);
} else {
this.dec_buffer.copyBytes(this.source.bytes, blkoff, take);
}
}
};
/**
* xref: xd3_decode_parse_halfinst
* @param {!xd3_hinst} inst
*/
_XDelta3Decoder.prototype.xd3_decode_parse_halfinst = function(inst) {
// Get size and address if necessary.
if (inst.size == 0) {
inst.size = this.inst_sect.getInteger();
}
/* For copy instructions, read address. */
if (inst.type >= XD3_CPY) {
var mode = inst.type - XD3_CPY;
inst.addr =
this.xd3_decode_address(this.dec_position, mode, this.addr_sect);
}
this.dec_position += inst.size;
};
/**
* xref: xd3_decode_instruction
*/
_XDelta3Decoder.prototype.xd3_decode_instruction = function() {
var code_table = this.code_table;
var instPair = this.inst_sect.getByte();
this.dec_current1.type = code_table.tableRows[instPair].type1;
this.dec_current1.size = code_table.tableRows[instPair].size1;
// dec_current1.addr keeps it previous value.
this.dec_current2.type = code_table.tableRows[instPair].type2;
this.dec_current2.size = code_table.tableRows[instPair].size2;
// dec_current2.addr keeps it previous value.
/* For each instruction with a real operation, decode the
* corresponding size and addresses if necessary. Assume a
* code-table may have NOOP in either position, although this is
* unlikely. */
if (this.dec_current1.type != XD3_NOOP) {
this.xd3_decode_parse_halfinst(this.dec_current1);
}
if (this.dec_current2.type != XD3_NOOP) {
this.xd3_decode_parse_halfinst(this.dec_current2);
}
};
_XDelta3Decoder.prototype.xd3_decode_finish_window = function() {
// stream->dec_winbytes = 0;
// stream->dec_state = DEC_FINISH;
this.data_sect.pos = 0;
this.inst_sect.pos = 0;
this.addr_sect.pos = 0;
};
_XDelta3Decoder.prototype.xd3_decode_emit = function() {
var instLength = this.inst_sect.bytes.byteLength;
/* Decode next instruction pair. */
while (this.inst_sect.pos < instLength) {
this.xd3_decode_instruction();
/* Output dec_current1 */
if (this.dec_current1.type != XD3_NOOP) {
this.xd3_decode_output_halfinst(this.dec_current1);
}
/* Output dec_current2 */
if (this.dec_current2.type != XD3_NOOP) {
this.xd3_decode_output_halfinst(this.dec_current2);
}
}
if (this.dec_win_ind & VCD_ADLER32) {
var a32 = adler32(1, this.dec_buffer.bytes, 0, this.dec_tgtlen);
if (a32 != this.dec_adler32) {
throw new Error('target window checksum mismatch');
}
}
/* Finished with a window. */
this.xd3_decode_finish_window();
};
_XDelta3Decoder.prototype.xd3_alloc = function(length) {
return new Uint8Array(length);
};
_XDelta3Decoder.prototype.xd3_decode_bytes = function(bytes, pos, length) {
for (var i = 0; i < length; i++) {
bytes[pos + i] = this.delta[this.position++];
}
};
_XDelta3Decoder.prototype.xd3_decode_allocate = function(length) {
var bytes =
new Uint8Array(this.delta.slice(this.position, this.position + length));
this.position += length;
return bytes;
};
_XDelta3Decoder.prototype.getByte = function() {
return this.delta[this.position++];
};
_XDelta3Decoder.prototype.getInteger = function() {
var maxBytes = Math.min(20, this.delta.length - this.position);
var integer = 0;
for (var i = 0; i < maxBytes; i++) {
var aPart = this.delta[this.position++];
integer += aPart & 0x7F;
if (!(aPart & 0x80)) {
return integer;
}
integer <<= 7;
}
throw new Error('delta integer too long');
};
/**
* The code table.
* @param {!Array<xd3_dinst>} tableRows
* @constructor
* @struct
*/
var xd3_dinst_table = function(tableRows) {
/** @type {!Array<xd3_dinst>} */
this.tableRows = tableRows;
};
/**
* xd3_hinst
* @constructor
*/
function xd3_hinst() {
this.type = XD3_NOOP;
this.size = 0;
this.addr = 0;
}
/**
* The code-table double instruction.
* @constructor
*/
function xd3_dinst() {
/** @type {number} */
this.type1 = XD3_NOOP;
/** @type {number} */
this.size1 = 0;
/** @type {number} */
this.type2 = XD3_NOOP;
/** @type {number} */
this.size2 = 0;
}
/**
* @param {!xd3_code_table_desc} desc
* @return {!xd3_dinst_table}
*/
function xd3_build_code_table(desc) {
var row = 0;
var tableRows = new Array(256);
for (var i = 0; i < 256; i++) {
tableRows[i] = new xd3_dinst();
}
var cpyModes = 2 + desc.near_modes + desc.same_modes;
// The single RUN command.
tableRows[row++].type1 = XD3_RUN;
// The ADD only commands.
tableRows[row++].type1 = XD3_ADD;
for (var size1 = 1; size1 <= desc.add_sizes; size1++) {
tableRows[row].type1 = XD3_ADD;
tableRows[row++].size1 = size1;
}
// The Copy only commands.
for (var mode = 0; mode < cpyModes; mode++) {
tableRows[row++].type1 = XD3_CPY + mode;
for (var size1 = MIN_MATCH; size1 < MIN_MATCH + desc.cpy_sizes; size1++) {
tableRows[row].type1 = XD3_CPY + mode;
tableRows[row++].size1 = size1;
}
}
// The Add/Copy commands.
for (var mode = 0; mode < cpyModes; mode++) {
for (var size1 = 1; size1 <= desc.addcopy_add_max; size1++) {
var max = (mode < 2 + desc.near_modes) ? //
desc.addcopy_near_cpy_max :
desc.addcopy_same_cpy_max;
for (var size2 = MIN_MATCH; size2 <= max; size2++) {
tableRows[row].type1 = XD3_ADD;
tableRows[row].size1 = size1;
tableRows[row].type2 = XD3_CPY + mode;
tableRows[row++].size2 = size2;
}
}
}
// The Copy/Add commands.
for (var mode = 0; mode < cpyModes; mode++) {
var max = (mode < 2 + desc.near_modes) ? //
desc.copyadd_near_cpy_max :
desc.copyadd_same_cpy_max;
for (var size1 = MIN_MATCH; size1 <= max; size1++) {
for (var size2 = 1; size2 <= desc.copyadd_add_max; size2++) {
tableRows[row].type1 = XD3_CPY + mode;
tableRows[row].size1 = size1;
tableRows[row].type2 = XD3_ADD;
tableRows[row++].size2 = size2;
}
}
}
return new xd3_dinst_table(tableRows);
}
/**
* @constructor
*/
function xd3_code_table_desc() {
this.add_sizes = 0;
this.near_modes = 0;
this.same_modes = 0;
this.cpy_sizes = 0;
this.addcopy_add_max = 0;
this.addcopy_near_cpy_max = 0;
this.addcopy_same_cpy_max = 0;
this.copyadd_add_max = 0;
this.copyadd_near_cpy_max = 0;
this.copyadd_same_cpy_max = 0;
}
/**
* This builds the __rfc3284_code_table_desc
* Assumes a single RUN instruction
* Assumes that MIN_MATCH is 4.
* @return {!xd3_code_table_desc}
*/
function build_rfc3284_code_table_desc() {
var desc = new xd3_code_table_desc();
desc.add_sizes = 17;
desc.near_modes = 4;
desc.same_modes = 3;
desc.cpy_sizes = 15;
desc.addcopy_add_max = 4;
desc.addcopy_near_cpy_max = 6;
desc.addcopy_same_cpy_max = 4;
desc.copyadd_add_max = 1;
desc.copyadd_near_cpy_max = 4;
desc.copyadd_same_cpy_max = 4;
// xd3_code_table_sizes addcopy_max_sizes[MAX_MODES];
// { {6,163,3},{6,175,3},{6,187,3},{6,199,3},{6,211,3},{6,223,3},
// {4,235,1},{4,239,1},{4,243,1} },
// xd3_code_table_sizes copyadd_max_sizes[MAX_MODES];
// { {4,247,1},{4,248,1},{4,249,1},{4,250,1},{4,251,1},{4,252,1},
// {4,253,1},{4,254,1},{4,255,1} },
return desc;
}
var __rfc3284_code_table_desc = build_rfc3284_code_table_desc();
var A32_BASE = 65521; /* Largest prime smaller than 2^16 */
var A32_NMAX = 5552; /* NMAX is the largest n such that 255n(n+1)/2
+ (n+1)(BASE-1) <= 2^32-1 */
// 1140 #define A32_DO1(buf,i) {s1 += buf[i]; s2 += s1;}
// 1141 #define A32_DO2(buf,i) A32_DO1(buf,i); A32_DO1(buf,i+1);
// 1142 #define A32_DO4(buf,i) A32_DO2(buf,i); A32_DO2(buf,i+2);
// 1143 #define A32_DO8(buf,i) A32_DO4(buf,i); A32_DO4(buf,i+4);
// 1144 #define A32_DO16(buf) A32_DO8(buf,0); A32_DO8(buf,8);
/**
* Calculated the Adler32 checksum.
* @param {number} adler I'm not sure what this is.
* @param {!Uint8Array} buf
* @param {number} pos
* @param {number} len
* @return {number}
*/
function adler32(adler, buf, pos, len) {
var s1 = adler & 0xffff;
var s2 = (adler >> 16) & 0xffff;
var k;
while (len > 0) {
k = (len < A32_NMAX) ? len : A32_NMAX;
len -= k;
if (k != 0) {
do {
s1 += buf[pos++];
s2 += s1;
} while (--k);
}
s1 %= A32_BASE;
s2 %= A32_BASE;
}
return (s2 << 16) | s1;
}
/**
* @constructor
*/
function xd3_addr_cache(s_near, s_same) {
this.s_near = s_near;
this.s_same = s_same;
this.next_slot = 0; /* the circular index for near */
this.near_array = null; /* array of size s_near */
this.same_array = null; /* array of size s_same*256 */
}
/**
* @param {!xd3_addr_cache} acache
*/
function xd3_init_cache(acache) {
if (acache.s_near > 0) {
for (var i = 0; i < acache.near_array.length; i++) {
acache.near_array[i] = 0;
}
acache.next_slot = 0;
}
if (acache.s_same > 0) {
for (var i = 0; i < acache.same_array.length; i++) {
acache.same_array[i] = 0;
}
}
}
/**
* Used by the decoder to buffer input in sections.
* XDelta3 C++ struct.
* @constructor
* @struct
*/
function xd3_desect() {
/**
* The buffer as a slice of the backingBuffer;
* @type {?Uint8Array}
*/
this.bytes = null;
/** @type {number} */
this.size = 0;
/** @type {number} */
this.pos = 0;
}
/**
* Gets a byte from the section.
* @return {number}
*/
xd3_desect.prototype.getByte = function() {
if (!this.bytes) {
throw new Error('bytes not set');
}
return this.bytes[this.pos++];
};
/**
* Gets an integer from the section.
* XDelta3 integers are encodes as a variable number of 7 bit bytes. Bit 8, the
* most significant bit is used to indicate more bytes needed.
* @return {number}
*/
xd3_desect.prototype.getInteger = function() {
if (!this.bytes) {
throw new Error('bytes not set');
}
var val = 0;
for (var i = 0; i < 10; i++) {
var aByte = this.bytes[this.pos++];
val += aByte & 0x7F;
if (!(aByte & 0x80)) {
return val;
}
val <<= 7;
}
throw new Error('invalid number');
};
/**
* Builds a default code table.
* @return {!xd3_dinst_table}
*/
function xd3_rfc3284_code_table() {
return xd3_build_code_table(__rfc3284_code_table_desc);
}
/**
* Allocates and initializes a Javascript Array.
* @return {!Array<number>}
*/
function allocArray(len, val) {
var arr = new Array(len);
for (var i = 0; i < len; i++) {
arr[i] = val;
}
return arr;
}
/**
* @constructor
*/
function xd3_source() {
/** @type {number} */
this.cpyoff_blkoff = -1;
}
/**
* @param {!Uint8Array} bytes
* @constructor
*/
function DataObject(bytes) {
this.pos = 0;
this.bytes = bytes;
};
DataObject.prototype.getByte = function() {
return this.bytes[this.pos++];
};
DataObject.prototype.getInteger = function() {
var val = 0;
for (var i = 0; i < 10; i++) {
var aByte = this.bytes[this.pos++];
val += aByte & 0x7F;
if (!(aByte & 0x80)) {
return val;
}
val <<= 7;
}
throw new Error('invalid number');
};
DataObject.prototype.fill = function(val, length) {
// TODO(bstell): see if there is a function for this.
for (var i = 0; i < length; i++) {
this.bytes[this.pos++] = val;
}
};
/**
* @param {!xd3_desect} sect
* @param {number} length
*/
DataObject.prototype.copySect = function(sect, length) {
// TODO(bstell): see if there is a function for this.
for (var i = 0; i < length; i++) {
this.bytes[this.pos++] = sect.bytes[sect.pos++];
}
};
DataObject.prototype.copyBytes = function(bytes, offset, length) {
// TODO(bstell): see if there is a function for this.
for (var i = 0; i < length; i++) {
this.bytes[this.pos++] = bytes[offset++];
}
};
})();