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:
parent
9d85db3baa
commit
e7d2bccc47
19 changed files with 3016 additions and 1750 deletions
266
MarcFile.js
Normal file
266
MarcFile.js
Normal file
|
@ -0,0 +1,266 @@
|
|||
/* MODDED VERSION OF MarcFile.js v20181020 - Marc Robledo 2014-2018 - http://www.marcrobledo.com/license */
|
||||
|
||||
function MarcFile(source, onLoad){
|
||||
if(typeof source==='object' && source.files) /* get first file only if source is input with multiple files */
|
||||
source=source.files[0];
|
||||
|
||||
this.littleEndian=false;
|
||||
this.offset=0;
|
||||
this._lastRead=null;
|
||||
|
||||
if(typeof source==='object' && source.name && source.size){ /* source is file */
|
||||
if(typeof window.FileReader!=='function')
|
||||
throw new Error('Incompatible Browser');
|
||||
|
||||
this.fileName=source.name;
|
||||
this.fileType=source.type;
|
||||
this.fileSize=source.size;
|
||||
|
||||
this._fileReader=new FileReader();
|
||||
this._fileReader.marcFile=this;
|
||||
this._fileReader.addEventListener('load',function(){
|
||||
this.marcFile._u8array=new Uint8Array(this.result);
|
||||
this.marcFile._dataView=new DataView(this.result);
|
||||
|
||||
if(onLoad)
|
||||
onLoad.call();
|
||||
},false);
|
||||
|
||||
this._fileReader.readAsArrayBuffer(source);
|
||||
|
||||
|
||||
|
||||
}else if(typeof source==='object' && typeof source.fileName==='string' && typeof source.littleEndian==='boolean'){ /* source is MarcFile */
|
||||
this.fileName=source.fileName;
|
||||
this.fileType=source.fileType;
|
||||
this.fileSize=source.fileSize;
|
||||
|
||||
var ab=new ArrayBuffer(source);
|
||||
this._u8array=new Uint8Array(this.fileType);
|
||||
this._dataView=new DataView(this.fileType);
|
||||
|
||||
source.copyToFile(this, 0);
|
||||
if(onLoad)
|
||||
onLoad.call();
|
||||
|
||||
|
||||
|
||||
}else if(typeof source==='object' && typeof source.byteLength==='number'){ /* source is ArrayBuffer or TypedArray */
|
||||
this.fileName='file.bin';
|
||||
this.fileType='application/octet-stream';
|
||||
this.fileSize=source.byteLength;
|
||||
|
||||
if(typeof source.buffer !== 'undefined')
|
||||
source=source.buffer;
|
||||
this._u8array=new Uint8Array(source);
|
||||
this._dataView=new DataView(source);
|
||||
|
||||
if(onLoad)
|
||||
onLoad.call();
|
||||
|
||||
|
||||
|
||||
}else if(typeof source==='number'){ /* source is integer (new empty file) */
|
||||
this.fileName='file.bin';
|
||||
this.fileType='application/octet-stream';
|
||||
this.fileSize=source;
|
||||
|
||||
var ab=new ArrayBuffer(source);
|
||||
this._u8array=new Uint8Array(ab);
|
||||
this._dataView=new DataView(ab);
|
||||
|
||||
if(onLoad)
|
||||
onLoad.call();
|
||||
}else{
|
||||
throw new Error('Invalid source');
|
||||
}
|
||||
}
|
||||
MarcFile.IS_MACHINE_LITTLE_ENDIAN=(function(){ /* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView#Endianness */
|
||||
var buffer=new ArrayBuffer(2);
|
||||
new DataView(buffer).setInt16(0, 256, true /* littleEndian */);
|
||||
// Int16Array uses the platform's endianness.
|
||||
return new Int16Array(buffer)[0] === 256;
|
||||
})();
|
||||
|
||||
|
||||
|
||||
MarcFile.prototype.seek=function(offset){
|
||||
this.offset=offset;
|
||||
}
|
||||
MarcFile.prototype.skip=function(nBytes){
|
||||
this.offset+=nBytes;
|
||||
}
|
||||
MarcFile.prototype.isEOF=function(){
|
||||
return !(this.offset<this.fileSize)
|
||||
}
|
||||
|
||||
MarcFile.prototype.slice=function(offset, len){
|
||||
len=len || (this.fileSize-offset);
|
||||
|
||||
var newFile;
|
||||
|
||||
if(typeof this._u8array.buffer.slice!=='undefined'){
|
||||
newFile=new MarcFile(0);
|
||||
newFile.fileSize=len;
|
||||
newFile._u8array=new Uint8Array(this._u8array.buffer.slice(offset, offset+len));
|
||||
}else{
|
||||
newFile=new MarcFile(len);
|
||||
this.copyToFile(newFile, offset, len, 0);
|
||||
}
|
||||
newFile.fileName=this.fileName;
|
||||
newFile.fileType=this.fileType;
|
||||
newFile.littleEndian=this.littleEndian;
|
||||
return newFile;
|
||||
}
|
||||
|
||||
|
||||
MarcFile.prototype.copyToFile=function(target, offsetSource, len, offsetTarget){
|
||||
if(typeof offsetTarget==='undefined')
|
||||
offsetTarget=offsetSource;
|
||||
|
||||
len=len || (this.fileSize-offsetSource);
|
||||
|
||||
for(var i=0; i<len; i++){
|
||||
target._u8array[offsetTarget+i]=this._u8array[offsetSource+i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MarcFile.prototype.save=function(){
|
||||
var blob;
|
||||
try{
|
||||
blob=new Blob([this._u8array],{type:this.fileType});
|
||||
}catch(e){
|
||||
//old browser, use BlobBuilder
|
||||
window.BlobBuilder=window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder;
|
||||
if(e.name==='InvalidStateError' && window.BlobBuilder){
|
||||
var bb=new BlobBuilder();
|
||||
bb.append(this._u8array.buffer);
|
||||
blob=bb.getBlob(this.fileType);
|
||||
}else{
|
||||
throw new Error('Incompatible Browser');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
saveAs(blob,this.fileName);
|
||||
}
|
||||
|
||||
|
||||
MarcFile.prototype.readU8=function(){
|
||||
this._lastRead=this._u8array[this.offset];
|
||||
|
||||
this.offset++;
|
||||
return this._lastRead
|
||||
}
|
||||
MarcFile.prototype.readU16=function(){
|
||||
if(this.littleEndian)
|
||||
this._lastRead=this._u8array[this.offset] + (this._u8array[this.offset+1] << 8);
|
||||
else
|
||||
this._lastRead=(this._u8array[this.offset] << 8) + this._u8array[this.offset+1];
|
||||
|
||||
this.offset+=2;
|
||||
return this._lastRead >>> 0
|
||||
}
|
||||
MarcFile.prototype.readU24=function(){
|
||||
if(this.littleEndian)
|
||||
this._lastRead=this._u8array[this.offset] + (this._u8array[this.offset+1] << 8) + (this._u8array[this.offset+2] << 16);
|
||||
else
|
||||
this._lastRead=(this._u8array[this.offset] << 16) + (this._u8array[this.offset+1] << 8) + this._u8array[this.offset+2];
|
||||
|
||||
this.offset+=3;
|
||||
return this._lastRead >>> 0
|
||||
}
|
||||
MarcFile.prototype.readU32=function(){
|
||||
if(this.littleEndian)
|
||||
this._lastRead=this._u8array[this.offset] + (this._u8array[this.offset+1] << 8) + (this._u8array[this.offset+2] << 16) + (this._u8array[this.offset+3] << 24);
|
||||
else
|
||||
this._lastRead=(this._u8array[this.offset] << 24) + (this._u8array[this.offset+1] << 16) + (this._u8array[this.offset+2] << 8) + this._u8array[this.offset+3];
|
||||
|
||||
this.offset+=4;
|
||||
return this._lastRead >>> 0
|
||||
}
|
||||
|
||||
|
||||
|
||||
MarcFile.prototype.readBytes=function(len){
|
||||
this._lastRead=new Array(len);
|
||||
for(var i=0; i<len; i++){
|
||||
this._lastRead[i]=this._u8array[this.offset+i];
|
||||
}
|
||||
|
||||
this.offset+=len;
|
||||
return this._lastRead
|
||||
}
|
||||
|
||||
MarcFile.prototype.readString=function(len){
|
||||
this._lastRead='';
|
||||
for(var i=0;i<len && (this.offset+i)<this.fileSize && this._u8array[this.offset+i]>0;i++)
|
||||
this._lastRead=this._lastRead+String.fromCharCode(this._u8array[this.offset+i]);
|
||||
|
||||
this.offset+=len;
|
||||
return this._lastRead
|
||||
}
|
||||
|
||||
MarcFile.prototype.writeU8=function(u8){
|
||||
this._u8array[this.offset]=u8;
|
||||
|
||||
this.offset++;
|
||||
}
|
||||
MarcFile.prototype.writeU16=function(u16){
|
||||
if(this.littleEndian){
|
||||
this._u8array[this.offset]=u16 & 0xff;
|
||||
this._u8array[this.offset+1]=u16 >> 8;
|
||||
}else{
|
||||
this._u8array[this.offset]=u16 >> 8;
|
||||
this._u8array[this.offset+1]=u16 & 0xff;
|
||||
}
|
||||
|
||||
this.offset+=2;
|
||||
}
|
||||
MarcFile.prototype.writeU24=function(u24){
|
||||
if(this.littleEndian){
|
||||
this._u8array[this.offset]=u24 & 0x0000ff;
|
||||
this._u8array[this.offset+1]=(u24 & 0x00ff00) >> 8;
|
||||
this._u8array[this.offset+2]=(u24 & 0xff0000) >> 16;
|
||||
}else{
|
||||
this._u8array[this.offset]=(u24 & 0xff0000) >> 16;
|
||||
this._u8array[this.offset+1]=(u24 & 0x00ff00) >> 8;
|
||||
this._u8array[this.offset+2]=u24 & 0x0000ff;
|
||||
}
|
||||
|
||||
this.offset+=3;
|
||||
}
|
||||
MarcFile.prototype.writeU32=function(u32){
|
||||
if(this.littleEndian){
|
||||
this._u8array[this.offset]=u32 & 0x000000ff;
|
||||
this._u8array[this.offset+1]=(u32 & 0x0000ff00) >> 8;
|
||||
this._u8array[this.offset+2]=(u32 & 0x00ff0000) >> 16;
|
||||
this._u8array[this.offset+3]=(u32 & 0xff000000) >> 24;
|
||||
}else{
|
||||
this._u8array[this.offset]=(u32 & 0xff000000) >> 24;
|
||||
this._u8array[this.offset+1]=(u32 & 0x00ff0000) >> 16;
|
||||
this._u8array[this.offset+2]=(u32 & 0x0000ff00) >> 8;
|
||||
this._u8array[this.offset+3]=u32 & 0x000000ff;
|
||||
}
|
||||
|
||||
this.offset+=4;
|
||||
}
|
||||
|
||||
|
||||
MarcFile.prototype.writeBytes=function(a){
|
||||
for(var i=0;i<a.length;i++)
|
||||
this._u8array[this.offset+i]=a[i]
|
||||
|
||||
this.offset+=a.length;
|
||||
}
|
||||
|
||||
MarcFile.prototype.writeString=function(str,len){
|
||||
len=len || str.length;
|
||||
for(var i=0;i<str.length && i<len;i++)
|
||||
this._u8array[this.offset+i]=str.charCodeAt(i);
|
||||
|
||||
for(;i<len;i++)
|
||||
this._u8array[this.offset+i]=0x00;
|
||||
|
||||
this.offset+=len;
|
||||
}
|
|
@ -7,8 +7,11 @@ A ROM patcher made in HTML5.
|
|||
* UPS
|
||||
* APS (N64)
|
||||
* BPS
|
||||
* RUP
|
||||
* VCDiff (xdelta)
|
||||
* PPF
|
||||
* can patch and create patches
|
||||
* shows ROM CRC32, MD5 and SHA-1 before patching
|
||||
* can remove SNES headers before patching
|
||||
* can remove headers before patching
|
||||
* made in Vanilla JS
|
||||
* can be run in any modern web browser, including mobile
|
||||
|
|
135
RomPatcher.css
135
RomPatcher.css
|
@ -43,8 +43,8 @@ footer{padding: 50px 0 20px}
|
|||
align-items:center; /* vertical align */
|
||||
justify-content:space-between
|
||||
}
|
||||
.leftcol{width:35%;text-align:right}
|
||||
.rightcol{width:63%}
|
||||
.leftcol{width:28%;text-align:right}
|
||||
.rightcol{width:70%}
|
||||
.leftcol,.rightcol{margin-bottom:8px}
|
||||
|
||||
|
||||
|
@ -52,15 +52,7 @@ footer{padding: 50px 0 20px}
|
|||
|
||||
|
||||
/* 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{
|
||||
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}
|
||||
|
||||
/* tabs */
|
||||
#tabs div{
|
||||
width:50%;
|
||||
/* Switch mode */
|
||||
#switch-container{
|
||||
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;
|
||||
text-align:center;
|
||||
box-sizing:border-box;
|
||||
padding: 10px;
|
||||
color:#646871;
|
||||
border-radius: 2px 2px 0 0;
|
||||
transition: .15s all;
|
||||
border-top: 3px solid transparent;
|
||||
vertical-align:middle;
|
||||
width:30px;height:16px;
|
||||
border-radius:8px;
|
||||
position:relative;
|
||||
transition:background-color .2s;
|
||||
}
|
||||
#tabs div:hover{color:white;background-color:#2e3137}
|
||||
#tabs div.selected{
|
||||
background-color:#f9fafa;
|
||||
color:black;
|
||||
.switch:before{
|
||||
position:absolute;
|
||||
background-color:white;
|
||||
height:12px;width:12px;
|
||||
content:" ";
|
||||
border-radius:6px;
|
||||
top:2px;
|
||||
left:2px;
|
||||
transition:left .2s;
|
||||
}
|
||||
.tab{background-color:#f9fafa;padding:30px 15px}
|
||||
#tab0{border-radius: 0px 2px 2px 2px}
|
||||
#tab1{border-radius: 2px 0px 2px 2px;display:none}
|
||||
#tabs div:first-child.selected{border-color: #e74c3c;}
|
||||
#tabs div:last-child.selected{border-color: #25ba84;/*border-color: #3498db;*/}
|
||||
.switch.enabled:before{
|
||||
left:16px;
|
||||
}
|
||||
.switch.enabled{
|
||||
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;
|
||||
}
|
||||
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;
|
||||
border-radius:3px;border:0;
|
||||
|
||||
padding:8px 16px;
|
||||
padding:10px 20px;
|
||||
margin:0 5px;
|
||||
|
||||
background-color:#4c4f53;
|
||||
background-color:#2a9ca5;
|
||||
color:white;
|
||||
|
||||
transition:background-color .15s;
|
||||
|
||||
box-sizing:border-box
|
||||
}
|
||||
button:hover{
|
||||
button.enabled:hover{
|
||||
cursor:pointer;
|
||||
background-color:#6e7177;
|
||||
background-color:#3aacb5;
|
||||
}
|
||||
button:active{
|
||||
background-color:#47494f;
|
||||
button.enabled:active{
|
||||
background-color:#297b81;
|
||||
transform:translateY(1px)
|
||||
}
|
||||
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}
|
||||
#crc32.valid{color:green}
|
||||
#crc32.invalid{color: red}
|
||||
#crc32 span{text-decoration:underline}
|
||||
#crc32 span:hover{cursor:pointer;color:black}
|
||||
|
||||
|
||||
#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{
|
||||
box-sizing:border-box;
|
||||
max-width:95%;
|
||||
|
|
742
RomPatcher.js
742
RomPatcher.js
File diff suppressed because one or more lines are too long
|
@ -1,29 +1,42 @@
|
|||
/*
|
||||
original: https://github.com/GoogleChrome/samples/blob/gh-pages/service-worker/basic/service-worker.js
|
||||
|
||||
Copyright 2016 Google Inc. All Rights Reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
---
|
||||
mod by marcrobledo, original from: https://github.com/GoogleChrome/samples/blob/gh-pages/service-worker/basic/service-worker.js
|
||||
*/
|
||||
|
||||
const PRECACHE_ID='v20180926';
|
||||
const PRECACHE_FILES=[
|
||||
|
||||
const PRECACHE = 'precache-v2';
|
||||
const RUNTIME = 'runtime';
|
||||
const PRECACHE_URLS = [
|
||||
'index.html','./',
|
||||
'RomPatcher.css',
|
||||
'RomPatcher.js',
|
||||
'manifest.json',
|
||||
'favicon.png',
|
||||
'logo192.png',
|
||||
'RomPatcher.css',
|
||||
'RomPatcher.js',
|
||||
'locale.js',
|
||||
'worker_apply.js',
|
||||
'worker_create.js',
|
||||
'worker_crc.js',
|
||||
'MarcFile.js',
|
||||
'crc.js',
|
||||
'ips.js',
|
||||
'ups.js',
|
||||
'aps.js',
|
||||
'bps.js'
|
||||
'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;});});});}));}});
|
246
aps.js
246
aps.js
|
@ -1,9 +1,10 @@
|
|||
/* APS (N64) module for RomPatcher.js v20180428 - Marc Robledo 2017-2018 - http://www.marcrobledo.com/license */
|
||||
/* APS (N64) module for Rom Patcher JS v20180930 - Marc Robledo 2017-2018 - http://www.marcrobledo.com/license */
|
||||
/* File format specification: https://github.com/btimofeev/UniPatcher/wiki/APS-(N64) */
|
||||
|
||||
var RECORD_RLE=0x0000;
|
||||
var RECORD_SIMPLE=1;
|
||||
var APS_MAGIC='APS10';
|
||||
const APS_MAGIC='APS10';
|
||||
const APS_RECORD_RLE=0x0000;
|
||||
const APS_RECORD_SIMPLE=0x01;
|
||||
const APS_N64_MODE=0x01;
|
||||
|
||||
function APS(){
|
||||
this.records=[];
|
||||
|
@ -14,110 +15,96 @@ function APS(){
|
|||
this.header={};
|
||||
}
|
||||
APS.prototype.addRecord=function(o, d){
|
||||
this.records.push({offset:o, type:RECORD_SIMPLE, data:d})
|
||||
this.records.push({offset:o, type:APS_RECORD_SIMPLE, data:d})
|
||||
}
|
||||
APS.prototype.addRLERecord=function(o, l, b){
|
||||
this.records.push({offset:o, type:RECORD_RLE, length:l, byte:b})
|
||||
APS.prototype.addRLERecord=function(o, b, l){
|
||||
this.records.push({offset:o, type:APS_RECORD_RLE, length:l, byte:b})
|
||||
}
|
||||
APS.prototype.toString=function(){
|
||||
nSimpleRecords=0;
|
||||
nRLERecords=0;
|
||||
for(var i=0; i<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;
|
||||
var s='Total records: '+this.records.length;
|
||||
s+='\nHeader type: '+this.headerType;
|
||||
if(this.headerType===APS_N64_MODE){
|
||||
s+=' (N64)';
|
||||
}
|
||||
s+='\nEncoding method: '+this.encodingMethod;
|
||||
s+='\nDescription: '+this.description;
|
||||
s+='\nHeader: '+JSON.stringify(this.header);
|
||||
return s
|
||||
}
|
||||
APS.prototype.validateSource=function(sourceFile){
|
||||
if(this.headerType===APS_N64_MODE){
|
||||
sourceFile.seek(0x3c);
|
||||
if(sourceFile.readString(3)!==this.header.cartId)
|
||||
return false;
|
||||
|
||||
sourceFile.seek(0x10);
|
||||
var crc=sourceFile.readBytes(8);
|
||||
for(var i=0; i<8; i++){
|
||||
if(crc[i]!==this.header.crc[i])
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
APS.prototype.export=function(fileName){
|
||||
var patchFileSize=(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++){
|
||||
if(this.records[i].type===RECORD_RLE)
|
||||
if(this.records[i].type===APS_RECORD_RLE)
|
||||
patchFileSize+=7;
|
||||
else
|
||||
patchFileSize+=5+this.records[i].data.length; //offset+length+data
|
||||
}
|
||||
|
||||
tempFile=new MarcBinFile(patchFileSize);
|
||||
tempFile=new MarcFile(patchFileSize);
|
||||
tempFile.littleEndian=true;
|
||||
tempFile.fileName=fileName+'.aps';
|
||||
tempFile.writeString(0, APS_MAGIC, APS_MAGIC.length);
|
||||
tempFile.writeByte(5, this.headerType);
|
||||
tempFile.writeByte(6, this.encodingMethod);
|
||||
tempFile.writeString(7, this.description, 50);
|
||||
tempFile.writeString(APS_MAGIC, APS_MAGIC.length);
|
||||
tempFile.writeU8(this.headerType);
|
||||
tempFile.writeU8(this.encodingMethod);
|
||||
tempFile.writeString(this.description, 50);
|
||||
|
||||
var seek;
|
||||
if(this.headerType===1){
|
||||
tempFile.writeByte(57, this.header.originalFileFormat);
|
||||
tempFile.writeString(58, this.header.cartId, 3);
|
||||
tempFile.writeBytes(61, this.header.crc);
|
||||
tempFile.writeBytes(69, this.header.pad);
|
||||
tempFile.writeInt(74, this.header.sizeOutput);
|
||||
seek=78;
|
||||
}else{
|
||||
tempFile.writeInt(57, this.header.sizeOutput);
|
||||
seek=61;
|
||||
if(this.headerType===APS_N64_MODE){
|
||||
tempFile.writeU8(this.header.originalN64Format);
|
||||
tempFile.writeString(this.header.cartId, 3);
|
||||
tempFile.writeBytes(this.header.crc);
|
||||
tempFile.writeBytes(this.header.pad);
|
||||
}
|
||||
tempFile.writeU32(this.header.sizeOutput);
|
||||
|
||||
for(var i=0; i<this.records.length; i++){
|
||||
var rec=this.records[i];
|
||||
if(rec.type===RECORD_RLE){
|
||||
tempFile.writeInt(seek, rec.offset);
|
||||
tempFile.writeByte(seek+4, 0x00);
|
||||
tempFile.writeByte(seek+5, rec.byte);
|
||||
tempFile.writeByte(seek+6, rec.length);
|
||||
seek+=7;
|
||||
tempFile.writeU32(rec.offset);
|
||||
if(rec.type===APS_RECORD_RLE){
|
||||
tempFile.writeU8(0x00);
|
||||
tempFile.writeU8(rec.byte);
|
||||
tempFile.writeU8(rec.length);
|
||||
}else{
|
||||
tempFile.writeInt(seek, rec.offset);
|
||||
tempFile.writeByte(seek+4, rec.data.length);
|
||||
tempFile.writeBytes(seek+5, rec.data);
|
||||
seek+=5+rec.data.length;
|
||||
tempFile.writeU8(rec.data.length);
|
||||
tempFile.writeBytes(rec.data);
|
||||
}
|
||||
}
|
||||
|
||||
return tempFile
|
||||
}
|
||||
APS.prototype.apply=function(romFile){
|
||||
if(this.headerType===1){
|
||||
if(romFile.readString(0x3c, 3)!==this.header.cartId){
|
||||
MarcDialogs.alert('Error: invalid ROM cart id');
|
||||
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;
|
||||
}
|
||||
|
||||
APS.prototype.apply=function(romFile, validate){
|
||||
if(validate && !this.validateSource(romFile)){
|
||||
throw new Error('error_crc_input');
|
||||
}
|
||||
|
||||
tempFile=new MarcBinFile(this.header.sizeOutput);
|
||||
|
||||
for(var i=0; i<romFile.fileSize && i<this.header.sizeOutput; i++)
|
||||
tempFile.writeByte(i, romFile.readByte(i));
|
||||
tempFile=new MarcFile(this.header.sizeOutput);
|
||||
romFile.copyToFile(tempFile, 0, tempFile.fileSize);
|
||||
|
||||
for(var i=0; i<this.records.length; i++){
|
||||
var rec=this.records[i];
|
||||
if(rec.type===RECORD_RLE){
|
||||
for(var j=0; j<rec.length; j++)
|
||||
tempFile.writeByte(rec.offset+j, rec.byte);
|
||||
tempFile.seek(this.records[i].offset);
|
||||
if(this.records[i].type===APS_RECORD_RLE){
|
||||
for(var j=0; j<this.records[i].length; j++)
|
||||
tempFile.writeU8(this.records[i].byte);
|
||||
}else{
|
||||
for(var j=0; j<rec.data.length; j++)
|
||||
tempFile.writeByte(rec.offset+j, rec.data[j]);
|
||||
tempFile.writeBytes(this.records[i].data);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,88 +114,85 @@ APS.prototype.apply=function(romFile){
|
|||
|
||||
|
||||
|
||||
function readAPSFile(file){
|
||||
var patchFile=new APS();
|
||||
file.littleEndian=true;
|
||||
function parseAPSFile(patchFile){
|
||||
var patch=new APS();
|
||||
patchFile.littleEndian=true;
|
||||
|
||||
patchFile.headerType=file.readByte(5);
|
||||
patchFile.encodingMethod=file.readByte(6);
|
||||
patchFile.description=file.readString(7, 50);
|
||||
patchFile.seek(5);
|
||||
patch.headerType=patchFile.readU8();
|
||||
patch.encodingMethod=patchFile.readU8();
|
||||
patch.description=patchFile.readString(50);
|
||||
|
||||
var seek;
|
||||
if(patchFile.headerType===1){
|
||||
patchFile.header.originalFileFormat=file.readByte(57);
|
||||
patchFile.header.cartId=file.readString(58, 3);
|
||||
patchFile.header.crc=file.readBytes(61, 8);
|
||||
patchFile.header.pad=file.readBytes(69, 5);
|
||||
patchFile.header.sizeOutput=file.readInt(74);
|
||||
seek=78;
|
||||
}else{
|
||||
patchFile.header.sizeOutput=file.readInt(57);
|
||||
seek=61;
|
||||
if(patch.headerType===APS_N64_MODE){
|
||||
patch.header.originalN64Format=patchFile.readU8();
|
||||
patch.header.cartId=patchFile.readString(3);
|
||||
patch.header.crc=patchFile.readBytes(8);
|
||||
patch.header.pad=patchFile.readBytes(5);
|
||||
}
|
||||
patch.header.sizeOutput=patchFile.readU32();
|
||||
|
||||
while(!patchFile.isEOF()){
|
||||
var offset=patchFile.readU32();
|
||||
var length=patchFile.readU8();
|
||||
|
||||
if(length===APS_RECORD_RLE)
|
||||
patch.addRLERecord(offset, patchFile.readU8(seek), patchFile.readU8(seek+1));
|
||||
else
|
||||
patch.addRecord(offset, patchFile.readBytes(length));
|
||||
}
|
||||
return patch;
|
||||
}
|
||||
|
||||
while(seek<file.fileSize){
|
||||
var offset=file.readInt(seek);
|
||||
seek+=4;
|
||||
function createAPSFromFiles(original, modified){
|
||||
var patch=new APS();
|
||||
|
||||
var length=file.readByte(seek);
|
||||
seek+=1;
|
||||
|
||||
if(length==RECORD_RLE){
|
||||
patchFile.addRLERecord(offset, file.readByte(seek+1), file.readByte(seek));
|
||||
seek+=2;
|
||||
}else{
|
||||
patchFile.addRecord(offset, file.readBytes(seek, length));
|
||||
seek+=length;
|
||||
}
|
||||
}
|
||||
return patchFile;
|
||||
|
||||
if(original.readU32()===0x80371240){ //is N64 ROM
|
||||
patch.headerType=APS_N64_MODE;
|
||||
|
||||
patch.header.originalN64Format=/\.v64$/i.test(original.fileName)?0:1;
|
||||
original.seek(0x3c);
|
||||
patch.header.cartId=original.readString(3);
|
||||
original.seek(0x10);
|
||||
patch.header.crc=original.readBytes(8);
|
||||
patch.header.pad=[0,0,0,0,0];
|
||||
}
|
||||
patch.header.sizeOutput=modified.fileSize;
|
||||
|
||||
function createAPSFromFiles(original, modified, N64header){
|
||||
tempFile=new APS();
|
||||
original.seek(0);
|
||||
modified.seek(0);
|
||||
|
||||
if(N64header){
|
||||
tempFile.headerType=1;
|
||||
|
||||
tempFile.header.originalFileFormat=0;
|
||||
tempFile.header.cartId=original.readString(0x3c, 3);
|
||||
tempFile.header.crc=original.readBytes(0x10, 8);
|
||||
tempFile.header.pad=[0,0,0,0,0];
|
||||
}
|
||||
tempFile.header.sizeOutput=modified.fileSize;
|
||||
|
||||
var seek=0;
|
||||
while(seek<modified.fileSize){
|
||||
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){
|
||||
var RLERecord=true;
|
||||
var differentBytes=[];
|
||||
var offset=seek;
|
||||
var offset=modified.offset-1;
|
||||
|
||||
while(b1!==b2 && differentBytes.length<255){
|
||||
while(b1!==b2 && differentBytes.length<0xff){
|
||||
differentBytes.push(b2);
|
||||
if(b2!==differentBytes[0])
|
||||
RLERecord=false;
|
||||
seek++;
|
||||
if(seek===modified.fileSize)
|
||||
|
||||
if(modified.isEOF() || differentBytes.length===0xff)
|
||||
break;
|
||||
b1=seek>=original.fileSize?0x00:original.readByte(seek);
|
||||
b2=modified.readByte(seek);
|
||||
|
||||
b1=original.isEOF()?0x00:original.readU8();
|
||||
b2=modified.readU8();
|
||||
}
|
||||
|
||||
if(RLERecord && differentBytes.length>2){
|
||||
tempFile.addRLERecord(offset, differentBytes.length, differentBytes[0]);
|
||||
patch.addRLERecord(offset, differentBytes[0], differentBytes.length);
|
||||
}else{
|
||||
tempFile.addRecord(offset, differentBytes);
|
||||
patch.addRecord(offset, differentBytes);
|
||||
}
|
||||
//seek++;
|
||||
}else{
|
||||
seek++;
|
||||
//NO se puede comentar??? why????
|
||||
}
|
||||
}
|
||||
return tempFile
|
||||
|
||||
return patch
|
||||
}
|
499
bps.js
499
bps.js
|
@ -1,106 +1,71 @@
|
|||
/* BPS module for RomPatcher.js v20180926 - Marc Robledo 2016-2018 - http://www.marcrobledo.com/license */
|
||||
/* BPS module for Rom Patcher JS v20180930 - Marc Robledo 2016-2018 - http://www.marcrobledo.com/license */
|
||||
/* File format specification: https://www.romhacking.net/documents/746/ */
|
||||
|
||||
var BPS_MAGIC='BPS1';
|
||||
var BPS_ACTION_SOURCE_READ=0;
|
||||
var BPS_ACTION_TARGET_READ=1;
|
||||
var BPS_ACTION_SOURCE_COPY=2;
|
||||
var BPS_ACTION_TARGET_COPY=3;
|
||||
const BPS_MAGIC='BPS1';
|
||||
const BPS_ACTION_SOURCE_READ=0;
|
||||
const BPS_ACTION_TARGET_READ=1;
|
||||
const BPS_ACTION_SOURCE_COPY=2;
|
||||
const BPS_ACTION_TARGET_COPY=3;
|
||||
|
||||
|
||||
function BPS(){
|
||||
this.sourceSize=0;
|
||||
this.targetSize=0;
|
||||
this.metaData='';
|
||||
this.actionsOffset=0;
|
||||
this.file=null;
|
||||
this.actions=[];
|
||||
this.sourceChecksum=0;
|
||||
this.targetChecksum=0;
|
||||
this.patchChecksum=0;
|
||||
}
|
||||
BPS.prototype.toString=function(){
|
||||
var s='Source size: '+this.sourceSize;
|
||||
s+='\Target size: '+this.targetSize;
|
||||
s+='\nTarget size: '+this.targetSize;
|
||||
s+='\nMetadata: '+this.metaData;
|
||||
s+='\nActions offset: '+this.actionsOffset;
|
||||
s+='\n#Actions: '+this.actions.length;
|
||||
return s
|
||||
}
|
||||
/*BPS.prototype.export=function(){
|
||||
|
||||
}*/
|
||||
BPS.prototype.validateSource=function(romFile){return this.sourceChecksum===crc32(romFile,false)}
|
||||
BPS.prototype.apply=function(romFile){
|
||||
if(!this.validateSource(romFile)){
|
||||
MarcDialogs.alert('Error: invalid source ROM checksum');
|
||||
return false;
|
||||
BPS.prototype.validateSource=function(romFile,headerSize){return this.sourceChecksum===crc32(romFile, headerSize)}
|
||||
BPS.prototype.apply=function(romFile, validate){
|
||||
if(validate && !this.validateSource(romFile)){
|
||||
throw new Error('error_crc_input');
|
||||
}
|
||||
|
||||
|
||||
// first we determine target file size
|
||||
var newFileSize=0;
|
||||
var seek=this.actionsOffset;
|
||||
while(seek<(this.file.fileSize-12)){
|
||||
var data=decodeBPS(this.file, seek);
|
||||
var action={type: data.number & 3, length: (data.number >> 2)+1};
|
||||
seek+=data.length;
|
||||
|
||||
newFileSize+=action.length;
|
||||
if(action.type===BPS_ACTION_TARGET_READ){
|
||||
seek+=action.length;
|
||||
}else if(action.type===BPS_ACTION_SOURCE_COPY || action.type===BPS_ACTION_TARGET_COPY){
|
||||
seek+=decodeBPS(this.file, seek).length;
|
||||
}else{
|
||||
//console.log(action.type)
|
||||
}
|
||||
}
|
||||
tempFile=new MarcBinFile(newFileSize);
|
||||
//alert(newFileSize);
|
||||
tempFile=new MarcFile(this.targetSize);
|
||||
|
||||
|
||||
//patch
|
||||
var outputOffset=0;
|
||||
var sourceRelativeOffset=0;
|
||||
var targetRelativeOffset=0;
|
||||
seek=this.actionsOffset;
|
||||
while(seek<(this.file.fileSize-12)){
|
||||
var data=decodeBPS(this.file, seek);
|
||||
var action={type: data.number & 3, length: (data.number >> 2)+1};
|
||||
//console.log('0x'+seek.toString(16)+' - action: '+action.type+':'+action.length);
|
||||
seek+=data.length;
|
||||
for(var i=0; i<this.actions.length; i++){
|
||||
var action=this.actions[i];
|
||||
|
||||
if(action.type===BPS_ACTION_SOURCE_READ){
|
||||
tempFile.writeBytes(outputOffset, romFile.readBytes(outputOffset, action.length));
|
||||
outputOffset+=action.length;
|
||||
//seek+=action.length;
|
||||
romFile.copyToFile(tempFile, tempFile.offset, action.length);
|
||||
tempFile.skip(action.length);
|
||||
|
||||
}else if(action.type===BPS_ACTION_TARGET_READ){
|
||||
tempFile.writeBytes(outputOffset, this.file.readBytes(seek, action.length));
|
||||
outputOffset+=action.length;
|
||||
seek+=action.length;
|
||||
tempFile.writeBytes(action.bytes);
|
||||
|
||||
}else if(action.type===BPS_ACTION_SOURCE_COPY){
|
||||
var data2=decodeBPS(this.file, seek);
|
||||
seek+=data2.length;
|
||||
sourceRelativeOffset+=(data2.number & 1 ? -1 : +1) * (data2.number >> 1);
|
||||
while(action.length--){
|
||||
tempFile.writeByte(outputOffset, romFile.readByte(sourceRelativeOffset));
|
||||
outputOffset++;
|
||||
sourceRelativeOffset+=action.relativeOffset;
|
||||
var actionLength=action.length;
|
||||
while(actionLength--){
|
||||
tempFile.writeU8(romFile._u8array[sourceRelativeOffset]);
|
||||
sourceRelativeOffset++;
|
||||
}
|
||||
}else if(action.type===BPS_ACTION_TARGET_COPY){
|
||||
var data2=decodeBPS(this.file, seek);
|
||||
seek+=data2.length;
|
||||
targetRelativeOffset += (data2.number & 1 ? -1 : +1) * (data2.number >> 1);
|
||||
while(action.length--) {
|
||||
tempFile.writeByte(outputOffset, tempFile.readByte(targetRelativeOffset));
|
||||
outputOffset++;
|
||||
targetRelativeOffset+=action.relativeOffset;
|
||||
var actionLength=action.length;
|
||||
while(actionLength--) {
|
||||
tempFile.writeU8(tempFile._u8array[targetRelativeOffset]);
|
||||
targetRelativeOffset++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(this.targetChecksum!==crc32(tempFile,false)){
|
||||
MarcDialogs.alert('Warning: invalid target ROM checksum');
|
||||
if(validate && this.targetChecksum!==crc32(tempFile)){
|
||||
throw new Error('error_crc_output');
|
||||
}
|
||||
|
||||
return tempFile
|
||||
|
@ -108,73 +73,381 @@ BPS.prototype.apply=function(romFile){
|
|||
|
||||
|
||||
|
||||
function readBPSFile(file){
|
||||
function parseBPSFile(file){
|
||||
file.readVLV=BPS_readVLV;
|
||||
|
||||
file.littleEndian=true;
|
||||
var patchFile=new BPS();
|
||||
var patch=new BPS();
|
||||
|
||||
var seek=4; //skip BPS1
|
||||
var decodedSourceSize=decodeBPS(file, seek);
|
||||
patchFile.sourceSize=decodedSourceSize.number;
|
||||
seek+=decodedSourceSize.length;
|
||||
var decodedTargetSize=decodeBPS(file, seek);
|
||||
patchFile.targetSize=decodedTargetSize.number;
|
||||
seek+=decodedTargetSize.length;
|
||||
|
||||
var decodedMetaDataLength=decodeBPS(file, seek);
|
||||
seek+=decodedMetaDataLength.length;
|
||||
if(decodedMetaDataLength.number){
|
||||
patchFile.metaData=file.readString(seek, decodedMetaDataLength.number);
|
||||
seek+=patchFile.metaData.number;
|
||||
file.seek(4); //skip BPS1
|
||||
|
||||
patch.sourceSize=file.readVLV();
|
||||
patch.targetSize=file.readVLV();
|
||||
|
||||
var metaDataLength=file.readVLV();
|
||||
if(metaDataLength){
|
||||
patch.metaData=file.readString(metaDataLength);
|
||||
}
|
||||
|
||||
patchFile.actionsOffset=seek;
|
||||
patchFile.file=file;
|
||||
|
||||
patchFile.sourceChecksum=file.readInt(file.fileSize-12);
|
||||
patchFile.targetChecksum=file.readInt(file.fileSize-8);
|
||||
patchFile.patchChecksum=file.readInt(file.fileSize-4);
|
||||
var endActionsOffset=file.fileSize-12;
|
||||
while(file.offset<endActionsOffset){
|
||||
var data=file.readVLV();
|
||||
var action={type: data & 3, length: (data >> 2)+1};
|
||||
|
||||
if(patchFile.patchChecksum!==crc32(file,true)){
|
||||
MarcDialogs.alert('Warning: invalid patch checksum');
|
||||
if(action.type===BPS_ACTION_TARGET_READ){
|
||||
action.bytes=file.readBytes(action.length);
|
||||
|
||||
}else if(action.type===BPS_ACTION_SOURCE_COPY || action.type===BPS_ACTION_TARGET_COPY){
|
||||
var relativeOffset=file.readVLV();
|
||||
action.relativeOffset=(relativeOffset & 1? -1 : +1) * (relativeOffset >> 1)
|
||||
}
|
||||
|
||||
patch.actions.push(action);
|
||||
}
|
||||
|
||||
//file.seek(endActionsOffset);
|
||||
patch.sourceChecksum=file.readU32();
|
||||
patch.targetChecksum=file.readU32();
|
||||
patch.patchChecksum=file.readU32();
|
||||
|
||||
if(patch.patchChecksum!==crc32(file, 0, true)){
|
||||
throw new Error('error_crc_patch');
|
||||
}
|
||||
|
||||
|
||||
return patch;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function BPS_readVLV(){
|
||||
var data=0, shift=1;
|
||||
while(true){
|
||||
var x = this.readU8();
|
||||
data += (x & 0x7f) * shift;
|
||||
if(x & 0x80)
|
||||
break;
|
||||
shift <<= 7;
|
||||
data += shift;
|
||||
}
|
||||
|
||||
this._lastRead=data;
|
||||
return data;
|
||||
}
|
||||
function BPS_writeVLV(data){
|
||||
while(true){
|
||||
var x = data & 0x7f;
|
||||
data >>= 7;
|
||||
if(data === 0){
|
||||
this.writeU8(0x80 | x);
|
||||
break;
|
||||
}
|
||||
this.writeU8(x);
|
||||
data--;
|
||||
}
|
||||
}
|
||||
function BPS_getVLVLen(data){
|
||||
var len=0;
|
||||
while(true){
|
||||
var x = data & 0x7f;
|
||||
data >>= 7;
|
||||
if(data === 0){
|
||||
len++;
|
||||
break;
|
||||
}
|
||||
len++;
|
||||
data--;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
BPS.prototype.export=function(fileName){
|
||||
var patchFileSize=BPS_MAGIC.length;
|
||||
patchFileSize+=BPS_getVLVLen(this.sourceSize);
|
||||
patchFileSize+=BPS_getVLVLen(this.targetSize);
|
||||
patchFileSize+=BPS_getVLVLen(this.metaData.length);
|
||||
patchFileSize+=this.metaData.length;
|
||||
for(var i=0; i<this.actions.length; i++){
|
||||
var action=this.actions[i];
|
||||
patchFileSize+=BPS_getVLVLen(((action.length-1)<<2) + action.type);
|
||||
|
||||
if(action.type===BPS_ACTION_TARGET_READ){
|
||||
patchFileSize+=action.length;
|
||||
}else if(action.type===BPS_ACTION_SOURCE_COPY || action.type===BPS_ACTION_TARGET_COPY){
|
||||
patchFileSize+=BPS_getVLVLen((Math.abs(action.relativeOffset)<<1)+(action.relativeOffset<0?1:0));
|
||||
}
|
||||
}
|
||||
patchFileSize+=12;
|
||||
|
||||
var patchFile=new MarcFile(patchFileSize);
|
||||
patchFile.fileName=fileName+'.bps';
|
||||
patchFile.littleEndian=true;
|
||||
patchFile.writeVLV=BPS_writeVLV;
|
||||
|
||||
patchFile.writeString(BPS_MAGIC);
|
||||
patchFile.writeVLV(this.sourceSize);
|
||||
patchFile.writeVLV(this.targetSize);
|
||||
patchFile.writeVLV(this.metaData.length);
|
||||
patchFile.writeString(this.metaData, this.metaData.length);
|
||||
|
||||
for(var i=0; i<this.actions.length; i++){
|
||||
var action=this.actions[i];
|
||||
patchFile.writeVLV(((action.length-1)<<2) + action.type);
|
||||
|
||||
if(action.type===BPS_ACTION_TARGET_READ){
|
||||
patchFile.writeBytes(action.bytes);
|
||||
}else if(action.type===BPS_ACTION_SOURCE_COPY || action.type===BPS_ACTION_TARGET_COPY){
|
||||
patchFile.writeVLV((Math.abs(action.relativeOffset)<<1)+(action.relativeOffset<0?1:0));
|
||||
}
|
||||
}
|
||||
patchFile.writeU32(this.sourceChecksum);
|
||||
patchFile.writeU32(this.targetChecksum);
|
||||
patchFile.writeU32(this.patchChecksum);
|
||||
|
||||
return patchFile;
|
||||
}
|
||||
|
||||
function BPS_Node(){
|
||||
this.offset=0;
|
||||
this.next=null;
|
||||
};
|
||||
BPS_Node.prototype.delete=function(){
|
||||
if(this.next)
|
||||
delete this.next;
|
||||
}
|
||||
function createBPSFromFiles(original, modified, deltaMode){
|
||||
var patch=new BPS();
|
||||
patch.sourceSize=original.fileSize;
|
||||
patch.targetSize=modified.fileSize;
|
||||
|
||||
/*function createBPSFromFiles(original, modified){
|
||||
if(deltaMode){
|
||||
patch.actions=createBPSFromFilesDelta(original, modified);
|
||||
}else{
|
||||
patch.actions=createBPSFromFilesLinear(original, modified);
|
||||
}
|
||||
|
||||
}*/
|
||||
patch.sourceChecksum=crc32(original);
|
||||
patch.targetChecksum=crc32(modified);
|
||||
patch.patchChecksum=crc32(patch.export(), 0, true);
|
||||
return patch;
|
||||
}
|
||||
|
||||
|
||||
/*function encodeBPS(number){
|
||||
number=number>>>0;
|
||||
var dataBytes=[];
|
||||
while(true){
|
||||
var x = number & 0x7f;
|
||||
number >>= 7;
|
||||
if(number == 0){
|
||||
dataBytes.push(0x80 | x);
|
||||
/* delta implementation from https://github.com/chiya/beat/blob/master/nall/beat/linear.hpp */
|
||||
function createBPSFromFilesLinear(original, modified){
|
||||
var patchActions=[];
|
||||
|
||||
/* references to match original beat code */
|
||||
var sourceData=original._u8array;
|
||||
var targetData=modified._u8array;
|
||||
var sourceSize=original.fileSize;
|
||||
var targetSize=modified.fileSize;
|
||||
var Granularity=1;
|
||||
|
||||
|
||||
|
||||
var targetRelativeOffset=0;
|
||||
var outputOffset=0;
|
||||
var targetReadLength=0;
|
||||
|
||||
function targetReadFlush(){
|
||||
if(targetReadLength){
|
||||
//encode(TargetRead | ((targetReadLength - 1) << 2));
|
||||
var action={type:BPS_ACTION_TARGET_READ, length:targetReadLength, bytes:[]};
|
||||
patchActions.push(action);
|
||||
var offset = outputOffset - targetReadLength;
|
||||
while(targetReadLength){
|
||||
//write(targetData[offset++]);
|
||||
action.bytes.push(targetData[offset++]);
|
||||
targetReadLength--;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
while(outputOffset < targetSize) {
|
||||
var sourceLength = 0;
|
||||
for(var n = 0; outputOffset + n < Math.min(sourceSize, targetSize); n++) {
|
||||
if(sourceData[outputOffset + n] != targetData[outputOffset + n]) break;
|
||||
sourceLength++;
|
||||
}
|
||||
|
||||
var rleLength = 0;
|
||||
for(var n = 1; outputOffset + n < targetSize; n++) {
|
||||
if(targetData[outputOffset] != targetData[outputOffset + n]) break;
|
||||
rleLength++;
|
||||
}
|
||||
|
||||
if(rleLength >= 4) {
|
||||
//write byte to repeat
|
||||
targetReadLength++;
|
||||
outputOffset++;
|
||||
targetReadFlush();
|
||||
|
||||
//copy starting from repetition byte
|
||||
//encode(TargetCopy | ((rleLength - 1) << 2));
|
||||
var relativeOffset = (outputOffset - 1) - targetRelativeOffset;
|
||||
//encode(relativeOffset << 1);
|
||||
patchActions.push({type:BPS_ACTION_TARGET_COPY, length:rleLength, relativeOffset:relativeOffset});
|
||||
outputOffset += rleLength;
|
||||
targetRelativeOffset = outputOffset - 1;
|
||||
} else if(sourceLength >= 4) {
|
||||
targetReadFlush();
|
||||
//encode(SourceRead | ((sourceLength - 1) << 2));
|
||||
patchActions.push({type:BPS_ACTION_SOURCE_READ, length:sourceLength});
|
||||
outputOffset += sourceLength;
|
||||
} else {
|
||||
targetReadLength += Granularity;
|
||||
outputOffset += Granularity;
|
||||
}
|
||||
}
|
||||
|
||||
targetReadFlush();
|
||||
|
||||
|
||||
|
||||
return patchActions;
|
||||
}
|
||||
|
||||
/* delta implementation from https://github.com/chiya/beat/blob/master/nall/beat/delta.hpp */
|
||||
function createBPSFromFilesDelta(original, modified){
|
||||
var patchActions=[];
|
||||
|
||||
|
||||
/* references to match original beat code */
|
||||
var sourceData=original._u8array;
|
||||
var targetData=modified._u8array;
|
||||
var sourceSize=original.fileSize;
|
||||
var targetSize=modified.fileSize;
|
||||
var Granularity=1;
|
||||
|
||||
|
||||
|
||||
var sourceRelativeOffset=0;
|
||||
var targetRelativeOffset=0;
|
||||
var outputOffset=0;
|
||||
|
||||
|
||||
|
||||
var sourceTree=new Array(65536);
|
||||
var targetTree=new Array(65536);
|
||||
for(var n=0; n<65536; n++){
|
||||
sourceTree[n]=null;
|
||||
targetTree[n]=null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//source tree creation
|
||||
for(var offset=0; offset<sourceSize; offset++) {
|
||||
var symbol = sourceData[offset + 0];
|
||||
//sourceChecksum = crc32_adjust(sourceChecksum, symbol);
|
||||
if(offset < sourceSize - 1)
|
||||
symbol |= sourceData[offset + 1] << 8;
|
||||
var node=new BPS_Node();
|
||||
node.offset=offset;
|
||||
node.next=sourceTree[symbol];
|
||||
sourceTree[symbol] = node;
|
||||
}
|
||||
|
||||
var targetReadLength=0;
|
||||
|
||||
function targetReadFlush(){
|
||||
if(targetReadLength) {
|
||||
//encode(TargetRead | ((targetReadLength - 1) << 2));
|
||||
var action={type:BPS_ACTION_TARGET_READ, length:targetReadLength, bytes:[]};
|
||||
patchActions.push(action);
|
||||
var offset = outputOffset - targetReadLength;
|
||||
while(targetReadLength){
|
||||
//write(targetData[offset++]);
|
||||
action.bytes.push(targetData[offset++]);
|
||||
targetReadLength--;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
while(outputOffset<modified.fileSize){
|
||||
var maxLength = 0, maxOffset = 0, mode = BPS_ACTION_TARGET_READ;
|
||||
|
||||
var symbol = targetData[outputOffset + 0];
|
||||
if(outputOffset < targetSize - 1) symbol |= targetData[outputOffset + 1] << 8;
|
||||
|
||||
{ //source read
|
||||
var length = 0, offset = outputOffset;
|
||||
while(offset < sourceSize && offset < targetSize && sourceData[offset] == targetData[offset]) {
|
||||
length++;
|
||||
offset++;
|
||||
}
|
||||
if(length > maxLength) maxLength = length, mode = BPS_ACTION_SOURCE_READ;
|
||||
}
|
||||
|
||||
{ //source copy
|
||||
var node = sourceTree[symbol];
|
||||
while(node) {
|
||||
var length = 0, x = node.offset, y = outputOffset;
|
||||
while(x < sourceSize && y < targetSize && sourceData[x++] == targetData[y++]) length++;
|
||||
if(length > maxLength) maxLength = length, maxOffset = node.offset, mode = BPS_ACTION_SOURCE_COPY;
|
||||
node = node.next;
|
||||
}
|
||||
}
|
||||
|
||||
{ //target copy
|
||||
var node = targetTree[symbol];
|
||||
while(node) {
|
||||
var length = 0, x = node.offset, y = outputOffset;
|
||||
while(y < targetSize && targetData[x++] == targetData[y++]) length++;
|
||||
if(length > maxLength) maxLength = length, maxOffset = node.offset, mode = BPS_ACTION_TARGET_COPY;
|
||||
node = node.next;
|
||||
}
|
||||
|
||||
//target tree append
|
||||
node = new BPS_Node();
|
||||
node.offset = outputOffset;
|
||||
node.next = targetTree[symbol];
|
||||
targetTree[symbol] = node;
|
||||
}
|
||||
|
||||
{ //target read
|
||||
if(maxLength < 4) {
|
||||
maxLength = Math.min(Granularity, targetSize - outputOffset);
|
||||
mode = BPS_ACTION_TARGET_READ;
|
||||
}
|
||||
}
|
||||
|
||||
if(mode != BPS_ACTION_TARGET_READ) targetReadFlush();
|
||||
|
||||
switch(mode) {
|
||||
case BPS_ACTION_SOURCE_READ:
|
||||
//encode(BPS_ACTION_SOURCE_READ | ((maxLength - 1) << 2));
|
||||
patchActions.push({type:BPS_ACTION_SOURCE_READ, length:maxLength});
|
||||
break;
|
||||
case BPS_ACTION_TARGET_READ:
|
||||
//delay write to group sequential TargetRead commands into one
|
||||
targetReadLength += maxLength;
|
||||
break;
|
||||
case BPS_ACTION_SOURCE_COPY:
|
||||
case BPS_ACTION_TARGET_COPY:
|
||||
//encode(mode | ((maxLength - 1) << 2));
|
||||
var relativeOffset;
|
||||
if(mode == BPS_ACTION_SOURCE_COPY) {
|
||||
relativeOffset = maxOffset - sourceRelativeOffset;
|
||||
sourceRelativeOffset = maxOffset + maxLength;
|
||||
} else {
|
||||
relativeOffset = maxOffset - targetRelativeOffset;
|
||||
targetRelativeOffset = maxOffset + maxLength;
|
||||
}
|
||||
//encode((relativeOffset < 0) | (abs(relativeOffset) << 1));
|
||||
patchActions.push({type:mode, length:maxLength, relativeOffset:relativeOffset});
|
||||
break;
|
||||
}
|
||||
dataBytes.push(x);
|
||||
number--;
|
||||
|
||||
outputOffset += maxLength;
|
||||
}
|
||||
return dataBytes;
|
||||
}*/
|
||||
function decodeBPS(dataBytes, i){
|
||||
var number = 0, shift = 1;
|
||||
var len=0;
|
||||
while(true){
|
||||
var x = dataBytes.readByte(i);
|
||||
i++;
|
||||
len++;
|
||||
number += (x & 0x7f) * shift;
|
||||
if(x & 0x80)
|
||||
break;
|
||||
shift <<= 7;
|
||||
number += shift;
|
||||
}
|
||||
return {number:number,length:len};
|
||||
|
||||
targetReadFlush();
|
||||
|
||||
|
||||
return patchActions;
|
||||
}
|
105
crc.js
Normal file
105
crc.js
Normal file
|
@ -0,0 +1,105 @@
|
|||
/* Rom Patcher JS - CRC32/MD5/SHA-1 calculators v20181017 - Marc Robledo 2016-2018 - http://www.marcrobledo.com/license */
|
||||
|
||||
function padZeroes(intVal, nBytes){
|
||||
var hexString=intVal.toString(16);
|
||||
while(hexString.length<nBytes*2)
|
||||
hexString='0'+hexString;
|
||||
return hexString
|
||||
}
|
||||
|
||||
|
||||
/* SHA-1 using WebCryptoAPI */
|
||||
function _sha1_promise(hash){
|
||||
var bytes=new Uint8Array(hash);
|
||||
var hexString='';
|
||||
for(var i=0;i<bytes.length;i++)
|
||||
hexString+=padZeroes(bytes[i], 1);
|
||||
el('sha1').innerHTML=hexString;
|
||||
}
|
||||
function sha1(marcFile){
|
||||
window.crypto.subtle.digest('SHA-1', marcFile._u8array.buffer)
|
||||
.then(_sha1_promise)
|
||||
.catch(function(error){
|
||||
el('sha1').innerHTML='Error';
|
||||
})
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* MD5 - from Joseph's Myers - http://www.myersdaily.org/joseph/javascript/md5.js */
|
||||
const HEX_CHR='0123456789abcdef'.split('');
|
||||
function _add32(a,b){return (a+b)&0xffffffff}
|
||||
function _md5cycle(x,k){var a=x[0],b=x[1],c=x[2],d=x[3];a=ff(a,b,c,d,k[0],7,-680876936);d=ff(d,a,b,c,k[1],12,-389564586);c=ff(c,d,a,b,k[2],17,606105819);b=ff(b,c,d,a,k[3],22,-1044525330);a=ff(a,b,c,d,k[4],7,-176418897);d=ff(d,a,b,c,k[5],12,1200080426);c=ff(c,d,a,b,k[6],17,-1473231341);b=ff(b,c,d,a,k[7],22,-45705983);a=ff(a,b,c,d,k[8],7,1770035416);d=ff(d,a,b,c,k[9],12,-1958414417);c=ff(c,d,a,b,k[10],17,-42063);b=ff(b,c,d,a,k[11],22,-1990404162);a=ff(a,b,c,d,k[12],7,1804603682);d=ff(d,a,b,c,k[13],12,-40341101);c=ff(c,d,a,b,k[14],17,-1502002290);b=ff(b,c,d,a,k[15],22,1236535329);a=gg(a,b,c,d,k[1],5,-165796510);d=gg(d,a,b,c,k[6],9,-1069501632);c=gg(c,d,a,b,k[11],14,643717713);b=gg(b,c,d,a,k[0],20,-373897302);a=gg(a,b,c,d,k[5],5,-701558691);d=gg(d,a,b,c,k[10],9,38016083);c=gg(c,d,a,b,k[15],14,-660478335);b=gg(b,c,d,a,k[4],20,-405537848);a=gg(a,b,c,d,k[9],5,568446438);d=gg(d,a,b,c,k[14],9,-1019803690);c=gg(c,d,a,b,k[3],14,-187363961);b=gg(b,c,d,a,k[8],20,1163531501);a=gg(a,b,c,d,k[13],5,-1444681467);d=gg(d,a,b,c,k[2],9,-51403784);c=gg(c,d,a,b,k[7],14,1735328473);b=gg(b,c,d,a,k[12],20,-1926607734);a=hh(a,b,c,d,k[5],4,-378558);d=hh(d,a,b,c,k[8],11,-2022574463);c=hh(c,d,a,b,k[11],16,1839030562);b=hh(b,c,d,a,k[14],23,-35309556);a=hh(a,b,c,d,k[1],4,-1530992060);d=hh(d,a,b,c,k[4],11,1272893353);c=hh(c,d,a,b,k[7],16,-155497632);b=hh(b,c,d,a,k[10],23,-1094730640);a=hh(a,b,c,d,k[13],4,681279174);d=hh(d,a,b,c,k[0],11,-358537222);c=hh(c,d,a,b,k[3],16,-722521979);b=hh(b,c,d,a,k[6],23,76029189);a=hh(a,b,c,d,k[9],4,-640364487);d=hh(d,a,b,c,k[12],11,-421815835);c=hh(c,d,a,b,k[15],16,530742520);b=hh(b,c,d,a,k[2],23,-995338651);a=ii(a,b,c,d,k[0],6,-198630844);d=ii(d,a,b,c,k[7],10,1126891415);c=ii(c,d,a,b,k[14],15,-1416354905);b=ii(b,c,d,a,k[5],21,-57434055);a=ii(a,b,c,d,k[12],6,1700485571);d=ii(d,a,b,c,k[3],10,-1894986606);c=ii(c,d,a,b,k[10],15,-1051523);b=ii(b,c,d,a,k[1],21,-2054922799);a=ii(a,b,c,d,k[8],6,1873313359);d=ii(d,a,b,c,k[15],10,-30611744);c=ii(c,d,a,b,k[6],15,-1560198380);b=ii(b,c,d,a,k[13],21,1309151649);a=ii(a,b,c,d,k[4],6,-145523070);d=ii(d,a,b,c,k[11],10,-1120210379);c=ii(c,d,a,b,k[2],15,718787259);b=ii(b,c,d,a,k[9],21,-343485551);x[0]=_add32(a,x[0]);x[1]=_add32(b,x[1]);x[2]=_add32(c,x[2]);x[3]=_add32(d,x[3])}
|
||||
function _md5blk(d){var md5blks=[],i;for(i=0;i<64;i+=4)md5blks[i>>2]=d[i]+(d[i+1]<<8)+(d[i+2]<<16)+(d[i+3]<<24);return md5blks}
|
||||
function _cmn(q,a,b,x,s,t){a=_add32(_add32(a,q),_add32(x,t));return _add32((a<<s)|(a>>>(32-s)),b)}
|
||||
function ff(a,b,c,d,x,s,t){return _cmn((b&c)|((~b)&d),a,b,x,s,t)}
|
||||
function gg(a,b,c,d,x,s,t){return _cmn((b&d)|(c&(~d)),a,b,x,s,t)}
|
||||
function hh(a,b,c,d,x,s,t){return _cmn(b^c^d,a,b,x,s,t)}
|
||||
function ii(a,b,c,d,x,s,t){return _cmn(c^(b|(~d)),a,b,x,s,t)}
|
||||
function md5(marcFile, headerSize){
|
||||
var data=headerSize? new Uint8Array(marcFile._u8array.buffer, headerSize):marcFile._u8array;
|
||||
|
||||
var n=data.length,state=[1732584193,-271733879,-1732584194,271733878],i;
|
||||
for(i=64;i<=data.length;i+=64)
|
||||
_md5cycle(state,_md5blk(data.slice(i-64,i)));
|
||||
data=data.slice(i-64);
|
||||
var tail=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
|
||||
for(i=0;i<data.length;i++)
|
||||
tail[i>>2]|=data[i]<<((i%4)<<3);
|
||||
tail[i>>2]|=0x80<<((i%4)<<3);
|
||||
if(i>55){
|
||||
_md5cycle(state,tail);
|
||||
for(i=0;i<16;i++)tail[i]=0;
|
||||
}
|
||||
tail[14]=n*8;
|
||||
_md5cycle(state,tail);
|
||||
|
||||
for(var i=0;i<state.length;i++){
|
||||
var s='',j=0;
|
||||
for(;j<4;j++)
|
||||
s+=HEX_CHR[(state[i]>>(j*8+4))&0x0f]+HEX_CHR[(state[i]>>(j*8))&0x0f];
|
||||
state[i]=s;
|
||||
}
|
||||
return state.join('')
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* CRC32 - from Alex - https://stackoverflow.com/a/18639999 */
|
||||
const CRC32_TABLE=(function(){
|
||||
var c,crcTable=[];
|
||||
for(var n=0;n<256;n++){
|
||||
c=n;
|
||||
for(var k=0;k<8;k++)
|
||||
c=((c&1)?(0xedb88320^(c>>>1)):(c>>>1));
|
||||
crcTable[n]=c;
|
||||
}
|
||||
return crcTable;
|
||||
}());
|
||||
function crc32(marcFile, headerSize, ignoreLast4Bytes){
|
||||
var data=headerSize? new Uint8Array(marcFile._u8array.buffer, headerSize):marcFile._u8array;
|
||||
|
||||
var crc=0^(-1);
|
||||
|
||||
var len=ignoreLast4Bytes?data.length-4:data.length;
|
||||
for(var i=0;i<len;i++)
|
||||
crc=(crc>>>8)^CRC32_TABLE[(crc^data[i])&0xff];
|
||||
|
||||
return ((crc^(-1))>>>0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Adler-32 - https://en.wikipedia.org/wiki/Adler-32#Example_implementation */
|
||||
const ADLER32_MOD=0xfff1;
|
||||
function adler32(marcFile, offset, len){
|
||||
var a=1, b=0;
|
||||
|
||||
for(var i=0; i<len; i++){
|
||||
a=(a+marcFile._u8array[i+offset])%ADLER32_MOD;
|
||||
b=(b+a)%ADLER32_MOD;
|
||||
}
|
||||
|
||||
return ((b<<16) | a)>>>0;
|
||||
}
|
63
index.html
63
index.html
|
@ -3,36 +3,48 @@
|
|||
<head>
|
||||
<title>Rom Patcher JS</title>
|
||||
<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="keywords" content="ips,ups,aps,bps,patcher,online,html5,web,online,rom,patch,hack,translation"/>
|
||||
<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,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"/>
|
||||
<link rel="manifest" href="./manifest.json"/>
|
||||
<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="apple-touch-icon" href="./logo192.png" sizes="192x192" /> -->
|
||||
<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="./MarcFile.js"></script>
|
||||
<script type="text/javascript" src="./crc.js"></script>
|
||||
<script type="text/javascript" src="./ips.js"></script>
|
||||
<script type="text/javascript" src="./ups.js"></script>
|
||||
<script type="text/javascript" src="./aps.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>
|
||||
<body><div id="column">
|
||||
|
||||
|
||||
<!-- HEADER -->
|
||||
<header><img src="logo192.png" /><h1>RomPatcher.js</h1></header>
|
||||
<header><img src="logo192.png" /><h1>Rom Patcher JS</h1></header>
|
||||
|
||||
<!-- APP -->
|
||||
<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 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">
|
||||
<input type="file" id="input-file-rom" />
|
||||
<input type="file" id="input-file-rom" class="enabled" />
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<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">
|
||||
<input type="checkbox" id="checkbox-removeheader" />
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<input type="checkbox" id="checkbox-addheader" />
|
||||
<input type="checkbox" id="checkbox-addheader" /> <label id="headersize" for="checkbox-addheader"></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" title="Compatible formats: IPS, UPS, APS, and BPS">
|
||||
<div class="leftcol"><label for="input-file-patch">Patch file:</label></div>
|
||||
<div class="row" id="row-file-patch">
|
||||
<div class="leftcol"><label for="input-file-patch" data-localize="patch_file">Patch file:</label></div>
|
||||
<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 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>
|
||||
|
||||
|
@ -69,41 +82,47 @@
|
|||
|
||||
<div id="tab1" class="tab">
|
||||
<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">
|
||||
<input type="file" id="input-file-rom1" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<input type="file" id="input-file-rom2" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="leftcol">Patch type:</div>
|
||||
<div class="leftcol" data-localize="patch_type">Patch type:</div>
|
||||
<div class="rightcol">
|
||||
<select id="patch-type">
|
||||
<select id="select-patch-type">
|
||||
<option value="ips">IPS</option>
|
||||
<option value="bps">BPS</option>
|
||||
<option value="ppf">PPF</option>
|
||||
<option value="ups">UPS</option>
|
||||
<option value="aps">APS</option>
|
||||
<option value="apsn64">APS (N64)</option>
|
||||
<!-- <option value="bps">BPS</option> -->
|
||||
<option value="rup">RUP</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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 id="snackbar" class="closed"></div>
|
||||
|
||||
|
||||
|
||||
<!-- 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 />
|
||||
<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>
|
||||
|
|
245
ips.js
245
ips.js
|
@ -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 */
|
||||
|
||||
var MAX_IPS_SIZE=16777216;
|
||||
var RECORD_RLE=0x0000;
|
||||
var RECORD_SIMPLE=1;
|
||||
var IPS_MAGIC='PATCH';
|
||||
|
||||
|
||||
const IPS_MAGIC='PATCH';
|
||||
const IPS_MAX_SIZE=0x1000000; //16 megabytes
|
||||
const IPS_RECORD_RLE=0x0000;
|
||||
const IPS_RECORD_SIMPLE=0x01;
|
||||
|
||||
function IPS(){
|
||||
this.records=[];
|
||||
this.truncate=false;
|
||||
}
|
||||
IPS.prototype.addSimpleRecord=function(o, d){
|
||||
this.records.push({offset:o, type: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){
|
||||
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(){
|
||||
nSimpleRecords=0;
|
||||
nRLERecords=0;
|
||||
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++;
|
||||
else
|
||||
nSimpleRecords++;
|
||||
}
|
||||
var s='';
|
||||
s+='\Simple records: '+nSimpleRecords;
|
||||
var s='Simple records: '+nSimpleRecords;
|
||||
s+='\nRLE records: '+nRLERecords;
|
||||
s+='\nTotal records: '+this.records.length;
|
||||
if(this.truncate)
|
||||
|
@ -34,53 +35,49 @@ IPS.prototype.toString=function(){
|
|||
return s
|
||||
}
|
||||
IPS.prototype.export=function(fileName){
|
||||
var binFileSize=0;
|
||||
binFileSize+=5; //PATCH string
|
||||
var patchFileSize=5; //PATCH string
|
||||
for(var i=0; i<this.records.length; i++){
|
||||
if(this.records[i].type===RECORD_RLE)
|
||||
binFileSize+=(3+2+2+1); //offset+0x0000+length+RLE byte to be written
|
||||
if(this.records[i].type===IPS_RECORD_RLE)
|
||||
patchFileSize+=(3+2+2+1); //offset+0x0000+length+RLE byte to be written
|
||||
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)
|
||||
binFileSize+=3; //truncate
|
||||
patchFileSize+=3; //truncate
|
||||
|
||||
tempFile=new MarcBinFile(binFileSize);
|
||||
tempFile.littleEndian=false;
|
||||
tempFile=new MarcFile(patchFileSize);
|
||||
tempFile.fileName=fileName+'.ips';
|
||||
tempFile.writeString(0, 'PATCH', 5);
|
||||
var seek=5;
|
||||
tempFile.writeString(IPS_MAGIC);
|
||||
for(var i=0; i<this.records.length; i++){
|
||||
var rec=this.records[i];
|
||||
if(rec.type===RECORD_RLE){
|
||||
tempFile.writeThreeBytes(seek, rec.offset);
|
||||
tempFile.writeShort(seek+3, 0x0000);
|
||||
tempFile.writeShort(seek+5, rec.length);
|
||||
tempFile.writeByte(seek+7, rec.byte);
|
||||
seek+=3+2+2+1;
|
||||
tempFile.writeU24(rec.offset);
|
||||
if(rec.type===IPS_RECORD_RLE){
|
||||
tempFile.writeU16(0x0000);
|
||||
tempFile.writeU16(rec.length);
|
||||
tempFile.writeU8(rec.byte);
|
||||
}else{
|
||||
tempFile.writeThreeBytes(seek, rec.offset);
|
||||
tempFile.writeShort(seek+3, rec.data.length);
|
||||
tempFile.writeBytes(seek+5, rec.data);
|
||||
seek+=3+2+rec.data.length;
|
||||
tempFile.writeU16(rec.data.length);
|
||||
tempFile.writeBytes(rec.data);
|
||||
}
|
||||
}
|
||||
|
||||
tempFile.writeString(seek, 'EOF', 3);
|
||||
seek+=3;
|
||||
if(rec.truncate){
|
||||
tempFile.writeThreeBytes(seek, rec.truncate);
|
||||
}
|
||||
tempFile.writeString('EOF');
|
||||
if(rec.truncate)
|
||||
tempFile.writeU24(rec.truncate);
|
||||
|
||||
|
||||
return tempFile
|
||||
}
|
||||
IPS.prototype.validateInput=function(romFile){return '?'}
|
||||
IPS.prototype.apply=function(romFile){
|
||||
if(this.truncate){
|
||||
tempFile=romFile.slice(0, this.truncate);
|
||||
}else{
|
||||
|
||||
var newFileSize=romFile.fileSize;
|
||||
for(var i=0; i<this.records.length; i++){
|
||||
var rec=this.records[i];
|
||||
if(rec.type===RECORD_RLE){
|
||||
if(rec.type===IPS_RECORD_RLE){
|
||||
if(rec.offset+rec.length>newFileSize){
|
||||
newFileSize=rec.offset+rec.length;
|
||||
}
|
||||
|
@ -91,20 +88,24 @@ IPS.prototype.apply=function(romFile){
|
|||
}
|
||||
}
|
||||
|
||||
tempFile=new MarcBinFile(newFileSize);
|
||||
if(newFileSize===romFile.fileSize){
|
||||
tempFile=romFile.slice(0, romFile.fileSize);
|
||||
}else{
|
||||
tempFile=new MarcFile(newFileSize);
|
||||
romFile.copyToFile(tempFile,0);
|
||||
}
|
||||
}
|
||||
|
||||
var clonedFileSize=this.truncate || romFile.fileSize;
|
||||
for(var i=0; i<romFile.fileSize; i++)
|
||||
tempFile.writeByte(i, romFile.readByte(i));
|
||||
|
||||
romFile.seek(0);
|
||||
|
||||
for(var i=0; i<this.records.length; i++){
|
||||
var rec=this.records[i];
|
||||
if(rec.type===RECORD_RLE){
|
||||
for(var j=0; j<rec.length; j++)
|
||||
tempFile.writeByte(rec.offset+j, rec.byte);
|
||||
tempFile.seek(this.records[i].offset);
|
||||
if(this.records[i].type===IPS_RECORD_RLE){
|
||||
for(var j=0; j<this.records[i].length; j++)
|
||||
tempFile.writeU8(this.records[i].byte);
|
||||
}else{
|
||||
for(var j=0; j<rec.data.length; j++)
|
||||
tempFile.writeByte(rec.offset+j, rec.data[j]);
|
||||
tempFile.writeBytes(this.records[i].data);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,101 +115,113 @@ IPS.prototype.apply=function(romFile){
|
|||
|
||||
|
||||
|
||||
function readIPSFile(file){
|
||||
function parseIPSFile(file){
|
||||
var patchFile=new IPS();
|
||||
var EOF=false;
|
||||
var seek=5;
|
||||
file.seek(5);
|
||||
|
||||
while(seek<file.fileSize){
|
||||
var address=file.readThreeBytes(seek);
|
||||
seek+=3;
|
||||
while(!file.isEOF()){
|
||||
var offset=file.readU24();
|
||||
|
||||
if(!EOF && address===0x454f46){ /* EOF */
|
||||
EOF=true;
|
||||
}else if(EOF){
|
||||
patchFile.truncate=address;
|
||||
}else{
|
||||
var length=file.readShort(seek);
|
||||
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;
|
||||
if(offset===0x454f46){ /* EOF */
|
||||
if(file.isEOF()){
|
||||
break;
|
||||
}else if((file.offset+3)===file.fileSize){
|
||||
patchFile.truncate=file.readU24();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var length=file.readU16();
|
||||
|
||||
if(length===IPS_RECORD_RLE){
|
||||
patchFile.addRLERecord(offset, file.readU16(), file.readU8());
|
||||
}else{
|
||||
patchFile.addSimpleRecord(offset, file.readBytes(length));
|
||||
}
|
||||
}
|
||||
return patchFile;
|
||||
}
|
||||
|
||||
|
||||
function createIPSFromFiles(original, modified){
|
||||
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){
|
||||
tempFile.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;
|
||||
patch.truncate=modified.fileSize;
|
||||
}
|
||||
|
||||
var seek=0;
|
||||
while(seek<modified.fileSize){
|
||||
var b1=original.readByte(seek);
|
||||
var b2=modified.readByte(seek);
|
||||
//solucion: guardar startOffset y endOffset (ir mirando de 6 en 6 hacia atrás)
|
||||
var previousRecord={type:0xdeadbeef,startOffset:0,length:0};
|
||||
while(!modified.isEOF()){
|
||||
var b1=original.isEOF()?0x00:original.readU8();
|
||||
var b2=modified.readU8();
|
||||
|
||||
if(b1!==b2){
|
||||
var RLERecord=true;
|
||||
var originalSeek=seek;
|
||||
var length=1;
|
||||
var RLEmode=true;
|
||||
var differentData=[];
|
||||
var startOffset=modified.offset-1;
|
||||
|
||||
/* find difference in next 6 bytes (in order to save space) */
|
||||
/* force length to be 0xffff-6 bytes to keep IPS standard */
|
||||
var nearbyDifference=true;
|
||||
while(nearbyDifference && length<(0xffff-6)){
|
||||
if((seek+6)>=modified.fileSize){
|
||||
var finalSeek=modified.fileSize-seek-1;
|
||||
length+=finalSeek;
|
||||
seek+=finalSeek;
|
||||
while(b1!==b2 && differentData.length<0xffff){
|
||||
differentData.push(b2);
|
||||
if(b2!==differentData[0])
|
||||
RLEmode=false;
|
||||
|
||||
if(modified.isEOF() || differentData.length===0xffff)
|
||||
break;
|
||||
|
||||
b1=original.isEOF()?0x00:original.readU8();
|
||||
b2=modified.readU8();
|
||||
}
|
||||
|
||||
for(var i=6;i>0 && nearbyDifference;i--){
|
||||
if(original.readByte(seek+i)!==modified.readByte(seek+i)){
|
||||
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){
|
||||
if(length<3){
|
||||
tempFile.addSimpleRecord(originalSeek, data);
|
||||
//check if this record is near the previous one
|
||||
var distance=startOffset-(previousRecord.offset+previousRecord.length);
|
||||
if(
|
||||
previousRecord.type===IPS_RECORD_SIMPLE &&
|
||||
distance<6 && (previousRecord.length+distance+differentData.length)<0xffff
|
||||
){
|
||||
if(RLEmode && differentData.length>6){
|
||||
// separate a potential RLE record
|
||||
original.seek(startOffset);
|
||||
modified.seek(startOffset);
|
||||
previousRecord={type:0xdeadbeef,startOffset:0,length:0};
|
||||
}else{
|
||||
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{
|
||||
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++;
|
||||
|
||||
|
||||
|
||||
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 tempFile
|
||||
|
||||
|
||||
return patch
|
||||
}
|
77
locale.js
Normal file
77
locale.js
Normal 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
245
ppf.js
Normal 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
324
rup.js
Normal 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
226
ups.js
|
@ -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/ */
|
||||
|
||||
var UPS_MAGIC='UPS1';
|
||||
const UPS_MAGIC='UPS1';
|
||||
|
||||
function UPS(){
|
||||
this.records=[];
|
||||
|
@ -10,81 +10,76 @@ function UPS(){
|
|||
this.checksumInput=0;
|
||||
this.checksumOutput=0;
|
||||
}
|
||||
UPS.prototype.addRecord=function(o, d){
|
||||
this.records.push({offset:o, XORdata:d})
|
||||
UPS.prototype.addRecord=function(relativeOffset, d){
|
||||
this.records.push({offset:relativeOffset, XORdata:d})
|
||||
}
|
||||
UPS.prototype.toString=function(){
|
||||
var s='Records: '+this.records.length;
|
||||
s+='\nInput file size: '+this.sizeInput;
|
||||
s+='\nOutput file size: '+this.sizeOutput;
|
||||
s+='\nInput file checksum: '+this.checksumInput;
|
||||
s+='\nOutput file checksum: '+this.checksumOutput;
|
||||
s+='\nInput file checksum: '+padZeroes(this.checksumInput,4);
|
||||
s+='\nOutput file checksum: '+padZeroes(this.checksumOutput,4);
|
||||
return s
|
||||
}
|
||||
UPS.prototype.export=function(fileName){
|
||||
var encodedSizeInput=encodeVLV(this.sizeInput);
|
||||
var encodedSizeOutput=encodeVLV(this.sizeOutput);
|
||||
var encodedRecords=[];
|
||||
var binFileSize=0;
|
||||
binFileSize+=UPS_MAGIC.length; //UPS1 string
|
||||
binFileSize+=encodedSizeInput.length; //input file size
|
||||
binFileSize+=encodedSizeOutput.length; //output file size
|
||||
var patchFileSize=UPS_MAGIC.length;//UPS1 string
|
||||
patchFileSize+=UPS_getVLVLength(this.sizeInput); //input file size
|
||||
patchFileSize+=UPS_getVLVLength(this.sizeOutput); //output file size
|
||||
for(var i=0; i<this.records.length; i++){
|
||||
encodedRecords.push(encodeVLV(this.records[i].offset));
|
||||
binFileSize+=encodedRecords[i].length;
|
||||
binFileSize+=this.records[i].XORdata.length+1;
|
||||
patchFileSize+=UPS_getVLVLength(this.records[i].offset);
|
||||
patchFileSize+=this.records[i].XORdata.length+1;
|
||||
}
|
||||
binFileSize+=12; //input/output/patch checksums
|
||||
patchFileSize+=12; //input/output/patch checksums
|
||||
|
||||
tempFile=new MarcBinFile(binFileSize);
|
||||
tempFile.littleEndian=false;
|
||||
tempFile=new MarcFile(patchFileSize);
|
||||
tempFile.writeVLV=UPS_writeVLV;
|
||||
tempFile.fileName=fileName+'.ups';
|
||||
tempFile.writeString(0, UPS_MAGIC, UPS_MAGIC.length);
|
||||
tempFile.writeString(UPS_MAGIC);
|
||||
|
||||
tempFile.writeBytes(4, encodedSizeInput);
|
||||
tempFile.writeBytes(4+encodedSizeInput.length, encodedSizeOutput);
|
||||
tempFile.writeVLV(this.sizeInput);
|
||||
tempFile.writeVLV(this.sizeOutput);
|
||||
|
||||
var seek=4+encodedSizeInput.length+encodedSizeOutput.length;
|
||||
for(var i=0; i<this.records.length; i++){
|
||||
tempFile.writeBytes(seek, encodedRecords[i]);
|
||||
seek+=encodedRecords[i].length;
|
||||
tempFile.writeBytes(seek, this.records[i].XORdata);
|
||||
seek+=this.records[i].XORdata.length;
|
||||
tempFile.writeByte(seek, 0);
|
||||
seek+=1;
|
||||
tempFile.writeVLV(this.records[i].offset);
|
||||
tempFile.writeBytes(this.records[i].XORdata);
|
||||
tempFile.writeU8(0x00);
|
||||
}
|
||||
tempFile.littleEndian=true;
|
||||
tempFile.writeInt(seek, this.checksumInput);
|
||||
tempFile.writeInt(seek+4, this.checksumOutput);
|
||||
tempFile.writeInt(seek+8, crc32(tempFile, true));
|
||||
tempFile.writeU32(this.checksumInput);
|
||||
tempFile.writeU32(this.checksumOutput);
|
||||
tempFile.writeU32(crc32(tempFile, 0, true));
|
||||
|
||||
return tempFile
|
||||
}
|
||||
UPS.prototype.validateSource=function(romFile){return crc32(romFile)===this.checksumInput}
|
||||
UPS.prototype.apply=function(romFile){
|
||||
if(!this.validateSource(romFile)){
|
||||
MarcDialogs.alert('Error: invalid input ROM checksum');
|
||||
return false;
|
||||
UPS.prototype.validateSource=function(romFile,headerSize){return crc32(romFile,headerSize)===this.checksumInput}
|
||||
UPS.prototype.apply=function(romFile, validate){
|
||||
if(validate && !this.validateSource(romFile)){
|
||||
throw new Error('error_crc_input');
|
||||
}
|
||||
|
||||
tempFile=new MarcBinFile(this.sizeOutput);
|
||||
|
||||
/* copy original file */
|
||||
for(var i=0; i<romFile.fileSize; i++)
|
||||
tempFile.writeByte(i, romFile.readByte(i));
|
||||
tempFile=new MarcFile(this.sizeOutput);
|
||||
romFile.copyToFile(tempFile, 0, this.sizeInput);
|
||||
|
||||
romFile.seek(0);
|
||||
|
||||
|
||||
var nextOffset=0;
|
||||
for(var i=0; i<this.records.length; i++){
|
||||
var nextDifference=this.records[i];
|
||||
nextOffset+=nextDifference.offset;
|
||||
for(var j=0; j<nextDifference.XORdata.length; j++){
|
||||
tempFile.writeByte(nextOffset+j, ((nextOffset+j)<romFile.fileSize?romFile.readByte(nextOffset+j):0x00) ^ nextDifference.XORdata[j]);
|
||||
var record=this.records[i];
|
||||
tempFile.skip(record.offset);
|
||||
romFile.skip(record.offset);
|
||||
|
||||
for(var j=0; j<record.XORdata.length; j++){
|
||||
tempFile.writeU8((romFile.isEOF()?0x00:romFile.readU8()) ^ record.XORdata[j]);
|
||||
}
|
||||
nextOffset+=nextDifference.XORdata.length+1;
|
||||
tempFile.skip(1);
|
||||
romFile.skip(1);
|
||||
}
|
||||
|
||||
if(crc32(tempFile)!==this.checksumOutput){
|
||||
MarcDialogs.alert('Warning: invalid output ROM checksum');
|
||||
if(validate && crc32(tempFile)!==this.checksumOutput){
|
||||
throw new Error('error_crc_output');
|
||||
}
|
||||
|
||||
return tempFile
|
||||
|
@ -92,115 +87,118 @@ UPS.prototype.apply=function(romFile){
|
|||
|
||||
|
||||
/* encode/decode variable length values, used by UPS file structure */
|
||||
function encodeVLV(offset){
|
||||
var bytes=[];
|
||||
function UPS_writeVLV(data){
|
||||
while(1){
|
||||
var x=offset & 0x7f;
|
||||
offset=offset>>7;
|
||||
if(offset===0){
|
||||
bytes.push(0x80 | x);
|
||||
var x=data & 0x7f;
|
||||
data=data>>7;
|
||||
if(data===0){
|
||||
this.writeU8(0x80 | x);
|
||||
break;
|
||||
}
|
||||
bytes.push(x);
|
||||
offset=offset-1;
|
||||
this.writeU8(x);
|
||||
data=data-1;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
function decodeVLV(file, pos){
|
||||
var offset=0;
|
||||
var size=0;
|
||||
function UPS_readVLV(){
|
||||
var data=0;
|
||||
|
||||
var shift=1;
|
||||
while(1){
|
||||
var x=file.readByte(pos);
|
||||
pos++;
|
||||
var x=this.readU8();
|
||||
|
||||
if(x==-1)
|
||||
console.error('corrupted UPS file?');
|
||||
size++;
|
||||
offset+=(x&0x7f)*shift;
|
||||
throw new Error('Can\'t read UPS VLV at 0x'+(this.offset-1).toString(16));
|
||||
|
||||
data+=(x&0x7f)*shift;
|
||||
if((x&0x80)!==0)
|
||||
break;
|
||||
shift=shift<<7;
|
||||
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){
|
||||
var patchFile=new UPS();
|
||||
function parseUPSFile(file){
|
||||
var patch=new UPS();
|
||||
file.readVLV=UPS_readVLV;
|
||||
|
||||
var decodedInputFilesize=decodeVLV(tempFile,4);
|
||||
patchFile.sizeInput=decodedInputFilesize.offset;
|
||||
file.seek(UPS_MAGIC.length);
|
||||
|
||||
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;
|
||||
while(seek<(tempFile.fileSize-12)){
|
||||
var decodedOffset=decodeVLV(tempFile, seek);
|
||||
seek+=decodedOffset.size;
|
||||
while(file.offset<(file.fileSize-12)){
|
||||
var relativeOffset=file.readVLV();
|
||||
|
||||
nextOffset+=decodedOffset.offset;
|
||||
|
||||
var bytes=[];
|
||||
var lastByte;
|
||||
while(lastByte=tempFile.readByte(seek)){
|
||||
bytes.push(lastByte);
|
||||
seek++;
|
||||
var XORdifferences=[];
|
||||
while(file.readU8()){
|
||||
XORdifferences.push(file._lastRead);
|
||||
}
|
||||
seek++;
|
||||
patchFile.addRecord(decodedOffset.offset, bytes);
|
||||
patch.addRecord(relativeOffset, XORdifferences);
|
||||
}
|
||||
|
||||
file.littleEndian=true;
|
||||
patchFile.checksumInput=tempFile.readInt(seek);
|
||||
patchFile.checksumOutput=tempFile.readInt(seek+4);
|
||||
patch.checksumInput=file.readU32();
|
||||
patch.checksumOutput=file.readU32();
|
||||
|
||||
if(tempFile.readInt(seek+8)!==crc32(file, true)){
|
||||
MarcDialogs.alert('Warning: invalid patch checksum');
|
||||
if(file.readU32()!==crc32(file, 0, true)){
|
||||
throw new Error('error_crc_patch');
|
||||
}
|
||||
return patchFile;
|
||||
|
||||
file.littleEndian=false;
|
||||
return patch;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function createUPSFromFiles(original, modified){
|
||||
tempFile=new UPS();
|
||||
tempFile.sizeInput=original.fileSize;
|
||||
tempFile.sizeOutput=modified.fileSize;
|
||||
var patch=new UPS();
|
||||
patch.sizeInput=original.fileSize;
|
||||
patch.sizeOutput=modified.fileSize;
|
||||
|
||||
var seek=0;
|
||||
var previousSeek=0;
|
||||
while(seek<modified.fileSize){
|
||||
var b1=seek>=original.fileSize?0x00:original.readByte(seek);
|
||||
var b2=modified.readByte(seek);
|
||||
|
||||
var previousSeek=1;
|
||||
while(!modified.isEOF()){
|
||||
var b1=original.isEOF()?0x00:original.readU8();
|
||||
var b2=modified.readU8();
|
||||
|
||||
if(b1!==b2){
|
||||
var currentSeek=seek;
|
||||
var differentBytes=[];
|
||||
var currentSeek=modified.offset;
|
||||
var XORdata=[];
|
||||
|
||||
while(b1!==b2){
|
||||
differentBytes.push(b1 ^ b2);
|
||||
seek++;
|
||||
if(seek===modified.fileSize)
|
||||
XORdata.push(b1 ^ b2);
|
||||
|
||||
if(modified.isEOF())
|
||||
break;
|
||||
b1=seek>=original.fileSize?0x00:original.readByte(seek);
|
||||
b2=modified.readByte(seek);
|
||||
b1=original.isEOF()?0x00:original.readU8();
|
||||
b2=modified.readU8();
|
||||
}
|
||||
|
||||
var nextDifference=currentSeek-previousSeek;
|
||||
tempFile.addRecord(nextDifference, differentBytes);
|
||||
previousSeek=currentSeek+differentBytes.length+1;
|
||||
seek++;
|
||||
}else{
|
||||
seek++;
|
||||
patch.addRecord(currentSeek-previousSeek, XORdata);
|
||||
previousSeek=currentSeek+XORdata.length+1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
tempFile.checksumInput=crc32(original);
|
||||
tempFile.checksumOutput=crc32(modified);
|
||||
return tempFile
|
||||
patch.checksumInput=crc32(original);
|
||||
patch.checksumOutput=crc32(modified);
|
||||
return patch
|
||||
}
|
374
vcdiff.js
Normal file
374
vcdiff.js
Normal file
|
@ -0,0 +1,374 @@
|
|||
/* VCDIFF module for RomPatcher.js v20181021 - Marc Robledo 2018 - http://www.marcrobledo.com/license */
|
||||
/* File format specification: https://tools.ietf.org/html/rfc3284 */
|
||||
/*
|
||||
Mostly based in:
|
||||
https://github.com/vic-alexiev/TelerikAcademy/tree/master/C%23%20Fundamentals%20II/Homework%20Assignments/3.%20Methods/000.%20MiscUtil/Compression/Vcdiff
|
||||
some code and ideas borrowed from:
|
||||
https://hack64.net/jscripts/libpatch.js?6
|
||||
*/
|
||||
|
||||
//const VCDIFF_MAGIC=0xd6c3c400;
|
||||
const VCDIFF_MAGIC='\xd6\xc3\xc4';
|
||||
/*
|
||||
const XDELTA_014_MAGIC='%XDELTA';
|
||||
const XDELTA_018_MAGIC='%XDZ000';
|
||||
const XDELTA_020_MAGIC='%XDZ001';
|
||||
const XDELTA_100_MAGIC='%XDZ002';
|
||||
const XDELTA_104_MAGIC='%XDZ003';
|
||||
const XDELTA_110_MAGIC='%XDZ004';
|
||||
*/
|
||||
|
||||
|
||||
function VCDIFF(patchFile){
|
||||
this.file=patchFile;
|
||||
}
|
||||
VCDIFF.prototype.toString=function(){
|
||||
return 'VCDIFF patch'
|
||||
}
|
||||
|
||||
VCDIFF.prototype.apply=function(romFile, validate){
|
||||
//romFile._u8array=new Uint8Array(romFile._dataView.buffer);
|
||||
|
||||
//var t0=performance.now();
|
||||
var parser=new VCDIFF_Parser(this.file);
|
||||
|
||||
//read header
|
||||
parser.seek(4);
|
||||
var headerIndicator=parser.readU8();
|
||||
|
||||
if(headerIndicator & VCD_DECOMPRESS){
|
||||
//has secondary decompressor, read its id
|
||||
var secondaryDecompressorId=parser.readU8();
|
||||
|
||||
if(secondaryDecompressorId!==0)
|
||||
throw new Error('not implemented: secondary decompressor');
|
||||
}
|
||||
|
||||
|
||||
if(headerIndicator & VCD_CODETABLE){
|
||||
var codeTableDataLength=parser.read7BitEncodedInt();
|
||||
|
||||
if(codeTableDataLength!==0)
|
||||
throw new Error('not implemented: custom code table'); // custom code table
|
||||
}
|
||||
|
||||
if(headerIndicator & VCD_APPHEADER){
|
||||
// ignore app header data
|
||||
var appDataLength=parser.read7BitEncodedInt();
|
||||
parser.skip(appDataLength);
|
||||
}
|
||||
var headerEndOffset=parser.offset;
|
||||
|
||||
//calculate target file size
|
||||
var newFileSize=0;
|
||||
while(!parser.isEOF()){
|
||||
var winHeader=parser.decodeWindowHeader();
|
||||
newFileSize+=winHeader.targetWindowLength;
|
||||
parser.skip(winHeader.addRunDataLength + winHeader.addressesLength + winHeader.instructionsLength);
|
||||
}
|
||||
tempFile=new MarcFile(newFileSize);
|
||||
|
||||
|
||||
|
||||
|
||||
parser.seek(headerEndOffset);
|
||||
|
||||
|
||||
|
||||
var cache = new VCD_AdressCache(4,3);
|
||||
var codeTable = VCD_DEFAULT_CODE_TABLE;
|
||||
|
||||
var targetWindowPosition = 0; //renombrar
|
||||
|
||||
while(!parser.isEOF()){
|
||||
var winHeader = parser.decodeWindowHeader();
|
||||
|
||||
var addRunDataStream = new VCDIFF_Parser(this.file, parser.offset);
|
||||
var instructionsStream = new VCDIFF_Parser(this.file, addRunDataStream.offset + winHeader.addRunDataLength);
|
||||
var addressesStream = new VCDIFF_Parser(this.file, instructionsStream.offset + winHeader.instructionsLength);
|
||||
|
||||
var addRunDataIndex = 0;
|
||||
|
||||
cache.reset(addressesStream);
|
||||
|
||||
var addressesStreamEndOffset = addressesStream.offset;
|
||||
while(instructionsStream.offset<addressesStreamEndOffset){
|
||||
/*
|
||||
var instructionIndex=instructionsStream.readS8();
|
||||
if(instructionIndex===-1){
|
||||
break;
|
||||
}
|
||||
*/
|
||||
var instructionIndex = instructionsStream.readU8();
|
||||
|
||||
|
||||
for(var i=0; i<2; i++){
|
||||
var instruction=codeTable[instructionIndex][i];
|
||||
var size=instruction.size;
|
||||
|
||||
if(size===0 && instruction.type!==VCD_NOOP){
|
||||
size=instructionsStream.read7BitEncodedInt()
|
||||
}
|
||||
|
||||
if(instruction.type===VCD_NOOP){
|
||||
continue;
|
||||
|
||||
}else if(instruction.type===VCD_ADD){
|
||||
addRunDataStream.copyToFile2(tempFile, addRunDataIndex+targetWindowPosition, size);
|
||||
addRunDataIndex += size;
|
||||
|
||||
}else if(instruction.type===VCD_COPY){
|
||||
var addr = cache.decodeAddress(addRunDataIndex+winHeader.sourceLength, instruction.mode);
|
||||
var absAddr = 0;
|
||||
|
||||
// source segment and target segment are treated as if they're concatenated
|
||||
var sourceData = null;
|
||||
if(addr < winHeader.sourceLength){
|
||||
absAddr = winHeader.sourcePosition + addr;
|
||||
if(winHeader.indicator & VCD_SOURCE){
|
||||
sourceData = romFile;
|
||||
}else if(winHeader.indicator & VCD_TARGET){
|
||||
sourceData = tempFile;
|
||||
}
|
||||
}else{
|
||||
absAddr = targetWindowPosition + (addr - winHeader.sourceLength);
|
||||
sourceData = tempFile;
|
||||
}
|
||||
|
||||
while(size--){
|
||||
tempFile._u8array[targetWindowPosition + addRunDataIndex++]=sourceData._u8array[absAddr++];
|
||||
//targetU8[targetWindowPosition + targetWindowOffs++] = copySourceU8[absAddr++];
|
||||
}
|
||||
//to-do: test
|
||||
//sourceData.copyToFile2(tempFile, absAddr, size, targetWindowPosition + addRunDataIndex);
|
||||
//addRunDataIndex += size;
|
||||
}else if(instruction.type===VCD_RUN){
|
||||
var runByte = addRunDataStream.readU8();
|
||||
var offset = targetWindowPosition + addRunDataIndex;
|
||||
for(var j=0; j<size; j++){
|
||||
tempFile._u8array[offset+j]=runByte;
|
||||
}
|
||||
|
||||
addRunDataIndex += size;
|
||||
}else{
|
||||
throw new Error('invalid instruction type found');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(validate && winHeader.adler32 && (winHeader.adler32 !== adler32(tempFile, targetWindowPosition, winHeader.targetWindowLength))){
|
||||
throw new Error('error_crc_output');
|
||||
}
|
||||
|
||||
parser.skip(winHeader.addRunDataLength + winHeader.addressesLength + winHeader.instructionsLength);
|
||||
targetWindowPosition += winHeader.targetWindowLength;
|
||||
}
|
||||
|
||||
//console.log((performance.now()-t0)/1000);
|
||||
return tempFile;
|
||||
}
|
||||
|
||||
|
||||
function parseVCDIFF(file){
|
||||
return new VCDIFF(file);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function VCDIFF_Parser(marcFile, offset)
|
||||
{
|
||||
MarcFile.call(this, 0);
|
||||
this.fileSize=marcFile.fileSize;
|
||||
this._u8array=marcFile._u8array;
|
||||
this._dataView=marcFile._dataView;
|
||||
this.offset=offset || 0;
|
||||
}
|
||||
|
||||
VCDIFF_Parser.prototype=Object.create(MarcFile.prototype);
|
||||
VCDIFF_Parser.prototype.read7BitEncodedInt=function(){
|
||||
var num=0, bits = 0;
|
||||
|
||||
do {
|
||||
bits = this.readU8();
|
||||
num = (num << 7) + (bits & 0x7f);
|
||||
} while(bits & 0x80);
|
||||
|
||||
return num;
|
||||
}
|
||||
VCDIFF_Parser.prototype.decodeWindowHeader=function(){
|
||||
var windowHeader={
|
||||
indicator:this.readU8(),
|
||||
sourceLength:0,
|
||||
sourcePosition:0,
|
||||
adler32:false
|
||||
};
|
||||
|
||||
|
||||
if(windowHeader.indicator & (VCD_SOURCE | VCD_TARGET)){
|
||||
windowHeader.sourceLength = this.read7BitEncodedInt();
|
||||
windowHeader.sourcePosition = this.read7BitEncodedInt();
|
||||
}
|
||||
|
||||
windowHeader.deltaLength = this.read7BitEncodedInt();
|
||||
windowHeader.targetWindowLength = this.read7BitEncodedInt();
|
||||
windowHeader.deltaIndicator = this.readU8(); // secondary compression: 1=VCD_DATACOMP,2=VCD_INSTCOMP,4=VCD_ADDRCOMP
|
||||
if(windowHeader.deltaIndicator!==0){
|
||||
throw new Error('unimplemented windowHeader.deltaIndicator:'+windowHeader.deltaIndicator);
|
||||
}
|
||||
|
||||
windowHeader.addRunDataLength = this.read7BitEncodedInt();
|
||||
windowHeader.instructionsLength = this.read7BitEncodedInt();
|
||||
windowHeader.addressesLength = this.read7BitEncodedInt();
|
||||
|
||||
if(windowHeader.indicator & VCD_ADLER32){
|
||||
windowHeader.adler32 = this.readU32();
|
||||
}
|
||||
|
||||
|
||||
return windowHeader;
|
||||
}
|
||||
|
||||
|
||||
VCDIFF_Parser.prototype.copyToFile2=function(target, targetOffset, len){
|
||||
for(var i=0; i<len; i++){
|
||||
target._u8array[targetOffset+i]=this._u8array[this.offset+i];
|
||||
}
|
||||
//this.file.copyToFile(target, this.offset, len, targetOffset);
|
||||
this.skip(len);
|
||||
}
|
||||
|
||||
//------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
// hdrIndicator
|
||||
const VCD_DECOMPRESS = 0x01;
|
||||
const VCD_CODETABLE = 0x02;
|
||||
const VCD_APPHEADER = 0x04; // nonstandard?
|
||||
|
||||
// winIndicator
|
||||
const VCD_SOURCE = 0x01;
|
||||
const VCD_TARGET = 0x02;
|
||||
const VCD_ADLER32 = 0x04;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function VCD_Instruction(instruction, size, mode){
|
||||
this.type=instruction;
|
||||
this.size=size;
|
||||
this.mode=mode;
|
||||
}
|
||||
|
||||
/*
|
||||
build the default code table (used to encode/decode instructions) specified in RFC 3284
|
||||
heavily based on
|
||||
https://github.com/vic-alexiev/TelerikAcademy/blob/master/C%23%20Fundamentals%20II/Homework%20Assignments/3.%20Methods/000.%20MiscUtil/Compression/Vcdiff/CodeTable.cs
|
||||
*/
|
||||
const VCD_NOOP=0;
|
||||
const VCD_ADD=1;
|
||||
const VCD_RUN=2;
|
||||
const VCD_COPY=3;
|
||||
const VCD_DEFAULT_CODE_TABLE=(function(){
|
||||
var entries=[];
|
||||
|
||||
var empty = {type: VCD_NOOP, size: 0, mode: 0};
|
||||
|
||||
// 0
|
||||
entries.push([{type: VCD_RUN, size: 0, mode: 0}, empty]);
|
||||
|
||||
// 1,18
|
||||
for(var size=0; size<18; size++){
|
||||
entries.push([{type: VCD_ADD, size: size, mode: 0}, empty]);
|
||||
}
|
||||
|
||||
// 19,162
|
||||
for(var mode=0; mode<9; mode++){
|
||||
entries.push([{type: VCD_COPY, size: 0, mode: mode}, empty]);
|
||||
|
||||
for(var size=4; size<19; size++){
|
||||
entries.push([{type: VCD_COPY, size: size, mode: mode}, empty]);
|
||||
}
|
||||
}
|
||||
|
||||
// 163,234
|
||||
for(var mode=0; mode<6; mode++){
|
||||
for(var addSize=1; addSize<5; addSize++){
|
||||
for(var copySize=4; copySize<7; copySize++){
|
||||
entries.push([{type: VCD_ADD, size: addSize, mode: 0},
|
||||
{type: VCD_COPY, size: copySize, mode: mode}]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 235,246
|
||||
for(var mode=6; mode<9; mode++){
|
||||
for(var addSize=1; addSize<5; addSize++){
|
||||
entries.push([{type: VCD_ADD, size: addSize, mode: 0},
|
||||
{type: VCD_COPY, size: 4, mode: mode}]);
|
||||
}
|
||||
}
|
||||
|
||||
// 247,255
|
||||
for(var mode=0; mode<9; mode++){
|
||||
entries.push([{type: VCD_COPY, size: 4, mode: mode},
|
||||
{type: VCD_ADD, size: 1, mode: 0}]);
|
||||
}
|
||||
|
||||
return entries;
|
||||
})();
|
||||
|
||||
|
||||
|
||||
/*
|
||||
ported from https://github.com/vic-alexiev/TelerikAcademy/tree/master/C%23%20Fundamentals%20II/Homework%20Assignments/3.%20Methods/000.%20MiscUtil/Compression/Vcdiff
|
||||
by Victor Alexiev (https://github.com/vic-alexiev)
|
||||
*/
|
||||
const VCD_MODE_SELF=0;
|
||||
const VCD_MODE_HERE=1;
|
||||
function VCD_AdressCache(nearSize, sameSize){
|
||||
this.nearSize=nearSize;
|
||||
this.sameSize=sameSize;
|
||||
|
||||
this.near=new Array(nearSize);
|
||||
this.same=new Array(sameSize*256);
|
||||
}
|
||||
VCD_AdressCache.prototype.reset=function(addressStream){
|
||||
this.nextNearSlot=0;
|
||||
this.near.fill(0);
|
||||
this.same.fill(0);
|
||||
|
||||
this.addressStream=addressStream;
|
||||
}
|
||||
VCD_AdressCache.prototype.decodeAddress=function(here, mode){
|
||||
var address=0;
|
||||
|
||||
if(mode===VCD_MODE_SELF){
|
||||
address=this.addressStream.read7BitEncodedInt();
|
||||
}else if(mode===VCD_MODE_HERE){
|
||||
address=here-this.addressStream.read7BitEncodedInt();
|
||||
}else if(mode-2<this.nearSize){ //near cache
|
||||
address=this.near[mode-2]+this.addressStream.read7BitEncodedInt();
|
||||
}else{ //same cache
|
||||
var m=mode-(2+this.nearSize);
|
||||
address=this.same[m*256+this.addressStream.readU8()];
|
||||
}
|
||||
|
||||
this.update(address);
|
||||
return address;
|
||||
}
|
||||
VCD_AdressCache.prototype.update=function(address){
|
||||
if(this.nearSize>0){
|
||||
this.near[this.nextNearSlot]=address;
|
||||
this.nextNearSlot=(this.nextNearSlot+1)%this.nearSize;
|
||||
}
|
||||
|
||||
if(this.sameSize>0){
|
||||
this.same[address%(this.sameSize*256)]=address;
|
||||
}
|
||||
}
|
62
worker_apply.js
Normal file
62
worker_apply.js
Normal 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
23
worker_crc.js
Normal 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
63
worker_create.js
Normal 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
973
xdelta.js
|
@ -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++];
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
Loading…
Add table
Add a link
Reference in a new issue