1
0
Fork 0
mirror of https://github.com/marcrobledo/RomPatcher.js.git synced 2025-09-15 17:26:57 +00:00

version 3.0

This commit is contained in:
Marc Robledo 2024-08-09 19:30:49 +02:00
parent 20a97ae39f
commit f7014075ec
86 changed files with 7587 additions and 238 deletions

0
legacy/.nojekyll Normal file
View file

25
legacy/LICENSE Normal file
View file

@ -0,0 +1,25 @@
MIT License
Copyright (c) 2017-2023 Marc Robledo
This project incorporates components from Octicons
(https://github.com/primer/octicons/) Copyright (c) 2023 GitHub Inc.,
also released under MIT license.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

2
legacy/README.md Normal file
View file

@ -0,0 +1,2 @@
# Rom Patcher JS - Legacy version
This is the legacy 2.9.1 Rom Patcher JS, kept for compatibility purposes.

207
legacy/index.html Normal file
View file

@ -0,0 +1,207 @@
<!DOCTYPE html>
<html translate="no">
<head>
<title>Rom Patcher JS</title>
<meta http-equiv="content-Type" content="text/html; charset=UTF-8"/>
<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="./style/app_icon_16.png" type="image/png" sizes="16x16"/>
<link rel="shortcut icon" href="./style/app_icon_192.png" type="image/png" sizes="192x192"/>
<!-- iOS icons -->
<link rel="apple-touch-icon" sizes="57x57" href="./style/app_icon_114.png" />
<link rel="apple-touch-icon" sizes="114x114" href="./style/app_icon_114.png" />
<link rel="apple-touch-icon" sizes="72x72" href="./style/app_icon_144.png" />
<link rel="apple-touch-icon" sizes="144x144" href="./style/app_icon_144.png" />
<link rel="apple-touch-icon" href="./style/app_icon_192.png" />
<!-- cache -->
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<!-- social network metatags -->
<meta name="twitter:site" content="@marc_robledo">
<meta name="twitter:creator" content="@marc_robledo">
<meta name="twitter:domain" content="marcrobledo.com">
<meta property="og:title" content="Rom Patcher JS">
<meta name="twitter:title" content="Rom Patcher JS">
<meta name="twitter:description" content="An online web-based ROM patcher. Supported formats: IPS, BPS, UPS, APS, RUP, PPF and xdelta.">
<meta property="og:image" content="https://www.marcrobledo.com/RomPatcher.js/style/thumbnail.jpg">
<meta name="twitter:image" content="https://www.marcrobledo.com/RomPatcher.js/style/thumbnail.jpg">
<meta name="twitter:card" content="photo">
<link type="text/css" rel="stylesheet" href="./style/RomPatcher.css" media="all"/>
<script type="text/javascript" src="./js/locale.js"></script>
<script type="text/javascript" src="./js/RomPatcher.js"></script>
<script type="text/javascript" src="./js/MarcFile.js"></script>
<script type="text/javascript" src="./js/crc.js"></script>
<script type="text/javascript" src="./js/formats/zip.js"></script>
<script type="text/javascript" src="./js/formats/ips.js"></script>
<script type="text/javascript" src="./js/formats/ups.js"></script>
<script type="text/javascript" src="./js/formats/aps_n64.js"></script>
<script type="text/javascript" src="./js/formats/aps_gba.js"></script>
<script type="text/javascript" src="./js/formats/bps.js"></script>
<script type="text/javascript" src="./js/formats/rup.js"></script>
<script type="text/javascript" src="./js/formats/ppf.js"></script>
<script type="text/javascript" src="./js/formats/pmsr.js"></script>
<script type="text/javascript" src="./js/formats/vcdiff.js"></script>
<script type="text/javascript" src="./js/zip.js/zip.js"></script>
</head>
<body><div id="column">
<!-- HEADER -->
<header><img src="./style/logo.png" /><h1>Rom Patcher JS</h1></header>
<!-- APP -->
<div id="wrapper">
<div id="switch-container"><span id="switch-create-button" class="button-outer"><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 m-b">
<div class="leftcol text-right"><label for="input-file-rom" data-localize="rom_file">ROM file:</label></div>
<div class="rightcol">
<input type="file" id="input-file-rom" class="enabled" />
</div>
</div>
<div class="row m-b" id="rom-info">
<div class="leftcol text-right">CRC32:</div><div class="rightcol"><span id="crc32"></span></div>
<div class="leftcol text-right">MD5:</div><div class="rightcol"><span id="md5"></span></div>
<div class="leftcol text-right">SHA-1:</div><div class="rightcol"><span id="sha1"></span></div>
</div>
<div class="row m-b hide" id="row-removeheader">
<div class="leftcol text-right"></div>
<div class="rightcol">
<input type="checkbox" id="checkbox-removeheader" /> <label for="checkbox-removeheader" data-localize="remove_header">Remove header before patching</label>
</div>
</div>
<div class="row m-b hide" id="row-addheader">
<div class="leftcol text-right"></div>
<div class="rightcol">
<input type="checkbox" id="checkbox-addheader" /> <label for="checkbox-addheader" data-localize="add_header">Add temporary header</label> <small>(<label id="headersize" for="checkbox-addheader"></label>)</small>
</div>
</div>
<div class="row m-b" id="row-file-patch">
<div class="leftcol text-right"><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,.rup,.ppf,.mod,.xdelta,.vcdiff,.zip"/>
</div>
</div>
<div class="row m-b hide" id="row-expected-source-info"></div>
<div class="buttons">
<span id="message-apply" class="message"></span>
<button id="button-apply" data-localize="apply_patch" class="disabled" disabled>Apply patch</button>
</div>
</div>
<div id="tab1" class="tab">
<div class="row m-b">
<div class="leftcol text-right"><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 m-b">
<div class="leftcol text-right"><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 m-b">
<div class="leftcol text-right" data-localize="patch_type">Patch type:</div>
<div class="rightcol">
<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="rup">RUP</option>
</select>
</div>
</div>
<div class="buttons">
<span id="message-create" class="message"></span>
<button id="button-create" class="disabled" disabled data-localize="create_patch">Create patch</button>
</div>
</div>
</div>
<!-- FOOTER -->
<footer>
<div>
<button id="button-settings" class="button-outer"><img src="style/icon_settings.svg" class="icon settings" /> <span data-localize="settings">Settings</span></button>
</div>
Rom Patcher JS <small>v2.9.1 <a style="color:rgb(255, 197, 7)" href="../" rel="nofollow">Legacy</a></small> by <a href="/">Marc Robledo</a>
<br />
<img src="style/icon_github.svg" class="icon github" /> <a href="https://github.com/marcrobledo/RomPatcher.js/" target="_blank">See on GitHub</a>
<img src="style/icon_heart.svg" class="icon heart" /> <a href="https://www.paypal.me/marcrobledo/5" target="_blank" rel="nofollow">Donate</a>
</footer>
</div>
<!-- SETTINGS DIALOG -->
<div id="dialog-backdrop">
<div id="zip-dialog" class="dialog">
<div id="zip-dialog-message" class="text-center"></div>
<ul id="zip-dialog-file-list"></ul>
</div>
<div id="settings-dialog" class="dialog">
<div class="text-right m-b"><img id="settings-close-dialog" src="style/icon_close.svg" /></div>
<div class="row m-b">
<div class="leftcol"><label for="select-language">Language</label></div>
<div class="rightcol text-right">
<select id="select-language" class="enabled w100">
<option value="en">English</option>
<option value="fr">Français</option>
<option value="de">Deutsch</option>
<option value="it">Italiano</option>
<option value="es">Español</option>
<option value="nl">Nederlands</option>
<option value="sv">Svenska</option>
<option value="ca">Català</option>
<option value="ca-va">Valencià</option>
<option value="pt-br">Português Brasileiro</option>
<option value="ru">Russian</option>
<option value="ja">日本語</option>
<option value="zh-cn">中文(简体)</option>
<option value="zh-tw">中文(正體)</option>
</select>
</div>
</div>
<div class="row m-b">
<div class="leftcol-lg"><label data-localize="alternate_output_name">Use patch name for output</label></div>
<div class="rightcol-lg text-right"><span id="switch-output-name" class="switch disabled"></span></div>
</div>
<div class="row m-b">
<div class="leftcol-lg"><label data-localize="fix_checksum">Fix ROM checksum</label></div>
<div class="rightcol-lg text-right"><span id="switch-fix-checksum" class="switch disabled"></span></div>
</div>
<div class="row">
<div class="leftcol-lg"><label data-localize="light_theme">Light theme</label></div>
<div class="rightcol-lg text-right"><span id="switch-theme" class="switch disabled"></span></div>
</div>
</div>
</div>
</body></html>

273
legacy/js/MarcFile.js Normal file
View file

@ -0,0 +1,273 @@
/* MODDED VERSION OF MarcFile.js v20230202 - Marc Robledo 2014-2023 - 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.getExtension=function(){
var ext=this.fileName? this.fileName.toLowerCase().match(/\.(\w+)$/) : '';
return ext? ext[1] : '';
}
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;
}

997
legacy/js/RomPatcher.js Normal file
View file

@ -0,0 +1,997 @@
/* Rom Patcher JS v20240721 - Marc Robledo 2016-2024 - http://www.marcrobledo.com/license */
const TOO_BIG_ROM_SIZE=67108863;
const HEADERS_INFO=[
[/\.nes$/, 16, 1024], //interNES
[/\.fds$/, 16, 65500], //fwNES
[/\.lnx$/, 64, 1024],
//[/\.rom$/, 8192, 1024], //jaguar
[/\.(pce|nes|gbc?|smc|sfc|fig|swc)$/, 512, 1024]
];
/* service worker */
/*
const FORCE_HTTPS=true;
if(FORCE_HTTPS && location.protocol==='http:')
location.href=window.location.href.replace('http:','https:');
else if(location.protocol==='https:' && 'serviceWorker' in navigator && window.location.hostname==='www.marcrobledo.com')
navigator.serviceWorker.register('/RomPatcher.js/_cache_service_worker.js', {scope: '/RomPatcher.js/'});
*/
var romFile, patchFile, patch, romFile1, romFile2, tempFile, headerSize, oldHeader;
var CAN_USE_WEB_WORKERS=true;
var WEBWORKERS_PATH='./js/';
var webWorkerApply,webWorkerCreate,webWorkerCrc;
try{
webWorkerApply=new Worker(WEBWORKERS_PATH + 'worker_apply.js');
webWorkerApply.onmessage = event => { // listen for events from the worker
//retrieve arraybuffers back from webworker
if(!el('checkbox-removeheader').checked && !el('checkbox-addheader').checked){ //when adding/removing header we don't need the arraybuffer back since we made a copy previously
romFile._u8array=event.data.romFileU8Array;
romFile._dataView=new DataView(romFile._u8array.buffer);
}
patchFile._u8array=event.data.patchFileU8Array;
patchFile._dataView=new DataView(patchFile._u8array.buffer);
if(event.data.patchedRomU8Array)
preparePatchedRom(romFile, new MarcFile(event.data.patchedRomU8Array.buffer), headerSize);
setTabApplyEnabled(true);
if(event.data.errorMessage)
setMessage('apply', _(event.data.errorMessage.replace('Error: ','')), 'error');
else
setMessage('apply');
};
webWorkerApply.onerror = event => { // listen for events from the worker
setTabApplyEnabled(true);
setMessage('apply', _(event.message.replace('Error: ','')), 'error');
};
webWorkerCreate=new Worker(WEBWORKERS_PATH + 'worker_create.js');
webWorkerCreate.onmessage = event => { // listen for events from the worker
var newPatchFile=new MarcFile(event.data.patchFileU8Array);
newPatchFile.fileName=romFile2.fileName.replace(/\.[^\.]+$/,'')+'.'+el('select-patch-type').value;
newPatchFile.save();
setMessage('create');
setTabCreateEnabled(true);
};
webWorkerCreate.onerror = event => { // listen for events from the worker
setTabCreateEnabled(true);
setMessage('create', _(event.message.replace('Error: ','')), 'error');
};
webWorkerCrc=new Worker(WEBWORKERS_PATH + 'worker_crc.js');
webWorkerCrc.onmessage = event => { // listen for events from the worker
//console.log('received_crc');
el('crc32').innerHTML=padZeroes(event.data.crc32, 4);
el('md5').innerHTML=padZeroes(event.data.md5, 16);
romFile._u8array=event.data.u8array;
romFile._dataView=new DataView(event.data.u8array.buffer);
if(window.crypto&&window.crypto.subtle&&window.crypto.subtle.digest){
sha1(romFile);
}
validateSource();
setTabApplyEnabled(true);
};
webWorkerCrc.onerror = event => { // listen for events from the worker
setMessage('apply', event.message.replace('Error: ',''), 'error');
};
}catch(e){
CAN_USE_WEB_WORKERS=false;
}
/* Shortcuts */
function addEvent(e,ev,f){e.addEventListener(ev,f,false)}
function el(e){return document.getElementById(e)}
function _(str){return (LOCALIZATION[AppSettings.langCode] && LOCALIZATION[AppSettings.langCode][str]) || LOCALIZATION['en'][str] || str}
/* custom patcher */
function isCustomPatcherEnabled(){
return typeof CUSTOM_PATCHER!=='undefined' && typeof CUSTOM_PATCHER==='object' && CUSTOM_PATCHER.length
}
function parseCustomPatch(customPatch){
patchFile=customPatch.fetchedFile;
patchFile.seek(0);
_readPatchFile();
if(typeof patch.validateSource === 'undefined'){
if(typeof customPatch.crc==='number'){
patch.validateSource=function(romFile,headerSize){
return customPatch.crc===crc32(romFile, headerSize)
};
patch.getValidationInfo=function(){
return [{
'type':'CRC32',
'value':padZeroes(customPatch.crc,4)
}]
};
}else if(typeof customPatch.crc==='object'){
patch.validateSource=function(romFile,headerSize){
for(var i=0; i<customPatch.crc.length; i++)
if(customPatch.crc[i]===crc32(romFile, headerSize))
return true;
return false;
}
patch.getValidationInfo=function(){
return customPatch.crc.map(function(crc){
return {
'type':'CRC32',
'value':padZeroes(crc,4)
}
});
};
}
validateSource();
}
}
function fetchPatch(customPatchIndex, compressedFileIndex){
var customPatch=CUSTOM_PATCHER[customPatchIndex];
setTabApplyEnabled(false);
setMessage('apply', 'downloading', 'loading');
var uri=decodeURI(customPatch.file.trim());
//console.log(patchURI);
if(typeof window.fetch==='function'){
fetch(uri)
.then(result => result.arrayBuffer()) // Gets the response and returns it as a blob
.then(arrayBuffer => {
patchFile=CUSTOM_PATCHER[customPatchIndex].fetchedFile=new MarcFile(arrayBuffer);
patchFile.fileName=customPatch.file.replace(/^.*[\/\\]/g,'');
if(patchFile.getExtension()!=='jar' && patchFile.readString(4).startsWith(ZIP_MAGIC))
ZIPManager.parseFile(CUSTOM_PATCHER[customPatchIndex].fetchedFile, compressedFileIndex);
else
parseCustomPatch(CUSTOM_PATCHER[customPatchIndex]);
setMessage('apply');
})
.catch(function(evt){
setMessage('apply', (_('error_downloading')/* + evt.message */).replace('%s', CUSTOM_PATCHER[customPatchIndex].file.replace(/^.*[\/\\]/g,'')), 'error');
});
}else{
var xhr=new XMLHttpRequest();
xhr.open('GET', uri, true);
xhr.responseType='arraybuffer';
xhr.onload=function(evt){
if(this.status===200){
patchFile=CUSTOM_PATCHER[customPatchIndex].fetchedFile=new MarcFile(xhr.response);
patchFile.fileName=customPatch.file.replace(/^.*[\/\\]/g,'');
if(patchFile.getExtension()!=='jar' && patchFile.readString(4).startsWith(ZIP_MAGIC))
ZIPManager.parseFile(CUSTOM_PATCHER[customPatchIndex].fetchedFile, compressedFileIndex);
else
parseCustomPatch(CUSTOM_PATCHER[customPatchIndex]);
setMessage('apply');
}else{
setMessage('apply', _('error_downloading').replace('%s', CUSTOM_PATCHER[customPatchIndex].file.replace(/^.*[\/\\]/g,''))+' ('+this.status+')', 'error');
}
};
xhr.onerror=function(evt){
setMessage('apply', 'error_downloading', 'error');
};
xhr.send(null);
}
}
function _parseROM(){
el('checkbox-addheader').checked=false;
el('checkbox-removeheader').checked=false;
if(romFile.getExtension()!=='jar' && romFile.readString(4).startsWith(ZIP_MAGIC)){
ZIPManager.parseFile(romFile);
setTabApplyEnabled(false);
}else{
if(headerSize=canHaveFakeHeader(romFile)){
el('row-addheader').className='row m-b';
if(headerSize<1024){
el('headersize').innerHTML=headerSize+'b';
}else{
el('headersize').innerHTML=parseInt(headerSize/1024)+'kb';
}
el('row-removeheader').className='row m-b hide';
}else if(headerSize=hasHeader(romFile)){
el('row-addheader').className='row m-b hide';
el('row-removeheader').className='row m-b';
}else{
el('row-addheader').className='row m-b hide';
el('row-removeheader').className='row m-b hide';
}
updateChecksums(romFile, 0);
}
}
var UI={
localize:function(){
if(typeof LOCALIZATION[AppSettings.langCode]==='undefined')
return false;
var translatableElements=document.querySelectorAll('*[data-localize]');
for(var i=0; i<translatableElements.length; i++){
translatableElements[i].innerHTML=_(translatableElements[i].dataset.localize);
}
},
showDialog:function(id){
el('dialog-backdrop').className='show';
el(id+'-dialog').className='dialog show';
},
hideDialog:function(id){
el('dialog-backdrop').className='';
el(id+'-dialog').className='dialog';
},
isDialogOpen:function(id){
return el(id+'-dialog').className==='dialog show';
},
setLightTheme:function(val){
if(val){
document.body.className='light';
}else{
document.body.className='';
}
}
};
var APP_SETTINGS_ID='rom-patcher-js-settings-legacy';
var AppSettings={
langCode:(typeof navigator.userLanguage==='string')? navigator.userLanguage.substr(0,2) : 'en',
outputFileNameMatch:false,
fixChecksum:false,
lightTheme:false,
load:function(){
if(typeof localStorage!=='undefined' && localStorage.getItem(APP_SETTINGS_ID)){
try{
var loadedSettings=JSON.parse(localStorage.getItem(APP_SETTINGS_ID));
if(typeof loadedSettings.langCode==='string' && typeof LOCALIZATION[loadedSettings.langCode]){
this.langCode=loadedSettings.langCode;
el('select-language').value=this.langCode;
}
if(loadedSettings.outputFileNameMatch===true){
this.outputFileNameMatch=loadedSettings.outputFileNameMatch;
el('switch-output-name').className='switch enabled';
}
if(loadedSettings.fixChecksum===true){
this.fixChecksum=loadedSettings.fixChecksum;
el('switch-fix-checksum').className='switch enabled';
}
if(loadedSettings.lightTheme===true){
this.lightTheme=loadedSettings.lightTheme;
el('switch-theme').className='switch enabled';
UI.setLightTheme(true);
}
}catch(ex){
console.error('Error while loading settings: '+ex.message);
}
}
},
save:function(){
if(typeof localStorage!=='undefined')
localStorage.setItem(APP_SETTINGS_ID, JSON.stringify(this));
}
};
/* initialize app */
addEvent(window,'load',function(){
/* zip-js web worker */
if(CAN_USE_WEB_WORKERS){
zip.useWebWorkers=true;
zip.workerScriptsPath=WEBWORKERS_PATH + 'zip.js/';
}else{
zip.useWebWorkers=false;
var script=document.createElement('script');
script.src=WEBWORKERS_PATH + 'zip.js/inflate.js';
document.getElementsByTagName('head')[0].appendChild(script);
}
/* load settings */
AppSettings.load();
UI.localize();
el('row-file-patch').title=_('compatible_formats')+' IPS, UPS, APS, BPS, RUP, PPF, MOD (Paper Mario Star Rod), xdelta';
el('input-file-rom').value='';
el('input-file-patch').value='';
setTabApplyEnabled(true);
/* dirty fix for mobile Safari https://stackoverflow.com/a/19323498 */
if(/Mobile\/\S+ Safari/.test(navigator.userAgent)){
el('input-file-patch').accept='';
}
/* predefined patches */
if(isCustomPatcherEnabled()){
var select=document.createElement('select');
select.disabled=true;
select.id='input-file-patch';
el('input-file-patch').parentElement.replaceChild(select, el('input-file-patch'));
select.parentElement.title='';
for(var i=0; i<CUSTOM_PATCHER.length; i++){
CUSTOM_PATCHER[i].fetchedFile=false;
CUSTOM_PATCHER[i].selectOption=document.createElement('option');
CUSTOM_PATCHER[i].selectOption.value=i;
CUSTOM_PATCHER[i].selectOption.innerHTML=CUSTOM_PATCHER[i].name || CUSTOM_PATCHER[i].file;
select.appendChild(CUSTOM_PATCHER[i].selectOption);
if(typeof CUSTOM_PATCHER[i].patches==='object'){
for(var j=0; j<CUSTOM_PATCHER[i].patches.length; j++){
if(j===0){
CUSTOM_PATCHER[i].patches[0].selectOption=CUSTOM_PATCHER[i].selectOption;
CUSTOM_PATCHER[i].selectOption=null;
}else{
CUSTOM_PATCHER[i].patches[j].selectOption=document.createElement('option');
select.appendChild(CUSTOM_PATCHER[i].patches[j].selectOption);
}
CUSTOM_PATCHER[i].patches[j].selectOption.value=i+','+j;
CUSTOM_PATCHER[i].patches[j].selectOption.innerHTML=CUSTOM_PATCHER[i].patches[j].name || CUSTOM_PATCHER[i].patches[j].file;
}
}
}
addEvent(select,'change',function(){
var selectedCustomPatchIndex, selectedCustomPatchCompressedIndex, selectedPatch;
if(/^\d+,\d+$/.test(this.value)){
var indexes=this.value.split(',');
selectedCustomPatchIndex=parseInt(indexes[0]);
selectedCustomPatchCompressedIndex=parseInt(indexes[1]);
selectedPatch=CUSTOM_PATCHER[selectedCustomPatchIndex].patches[selectedCustomPatchCompressedIndex];
}else{
selectedCustomPatchIndex=parseInt(this.value);
selectedCustomPatchCompressedIndex=null;
selectedPatch=CUSTOM_PATCHER[selectedCustomPatchIndex];
}
if(selectedPatch.fetchedFile){
parseCustomPatch(selectedPatch);
}else{
patch=null;
patchFile=null;
fetchPatch(selectedCustomPatchIndex, selectedCustomPatchCompressedIndex);
}
});
fetchPatch(0, 0);
}else{
setTabCreateEnabled(true);
el('input-file-rom1').value='';
el('input-file-rom2').value='';
el('switch-container').style.visibility='visible';
addEvent(el('input-file-patch'), 'change', function(){
setTabApplyEnabled(false);
patchFile=new MarcFile(this, _readPatchFile)
});
addEvent(el('input-file-rom1'), 'change', function(){
setTabCreateEnabled(false);
romFile1=new MarcFile(this, function(){setTabCreateEnabled(true)});
});
addEvent(el('input-file-rom2'), 'change', function(){
setTabCreateEnabled(false);
romFile2=new MarcFile(this, function(){setTabCreateEnabled(true)});
});
}
/* event listeners */
addEvent(el('button-settings'), 'click', function(){
UI.showDialog('settings');
});
addEvent(window, 'keyup', function(evt){
if(evt.keyCode===27 && UI.isDialogOpen('settings')){
UI.hideDialog('settings');
}
});
addEvent(el('settings-dialog'), 'click', function(evt){
evt.stopPropagation();
});
addEvent(el('zip-dialog'), 'click', function(evt){
evt.stopPropagation();
});
addEvent(el('settings-close-dialog'), 'click', function(){
UI.hideDialog('settings');
});
addEvent(el('dialog-backdrop'), 'click', function(){
if(UI.isDialogOpen('settings')){
UI.hideDialog('settings');
}
});
addEvent(el('select-language'), 'change', function(){
AppSettings.langCode=this.value;
AppSettings.save();
UI.localize();
});
addEvent(el('switch-output-name'), 'click', function(){
if(this.className==='switch enabled'){
this.className='switch disabled';
AppSettings.outputFileNameMatch=false;
}else{
this.className='switch enabled';
AppSettings.outputFileNameMatch=true;
}
AppSettings.save();
});
addEvent(el('switch-fix-checksum'), 'click', function(){
if(this.className==='switch enabled'){
this.className='switch disabled';
AppSettings.fixChecksum=false;
}else{
this.className='switch enabled';
AppSettings.fixChecksum=true;
}
AppSettings.save();
});
addEvent(el('switch-theme'), 'click', function(){
if(this.className==='switch enabled'){
this.className='switch disabled';
AppSettings.lightTheme=false;
}else{
this.className='switch enabled';
AppSettings.lightTheme=true;
}
UI.setLightTheme(AppSettings.lightTheme);
AppSettings.save();
});
addEvent(el('input-file-rom'), 'change', function(){
setTabApplyEnabled(false);
romFile=new MarcFile(this, _parseROM);
});
addEvent(el('checkbox-removeheader'), 'change', function(){
if(this.checked)
updateChecksums(romFile, headerSize);
else
updateChecksums(romFile, 0);
});
addEvent(el('switch-create-button'), 'click', function(){
setCreatorMode(!/enabled/.test(el('switch-create').className));
});
addEvent(el('button-apply'), 'click', function(){
applyPatch(patch, romFile, false);
});
addEvent(el('button-create'), 'click', function(){
createPatch(romFile1, romFile2, el('select-patch-type').value);
});
//setCreatorMode(true);
});
function canHaveFakeHeader(romFile){
if(romFile.fileSize<=0x600000){
for(var i=0; i<HEADERS_INFO.length; i++){
if(HEADERS_INFO[i][0].test(romFile.fileName) && (romFile.fileSize%HEADERS_INFO[i][2]===0)){
return HEADERS_INFO[i][1];
}
}
}
return 0;
}
function hasHeader(romFile){
if(romFile.fileSize<=0x600200){
if(romFile.fileSize%1024===0)
return 0;
for(var i=0; i<HEADERS_INFO.length; i++){
if(HEADERS_INFO[i][0].test(romFile.fileName) && (romFile.fileSize-HEADERS_INFO[i][1])%HEADERS_INFO[i][1]===0){
return HEADERS_INFO[i][1];
}
}
}
return 0;
}
function updateChecksums(file, startOffset, force){
if(file===romFile && file.fileSize>33554432 && !force){
el('crc32').innerHTML='File is too big. <span onclick=\"updateChecksums(romFile,'+startOffset+',true)\">Force calculate checksum</span>';
el('md5').innerHTML='';
el('sha1').innerHTML='';
setTabApplyEnabled(true);
return false;
}
el('crc32').innerHTML='Calculating...';
el('md5').innerHTML='Calculating...';
if(CAN_USE_WEB_WORKERS){
setTabApplyEnabled(false);
webWorkerCrc.postMessage({u8array:file._u8array, startOffset:startOffset}, [file._u8array.buffer]);
if(window.crypto&&window.crypto.subtle&&window.crypto.subtle.digest){
el('sha1').innerHTML='Calculating...';
}
}else{
window.setTimeout(function(){
el('crc32').innerHTML=padZeroes(crc32(file, startOffset), 4);
el('md5').innerHTML=padZeroes(md5(file, startOffset), 16);
validateSource();
setTabApplyEnabled(true);
}, 30);
if(window.crypto&&window.crypto.subtle&&window.crypto.subtle.digest){
el('sha1').innerHTML='Calculating...';
sha1(file);
}
}
}
function validateSource(){
if(patch && romFile && typeof patch.validateSource !== 'undefined'){
if(patch.validateSource(romFile, el('checkbox-removeheader').checked && hasHeader(romFile))){
el('crc32').className='valid';
setMessage('apply');
}else{
el('crc32').className='invalid';
setMessage('apply', 'error_crc_input', 'warning');
}
}else{
el('crc32').className='';
setMessage('apply');
}
}
function _getRomSystem(file){
if(file.fileSize>0x0200 && file.fileSize%4===0){
if(/\.gbc?/i.test(file.fileName)){
var NINTENDO_LOGO=[
0xce, 0xed, 0x66, 0x66, 0xcc, 0x0d, 0x00, 0x0b, 0x03, 0x73, 0x00, 0x83, 0x00, 0x0c, 0x00, 0x0d,
0x00, 0x08, 0x11, 0x1f, 0x88, 0x89, 0x00, 0x0e, 0xdc, 0xcc, 0x6e, 0xe6, 0xdd, 0xdd, 0xd9, 0x99
];
file.offset=0x104;
var valid=true;
for(var i=0; i<NINTENDO_LOGO.length && valid; i++){
if(NINTENDO_LOGO[i]!==file.readU8())
valid=false;
}
if(valid)
return 'gb';
}else if(/\.(bin|md)?/i.test(file.fileName)){
file.offset=0x0100;
if(/SEGA (GENESIS|MEGA DR)/.test(file.readString(12)))
return 'smd';
}
}
return null;
}
function _getHeaderChecksumInfo(file){
var info={
system:_getRomSystem(file),
current:null,
calculated:null,
fix:null
}
if(info.system==='gb'){
/* get current checksum */
file.offset=0x014d;
info.current=file.readU8();
/* calculate checksum */
info.calculated=0x00;
file.offset=0x0134;
for(var i=0; i<=0x18; i++){
info.calculated=((info.calculated - file.readU8() - 1) >>> 0) & 0xff;
}
/* fix checksum */
info.fix=function(file){
file.offset=0x014d;
file.writeU8(this.calculated);
}
}else if(info.system==='smd'){
/* get current checksum */
file.offset=0x018e;
info.current=file.readU16();
/* calculate checksum */
info.calculated=0x0000;
file.offset=0x0200;
while(!file.isEOF()){
info.calculated=((info.calculated + file.readU16()) >>> 0) & 0xffff;
}
/* fix checksum */
info.fix=function(file){
file.offset=0x018e;
file.writeU16(this.calculated);
}
}else{
info=null;
}
return info;
}
function _readPatchFile(){
setTabApplyEnabled(false);
patchFile.littleEndian=false;
var header=patchFile.readString(6);
if(patchFile.getExtension()!=='jar' && header.startsWith(ZIP_MAGIC)){
patch=false;
validateSource();
setTabApplyEnabled(false);
ZIPManager.parseFile(patchFile);
}else{
if(header.startsWith(IPS_MAGIC)){
patch=parseIPSFile(patchFile);
}else if(header.startsWith(UPS_MAGIC)){
patch=parseUPSFile(patchFile);
}else if(header.startsWith(APS_N64_MAGIC)){
patch=parseAPSFile(patchFile);
}else if(header.startsWith(APS_GBA_MAGIC)){
patch=APSGBA.fromFile(patchFile);
}else if(header.startsWith(BPS_MAGIC)){
patch=parseBPSFile(patchFile);
}else if(header.startsWith(RUP_MAGIC)){
patch=parseRUPFile(patchFile);
}else if(header.startsWith(PPF_MAGIC)){
patch=parsePPFFile(patchFile);
}else if(header.startsWith(PMSR_MAGIC)){
patch=parseMODFile(patchFile);
}else if(header.startsWith(VCDIFF_MAGIC)){
patch=parseVCDIFF(patchFile);
}else{
patch=null;
setMessage('apply', 'error_invalid_patch', 'error');
}
if(patch && patch.getValidationInfo){
const validationInfos=patch.getValidationInfo();
el('row-expected-source-info').className='row m-b';
el('row-expected-source-info').innerHTML='';
validationInfos.forEach(function(validationInfo){
var leftCol=document.createElement('div');
leftCol.className='leftcol text-right';
leftCol.innerHTML=_('expected_source').replace('%s', validationInfo.type);
var rightCol=document.createElement('div');
rightCol.className='rightcol';
/*
var a=document.createElement('a');
a.href='https://www.google.com/search?q=%22'+validationInfo.value+'%22';
a.target='_blank';
a.className='clickable';
a.innerHTML=validationInfo.value;
rightCol.appendChild(a);
*/
rightCol.innerHTML=validationInfo.value;
el('row-expected-source-info').appendChild(leftCol);
el('row-expected-source-info').appendChild(rightCol);
});
}else{
el('row-expected-source-info').className='row m-b hide';
el('row-expected-source-info').innerHTML='';
}
validateSource();
setTabApplyEnabled(true);
}
}
function preparePatchedRom(originalRom, patchedRom, headerSize){
if(AppSettings.outputFileNameMatch){
patchedRom.fileName=patchFile.fileName.replace(/\.\w+$/i, (/\.\w+$/i.test(originalRom.fileName)? originalRom.fileName.match(/\.\w+$/i)[0] : ''));
}else{
patchedRom.fileName=originalRom.fileName.replace(/\.([^\.]*?)$/, ' (patched).$1');
}
patchedRom.fileType=originalRom.fileType;
if(headerSize){
if(el('checkbox-removeheader').checked){
var patchedRomWithOldHeader=new MarcFile(headerSize+patchedRom.fileSize);
oldHeader.copyToFile(patchedRomWithOldHeader, 0);
patchedRom.copyToFile(patchedRomWithOldHeader, 0, patchedRom.fileSize, headerSize);
patchedRomWithOldHeader.fileName=patchedRom.fileName;
patchedRomWithOldHeader.fileType=patchedRom.fileType;
patchedRom=patchedRomWithOldHeader;
}else if(el('checkbox-addheader').checked){
patchedRom=patchedRom.slice(headerSize);
}
}
/* fix checksum if needed */
if(AppSettings.fixChecksum){
var checksumInfo=_getHeaderChecksumInfo(patchedRom);
if(checksumInfo && checksumInfo.current!==checksumInfo.calculated && confirm(_('fix_checksum_prompt')+' ('+padZeroes(checksumInfo.current)+' -> '+padZeroes(checksumInfo.calculated)+')')){
checksumInfo.fix(patchedRom);
}
}
setMessage('apply');
patchedRom.save();
//debug: create unheadered patch
/*if(headerSize && el('checkbox-addheader').checked){
createPatch(romFile, patchedRom);
}*/
}
/*function removeHeader(romFile){
//r._dataView=new DataView(r._dataView.buffer, headerSize);
oldHeader=romFile.slice(0,headerSize);
r=r.slice(headerSize);
}*/
function applyPatch(p,r,validateChecksums){
if(p && r){
if(headerSize){
if(el('checkbox-removeheader').checked){
//r._dataView=new DataView(r._dataView.buffer, headerSize);
oldHeader=r.slice(0,headerSize);
r=r.slice(headerSize);
}else if(el('checkbox-addheader').checked){
var romWithFakeHeader=new MarcFile(headerSize+r.fileSize);
romWithFakeHeader.fileName=r.fileName;
romWithFakeHeader.fileType=r.fileType;
r.copyToFile(romWithFakeHeader, 0, r.fileSize, headerSize);
//add FDS header
if(/\.fds$/.test(r.FileName) && r.fileSize%65500===0){
//romWithFakeHeader.seek(0);
romWithFakeHeader.writeBytes([0x46, 0x44, 0x53, 0x1a, r.fileSize/65500]);
}
r=romWithFakeHeader;
}
}
if(CAN_USE_WEB_WORKERS){
setMessage('apply', 'applying_patch', 'loading');
setTabApplyEnabled(false);
webWorkerApply.postMessage(
{
romFileU8Array:r._u8array,
patchFileU8Array:patchFile._u8array,
validateChecksums:validateChecksums
},[
r._u8array.buffer,
patchFile._u8array.buffer
]
);
}else{
setMessage('apply', 'applying_patch', 'loading');
try{
p.apply(r, validateChecksums);
preparePatchedRom(r, p.apply(r, validateChecksums), headerSize);
}catch(e){
setMessage('apply', 'Error: '+_(e.message), 'error');
}
}
}else{
setMessage('apply', 'No ROM/patch selected', 'error');
}
}
function createPatch(sourceFile, modifiedFile, mode){
if(!sourceFile){
setMessage('create', 'No source ROM file specified.', 'error');
return false;
}else if(!modifiedFile){
setMessage('create', 'No modified ROM file specified.', 'error');
return false;
}
if(CAN_USE_WEB_WORKERS){
setTabCreateEnabled(false);
setMessage('create', 'creating_patch', 'loading');
webWorkerCreate.postMessage(
{
sourceFileU8Array:sourceFile._u8array,
modifiedFileU8Array:modifiedFile._u8array,
modifiedFileName:modifiedFile.fileName,
patchMode:mode
},[
sourceFile._u8array.buffer,
modifiedFile._u8array.buffer
]
);
romFile1=new MarcFile(el('input-file-rom1'));
romFile2=new MarcFile(el('input-file-rom2'));
}else{
try{
sourceFile.seek(0);
modifiedFile.seek(0);
var newPatch;
if(mode==='ips'){
newPatch=createIPSFromFiles(sourceFile, modifiedFile);
}else if(mode==='bps'){
newPatch=createBPSFromFiles(sourceFile, modifiedFile, (sourceFile.fileSize<=4194304));
}else if(mode==='ups'){
newPatch=createUPSFromFiles(sourceFile, modifiedFile);
}else if(mode==='aps'){
newPatch=createAPSFromFiles(sourceFile, modifiedFile);
}else if(mode==='rup'){
newPatch=createRUPFromFiles(sourceFile, modifiedFile);
}else{
setMessage('create', 'error_invalid_patch', 'error');
}
if(crc32(modifiedFile)===crc32(newPatch.apply(sourceFile))){
newPatch.export(modifiedFile.fileName.replace(/\.[^\.]+$/,'')).save();
}else{
setMessage('create', 'Unexpected error: verification failed. Patched file and modified file mismatch. Please report this bug.', 'error');
}
}catch(e){
setMessage('create', 'Error: '+_(e.message), 'error');
}
}
}
/* GUI functions */
function setMessage(tab, key, className){
var messageBox=el('message-'+tab);
if(key){
messageBox.setAttribute('data-localize',key);
if(className==='loading'){
messageBox.className='message';
messageBox.innerHTML='<span class="loading"></span> '+_(key);
}else{
messageBox.className='message '+className;
if(className==='warning')
messageBox.innerHTML='&#9888; '+_(key);
else if(className==='error')
messageBox.innerHTML='&#10007; '+_(key);
else
messageBox.innerHTML=_(key);
}
messageBox.style.display='inline';
}else{
messageBox.style.display='none';
}
}
function setElementEnabled(element,status){
if(status){
el(element).className='enabled';
}else{
el(element).className='disabled';
}
el(element).disabled=!status;
}
function setTabCreateEnabled(status){
if(
(romFile1 && romFile1.fileSize>TOO_BIG_ROM_SIZE) ||
(romFile2 && romFile2.fileSize>TOO_BIG_ROM_SIZE)
){
setMessage('create',_('warning_too_big'),'warning');
}
setElementEnabled('input-file-rom1', status);
setElementEnabled('input-file-rom2', status);
setElementEnabled('select-patch-type', status);
if(romFile1 && romFile2 && status){
setElementEnabled('button-create', status);
}else{
setElementEnabled('button-create', false);
}
}
function setTabApplyEnabled(status){
setElementEnabled('input-file-rom', status);
setElementEnabled('input-file-patch', status);
if(romFile && status && (patch || isCustomPatcherEnabled())){
setElementEnabled('button-apply', status);
}else{
setElementEnabled('button-apply', false);
}
}
function setCreatorMode(creatorMode){
if(creatorMode){
el('tab0').style.display='none';
el('tab1').style.display='block';
el('switch-create').className='switch enabled'
}else{
el('tab0').style.display='block';
el('tab1').style.display='none';
el('switch-create').className='switch disabled'
}
}
/* FileSaver.js (source: http://purl.eligrey.com/github/FileSaver.js/blob/master/src/FileSaver.js)
* A saveAs() FileSaver implementation.
* 1.3.8
* 2018-03-22 14:03:47
*
* By Eli Grey, https://eligrey.com
* License: MIT
* See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
*/
var saveAs=saveAs||function(c){"use strict";if(!(void 0===c||"undefined"!=typeof navigator&&/MSIE [1-9]\./.test(navigator.userAgent))){var t=c.document,f=function(){return c.URL||c.webkitURL||c},s=t.createElementNS("http://www.w3.org/1999/xhtml","a"),d="download"in s,u=/constructor/i.test(c.HTMLElement)||c.safari,l=/CriOS\/[\d]+/.test(navigator.userAgent),p=c.setImmediate||c.setTimeout,v=function(t){p(function(){throw t},0)},w=function(t){setTimeout(function(){"string"==typeof t?f().revokeObjectURL(t):t.remove()},4e4)},m=function(t){return/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(t.type)?new Blob([String.fromCharCode(65279),t],{type:t.type}):t},r=function(t,n,e){e||(t=m(t));var r,o=this,a="application/octet-stream"===t.type,i=function(){!function(t,e,n){for(var r=(e=[].concat(e)).length;r--;){var o=t["on"+e[r]];if("function"==typeof o)try{o.call(t,n||t)}catch(t){v(t)}}}(o,"writestart progress write writeend".split(" "))};if(o.readyState=o.INIT,d)return r=f().createObjectURL(t),void p(function(){var t,e;s.href=r,s.download=n,t=s,e=new MouseEvent("click"),t.dispatchEvent(e),i(),w(r),o.readyState=o.DONE},0);!function(){if((l||a&&u)&&c.FileReader){var e=new FileReader;return e.onloadend=function(){var t=l?e.result:e.result.replace(/^data:[^;]*;/,"data:attachment/file;");c.open(t,"_blank")||(c.location.href=t),t=void 0,o.readyState=o.DONE,i()},e.readAsDataURL(t),o.readyState=o.INIT}r||(r=f().createObjectURL(t)),a?c.location.href=r:c.open(r,"_blank")||(c.location.href=r);o.readyState=o.DONE,i(),w(r)}()},e=r.prototype;return"undefined"!=typeof navigator&&navigator.msSaveOrOpenBlob?function(t,e,n){return e=e||t.name||"download",n||(t=m(t)),navigator.msSaveOrOpenBlob(t,e)}:(e.abort=function(){},e.readyState=e.INIT=0,e.WRITING=1,e.DONE=2,e.error=e.onwritestart=e.onprogress=e.onwrite=e.onabort=e.onerror=e.onwriteend=null,function(t,e,n){return new r(t,e||t.name||"download",n)})}}("undefined"!=typeof self&&self||"undefined"!=typeof window&&window||this);

126
legacy/js/crc.js Normal file
View file

@ -0,0 +1,126 @@
/* Rom Patcher JS - CRC32/MD5/SHA-1/checksums calculators v20210815 - Marc Robledo 2016-2021 - 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;
tail[15]=Math.floor(n/536870912) >>> 0; //if file is bigger than 512Mb*8, value is bigger than 32 bits, so it needs two words to store its length
_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;
}
/* CRC16/CCITT-FALSE */
function crc16(marcFile, offset, len){
var crc=0xffff;
offset=offset? offset : 0;
len=len && len>0? len : marcFile.fileSize;
for(var i=0; i<len; i++){
crc ^= marcFile._u8array[offset++] << 8;
for (j=0; j<8; ++j) {
crc = (crc & 0x8000) >>> 0 ? (crc << 1) ^ 0x1021 : crc << 1;
}
}
return crc & 0xffff;
}

View file

@ -0,0 +1,111 @@
/* APS (GBA) module for Rom Patcher JS v20230331 - Marc Robledo 2017-2023 - http://www.marcrobledo.com/license */
/* File format specification: https://github.com/btimofeev/UniPatcher/wiki/APS-(GBA) */
const APS_GBA_MAGIC='APS1';
const APS_GBA_BLOCK_SIZE=0x010000; //64Kb
const APS_GBA_RECORD_SIZE=4 + 2 + 2 + APS_GBA_BLOCK_SIZE;
function APSGBA(){
this.sourceSize=0;
this.targetSize=0;
this.records=[];
}
APSGBA.prototype.addRecord=function(offset, sourceCrc16, targetCrc16, xorBytes){
this.records.push({
offset:offset,
sourceCrc16:sourceCrc16,
targetCrc16:targetCrc16,
xorBytes:xorBytes}
);
}
APSGBA.prototype.toString=function(){
var s='Total records: '+this.records.length;
s+='\nInput file size: '+this.sourceSize;
s+='\nOutput file size: '+this.targetSize;
return s
}
APSGBA.prototype.validateSource=function(sourceFile){
if(sourceFile.fileSize!==this.sourceSize)
return false;
for(var i=0; i<this.records.length; i++){
sourceFile.seek(this.records[i].offset);
var bytes=sourceFile.readBytes(APS_GBA_BLOCK_SIZE);
if(crc16(sourceFile, this.records[i].offset, APS_GBA_BLOCK_SIZE) !== this.records[i].sourceCrc16)
return false
}
return true
}
APSGBA.prototype.export=function(fileName){
var patchFileSize=12 + (this.records.length * APS_GBA_RECORD_SIZE);
tempFile=new MarcFile(patchFileSize);
tempFile.littleEndian=true;
tempFile.fileName=fileName+'.aps';
tempFile.writeString(APS_GBA_MAGIC, APS_GBA_MAGIC.length);
tempFile.writeU32(this.sourceSize);
tempFile.writeU32(this.targetSize);
for(var i=0; i<this.records.length; i++){
tempFile.writeU32(this.records[i].offset);
tempFile.writeU16(this.records[i].sourceCrc16);
tempFile.writeU16(this.records[i].targetCrc16);
tempFile.writeBytes(this.records[i].xorBytes);
}
return tempFile
}
APSGBA.prototype.apply=function(romFile, validate){
if(validate && !this.validateSource(romFile)){
throw new Error('error_crc_input');
}
tempFile=new MarcFile(this.targetSize);
romFile.copyToFile(tempFile, 0, romFile.fileSize);
for(var i=0; i<this.records.length; i++){
romFile.seek(this.records[i].offset);
tempFile.seek(this.records[i].offset);
for(var j=0; j<APS_GBA_BLOCK_SIZE; j++){
tempFile.writeU8(romFile.readU8() ^ this.records[i].xorBytes[j]);
}
if(validate && crc16(tempFile, this.records[i].offset, APS_GBA_BLOCK_SIZE)!==this.records[i].targetCrc16){
throw new Error('error_crc_output');
}
}
return tempFile
}
APSGBA.fromFile=function(patchFile){
patchFile.seek(0);
patchFile.littleEndian=true;
if(
patchFile.readString(APS_GBA_MAGIC.length)!==APS_GBA_MAGIC ||
patchFile.fileSize < (12 + APS_GBA_RECORD_SIZE) ||
(patchFile.fileSize-12)%APS_GBA_RECORD_SIZE!==0
)
return null;
var patch=new APSGBA();
patch.sourceSize=patchFile.readU32();
patch.targetSize=patchFile.readU32();
while(!patchFile.isEOF()){
var offset=patchFile.readU32();
var sourceCrc16=patchFile.readU16();
var targetCrc16=patchFile.readU16();
var xorBytes=patchFile.readBytes(APS_GBA_BLOCK_SIZE);
patch.addRecord(offset, sourceCrc16, targetCrc16, xorBytes);
}
return patch;
}

View file

@ -0,0 +1,198 @@
/* APS (N64) module for Rom Patcher JS v20180930 - Marc Robledo 2017-2018 - http://www.marcrobledo.com/license */
/* File format specification: https://github.com/btimofeev/UniPatcher/wiki/APS-(N64) */
const APS_N64_MAGIC='APS10';
const APS_RECORD_RLE=0x0000;
const APS_RECORD_SIMPLE=0x01;
const APS_N64_MODE=0x01;
function APS(){
this.records=[];
this.headerType=0;
this.encodingMethod=0;
this.description='no description';
this.header={};
}
APS.prototype.addRecord=function(o, d){
this.records.push({offset:o, type:APS_RECORD_SIMPLE, data:d})
}
APS.prototype.addRLERecord=function(o, b, l){
this.records.push({offset:o, type:APS_RECORD_RLE, length:l, byte:b})
}
APS.prototype.toString=function(){
var s='Total records: '+this.records.length;
s+='\nHeader type: '+this.headerType;
if(this.headerType===APS_N64_MODE){
s+=' (N64)';
}
s+='\nEncoding method: '+this.encodingMethod;
s+='\nDescription: '+this.description;
s+='\nHeader: '+JSON.stringify(this.header);
return s
}
APS.prototype.validateSource=function(sourceFile){
if(this.headerType===APS_N64_MODE){
sourceFile.seek(0x3c);
if(sourceFile.readString(3)!==this.header.cartId)
return false;
sourceFile.seek(0x10);
var crc=sourceFile.readBytes(8);
for(var i=0; i<8; i++){
if(crc[i]!==this.header.crc[i])
return false
}
}
return true
}
APS.prototype.export=function(fileName){
var patchFileSize=61;
if(this.headerType===APS_N64_MODE)
patchFileSize+=17;
for(var i=0; i<this.records.length; i++){
if(this.records[i].type===APS_RECORD_RLE)
patchFileSize+=7;
else
patchFileSize+=5+this.records[i].data.length; //offset+length+data
}
tempFile=new MarcFile(patchFileSize);
tempFile.littleEndian=true;
tempFile.fileName=fileName+'.aps';
tempFile.writeString(APS_N64_MAGIC, APS_N64_MAGIC.length);
tempFile.writeU8(this.headerType);
tempFile.writeU8(this.encodingMethod);
tempFile.writeString(this.description, 50);
if(this.headerType===APS_N64_MODE){
tempFile.writeU8(this.header.originalN64Format);
tempFile.writeString(this.header.cartId, 3);
tempFile.writeBytes(this.header.crc);
tempFile.writeBytes(this.header.pad);
}
tempFile.writeU32(this.header.sizeOutput);
for(var i=0; i<this.records.length; i++){
var rec=this.records[i];
tempFile.writeU32(rec.offset);
if(rec.type===APS_RECORD_RLE){
tempFile.writeU8(0x00);
tempFile.writeU8(rec.byte);
tempFile.writeU8(rec.length);
}else{
tempFile.writeU8(rec.data.length);
tempFile.writeBytes(rec.data);
}
}
return tempFile
}
APS.prototype.apply=function(romFile, validate){
if(validate && !this.validateSource(romFile)){
throw new Error('error_crc_input');
}
tempFile=new MarcFile(this.header.sizeOutput);
romFile.copyToFile(tempFile, 0, tempFile.fileSize);
for(var i=0; i<this.records.length; i++){
tempFile.seek(this.records[i].offset);
if(this.records[i].type===APS_RECORD_RLE){
for(var j=0; j<this.records[i].length; j++)
tempFile.writeU8(this.records[i].byte);
}else{
tempFile.writeBytes(this.records[i].data);
}
}
return tempFile
}
function parseAPSFile(patchFile){
var patch=new APS();
patchFile.littleEndian=true;
patchFile.seek(5);
patch.headerType=patchFile.readU8();
patch.encodingMethod=patchFile.readU8();
patch.description=patchFile.readString(50);
var seek;
if(patch.headerType===APS_N64_MODE){
patch.header.originalN64Format=patchFile.readU8();
patch.header.cartId=patchFile.readString(3);
patch.header.crc=patchFile.readBytes(8);
patch.header.pad=patchFile.readBytes(5);
}
patch.header.sizeOutput=patchFile.readU32();
while(!patchFile.isEOF()){
var offset=patchFile.readU32();
var length=patchFile.readU8();
if(length===APS_RECORD_RLE)
patch.addRLERecord(offset, patchFile.readU8(seek), patchFile.readU8(seek+1));
else
patch.addRecord(offset, patchFile.readBytes(length));
}
return patch;
}
function createAPSFromFiles(original, modified){
var patch=new APS();
if(original.readU32()===0x80371240){ //is N64 ROM
patch.headerType=APS_N64_MODE;
patch.header.originalN64Format=/\.v64$/i.test(original.fileName)?0:1;
original.seek(0x3c);
patch.header.cartId=original.readString(3);
original.seek(0x10);
patch.header.crc=original.readBytes(8);
patch.header.pad=[0,0,0,0,0];
}
patch.header.sizeOutput=modified.fileSize;
original.seek(0);
modified.seek(0);
while(!modified.isEOF()){
var b1=original.isEOF()?0x00:original.readU8();
var b2=modified.readU8();
if(b1!==b2){
var RLERecord=true;
var differentBytes=[];
var offset=modified.offset-1;
while(b1!==b2 && differentBytes.length<0xff){
differentBytes.push(b2);
if(b2!==differentBytes[0])
RLERecord=false;
if(modified.isEOF() || differentBytes.length===0xff)
break;
b1=original.isEOF()?0x00:original.readU8();
b2=modified.readU8();
}
if(RLERecord && differentBytes.length>2){
patch.addRLERecord(offset, differentBytes[0], differentBytes.length);
}else{
patch.addRecord(offset, differentBytes);
}
//NO se puede comentar??? why????
}
}
return patch
}

459
legacy/js/formats/bps.js Normal file
View file

@ -0,0 +1,459 @@
/* BPS module for Rom Patcher JS v20240721 - Marc Robledo 2016-2024 - http://www.marcrobledo.com/license */
/* File format specification: https://www.romhacking.net/documents/746/ */
const BPS_MAGIC='BPS1';
const BPS_ACTION_SOURCE_READ=0;
const BPS_ACTION_TARGET_READ=1;
const BPS_ACTION_SOURCE_COPY=2;
const BPS_ACTION_TARGET_COPY=3;
function BPS(){
this.sourceSize=0;
this.targetSize=0;
this.metaData='';
this.actions=[];
this.sourceChecksum=0;
this.targetChecksum=0;
this.patchChecksum=0;
}
BPS.prototype.toString=function(){
var s='Source size: '+this.sourceSize;
s+='\nTarget size: '+this.targetSize;
s+='\nMetadata: '+this.metaData;
s+='\n#Actions: '+this.actions.length;
return s
}
BPS.prototype.validateSource=function(romFile,headerSize){return this.sourceChecksum===crc32(romFile, headerSize)}
BPS.prototype.getValidationInfo=function(){
return [{
'type':'CRC32',
'value':padZeroes(this.sourceChecksum,4)
}]
}
BPS.prototype.apply=function(romFile, validate){
if(validate && !this.validateSource(romFile)){
throw new Error('error_crc_input');
}
tempFile=new MarcFile(this.targetSize);
//patch
var sourceRelativeOffset=0;
var targetRelativeOffset=0;
for(var i=0; i<this.actions.length; i++){
var action=this.actions[i];
if(action.type===BPS_ACTION_SOURCE_READ){
romFile.copyToFile(tempFile, tempFile.offset, action.length);
tempFile.skip(action.length);
}else if(action.type===BPS_ACTION_TARGET_READ){
tempFile.writeBytes(action.bytes);
}else if(action.type===BPS_ACTION_SOURCE_COPY){
sourceRelativeOffset+=action.relativeOffset;
var actionLength=action.length;
while(actionLength--){
tempFile.writeU8(romFile._u8array[sourceRelativeOffset]);
sourceRelativeOffset++;
}
}else if(action.type===BPS_ACTION_TARGET_COPY){
targetRelativeOffset+=action.relativeOffset;
var actionLength=action.length;
while(actionLength--) {
tempFile.writeU8(tempFile._u8array[targetRelativeOffset]);
targetRelativeOffset++;
}
}
}
if(validate && this.targetChecksum!==crc32(tempFile)){
throw new Error('error_crc_output');
}
return tempFile
}
function parseBPSFile(file){
file.readVLV=BPS_readVLV;
file.littleEndian=true;
var patch=new BPS();
file.seek(4); //skip BPS1
patch.sourceSize=file.readVLV();
patch.targetSize=file.readVLV();
var metaDataLength=file.readVLV();
if(metaDataLength){
patch.metaData=file.readString(metaDataLength);
}
var endActionsOffset=file.fileSize-12;
while(file.offset<endActionsOffset){
var data=file.readVLV();
var action={type: data & 3, length: (data >> 2)+1};
if(action.type===BPS_ACTION_TARGET_READ){
action.bytes=file.readBytes(action.length);
}else if(action.type===BPS_ACTION_SOURCE_COPY || action.type===BPS_ACTION_TARGET_COPY){
var relativeOffset=file.readVLV();
action.relativeOffset=(relativeOffset & 1? -1 : +1) * (relativeOffset >> 1)
}
patch.actions.push(action);
}
//file.seek(endActionsOffset);
patch.sourceChecksum=file.readU32();
patch.targetChecksum=file.readU32();
patch.patchChecksum=file.readU32();
if(patch.patchChecksum!==crc32(file, 0, true)){
throw new Error('error_crc_patch');
}
return patch;
}
function BPS_readVLV(){
var data=0, shift=1;
while(true){
var x = this.readU8();
data += (x & 0x7f) * shift;
if(x & 0x80)
break;
shift <<= 7;
data += shift;
}
this._lastRead=data;
return data;
}
function BPS_writeVLV(data){
while(true){
var x = data & 0x7f;
data >>= 7;
if(data === 0){
this.writeU8(0x80 | x);
break;
}
this.writeU8(x);
data--;
}
}
function BPS_getVLVLen(data){
var len=0;
while(true){
var x = data & 0x7f;
data >>= 7;
if(data === 0){
len++;
break;
}
len++;
data--;
}
return len;
}
BPS.prototype.export=function(fileName){
var patchFileSize=BPS_MAGIC.length;
patchFileSize+=BPS_getVLVLen(this.sourceSize);
patchFileSize+=BPS_getVLVLen(this.targetSize);
patchFileSize+=BPS_getVLVLen(this.metaData.length);
patchFileSize+=this.metaData.length;
for(var i=0; i<this.actions.length; i++){
var action=this.actions[i];
patchFileSize+=BPS_getVLVLen(((action.length-1)<<2) + action.type);
if(action.type===BPS_ACTION_TARGET_READ){
patchFileSize+=action.length;
}else if(action.type===BPS_ACTION_SOURCE_COPY || action.type===BPS_ACTION_TARGET_COPY){
patchFileSize+=BPS_getVLVLen((Math.abs(action.relativeOffset)<<1)+(action.relativeOffset<0?1:0));
}
}
patchFileSize+=12;
var patchFile=new MarcFile(patchFileSize);
patchFile.fileName=fileName+'.bps';
patchFile.littleEndian=true;
patchFile.writeVLV=BPS_writeVLV;
patchFile.writeString(BPS_MAGIC);
patchFile.writeVLV(this.sourceSize);
patchFile.writeVLV(this.targetSize);
patchFile.writeVLV(this.metaData.length);
patchFile.writeString(this.metaData, this.metaData.length);
for(var i=0; i<this.actions.length; i++){
var action=this.actions[i];
patchFile.writeVLV(((action.length-1)<<2) + action.type);
if(action.type===BPS_ACTION_TARGET_READ){
patchFile.writeBytes(action.bytes);
}else if(action.type===BPS_ACTION_SOURCE_COPY || action.type===BPS_ACTION_TARGET_COPY){
patchFile.writeVLV((Math.abs(action.relativeOffset)<<1)+(action.relativeOffset<0?1:0));
}
}
patchFile.writeU32(this.sourceChecksum);
patchFile.writeU32(this.targetChecksum);
patchFile.writeU32(this.patchChecksum);
return patchFile;
}
function BPS_Node(){
this.offset=0;
this.next=null;
};
BPS_Node.prototype.delete=function(){
if(this.next)
delete this.next;
}
function createBPSFromFiles(original, modified, deltaMode){
var patch=new BPS();
patch.sourceSize=original.fileSize;
patch.targetSize=modified.fileSize;
if(deltaMode){
patch.actions=createBPSFromFilesDelta(original, modified);
}else{
patch.actions=createBPSFromFilesLinear(original, modified);
}
patch.sourceChecksum=crc32(original);
patch.targetChecksum=crc32(modified);
patch.patchChecksum=crc32(patch.export(), 0, true);
return patch;
}
/* delta implementation from https://github.com/chiya/beat/blob/master/nall/beat/linear.hpp */
function createBPSFromFilesLinear(original, modified){
var patchActions=[];
/* references to match original beat code */
var sourceData=original._u8array;
var targetData=modified._u8array;
var sourceSize=original.fileSize;
var targetSize=modified.fileSize;
var Granularity=1;
var targetRelativeOffset=0;
var outputOffset=0;
var targetReadLength=0;
function targetReadFlush(){
if(targetReadLength){
//encode(TargetRead | ((targetReadLength - 1) << 2));
var action={type:BPS_ACTION_TARGET_READ, length:targetReadLength, bytes:[]};
patchActions.push(action);
var offset = outputOffset - targetReadLength;
while(targetReadLength){
//write(targetData[offset++]);
action.bytes.push(targetData[offset++]);
targetReadLength--;
}
}
};
while(outputOffset < targetSize) {
var sourceLength = 0;
for(var n = 0; outputOffset + n < Math.min(sourceSize, targetSize); n++) {
if(sourceData[outputOffset + n] != targetData[outputOffset + n]) break;
sourceLength++;
}
var rleLength = 0;
for(var n = 1; outputOffset + n < targetSize; n++) {
if(targetData[outputOffset] != targetData[outputOffset + n]) break;
rleLength++;
}
if(rleLength >= 4) {
//write byte to repeat
targetReadLength++;
outputOffset++;
targetReadFlush();
//copy starting from repetition byte
//encode(TargetCopy | ((rleLength - 1) << 2));
var relativeOffset = (outputOffset - 1) - targetRelativeOffset;
//encode(relativeOffset << 1);
patchActions.push({type:BPS_ACTION_TARGET_COPY, length:rleLength, relativeOffset:relativeOffset});
outputOffset += rleLength;
targetRelativeOffset = outputOffset - 1;
} else if(sourceLength >= 4) {
targetReadFlush();
//encode(SourceRead | ((sourceLength - 1) << 2));
patchActions.push({type:BPS_ACTION_SOURCE_READ, length:sourceLength});
outputOffset += sourceLength;
} else {
targetReadLength += Granularity;
outputOffset += Granularity;
}
}
targetReadFlush();
return patchActions;
}
/* delta implementation from https://github.com/chiya/beat/blob/master/nall/beat/delta.hpp */
function createBPSFromFilesDelta(original, modified){
var patchActions=[];
/* references to match original beat code */
var sourceData=original._u8array;
var targetData=modified._u8array;
var sourceSize=original.fileSize;
var targetSize=modified.fileSize;
var Granularity=1;
var sourceRelativeOffset=0;
var targetRelativeOffset=0;
var outputOffset=0;
var sourceTree=new Array(65536);
var targetTree=new Array(65536);
for(var n=0; n<65536; n++){
sourceTree[n]=null;
targetTree[n]=null;
}
//source tree creation
for(var offset=0; offset<sourceSize; offset++) {
var symbol = sourceData[offset + 0];
//sourceChecksum = crc32_adjust(sourceChecksum, symbol);
if(offset < sourceSize - 1)
symbol |= sourceData[offset + 1] << 8;
var node=new BPS_Node();
node.offset=offset;
node.next=sourceTree[symbol];
sourceTree[symbol] = node;
}
var targetReadLength=0;
function targetReadFlush(){
if(targetReadLength) {
//encode(TargetRead | ((targetReadLength - 1) << 2));
var action={type:BPS_ACTION_TARGET_READ, length:targetReadLength, bytes:[]};
patchActions.push(action);
var offset = outputOffset - targetReadLength;
while(targetReadLength){
//write(targetData[offset++]);
action.bytes.push(targetData[offset++]);
targetReadLength--;
}
}
};
while(outputOffset<modified.fileSize){
var maxLength = 0, maxOffset = 0, mode = BPS_ACTION_TARGET_READ;
var symbol = targetData[outputOffset + 0];
if(outputOffset < targetSize - 1) symbol |= targetData[outputOffset + 1] << 8;
{ //source read
var length = 0, offset = outputOffset;
while(offset < sourceSize && offset < targetSize && sourceData[offset] == targetData[offset]) {
length++;
offset++;
}
if(length > maxLength) maxLength = length, mode = BPS_ACTION_SOURCE_READ;
}
{ //source copy
var node = sourceTree[symbol];
while(node) {
var length = 0, x = node.offset, y = outputOffset;
while(x < sourceSize && y < targetSize && sourceData[x++] == targetData[y++]) length++;
if(length > maxLength) maxLength = length, maxOffset = node.offset, mode = BPS_ACTION_SOURCE_COPY;
node = node.next;
}
}
{ //target copy
var node = targetTree[symbol];
while(node) {
var length = 0, x = node.offset, y = outputOffset;
while(y < targetSize && targetData[x++] == targetData[y++]) length++;
if(length > maxLength) maxLength = length, maxOffset = node.offset, mode = BPS_ACTION_TARGET_COPY;
node = node.next;
}
//target tree append
node = new BPS_Node();
node.offset = outputOffset;
node.next = targetTree[symbol];
targetTree[symbol] = node;
}
{ //target read
if(maxLength < 4) {
maxLength = Math.min(Granularity, targetSize - outputOffset);
mode = BPS_ACTION_TARGET_READ;
}
}
if(mode != BPS_ACTION_TARGET_READ) targetReadFlush();
switch(mode) {
case BPS_ACTION_SOURCE_READ:
//encode(BPS_ACTION_SOURCE_READ | ((maxLength - 1) << 2));
patchActions.push({type:BPS_ACTION_SOURCE_READ, length:maxLength});
break;
case BPS_ACTION_TARGET_READ:
//delay write to group sequential TargetRead commands into one
targetReadLength += maxLength;
break;
case BPS_ACTION_SOURCE_COPY:
case BPS_ACTION_TARGET_COPY:
//encode(mode | ((maxLength - 1) << 2));
var relativeOffset;
if(mode == BPS_ACTION_SOURCE_COPY) {
relativeOffset = maxOffset - sourceRelativeOffset;
sourceRelativeOffset = maxOffset + maxLength;
} else {
relativeOffset = maxOffset - targetRelativeOffset;
targetRelativeOffset = maxOffset + maxLength;
}
//encode((relativeOffset < 0) | (abs(relativeOffset) << 1));
patchActions.push({type:mode, length:maxLength, relativeOffset:relativeOffset});
break;
}
outputOffset += maxLength;
}
targetReadFlush();
return patchActions;
}

232
legacy/js/formats/ips.js Normal file
View file

@ -0,0 +1,232 @@
/* IPS module for Rom Patcher JS v20220417 - Marc Robledo 2016-2022 - http://www.marcrobledo.com/license */
/* File format specification: http://www.smwiki.net/wiki/IPS_file_format */
const IPS_MAGIC='PATCH';
const IPS_MAX_SIZE=0x1000000; //16 megabytes
const IPS_RECORD_RLE=0x0000;
const IPS_RECORD_SIMPLE=0x01;
function IPS(){
this.records=[];
this.truncate=false;
}
IPS.prototype.addSimpleRecord=function(o, d){
this.records.push({offset:o, type:IPS_RECORD_SIMPLE, length:d.length, data:d})
}
IPS.prototype.addRLERecord=function(o, l, b){
this.records.push({offset:o, type:IPS_RECORD_RLE, length:l, byte:b})
}
IPS.prototype.toString=function(){
nSimpleRecords=0;
nRLERecords=0;
for(var i=0; i<this.records.length; i++){
if(this.records[i].type===IPS_RECORD_RLE)
nRLERecords++;
else
nSimpleRecords++;
}
var s='Simple records: '+nSimpleRecords;
s+='\nRLE records: '+nRLERecords;
s+='\nTotal records: '+this.records.length;
if(this.truncate)
s+='\nTruncate at: 0x'+this.truncate.toString(16);
return s
}
IPS.prototype.export=function(fileName){
var patchFileSize=5; //PATCH string
for(var i=0; i<this.records.length; i++){
if(this.records[i].type===IPS_RECORD_RLE)
patchFileSize+=(3+2+2+1); //offset+0x0000+length+RLE byte to be written
else
patchFileSize+=(3+2+this.records[i].data.length); //offset+length+data
}
patchFileSize+=3; //EOF string
if(this.truncate)
patchFileSize+=3; //truncate
tempFile=new MarcFile(patchFileSize);
tempFile.fileName=fileName+'.ips';
tempFile.writeString(IPS_MAGIC);
for(var i=0; i<this.records.length; i++){
var rec=this.records[i];
tempFile.writeU24(rec.offset);
if(rec.type===IPS_RECORD_RLE){
tempFile.writeU16(0x0000);
tempFile.writeU16(rec.length);
tempFile.writeU8(rec.byte);
}else{
tempFile.writeU16(rec.data.length);
tempFile.writeBytes(rec.data);
}
}
tempFile.writeString('EOF');
if(this.truncate)
tempFile.writeU24(this.truncate);
return tempFile
}
IPS.prototype.apply=function(romFile){
if(this.truncate){
if(this.truncate>romFile.fileSize){ //expand (discussed here: https://github.com/marcrobledo/RomPatcher.js/pull/46)
tempFile=new MarcFile(this.truncate);
romFile.copyToFile(tempFile, 0, romFile.fileSize, 0);
}else{ //truncate
tempFile=romFile.slice(0, this.truncate);
}
}else{
//calculate target ROM size, expanding it if any record offset is beyond target ROM size
var newFileSize=romFile.fileSize;
for(var i=0; i<this.records.length; i++){
var rec=this.records[i];
if(rec.type===IPS_RECORD_RLE){
if(rec.offset+rec.length>newFileSize){
newFileSize=rec.offset+rec.length;
}
}else{
if(rec.offset+rec.data.length>newFileSize){
newFileSize=rec.offset+rec.data.length;
}
}
}
if(newFileSize===romFile.fileSize){
tempFile=romFile.slice(0, romFile.fileSize);
}else{
tempFile=new MarcFile(newFileSize);
romFile.copyToFile(tempFile,0);
}
}
romFile.seek(0);
for(var i=0; i<this.records.length; i++){
tempFile.seek(this.records[i].offset);
if(this.records[i].type===IPS_RECORD_RLE){
for(var j=0; j<this.records[i].length; j++)
tempFile.writeU8(this.records[i].byte);
}else{
tempFile.writeBytes(this.records[i].data);
}
}
return tempFile
}
function parseIPSFile(file){
var patchFile=new IPS();
file.seek(5);
while(!file.isEOF()){
var offset=file.readU24();
if(offset===0x454f46){ /* EOF */
if(file.isEOF()){
break;
}else if((file.offset+3)===file.fileSize){
patchFile.truncate=file.readU24();
break;
}
}
var length=file.readU16();
if(length===IPS_RECORD_RLE){
patchFile.addRLERecord(offset, file.readU16(), file.readU8());
}else{
patchFile.addSimpleRecord(offset, file.readBytes(length));
}
}
return patchFile;
}
function createIPSFromFiles(original, modified){
var patch=new IPS();
if(modified.fileSize<original.fileSize){
patch.truncate=modified.fileSize;
}
//solucion: guardar startOffset y endOffset (ir mirando de 6 en 6 hacia atrás)
var previousRecord={type:0xdeadbeef,startOffset:0,length:0};
while(!modified.isEOF()){
var b1=original.isEOF()?0x00:original.readU8();
var b2=modified.readU8();
if(b1!==b2){
var RLEmode=true;
var differentData=[];
var startOffset=modified.offset-1;
while(b1!==b2 && differentData.length<0xffff){
differentData.push(b2);
if(b2!==differentData[0])
RLEmode=false;
if(modified.isEOF() || differentData.length===0xffff)
break;
b1=original.isEOF()?0x00:original.readU8();
b2=modified.readU8();
}
//check if this record is near the previous one
var distance=startOffset-(previousRecord.offset+previousRecord.length);
if(
previousRecord.type===IPS_RECORD_SIMPLE &&
distance<6 && (previousRecord.length+distance+differentData.length)<0xffff
){
if(RLEmode && differentData.length>6){
// separate a potential RLE record
original.seek(startOffset);
modified.seek(startOffset);
previousRecord={type:0xdeadbeef,startOffset:0,length:0};
}else{
// merge both records
while(distance--){
previousRecord.data.push(modified._u8array[previousRecord.offset+previousRecord.length]);
previousRecord.length++;
}
previousRecord.data=previousRecord.data.concat(differentData);
previousRecord.length=previousRecord.data.length;
}
}else{
if(startOffset>=IPS_MAX_SIZE){
throw new Error('files are too big for IPS format');
return null;
}
if(RLEmode && differentData.length>2){
patch.addRLERecord(startOffset, differentData.length, differentData[0]);
}else{
patch.addSimpleRecord(startOffset, differentData);
}
previousRecord=patch.records[patch.records.length-1];
}
}
}
if(modified.fileSize>original.fileSize){
var lastRecord=patch.records[patch.records.length-1];
var lastOffset=lastRecord.offset+lastRecord.length;
if(lastOffset<modified.fileSize){
patch.addSimpleRecord(modified.fileSize-1, [0x00]);
}
}
return patch
}

93
legacy/js/formats/pmsr.js Normal file
View file

@ -0,0 +1,93 @@
/* PMSR (Paper Mario Star Rod) module for Rom Patcher JS v20240721 - Marc Robledo 2020-2024 - http://www.marcrobledo.com/license */
/* File format specification: http://origami64.net/attachment.php?aid=790 (dead link) */
const PMSR_MAGIC='PMSR';
const YAY0_MAGIC='Yay0';
const PAPER_MARIO_USA10_CRC32=0xa7f5cd7e;
const PAPER_MARIO_USA10_FILE_SIZE=41943040;
function PMSR(){
this.targetSize=0;
this.records=[];
}
PMSR.prototype.addRecord=function(offset, data){
this.records.push({offset:offset, data:data})
}
PMSR.prototype.toString=function(){
var s='Star Rod patch';
s+='\nTarget file size: '+this.targetSize;
s+='\n#Records: '+this.records.length;
return s;
}
PMSR.prototype.validateSource=function(romFile){
return romFile.fileSize===PAPER_MARIO_USA10_FILE_SIZE && crc32(romFile)===PAPER_MARIO_USA10_CRC32;
}
PMSR.prototype.getValidationInfo=function(){
return [{
'type':'CRC32',
'value':'a7f5cd7e'
}]
}
PMSR.prototype.apply=function(romFile, validate){
if(validate && !this.validateSource(romFile)){
throw new Error('error_crc_input');
}
console.log('a');
if(this.targetSize===romFile.fileSize){
tempFile=romFile.slice(0, romFile.fileSize);
}else{
tempFile=new MarcFile(this.targetSize);
romFile.copyToFile(tempFile,0);
}
console.log('b');
for(var i=0; i<this.records.length; i++){
tempFile.seek(this.records[i].offset);
tempFile.writeBytes(this.records[i].data);
}
return tempFile;
}
function parseMODFile(file){
var patch=new PMSR();
/*file.seek(0);
if(file.readString(YAY0_MAGIC.length)===YAY0_MAGIC){
file=PMSR.YAY0_decode(file);
}*/
patch.targetSize=PAPER_MARIO_USA10_FILE_SIZE;
file.seek(4);
var nRecords=file.readU32();
for(var i=0; i<nRecords; i++){
var offset=file.readU32();
var length=file.readU32();
patch.addRecord(offset, file.readBytes(length));
if((offset+length)>patch.targetSize)
patch.targetSize=offset+length;
}
return patch;
}
/* to-do */
//MOD.prototype.export=function(fileName){return null}
//function createMODFromFiles(original, modified){return null}
/* https://github.com/pho/WindViewer/wiki/Yaz0-and-Yay0 */
PMSR.YAY0_decode=function(file){
/* to-do */
}

266
legacy/js/formats/ppf.js Normal file
View file

@ -0,0 +1,266 @@
/* PPF module for Rom Patcher JS v20200221 - Marc Robledo 2019-2020 - http://www.marcrobledo.com/license */
/* File format specification: https://www.romhacking.net/utilities/353/ */
const PPF_MAGIC='PPF';
const PPF_IMAGETYPE_BIN=0x00;
const PPF_IMAGETYPE_GI=0x01;
const PPF_BEGIN_FILE_ID_DIZ_MAGIC='@BEG';//@BEGIN_FILE_ID.DIZ
function PPF(){
this.version=3;
this.imageType=PPF_IMAGETYPE_BIN;
this.blockCheck=false;
this.undoData=false;
this.records=[];
}
PPF.prototype.addRecord=function(offset, data, undoData){
if(this.undoData){
this.records.push({offset:offset, data:data, undoData:undoData});
}else{
this.records.push({offset:offset, data:data});
}
}
PPF.prototype.toString=function(){
var s=this.description;
s+='\nPPF version: '+this.version;
s+='\n#Records: '+this.records.length;
s+='\nImage type: '+this.imageType;
s+='\nBlock check: '+!!this.blockCheck;
s+='\nUndo data: '+this.undoData;
if(this.fileIdDiz)
s+='\nFILE_ID.DIZ: '+this.fileIdDiz;
return s
}
PPF.prototype.export=function(fileName){
var patchFileSize=5+1+50; //PPFx0
for(var i=0; i<this.records.length; i++){
patchFileSize+=4+1+this.records[i].data.length;
if(this.version===3)
patchFileSize+=4; //offsets are u64
}
if(this.version===3 || this.version===2){
patchFileSize+=4;
}
if(this.blockCheck){
patchFileSize+=1024;
}
if(this.fileIdDiz){
patchFileSize+=18+this.fileIdDiz.length+16+4;
}
tempFile=new MarcFile(patchFileSize);
tempFile.fileName=fileName+'.ppf';
tempFile.writeString(PPF_MAGIC);
tempFile.writeString((this.version*10).toString());
tempFile.writeU8(parseInt(this.version)-1);
tempFile.writeString(this.description, 50);
if(this.version===3){
tempFile.writeU8(this.imageType);
tempFile.writeU8(this.blockCheck?0x01:0x00);
tempFile.writeU8(this.undoData?0x01:0x00);
tempFile.writeU8(0x00); //dummy
}else if(this.version===2){
tempFile.writeU32(this.inputFileSize);
}
if(this.blockCheck){
tempFile.writeBytes(this.blockCheck);
}
tempFile.littleEndian=true;
for(var i=0; i<this.records.length; i++){
tempFile.writeU32(this.records[i].offset & 0xffffffff);
if(this.version===3){
var offset2=this.records[i].offset;
for(var j=0; j<32; j++)
offset2=parseInt((offset2/2)>>>0);
tempFile.writeU32(offset2);
}
tempFile.writeU8(this.records[i].data.length);
tempFile.writeBytes(this.records[i].data);
if(this.undoData)
tempFile.writeBytes(this.records[i].undoData);
}
if(this.fileIdDiz){
tempFile.writeString('@BEGIN_FILE_ID.DIZ');
tempFile.writeString(this.fileIdDiz);
tempFile.writeString('@END_FILE_ID.DIZ');
tempFile.writeU16(this.fileIdDiz.length);
tempFile.writeU16(0x00);
}
return tempFile
}
PPF.prototype.apply=function(romFile){
var newFileSize=romFile.fileSize;
for(var i=0; i<this.records.length; i++){
if(this.records[i].offset+this.records[i].data.length>newFileSize)
newFileSize=this.records[i].offset+this.records[i].data.length;
}
if(newFileSize===romFile.fileSize){
tempFile=romFile.slice(0, romFile.fileSize);
}else{
tempFile=new MarcFile(newFileSize);
romFile.copyToFile(tempFile,0);
}
//check if undoing
var undoingData=false;
if(this.undoData){
tempFile.seek(this.records[0].offset);
var originalBytes=tempFile.readBytes(this.records[0].data.length);
var foundDifferences=false;
for(var i=0; i<originalBytes.length && !foundDifferences; i++){
if(originalBytes[i]!==this.records[0].data[i]){
foundDifferences=true;
}
}
if(!foundDifferences){
undoingData=true;
}
}
for(var i=0; i<this.records.length; i++){
tempFile.seek(this.records[i].offset);
if(undoingData){
tempFile.writeBytes(this.records[i].undoData);
}else{
tempFile.writeBytes(this.records[i].data);
}
}
return tempFile
}
function parsePPFFile(patchFile){
var patch=new PPF();
patchFile.seek(3);
var version1=parseInt(patchFile.readString(2))/10;
var version2=patchFile.readU8()+1;
if(version1!==version2 || version1>3){
throw new Error('invalid PPF version');
}
patch.version=version1;
patch.description=patchFile.readString(50).replace(/ +$/,'');
if(patch.version===3){
patch.imageType=patchFile.readU8();
if(patchFile.readU8())
patch.blockCheck=true;
if(patchFile.readU8())
patch.undoData=true;
patchFile.skip(1);
}else if(patch.version===2){
patch.blockCheck=true;
patch.inputFileSize=patchFile.readU32();
}
if(patch.blockCheck){
patch.blockCheck=patchFile.readBytes(1024);
}
patchFile.littleEndian=true;
while(!patchFile.isEOF()){
if(patchFile.readString(4)===PPF_BEGIN_FILE_ID_DIZ_MAGIC){
patchFile.skip(14);
//console.log('found file_id.diz begin');
patch.fileIdDiz=patchFile.readString(3072);
patch.fileIdDiz=patch.fileIdDiz.substr(0, patch.fileIdDiz.indexOf('@END_FILE_ID.DIZ'));
break;
}
patchFile.skip(-4);
var offset;
if(patch.version===3){
var u64_1=patchFile.readU32();
var u64_2=patchFile.readU32();
offset=u64_1+(u64_2*0x100000000);
}else
offset=patchFile.readU32();
var len=patchFile.readU8();
var data=patchFile.readBytes(len);
var undoData=false;
if(patch.undoData){
undoData=patchFile.readBytes(len);
}
patch.addRecord(offset, data, undoData);
}
return patch;
}
function createPPFFromFiles(original, modified){
var patch=new PPF();
patch.description='Patch description';
if(original.fileSize>modified.fileSize){
var expandedModified=new MarcFile(original.fileSize);
modified.copyToFile(expandedModified,0);
modified=expandedModified;
}
original.seek(0);
modified.seek(0);
while(!modified.isEOF()){
var b1=original.isEOF()?0x00:original.readU8();
var b2=modified.readU8();
if(b1!==b2){
var differentData=[];
var offset=modified.offset-1;
while(b1!==b2 && differentData.length<0xff){
differentData.push(b2);
if(modified.isEOF() || differentData.length===0xff)
break;
b1=original.isEOF()?0x00:original.readU8();
b2=modified.readU8();
}
patch.addRecord(offset, differentData);
}
}
if(original.fileSize<modified.fileSize){
modified.seek(modified.fileSize-1);
if(modified.readU8()===0x00)
patch.addRecord(modified.fileSize-1, [0x00]);
}
return patch
}

338
legacy/js/formats/rup.js Normal file
View file

@ -0,0 +1,338 @@
/* RUP module for Rom Patcher JS v20240721 - Marc Robledo 2018-2024 - 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.getValidationInfo=function(){
var ret=[];
for(var i=0; i<this.files.length; i++){
ret.push({
'type':'MD5',
'value':this.files[i].sourceMD5
});
}
return ret;
}
RUP.prototype.apply=function(romFile, validate){
var validFile;
if(validate){
validFile=this.validateSource(romFile);
if(!validFile)
throw new Error('error_crc_input');
}else{
validFile=this.files[0];
}
tempFile=new MarcFile(validFile.targetFileSize);
/* copy original file */
romFile.copyToFile(tempFile, 0);
for(var i=0; i<validFile.records.length; i++){
var offset=validFile.records[i].offset;
romFile.seek(offset);
tempFile.seek(offset);
for(var j=0; j<validFile.records[i].xor.length; j++){
tempFile.writeU8(
(romFile.isEOF()?0x00:romFile.readU8()) ^ validFile.records[i].xor[j]
);
}
}
if(validate && md5(tempFile)!==validFile.targetMD5){
throw new Error('error_crc_output');
}
return tempFile
}
function parseRUPFile(file){
var patch=new RUP();
file.readVLV=RUP_readVLV;
file.seek(RUP_MAGIC.length);
patch.textEncoding=file.readU8();
patch.author=file.readString(84);
patch.version=file.readString(11);
patch.title=file.readString(256);
patch.genre=file.readString(48);
patch.language=file.readString(48);
patch.date=file.readString(8);
patch.web=file.readString(512);
patch.description=file.readString(1074).replace(/\\n/g,'\n');
file.seek(0x800);
var nextFile;
while(!file.isEOF()){
var command=file.readU8();
if(command===RUP_COMMAND_OPEN_NEW_FILE){
if(nextFile)
patch.files.push(nextFile)
nextFile={
records:[]
};
nextFile.fileName=file.readString(file.readVLV());
nextFile.romType=file.readU8();
nextFile.sourceFileSize=file.readVLV();
nextFile.targetFileSize=file.readVLV();
nextFile.sourceMD5='';
for(var i=0; i<16; i++)
nextFile.sourceMD5+=padZeroes(file.readU8(),1);
nextFile.targetMD5='';
for(var i=0; i<16; i++)
nextFile.targetMD5+=padZeroes(file.readU8(),1);
if(nextFile.sourceFileSize!==nextFile.targetFileSize){
file.skip(1); //skip 'M' (source>target) or 'A' (source<target)
nextFile.overflowText=file.readString(file.readVLV());
}
}else if(command===RUP_COMMAND_XOR_RECORD){
nextFile.records.push({
offset:file.readVLV(),
xor:file.readBytes(file.readVLV())
});
}else if(command===RUP_COMMAND_END){
if(nextFile)
patch.files.push(nextFile);
break;
}else{
throw new Error('invalid RUP command');
}
}
return patch;
}
function RUP_readVLV(){
var nBytes=this.readU8();
var data=0;
for(var i=0; i<nBytes; i++){
data+=this.readU8() << i*8;
}
return data;
}
function RUP_writeVLV(data){
var len=RUP_getVLVLen(data)-1;
this.writeU8(len);
while(data){
this.writeU8(data & 0xff);
data>>=8;
}
}
function RUP_getVLVLen(data){
var ret=1;
while(data){
ret++;
data>>=8;
}
return ret;
}
RUP.prototype.export=function(fileName){
var patchFileSize=2048;
for(var i=0; i<this.files.length; i++){
var file=this.files[i];
patchFileSize++; //command 0x01
patchFileSize+=RUP_getVLVLen(file.fileName.length);
patchFileSize+=file.fileName.length;
patchFileSize++; //rom type
patchFileSize+=RUP_getVLVLen(file.sourceFileSize);
patchFileSize+=RUP_getVLVLen(file.targetFileSize);
patchFileSize+=32; //MD5s
if(file.sourceFileSize!==file.targetFileSize){
patchFileSize++; // M or A
patchFileSize+=RUP_getVLVLen(file.overflowText);
patchFileSize+=file.overflowText;
}
for(var j=0; j<file.records.length; j++){
patchFileSize++; //command 0x01
patchFileSize+=RUP_getVLVLen(file.records[j].offset);
patchFileSize+=RUP_getVLVLen(file.records[j].xor.length);
patchFileSize+=file.records[j].xor.length;
}
}
patchFileSize++; //command 0x00
var patchFile=new MarcFile(patchFileSize);
patchFile.fileName=fileName+'.rup';
patchFile.writeVLV=RUP_writeVLV;
patchFile.writeString(RUP_MAGIC);
patchFile.writeU8(this.textEncoding);
patchFile.writeString(this.author, 84);
patchFile.writeString(this.version, 11);
patchFile.writeString(this.title, 256);
patchFile.writeString(this.genre, 48);
patchFile.writeString(this.language, 48);
patchFile.writeString(this.date, 8);
patchFile.writeString(this.web, 512);
patchFile.writeString(this.description.replace(/\n/g,'\\n'), 1074);
for(var i=0; i<this.files.length; i++){
var file=this.files[i];
patchFile.writeU8(RUP_COMMAND_OPEN_NEW_FILE);
patchFile.writeVLV(file.fileName);
patchFile.writeU8(file.romType);
patchFile.writeVLV(file.sourceFileSize);
patchFile.writeVLV(file.targetFileSize);
for(var j=0; j<16; j++)
patchFile.writeU8(parseInt(file.sourceMD5.substr(j*2,2), 16));
for(var j=0; j<16; j++)
patchFile.writeU8(parseInt(file.targetMD5.substr(j*2,2), 16));
if(file.sourceFileSize!==file.targetFileSize){
patchFile.writeString(file.sourceFileSize>file.targetFileSize?'M':'A');
patchFile.writeVLV(file.overflowText.length);
patchFile.writeString(file.overflowText);
}
for(var j=0; j<file.records.length; j++){
patchFile.writeU8(RUP_COMMAND_XOR_RECORD);
patchFile.writeVLV(file.records[j].offset);
patchFile.writeVLV(file.records[j].xor.length);
patchFile.writeBytes(file.records[j].xor);
}
}
patchFile.writeU8(RUP_COMMAND_END);
return patchFile;
}
function createRUPFromFiles(original, modified){
var patch=new RUP();
var today=new Date();
patch.date=(today.getYear()+1900)+padZeroes(today.getMonth()+1, 1)+padZeroes(today.getDate(), 1);
var file={
fileName:'',
romType:0,
sourceFileSize:original.fileSize,
targetFileSize:modified.fileSize,
sourceMD5:md5(original),
targetMD5:md5(modified),
overflowText:'',
records:[]
};
original.seek(0);
modified.seek(0);
while(!modified.isEOF()){
var b1=original.isEOF()?0x00:original.readU8();
var b2=modified.readU8();
if(b1!==b2){
var originalOffset=modified.offset-1;
var xorDifferences=[];
while(b1!==b2){
xorDifferences.push(b1^b2);
if(modified.isEOF())
break;
b1=original.isEOF()?0x00:original.readU8();
b2=modified.readU8();
}
file.records.push({offset:originalOffset, xor:xorDifferences});
}
}
patch.files.push(file);
return patch
}

218
legacy/js/formats/ups.js Normal file
View file

@ -0,0 +1,218 @@
/* UPS module for Rom Patcher JS v20240721 - Marc Robledo 2017-2024 - http://www.marcrobledo.com/license */
/* File format specification: http://www.romhacking.net/documents/392/ */
const UPS_MAGIC='UPS1';
function UPS(){
this.records=[];
this.sizeInput=0;
this.sizeOutput=0;
this.checksumInput=0;
this.checksumOutput=0;
}
UPS.prototype.addRecord=function(relativeOffset, d){
this.records.push({offset:relativeOffset, XORdata:d})
}
UPS.prototype.toString=function(){
var s='Records: '+this.records.length;
s+='\nInput file size: '+this.sizeInput;
s+='\nOutput file size: '+this.sizeOutput;
s+='\nInput file checksum: '+padZeroes(this.checksumInput,4);
s+='\nOutput file checksum: '+padZeroes(this.checksumOutput,4);
return s
}
UPS.prototype.export=function(fileName){
var patchFileSize=UPS_MAGIC.length;//UPS1 string
patchFileSize+=UPS_getVLVLength(this.sizeInput); //input file size
patchFileSize+=UPS_getVLVLength(this.sizeOutput); //output file size
for(var i=0; i<this.records.length; i++){
patchFileSize+=UPS_getVLVLength(this.records[i].offset);
patchFileSize+=this.records[i].XORdata.length+1;
}
patchFileSize+=12; //input/output/patch checksums
tempFile=new MarcFile(patchFileSize);
tempFile.writeVLV=UPS_writeVLV;
tempFile.fileName=fileName+'.ups';
tempFile.writeString(UPS_MAGIC);
tempFile.writeVLV(this.sizeInput);
tempFile.writeVLV(this.sizeOutput);
for(var i=0; i<this.records.length; i++){
tempFile.writeVLV(this.records[i].offset);
tempFile.writeBytes(this.records[i].XORdata);
tempFile.writeU8(0x00);
}
tempFile.littleEndian=true;
tempFile.writeU32(this.checksumInput);
tempFile.writeU32(this.checksumOutput);
tempFile.writeU32(crc32(tempFile, 0, true));
return tempFile
}
UPS.prototype.validateSource=function(romFile,headerSize){return crc32(romFile,headerSize)===this.checksumInput}
UPS.prototype.getValidationInfo=function(){return [{
'type':'CRC32',
'value':padZeroes(this.checksumInput,4)
}];}
UPS.prototype.apply=function(romFile, validate){
if(validate && !this.validateSource(romFile)){
throw new Error('error_crc_input');
}
/* fix the glitch that cut the end of the file if it's larger than the changed file patch was originally created with */
/* more info: https://github.com/marcrobledo/RomPatcher.js/pull/40#issuecomment-1069087423 */
sizeOutput = this.sizeOutput;
sizeInput = this.sizeInput;
if(!validate && sizeInput < romFile.fileSize){
sizeInput = romFile.fileSize;
if(sizeOutput < sizeInput){
sizeOutput = sizeInput;
}
}
/* copy original file */
tempFile=new MarcFile(sizeOutput);
romFile.copyToFile(tempFile, 0, sizeInput);
romFile.seek(0);
var nextOffset=0;
for(var i=0; i<this.records.length; i++){
var record=this.records[i];
tempFile.skip(record.offset);
romFile.skip(record.offset);
for(var j=0; j<record.XORdata.length; j++){
tempFile.writeU8((romFile.isEOF()?0x00:romFile.readU8()) ^ record.XORdata[j]);
}
tempFile.skip(1);
romFile.skip(1);
}
if(validate && crc32(tempFile)!==this.checksumOutput){
throw new Error('error_crc_output');
}
return tempFile
}
/* encode/decode variable length values, used by UPS file structure */
function UPS_writeVLV(data){
while(1){
var x=data & 0x7f;
data=data>>7;
if(data===0){
this.writeU8(0x80 | x);
break;
}
this.writeU8(x);
data=data-1;
}
}
function UPS_readVLV(){
var data=0;
var shift=1;
while(1){
var x=this.readU8();
if(x==-1)
throw new Error('Can\'t read UPS VLV at 0x'+(this.offset-1).toString(16));
data+=(x&0x7f)*shift;
if((x&0x80)!==0)
break;
shift=shift<<7;
data+=shift;
}
return data
}
function UPS_getVLVLength(data){
var len=0;
while(1){
var x=data & 0x7f;
data=data>>7;
len++;
if(data===0){
break;
}
data=data-1;
}
return len;
}
function parseUPSFile(file){
var patch=new UPS();
file.readVLV=UPS_readVLV;
file.seek(UPS_MAGIC.length);
patch.sizeInput=file.readVLV();
patch.sizeOutput=file.readVLV();
var nextOffset=0;
while(file.offset<(file.fileSize-12)){
var relativeOffset=file.readVLV();
var XORdifferences=[];
while(file.readU8()){
XORdifferences.push(file._lastRead);
}
patch.addRecord(relativeOffset, XORdifferences);
}
file.littleEndian=true;
patch.checksumInput=file.readU32();
patch.checksumOutput=file.readU32();
if(file.readU32()!==crc32(file, 0, true)){
throw new Error('error_crc_patch');
}
file.littleEndian=false;
return patch;
}
function createUPSFromFiles(original, modified){
var patch=new UPS();
patch.sizeInput=original.fileSize;
patch.sizeOutput=modified.fileSize;
var previousSeek=1;
while(!modified.isEOF()){
var b1=original.isEOF()?0x00:original.readU8();
var b2=modified.readU8();
if(b1!==b2){
var currentSeek=modified.offset;
var XORdata=[];
while(b1!==b2){
XORdata.push(b1 ^ b2);
if(modified.isEOF())
break;
b1=original.isEOF()?0x00:original.readU8();
b2=modified.readU8();
}
patch.addRecord(currentSeek-previousSeek, XORdata);
previousSeek=currentSeek+XORdata.length+1;
}
}
patch.checksumInput=crc32(original);
patch.checksumOutput=crc32(modified);
return patch
}

374
legacy/js/formats/vcdiff.js Normal file
View file

@ -0,0 +1,374 @@
/* VCDIFF module for RomPatcher.js v20181021 - Marc Robledo 2018 - http://www.marcrobledo.com/license */
/* File format specification: https://tools.ietf.org/html/rfc3284 */
/*
Mostly based in:
https://github.com/vic-alexiev/TelerikAcademy/tree/master/C%23%20Fundamentals%20II/Homework%20Assignments/3.%20Methods/000.%20MiscUtil/Compression/Vcdiff
some code and ideas borrowed from:
https://hack64.net/jscripts/libpatch.js?6
*/
//const VCDIFF_MAGIC=0xd6c3c400;
const VCDIFF_MAGIC='\xd6\xc3\xc4';
/*
const XDELTA_014_MAGIC='%XDELTA';
const XDELTA_018_MAGIC='%XDZ000';
const XDELTA_020_MAGIC='%XDZ001';
const XDELTA_100_MAGIC='%XDZ002';
const XDELTA_104_MAGIC='%XDZ003';
const XDELTA_110_MAGIC='%XDZ004';
*/
function VCDIFF(patchFile){
this.file=patchFile;
}
VCDIFF.prototype.toString=function(){
return 'VCDIFF patch'
}
VCDIFF.prototype.apply=function(romFile, validate){
//romFile._u8array=new Uint8Array(romFile._dataView.buffer);
//var t0=performance.now();
var parser=new VCDIFF_Parser(this.file);
//read header
parser.seek(4);
var headerIndicator=parser.readU8();
if(headerIndicator & VCD_DECOMPRESS){
//has secondary decompressor, read its id
var secondaryDecompressorId=parser.readU8();
if(secondaryDecompressorId!==0)
throw new Error('not implemented: secondary decompressor');
}
if(headerIndicator & VCD_CODETABLE){
var codeTableDataLength=parser.read7BitEncodedInt();
if(codeTableDataLength!==0)
throw new Error('not implemented: custom code table'); // custom code table
}
if(headerIndicator & VCD_APPHEADER){
// ignore app header data
var appDataLength=parser.read7BitEncodedInt();
parser.skip(appDataLength);
}
var headerEndOffset=parser.offset;
//calculate target file size
var newFileSize=0;
while(!parser.isEOF()){
var winHeader=parser.decodeWindowHeader();
newFileSize+=winHeader.targetWindowLength;
parser.skip(winHeader.addRunDataLength + winHeader.addressesLength + winHeader.instructionsLength);
}
tempFile=new MarcFile(newFileSize);
parser.seek(headerEndOffset);
var cache = new VCD_AdressCache(4,3);
var codeTable = VCD_DEFAULT_CODE_TABLE;
var targetWindowPosition = 0; //renombrar
while(!parser.isEOF()){
var winHeader = parser.decodeWindowHeader();
var addRunDataStream = new VCDIFF_Parser(this.file, parser.offset);
var instructionsStream = new VCDIFF_Parser(this.file, addRunDataStream.offset + winHeader.addRunDataLength);
var addressesStream = new VCDIFF_Parser(this.file, instructionsStream.offset + winHeader.instructionsLength);
var addRunDataIndex = 0;
cache.reset(addressesStream);
var addressesStreamEndOffset = addressesStream.offset;
while(instructionsStream.offset<addressesStreamEndOffset){
/*
var instructionIndex=instructionsStream.readS8();
if(instructionIndex===-1){
break;
}
*/
var instructionIndex = instructionsStream.readU8();
for(var i=0; i<2; i++){
var instruction=codeTable[instructionIndex][i];
var size=instruction.size;
if(size===0 && instruction.type!==VCD_NOOP){
size=instructionsStream.read7BitEncodedInt()
}
if(instruction.type===VCD_NOOP){
continue;
}else if(instruction.type===VCD_ADD){
addRunDataStream.copyToFile2(tempFile, addRunDataIndex+targetWindowPosition, size);
addRunDataIndex += size;
}else if(instruction.type===VCD_COPY){
var addr = cache.decodeAddress(addRunDataIndex+winHeader.sourceLength, instruction.mode);
var absAddr = 0;
// source segment and target segment are treated as if they're concatenated
var sourceData = null;
if(addr < winHeader.sourceLength){
absAddr = winHeader.sourcePosition + addr;
if(winHeader.indicator & VCD_SOURCE){
sourceData = romFile;
}else if(winHeader.indicator & VCD_TARGET){
sourceData = tempFile;
}
}else{
absAddr = targetWindowPosition + (addr - winHeader.sourceLength);
sourceData = tempFile;
}
while(size--){
tempFile._u8array[targetWindowPosition + addRunDataIndex++]=sourceData._u8array[absAddr++];
//targetU8[targetWindowPosition + targetWindowOffs++] = copySourceU8[absAddr++];
}
//to-do: test
//sourceData.copyToFile2(tempFile, absAddr, size, targetWindowPosition + addRunDataIndex);
//addRunDataIndex += size;
}else if(instruction.type===VCD_RUN){
var runByte = addRunDataStream.readU8();
var offset = targetWindowPosition + addRunDataIndex;
for(var j=0; j<size; j++){
tempFile._u8array[offset+j]=runByte;
}
addRunDataIndex += size;
}else{
throw new Error('invalid instruction type found');
}
}
}
if(validate && winHeader.adler32 && (winHeader.adler32 !== adler32(tempFile, targetWindowPosition, winHeader.targetWindowLength))){
throw new Error('error_crc_output');
}
parser.skip(winHeader.addRunDataLength + winHeader.addressesLength + winHeader.instructionsLength);
targetWindowPosition += winHeader.targetWindowLength;
}
//console.log((performance.now()-t0)/1000);
return tempFile;
}
function parseVCDIFF(file){
return new VCDIFF(file);
}
function VCDIFF_Parser(marcFile, offset)
{
MarcFile.call(this, 0);
this.fileSize=marcFile.fileSize;
this._u8array=marcFile._u8array;
this._dataView=marcFile._dataView;
this.offset=offset || 0;
}
VCDIFF_Parser.prototype=Object.create(MarcFile.prototype);
VCDIFF_Parser.prototype.read7BitEncodedInt=function(){
var num=0, bits = 0;
do {
bits = this.readU8();
num = (num << 7) + (bits & 0x7f);
} while(bits & 0x80);
return num;
}
VCDIFF_Parser.prototype.decodeWindowHeader=function(){
var windowHeader={
indicator:this.readU8(),
sourceLength:0,
sourcePosition:0,
adler32:false
};
if(windowHeader.indicator & (VCD_SOURCE | VCD_TARGET)){
windowHeader.sourceLength = this.read7BitEncodedInt();
windowHeader.sourcePosition = this.read7BitEncodedInt();
}
windowHeader.deltaLength = this.read7BitEncodedInt();
windowHeader.targetWindowLength = this.read7BitEncodedInt();
windowHeader.deltaIndicator = this.readU8(); // secondary compression: 1=VCD_DATACOMP,2=VCD_INSTCOMP,4=VCD_ADDRCOMP
if(windowHeader.deltaIndicator!==0){
throw new Error('unimplemented windowHeader.deltaIndicator:'+windowHeader.deltaIndicator);
}
windowHeader.addRunDataLength = this.read7BitEncodedInt();
windowHeader.instructionsLength = this.read7BitEncodedInt();
windowHeader.addressesLength = this.read7BitEncodedInt();
if(windowHeader.indicator & VCD_ADLER32){
windowHeader.adler32 = this.readU32();
}
return windowHeader;
}
VCDIFF_Parser.prototype.copyToFile2=function(target, targetOffset, len){
for(var i=0; i<len; i++){
target._u8array[targetOffset+i]=this._u8array[this.offset+i];
}
//this.file.copyToFile(target, this.offset, len, targetOffset);
this.skip(len);
}
//------------------------------------------------------
// hdrIndicator
const VCD_DECOMPRESS = 0x01;
const VCD_CODETABLE = 0x02;
const VCD_APPHEADER = 0x04; // nonstandard?
// winIndicator
const VCD_SOURCE = 0x01;
const VCD_TARGET = 0x02;
const VCD_ADLER32 = 0x04;
function VCD_Instruction(instruction, size, mode){
this.type=instruction;
this.size=size;
this.mode=mode;
}
/*
build the default code table (used to encode/decode instructions) specified in RFC 3284
heavily based on
https://github.com/vic-alexiev/TelerikAcademy/blob/master/C%23%20Fundamentals%20II/Homework%20Assignments/3.%20Methods/000.%20MiscUtil/Compression/Vcdiff/CodeTable.cs
*/
const VCD_NOOP=0;
const VCD_ADD=1;
const VCD_RUN=2;
const VCD_COPY=3;
const VCD_DEFAULT_CODE_TABLE=(function(){
var entries=[];
var empty = {type: VCD_NOOP, size: 0, mode: 0};
// 0
entries.push([{type: VCD_RUN, size: 0, mode: 0}, empty]);
// 1,18
for(var size=0; size<18; size++){
entries.push([{type: VCD_ADD, size: size, mode: 0}, empty]);
}
// 19,162
for(var mode=0; mode<9; mode++){
entries.push([{type: VCD_COPY, size: 0, mode: mode}, empty]);
for(var size=4; size<19; size++){
entries.push([{type: VCD_COPY, size: size, mode: mode}, empty]);
}
}
// 163,234
for(var mode=0; mode<6; mode++){
for(var addSize=1; addSize<5; addSize++){
for(var copySize=4; copySize<7; copySize++){
entries.push([{type: VCD_ADD, size: addSize, mode: 0},
{type: VCD_COPY, size: copySize, mode: mode}]);
}
}
}
// 235,246
for(var mode=6; mode<9; mode++){
for(var addSize=1; addSize<5; addSize++){
entries.push([{type: VCD_ADD, size: addSize, mode: 0},
{type: VCD_COPY, size: 4, mode: mode}]);
}
}
// 247,255
for(var mode=0; mode<9; mode++){
entries.push([{type: VCD_COPY, size: 4, mode: mode},
{type: VCD_ADD, size: 1, mode: 0}]);
}
return entries;
})();
/*
ported from https://github.com/vic-alexiev/TelerikAcademy/tree/master/C%23%20Fundamentals%20II/Homework%20Assignments/3.%20Methods/000.%20MiscUtil/Compression/Vcdiff
by Victor Alexiev (https://github.com/vic-alexiev)
*/
const VCD_MODE_SELF=0;
const VCD_MODE_HERE=1;
function VCD_AdressCache(nearSize, sameSize){
this.nearSize=nearSize;
this.sameSize=sameSize;
this.near=new Array(nearSize);
this.same=new Array(sameSize*256);
}
VCD_AdressCache.prototype.reset=function(addressStream){
this.nextNearSlot=0;
this.near.fill(0);
this.same.fill(0);
this.addressStream=addressStream;
}
VCD_AdressCache.prototype.decodeAddress=function(here, mode){
var address=0;
if(mode===VCD_MODE_SELF){
address=this.addressStream.read7BitEncodedInt();
}else if(mode===VCD_MODE_HERE){
address=here-this.addressStream.read7BitEncodedInt();
}else if(mode-2<this.nearSize){ //near cache
address=this.near[mode-2]+this.addressStream.read7BitEncodedInt();
}else{ //same cache
var m=mode-(2+this.nearSize);
address=this.same[m*256+this.addressStream.readU8()];
}
this.update(address);
return address;
}
VCD_AdressCache.prototype.update=function(address){
if(this.nearSize>0){
this.near[this.nextNearSlot]=address;
this.nextNearSlot=(this.nextNearSlot+1)%this.nearSize;
}
if(this.sameSize>0){
this.same[address%(this.sameSize*256)]=address;
}
}

162
legacy/js/formats/zip.js Normal file
View file

@ -0,0 +1,162 @@
/* ZIP module for Rom Patcher JS v20230721 - Marc Robledo 2016-2024 - http://www.marcrobledo.com/license */
const ZIP_MAGIC='\x50\x4b\x03\x04';
var ZIPManager=(function(){
const FILTER_PATCHES=/\.(ips|ups|bps|aps|rup|ppf|mod|xdelta|vcdiff)$/i;
//const FILTER_ROMS=/(?<!\.(txt|diz|rtf|docx?|xlsx?|html?|pdf|jpe?g|gif|png|bmp|webp|zip|rar|7z))$/i; //negative lookbehind is not compatible with Safari https://stackoverflow.com/a/51568859
const FILTER_NON_ROMS=/(\.(txt|diz|rtf|docx?|xlsx?|html?|pdf|jpe?g|gif|png|bmp|webp|zip|rar|7z))$/i;
var _unzipEntry=function(zippedEntry, dest, dest2, parse){
zippedEntry.getData(new zip.BlobWriter(), function(blob){
var fileReader=new FileReader();
fileReader.onload=function(){
var unzippedFile=new MarcFile(this.result);
unzippedFile.fileName=zippedEntry.filename;
if(dest.patches){
dest.patches[dest2].fetchedFile=unzippedFile;
if(parse)
parseCustomPatch(dest.patches[dest2]);
}if(dest===romFile){
romFile=unzippedFile;
_parseROM();
}else if(dest===patchFile){
patchFile=unzippedFile;
_readPatchFile();
}
};
fileReader.readAsArrayBuffer(blob);
});
};
return{
parseFile:function(sourceFile, compressedFileIndex){
setMessage('apply', _('unzipping'), 'loading');
var arrayBuffer=sourceFile._u8array.buffer;
zip.createReader(
new zip.BlobReader(new Blob([arrayBuffer])),
/* success */
function(zipReader){
zipReader.getEntries(function(zipEntries){
var filteredEntries=[];
for(var i=0; i<zipEntries.length; i++){
if(
(
(sourceFile===romFile && !FILTER_NON_ROMS.test(zipEntries[i].filename) && !FILTER_PATCHES.test(zipEntries[i].filename)) ||
(sourceFile!==romFile && FILTER_PATCHES.test(zipEntries[i].filename))
) && !zipEntries[i].directory
){
filteredEntries.push(zipEntries[i]);
}
}
/* sort patch files by name and folder */
filteredEntries
.sort(function(file1, file2){
return file1.filename.toLowerCase().localeCompare(file2.filename.toLowerCase());
})
.sort(function(file1, file2){
var file1Folder=file1.filename.indexOf('/')===-1? 0 : 1;
var file2Folder=file2.filename.indexOf('/')===-1? 0 : 1;
return file1Folder - file2Folder;
});
var customPatch=false;
if(isCustomPatcherEnabled()){
for(var i=0; i<CUSTOM_PATCHER.length && !customPatch; i++){
if(CUSTOM_PATCHER[i].fetchedFile===sourceFile)
customPatch=CUSTOM_PATCHER[i];
}
}
if(customPatch){
if(customPatch.patches){
for(var i=0; i<customPatch.patches.length; i++){
for(var j=0; j<filteredEntries.length; j++){
if(customPatch.patches[i].file===filteredEntries[j].filename){
_unzipEntry(filteredEntries[j], customPatch, i, i===compressedFileIndex);
break;
}
}
}
}else{
var nextOption;
var customPatchIndex=CUSTOM_PATCHER.indexOf(customPatch);
customPatch.patches=[];
for(var i=0; i<filteredEntries.length; i++){
customPatch.patches.push({
file:filteredEntries[i].filename,
fetchedFile:false,
name:customPatch.name + ' - ' + filteredEntries[i].filename,
crc:customPatch.crc
});
var option;
if(i===0){
option=customPatch.selectOption;
nextOption=option.nextSibling;
}else{
option=document.createElement('option');
if(nextOption)
el('input-file-patch').insertBefore(option, nextOption);
else
el('input-file-patch').appendChild(option);
}
option.value=customPatchIndex+','+i;
option.innerHTML=customPatch.patches[i].name;
nextOption=option.nextSibling;
_unzipEntry(filteredEntries[i], customPatch, i, i===0);
}
}
setTabApplyEnabled(false);
}else{
var _evtClickDialogEntry=function(evt){
UI.hideDialog('zip');
_unzipEntry(this.zipEntry, sourceFile);
}
if(filteredEntries.length>1){
document.getElementById('zip-dialog-message').innerHTML=_(sourceFile===romFile?'rom_file':'patch_file');
var zipList=document.getElementById('zip-dialog-file-list');
zipList.innerHTML='';
for(var i=0; i<filteredEntries.length; i++){
var li=document.createElement('li');
li.zipEntry=filteredEntries[i];
li.innerHTML=filteredEntries[i].filename;
addEvent(li, 'click', _evtClickDialogEntry);
zipList.appendChild(li);
}
UI.showDialog('zip');
}else if(filteredEntries.length===1){
_unzipEntry(filteredEntries[0], sourceFile);
}else{
if(sourceFile===romFile){
romFile=null;
setMessage('apply', _('no_valid_file_found'), 'error');
}else if(sourceFile===patchFile){
patchFile=null;
setMessage('apply', _('error_invalid_patch'), 'error');
}
}
setTabApplyEnabled(true);
}
});
},
/* failed */
function(zipReader){
setTabApplyEnabled(true);
setMessage('apply', _('error_unzipping'), 'error');
}
);
}
}
})();

446
legacy/js/locale.js Normal file
View file

@ -0,0 +1,446 @@
const LOCALIZATION={
'en':{
'creator_mode': 'Creator mode',
'settings': 'Settings',
'alternate_output_name': 'Use patch name for output',
'fix_checksum': 'Fix ROM header checksum',
'light_theme': 'Light theme',
'apply_patch': 'Apply patch',
'rom_file': 'ROM file:',
'patch_file': 'Patch file:',
'expected_source': 'Expected ROM %s:',
'remove_header': 'Remove header',
'add_header': 'Add temporary header',
'compatible_formats': 'Compatible formats:',
'applying_patch': 'Applying patch...',
'downloading': 'Downloading...',
'unzipping': 'Unzipping...',
'fix_checksum_prompt': 'Fix ROM header checksum?',
'create_patch': 'Create patch',
'original_rom': 'Original ROM:',
'modified_rom': 'Modified ROM:',
'patch_type': 'Patch type:',
'creating_patch': 'Creating patch...',
'error_crc_input': 'Source ROM checksum mismatch',
'error_crc_output': 'Target ROM checksum mismatch',
'error_crc_patch': 'Patch checksum mismatch',
'error_downloading': 'Error downloading %s',
'error_unzipping': 'Error unzipping file',
'error_invalid_patch': 'Invalid patch file',
'warning_too_big': 'Using big files is not recommended.'
},
'es':{
'creator_mode': 'Modo creador',
'settings': 'Configuración',
'alternate_output_name': 'Guardar con nombre del parche',
'fix_checksum': 'Corregir checksum cabecera ROM',
'light_theme': 'Tema claro',
'apply_patch': 'Aplicar parche',
'rom_file': 'Archivo ROM:',
'patch_file': 'Archivo parche:',
'expected_source': '%s esperado de ROM:',
'remove_header': 'Quitar cabecera',
'add_header': 'Añadir cabecera temporal',
'compatible_formats': 'Formatos compatibles:',
'applying_patch': 'Aplicando parche...',
'downloading': 'Descargando...',
'unzipping': 'Descomprimiendo...',
'fix_checksum_prompt': '¿Corregir checksum en la cabecera de la ROM?',
'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 %s',
'error_unzipping': 'Error descomprimiendo archivo',
'error_invalid_patch': 'Archivo de parche no válido',
'warning_too_big': 'No se recomienda usar archivos muy grandes.'
},
'nl':{
'creator_mode': 'Creator-modus',
'settings': 'Settings',
'alternate_output_name': 'Use patch name for output',
'light_theme': 'Light theme',
'apply_patch': 'Pas patch toe',
'rom_file': 'ROM bestand:',
'patch_file': 'Patch bestand:',
'expected_source': 'Expected ROM %s:',
'remove_header': 'Verwijder rubriek',
'add_header': 'Voeg tijdelijk rubriek toe',
'compatible_formats': 'Compatibele formaten:',
'applying_patch': 'Patch toepassen...',
'downloading': 'Downloaden...',
'unzipping': 'Uitpakken...',
'create_patch': 'Maak patch',
'original_rom': 'Originale ROM:',
'modified_rom': 'Aangepaste ROM:',
'patch_type': 'Type patch:',
'creating_patch': 'Patch maken...',
'error_crc_input': 'Controlesom van bron-ROM komt niet overeen',
'error_crc_output': 'Controlesom van doel-ROM komt niet overeen',
'error_crc_patch': 'Controlesom van patch komt niet overeen',
'error_downloading': 'Fout bij downloaden van patch',
'error_unzipping': 'Fout bij uitpakken van bestand',
'error_invalid_patch': 'Ongeldig patchbestand',
'warning_too_big': 'Het gebruik van grote bestanden wordt niet aanbevolen.'
},
'sv':{
'creator_mode': 'Skaparläge',
'settings': 'Settings',
'alternate_output_name': 'Use patch name for output',
'light_theme': 'Light theme',
'apply_patch': 'Tillämpa korrigeringsfil',
'rom_file': 'ROM-fil:',
'patch_file': 'Korrigeringsfil:',
'expected_source': 'Expected ROM %s:',
'remove_header': 'Ta bort rubrik',
'add_header': 'Lägg till tillfällig rubrik',
'compatible_formats': 'Kompatibla format:',
'applying_patch': 'Tillämpar korrigeringsfil...',
'downloading': 'Ladda ner...',
'unzipping': 'Packa upp...',
'create_patch': 'Skapa korrigeringsfil',
'original_rom': 'Original-ROM:',
'modified_rom': 'Modifierad ROM:',
'patch_type': 'Korrigeringsfil-typ:',
'creating_patch': 'Skapa korrigeringsfil...',
'error_crc_input': 'ROM-källans kontrollsumman matchar inte',
'error_crc_output': 'ROM-målets kontrollsumman matchar inte',
'error_crc_patch': 'korrigeringsfilens kontrollsumman matchar inte',
'error_downloading': 'Fel vid nedladdning av korrigeringsfilen',
'error_unzipping': 'Det gick inte att packa upp filen',
'error_invalid_patch': 'Ogiltig korrigeringsfil',
'warning_too_big': 'Användning av stora filer rekommenderas inte.'
},
'ca':{
'creator_mode': 'Mode creador',
'settings': 'Configuració',
'alternate_output_name': 'Desar amb nom del pedaç',
'light_theme': 'Tema clar',
'apply_patch': 'Aplicar pedaç',
'rom_file': 'Arxiu ROM:',
'patch_file': 'Arxiu pedaç:',
'expected_source': '%s esperat de ROM:',
'remove_header': 'Treure capçalera',
'add_header': 'Afegir capçalera temporal',
'compatible_formats': 'Formats compatibles:',
'applying_patch': 'Aplicant pedaç...',
'downloading': 'Descarregant...',
'unzipping': 'Descomprimint...',
'create_patch': 'Crear pedaç',
'original_rom': 'ROM original:',
'modified_rom': 'ROM modificada:',
'patch_type': 'Tipus de pedaç:',
'creating_patch': 'Creant pedaç...',
'error_crc_input': 'Checksum de ROM original no vàlida',
'error_crc_output': 'Checksum de ROM creada no vàlida',
'error_crc_patch': 'Checksum de pedaç no vàlida',
'error_downloading': 'Error descarregant %s',
'error_unzipping': 'Error descomprimint arxiu',
'error_invalid_patch': 'Arxiu de pedaç no vàlid',
'warning_too_big': 'No es recomana usar arxius molt grans.'
},
'ca-va':{
'creator_mode': 'Mode creador',
'settings': 'Configuració',
'alternate_output_name': 'Guardar amb nom del pedaç',
'light_theme': 'Tema clar',
'apply_patch': 'Aplicar pedaç',
'rom_file': 'Arxiu ROM:',
'patch_file': 'Arxiu pedaç:',
'expected_source': '%s esperat de ROM:',
'remove_header': 'Llevar capçalera',
'add_header': 'Afegir capçalera temporal',
'compatible_formats': 'Formats compatibles:',
'applying_patch': 'Aplicant pedaç...',
'downloading': 'Descarregant...',
'unzipping': 'Descomprimint...',
'create_patch': 'Crear pedaç',
'original_rom': 'ROM original:',
'modified_rom': 'ROM modificada:',
'patch_type': 'Tipus de pedaç:',
'creating_patch': 'Creant pedaç...',
'error_crc_input': 'Checksum de ROM original incorrecta',
'error_crc_output': 'Checksum de ROM creada incorrecta',
'error_crc_patch': 'Checksum de pedaç incorrecte',
'error_downloading': 'Error descarregant %s',
'error_unzipping': 'Error descomprimint arxiu',
'error_invalid_patch': 'Arxiu de pedaç incorrecte',
'warning_too_big': 'No és recomanable utilitzar arxius molt grans.'
},
'ru':{
'creator_mode': 'Режим создания',
'settings': 'Settings',
'alternate_output_name': 'Use patch name for output',
'light_theme': 'Light theme',
'apply_patch': 'Применить патч',
'rom_file': 'Файл ROM:',
'patch_file': 'Файл патча:',
'expected_source': 'Expected ROM %s:',
'remove_header': 'Удалить заголовок перед применением',
'add_header': 'Добавить временный заголовок',
'compatible_formats': 'Совместимые форматы:',
'applying_patch': 'Применяется патч...',
'downloading': 'Загрузка...',
'unzipping': 'Unzipping...',
'create_patch': 'Создать патч',
'original_rom': 'Оригинальный ROM:',
'modified_rom': 'Изменённый ROM:',
'patch_type': 'Тип патча:',
'creating_patch': 'Патч создаётся...',
'error_crc_input': 'Неправильная контрольная сумма входного ROM',
'error_crc_output': 'Неправильная контрольная сумма выходного ROM',
'error_crc_patch': 'Неправильная контрольная сумма патча',
'error_downloading': 'Ошибка при скачивании патча',
'error_unzipping': 'Error unzipping file',
'error_invalid_patch': 'Неправильный файл патча',
'warning_too_big': 'Не рекомендуется использовать большие файлы.'
},
'de':{
'creator_mode': 'Erstellmodus',
'settings': 'Einstellungen',
'alternate_output_name': 'Output ist Name vom Patch',
'fix_checksum': 'Prüfsumme im ROM Header korrigieren',
'light_theme': 'Helles Design',
'apply_patch': 'Patch anwenden',
'rom_file': 'ROM-Datei:',
'patch_file': 'Patch-Datei:',
'expected_source': 'Expected ROM %s:',
'remove_header': 'Header entfernen',
'add_header': 'Header temporär hinzufügen',
'compatible_formats': 'Unterstützte Formate:',
'applying_patch': 'Patch wird angewandt...',
'downloading': 'Herunterladen...',
'unzipping': 'Entpacken...',
'fix_checksum_prompt': 'Prüfsumme im ROM Header korrigieren?',
'create_patch': 'Patch erstellen',
'original_rom': 'Originale ROM:',
'modified_rom': 'Veränderte ROM:',
'patch_type': 'Patch-Format:',
'creating_patch': 'Patch wird erstellt...',
'error_crc_input': 'Prüfsumme der Input-ROM stimmt nicht überein',
'error_crc_output': 'Prüfsumme der Output-ROM stimmt nicht überein',
'error_crc_patch': 'Prüfsumme vom Patch stimmt nicht überein',
'error_downloading': 'Fehler beim Herunterladen vom %s',
'error_unzipping': 'Fehler beim Entpacken',
'error_invalid_patch': 'Ungültiger Patch',
'warning_too_big': 'Große Dateien zu verwenden ist nicht empfohlen.'
},
'pt-br':{
'creator_mode': 'Modo criador',
'settings': 'Configurações',
'alternate_output_name': 'Usar o nome do patch na saída',
'fix_checksum': 'Consertar o checksum do cabeçalho da ROM',
'light_theme': 'Tema leve',
'apply_patch': 'Aplicar patch',
'rom_file': 'Arquivo da ROM:',
'patch_file': 'Arquivo do patch:',
'expected_source': 'Expected ROM %s:',
'remove_header': 'Remover cabeçalho',
'add_header': 'Adicionar cabeçalho temporário',
'compatible_formats': 'Formatos compatíveis:',
'applying_patch': 'Aplicando patch...',
'downloading': 'Baixando...',
'unzipping': 'Descompactando...',
'fix_checksum_prompt': 'Consertar o checksum do cabeçalho da ROM?',
'create_patch': 'Criar patch',
'original_rom': 'ROM original:',
'modified_rom': 'ROM modificada:',
'patch_type': 'Tipo de patch:',
'creating_patch': 'Criando o patch...',
'error_crc_input': 'O checksum da ROM original é inválido',
'error_crc_output': 'O checksum da ROM alvo é inválido',
'error_crc_patch': 'O checksum do patch é inválido',
'error_downloading': 'Erro ao baixar o %s',
'error_unzipping': 'Erro ao descompactar o arquivo',
'error_invalid_patch': 'Arquivo do patch inválido',
'warning_too_big': 'O uso de arquivos grandes não é recomendado.'
},
'ja':{
'creator_mode': '作成モード',
'settings': '設定',
'alternate_output_name': 'パッチと同じ名前で出力',
'light_theme': 'ライトテーマ',
'apply_patch': 'パッチを当て',
'rom_file': 'ROMファィル',
'patch_file': 'パッチファイル:',
'expected_source': 'Expected ROM %s:',
'remove_header': 'ヘッダーを削除',
'add_header': '一時的なヘッダーを追加',
'compatible_formats': '互換性のあるフォーマット:',
'applying_patch': 'パッチを当ている…',
'downloading': 'ダウンロードしている…',
'unzipping': '解凍している…',
'create_patch': 'パッチを作成',
'original_rom': '元のROM',
'modified_rom': '変更されたROM',
'patch_type': 'パッチのタイプ:',
'creating_patch': 'パッチを作成している…',
'error_crc_input': 'ソースROMチェックサムの不一致',
'error_crc_output': 'ターゲットROMチェクサムの不一致',
'error_crc_patch': 'バッチチェックサムの不一致',
'error_downloading': 'パッチのダウンロードエラー',
'error_unzipping': 'パッチの解凍エラー',
'error_invalid_patch': '無効なパッチエラー',
'warning_too_big': '大きなファイルの使いはおすすめしない。'
},
'fr':{
'creator_mode': 'Mode créateur',
'settings': 'Configurations',
'alternate_output_name': 'Utiliser le nom du patch pour renommer la ROM une fois patchée',
'light_theme': 'Thème Clair',
'apply_patch': 'Appliquer le patch',
'rom_file': 'Fichier ROM:',
'patch_file': 'Fichier patch:',
'expected_source': 'Expected ROM %s:',
'remove_header': 'Supprimer l\'en-tête',
'add_header': 'Ajouter une en-tête temporaire',
'compatible_formats': 'Formats compatibles:',
'applying_patch': 'Application du patch...',
'downloading': 'Téléchargement...',
'unzipping': 'Décompresser...',
'create_patch': 'Créer le patch',
'original_rom': 'ROM originale:',
'modified_rom': 'ROM modifiée:',
'patch_type': 'Type de patch:',
'creating_patch': 'Création du patch...',
'error_crc_input': 'Non-concordance de la somme de contrôle de la ROM source',
'error_crc_output': 'Non-concordance de la somme de contrôle de la ROM cible',
'error_crc_patch': 'Non-concordance de la somme de contrôle du patch',
'error_downloading': 'Erreur lors du téléchargement du patch',
'error_unzipping': 'Erreur lors de la décompression du fichier',
'error_invalid_patch': 'Fichier patch invalide',
'warning_too_big': 'L\'utilisation de gros fichiers n\'est pas recommandée.'
},
'zh-cn':{
'creator_mode': '创建模式',
'settings': '设置',
'alternate_output_name': '修改后ROM文件名和补丁保持一致',
'light_theme': '浅色主题',
'apply_patch': '打补丁',
'rom_file': 'ROM文件',
'patch_file': '补丁文件:',
'expected_source': 'Expected ROM %s:',
'remove_header': '删除文件头',
'add_header': '增加临时文件头',
'compatible_formats': '兼容补丁格式:',
'applying_patch': '正在打补丁……',
'downloading': '正在下载……',
'unzipping': '正在解压……',
'create_patch': '创建补丁',
'original_rom': '原始ROM',
'modified_rom': '修改后ROM',
'patch_type': '补丁类型:',
'creating_patch': '正在创建补丁……',
'error_crc_input': '原始ROM校验和不匹配',
'error_crc_output': '目标ROM校验和不匹配',
'error_crc_patch': '补丁文件校验和不匹配',
'error_downloading': '下载出错:%s',
'error_unzipping': '解压出错',
'error_invalid_patch': '无效补丁',
'warning_too_big': '不推荐使用大文件。'
},
'zh-tw':{
'creator_mode': '創作者模式',
'settings': '設定',
'alternate_output_name': '修改後ROM檔名和patch保持一致',
'fix_checksum': '修正ROM檔頭校驗碼',
'light_theme': '淺色主題',
'apply_patch': '套用patch',
'rom_file': 'ROM檔',
'patch_file': 'patch檔',
'expected_source': 'Expected ROM %s:',
'remove_header': '刪除檔頭',
'add_header': '增加臨時檔頭',
'compatible_formats': '相容格式:',
'applying_patch': '套用patch中……',
'downloading': '下載中……',
'unzipping': '解壓中……',
'fix_checksum_prompt': '修復ROM檔頭較驗碼',
'create_patch': '創建patch',
'original_rom': '原始ROM',
'modified_rom': '修改後ROM',
'patch_type': 'patch類型',
'creating_patch': '正在創建patch……',
'error_crc_input': '原始ROM校驗碼不匹配',
'error_crc_output': '目標ROM校驗碼不匹配',
'error_crc_patch': 'patch檔校驗碼不匹配',
'error_downloading': '下載出錯:%s',
'error_unzipping': '解壓出錯',
'error_invalid_patch': '無效的patch檔',
'warning_too_big': '不建議使用大檔。'
},
'it':{
'creator_mode': 'Modalità creatore',
'settings': 'Impostazioni',
'alternate_output_name': 'Usa il nome della patch per uscita',
'light_theme': 'Tema chiaro',
'apply_patch': 'Applica patch',
'rom_file': 'File ROM:',
'patch_file': 'File patch:',
'expected_source': 'Expected ROM %s:',
'remove_header': 'Rimuovi header',
'add_header': 'Aggiungi header temporaneo',
'compatible_formats': 'Formati:',
'applying_patch': 'Applica patch...',
'downloading': 'Scaricamento...',
'unzipping': 'Estrazione...',
'create_patch': 'Crea patch',
'original_rom': 'ROM originale:',
'modified_rom': 'ROM modificata:',
'patch_type': 'Tipologia patch:',
'creating_patch': 'Creazione patch...',
'error_crc_input': 'Checksum della ROM sorgente non valido',
'error_crc_output': 'Checksum della ROM destinataria non valido',
'error_crc_patch': 'Checksum della patch non valido',
'error_downloading': 'Errore di scaricamento %s',
'error_unzipping': 'Errore estrazione file',
'error_invalid_patch': 'File della patch non valido',
'warning_too_big': 'Non è raccomandato usare file di grandi dimensioni.'
}
};

86
legacy/js/worker_apply.js Normal file
View file

@ -0,0 +1,86 @@
/* Rom Patcher JS v20230331 - Marc Robledo 2016-2023 - http://www.marcrobledo.com/license */
self.importScripts(
'./MarcFile.js',
'./crc.js',
'./formats/ips.js',
'./formats/aps_n64.js',
'./formats/aps_gba.js',
'./formats/ups.js',
'./formats/bps.js',
'./formats/rup.js',
'./formats/ppf.js',
'./formats/pmsr.js',
'./formats/vcdiff.js'
);
self.onmessage = event => { // listen for messages from the main thread
var romFile=new MarcFile(event.data.romFileU8Array);
var patchFile=new MarcFile(event.data.patchFileU8Array);
var errorMessage=false;
var patch;
var header=patchFile.readString(6);
if(header.startsWith(IPS_MAGIC)){
patch=parseIPSFile(patchFile);
}else if(header.startsWith(UPS_MAGIC)){
patch=parseUPSFile(patchFile);
}else if(header.startsWith(APS_N64_MAGIC)){
patch=parseAPSFile(patchFile);
}else if(header.startsWith(APS_GBA_MAGIC)){
patch=APSGBA.fromFile(patchFile);
}else if(header.startsWith(BPS_MAGIC)){
patch=parseBPSFile(patchFile);
}else if(header.startsWith(RUP_MAGIC)){
patch=parseRUPFile(patchFile);
}else if(header.startsWith(PPF_MAGIC)){
patch=parsePPFFile(patchFile);
}else if(header.startsWith(PMSR_MAGIC)){
patch=parseMODFile(patchFile);
}else if(header.startsWith(VCDIFF_MAGIC)){
patch=parseVCDIFF(patchFile);
}else{
errorMessage='error_invalid_patch';
}
//console.log('apply');
var patchedRom;
if(patch){
try{
patchedRom=patch.apply(romFile, event.data.validateChecksums);
}catch(evt){
errorMessage=evt.message;
}
}
//console.log('postMessage');
if(patchedRom){
self.postMessage(
{
romFileU8Array:event.data.romFileU8Array,
patchFileU8Array:event.data.patchFileU8Array,
patchedRomU8Array:patchedRom._u8array,
errorMessage:errorMessage
},
[
event.data.romFileU8Array.buffer,
event.data.patchFileU8Array.buffer,
patchedRom._u8array.buffer
]
);
}else{
self.postMessage(
{
romFileU8Array:event.data.romFileU8Array,
patchFileU8Array:event.data.patchFileU8Array,
errorMessage:errorMessage
},
[
event.data.romFileU8Array.buffer,
event.data.patchFileU8Array.buffer
]
);
}
};

23
legacy/js/worker_crc.js Normal file
View file

@ -0,0 +1,23 @@
/* Rom Patcher JS v20200502 - Marc Robledo 2016-2020 - http://www.marcrobledo.com/license */
self.importScripts(
'./MarcFile.js',
'./crc.js'
);
self.onmessage = event => { // listen for messages from the main thread
var sourceFile=new MarcFile(event.data.u8array);
self.postMessage(
{
crc32:crc32(sourceFile, event.data.startOffset),
md5:md5(sourceFile, event.data.startOffset),
u8array:event.data.u8array
},
[
event.data.u8array.buffer
]
);
};

View file

@ -0,0 +1,63 @@
/* Rom Patcher JS v20200502 - Marc Robledo 2016-2020 - http://www.marcrobledo.com/license */
self.importScripts(
'./MarcFile.js',
'./crc.js',
'./formats/ips.js',
'./formats/aps_n64.js',
'./formats/ups.js',
'./formats/bps.js',
'./formats/ppf.js',
'./formats/rup.js'
);
self.onmessage = event => { // listen for messages from the main thread
var sourceFile=new MarcFile(event.data.sourceFileU8Array);
var modifiedFile=new MarcFile(event.data.modifiedFileU8Array);
var mode=event.data.patchMode;
sourceFile.seek(0);
modifiedFile.seek(0);
var patch;
if(mode==='ips'){
patch=createIPSFromFiles(sourceFile, modifiedFile);
}else if(mode==='bps'){
//use delta mode (slower, but smaller patch size) only with <4mb files
patch=createBPSFromFiles(sourceFile, modifiedFile, (sourceFile.fileSize<=4194304));
}else if(mode==='ups'){
patch=createUPSFromFiles(sourceFile, modifiedFile);
}else if(mode==='aps'){
patch=createAPSFromFiles(sourceFile, modifiedFile);
}else if(mode==='rup'){
patch=createRUPFromFiles(sourceFile, modifiedFile);
}else if(mode==='ppf'){
patch=createPPFFromFiles(sourceFile, modifiedFile);
}else{
throw new Error('error_invalid_patch');
}
//special case: PPF+modified size>original size, skip verification
if(!(mode==='ppf' && sourceFile.fileSize>modifiedFile.fileSize) && crc32(modifiedFile)!==crc32(patch.apply(sourceFile))){
throw new Error('Unexpected error: verification failed. Patched file and modified file mismatch. Please report this bug.');
}
var newPatchFile=patch.export('file');
//console.log('postMessage');
self.postMessage(
{
//sourceFileU8Array:event.data.sourceFileU8Array,
//modifiedFileU8Array:event.data.modifiedFileU8Array,
patchFileU8Array:newPatchFile._u8array
},
[
//event.data.sourceFileU8Array.buffer,
//event.data.modifiedFileU8Array.buffer,
newPatchFile._u8array.buffer
]
);
};

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,2 @@
/* jshint worker:true */
!function(c){"use strict";if(c.zWorkerInitialized)throw new Error("z-worker.js should be run only once");c.zWorkerInitialized=!0,addEventListener("message",function(t){var e,r,c=t.data,n=c.type,s=c.sn,p=o[n];if(p)try{p(c)}catch(t){e={type:n,sn:s,error:(r=t,{message:r.message,stack:r.stack})},postMessage(e)}});var o={importScripts:function(t){t.scripts&&0<t.scripts.length&&importScripts.apply(void 0,t.scripts);postMessage({type:"importScripts"})},newTask:h,append:t,flush:t},f={};function h(t){var e=c[t.codecClass],r=t.sn;if(f[r])throw Error("duplicated sn");f[r]={codec:new e(t.options),crcInput:"input"===t.crcType,crcOutput:"output"===t.crcType,crc:new n},postMessage({type:"newTask",sn:r})}var l=c.performance?c.performance.now.bind(c.performance):Date.now;function t(t){var e=t.sn,r=t.type,c=t.data,n=f[e];!n&&t.codecClass&&(h(t),n=f[e]);var s,p="append"===r,o=l();if(p)try{s=n.codec.append(c,function(t){postMessage({type:"progress",sn:e,loaded:t})})}catch(t){throw delete f[e],t}else delete f[e],s=n.codec.flush();var a=l()-o;o=l(),c&&n.crcInput&&n.crc.append(c),s&&n.crcOutput&&n.crc.append(s);var i=l()-o,u={type:r,sn:e,codecTime:a,crcTime:i},d=[];s&&(u.data=s,d.push(s.buffer)),p||!n.crcInput&&!n.crcOutput||(u.crc=n.crc.get());try{postMessage(u,d)}catch(t){postMessage(u)}}function n(){this.crc=-1}function e(){}n.prototype.append=function(t){for(var e=0|this.crc,r=this.table,c=0,n=0|t.length;c<n;c++)e=e>>>8^r[255&(e^t[c])];this.crc=e},n.prototype.get=function(){return~this.crc},n.prototype.table=function(){var t,e,r,c=[];for(t=0;t<256;t++){for(r=t,e=0;e<8;e++)1&r?r=r>>>1^3988292384:r>>>=1;c[t]=r}return c}(),(c.NOOP=e).prototype.append=function(t,e){return t},e.prototype.flush=function(){}}(this);

28
legacy/js/zip.js/zip.js Normal file

File diff suppressed because one or more lines are too long

32
legacy/manifest.json Normal file
View file

@ -0,0 +1,32 @@
{
"short_name":"Rom Patcher JS",
"name":"Rom Patcher JS",
"icons":[
{
"src": "style/app_icon_114.png",
"sizes": "114x114",
"type": "image/png",
"density": "1.0"
},{
"src": "style/app_icon_144.png",
"sizes": "144x144",
"type": "image/png",
"density": "1.0"
},{
"src": "style/app_icon_192.png",
"sizes": "192x192",
"type": "image/png",
"density": "1.0"
},{
"src": "style/app_icon_maskable.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
}
],
"start_url": "index.html",
"display": "standalone",
"orientation": "portrait",
"theme_color": "#31343a",
"background_color": "#31343a"
}

414
legacy/style/RomPatcher.css Normal file
View file

@ -0,0 +1,414 @@
/* Rom Patcher JS CSS template by Marc Robledo v20220323 */
/* minify at https://cssminifier.com/ */
/* @FONT-FACES */
@import url('https://fonts.googleapis.com/css?family=Open+Sans:400,700');
@import url('https://fonts.googleapis.com/css?family=Roboto+Mono:300');
body{
margin:0;
font:15px 'Open Sans',sans-serif;
cursor:default;
line-height:1.8;
background-color:#31343a;
color:#3c3c3c;
-moz-user-select:none;
-webkit-user-select: none;
-ms-user-select:none;
-o-user-select:none;
user-select:none;
}
/* flex main column */
html, body{height:100%}
#column{
display: flex;
flex-wrap: nowrap;
height: 100%;
flex-direction: column;
}
header{margin: 1% 0 4%}
header h1{display:none}
footer{padding: 50px 0 20px}
#wrapper{flex-basis:100%}
.clickable{cursor:pointer}
.hide{display:none !important}
.text-center{text-align:center}
.text-right{text-align:right}
.m-b{margin-bottom:8px}
/* flex box */
.row{
display:flex;
flex-flow:row wrap; /* this is the same as flex-direction:row;flex-wrap:wrap; */
align-items:center; /* vertical align */
justify-content:space-between
}
.leftcol{width:28%}
.rightcol{width:70%}
.leftcol-md{width:60%}
.rightcol-md{width:40%}
.leftcol-lg{width:70%}
.rightcol-lg{width:30%}
/* icons */
.icon{
display:inline-block;
vertical-align:middle;
width:16px;height:16px
}
/* header+footer */
header{text-align:center}
header h1{margin:0}
header img{max-width:90%; height:192px;}
footer{
text-align:center;
color:#767b86;
font-size:85%;
}
footer a{
color:#969ba6;
text-decoration:none;
border-bottom:1px solid #464b56;
}
footer a:hover{
color:white;
border-color:#41d5ff;
}
hr{border:none;border-top:1px dotted #bbb;margin:15px 0}
/* outer buttons */
.button-outer{
background-color:transparent;
color:white;
cursor:pointer;
text-align:center;
}
.button-outer:hover,.button-outer:focus{
background-color:#2b2e33;
cursor:pointer;
}
.button-outer img{
height:16px;
display:inline-block;
vertical-align:middle;
}
/* Switch mode */
#switch-container{
visibility:hidden;
text-align:right;
margin-bottom:10px;
font-size:88%;
}
#switch-create-button{
border-radius:2px;
padding: 6px 8px;
transition:background-color .1s;
}
.switch{
display:inline-block;
vertical-align:middle;
width:30px;height:16px;
border-radius:8px;
position:relative;
transition:background-color .2s;
background-color:#babfbf;
}
#switch-create-button .switch.disabled{background-color:#474c56;}
#settings-dialog .switch{transition:opacity .1s;}
#settings-dialog .switch:hover{cursor:pointer;opacity:.7}
.switch:before{
position:absolute;
background-color:white;
height:12px;width:12px;
content:" ";
border-radius:6px;
top:2px;
left:2px;
transition:left .2s;
}
.switch.enabled:before{
left:16px;
}
.switch.enabled{background-color:#41bdc7;}
.tab{background-color:#f9fafa;padding:30px 15px;border-radius: 3px}
#tab1{display:none}
.buttons{
margin-top:20px;
text-align:right
}
/* forms */
input[type=file],select{
box-sizing:border-box;
max-width:100%;
font-family:inherit;
font-size:100%;
outline:0;
border:none;
border-radius:3px;
background-color:#edefef;
}
input[type=file].w100, select.w100{width:100%}
input[type=file]{padding:6px 10px}
select{
padding:6px 18px 6px 10px;
-webkit-appearance:none;
-moz-appearance:none;
text-overflow:'';
background-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMTJweCIgeT0iMHB4IiB3aWR0aD0iMjRweCIgaGVpZ2h0PSIzcHgiIHZpZXdCb3g9IjAgMCA2IDMiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDYgMyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHBvbHlnb24gcG9pbnRzPSI1Ljk5MiwwIDIuOTkyLDMgLTAuMDA4LDAgIi8+PC9zdmc+");
background-position:100% center;
background-repeat:no-repeat;
}
select::-ms-expand{display:none}
input[type=file].enabled:hover,select.enabled:hover{background-color:#dee1e1}
input[type=file].disabled,select.disabled{background-color:transparent}
/* buttons */
button{
font-family:inherit;
font-size:100%;
min-width:120px;
border-radius:3px;border:0;
padding:10px 20px;
margin:0 5px;
background-color:#2a9ca5;
color:white;
transition:background-color .15s;
box-sizing:border-box
}
button.enabled:hover{
cursor:pointer;
background-color:#3aacb5;
}
button.enabled:active{
background-color:#297b81;
transform:translateY(1px)
}
button:disabled{opacity:.35 !important;cursor:not-allowed}
button.no-text.with-icon:before{margin-right:0px}
#rom-info, #row-expected-source-info{font-family:'Roboto Mono',monospace;color:#888;font-size:12px;
-moz-user-select:text;
-webkit-user-select: text;
-ms-user-select:text;
-o-user-select:text;
user-select:text;
cursor:text;
}
#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{
display:inline-block;
vertical-align:middle;
content:"";
width:16px;height:16px;
margin-left:4px;
background-size:100% 100%;
background-image:url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggc3Ryb2tlPSIjMDgwIiBzdHJva2Utd2lkdGg9IjIuNzMiIGZpbGw9Im5vbmUiIGQ9Ik0gMS40Myw2LjU5IEMgNi4yNiwxMS40MiA2LjI2LDExLjQyIDYuMjYsMTEuNDIgTCAxNC42OCwzIi8+PC9zdmc+");
}
.message{padding: 5px 10px; border-radius:3px;display:inline-block;vertical-align:middle}
.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%;
width:600px;
margin:0 auto
}
/* dialogs */
#dialog-backdrop{
position:fixed;
display:flex;
top:0;
left:0;
width:100%;
height:100%;
background-color:rgba(0,0,0,.75);
margin:0;
padding:0;
display:none;
}
#dialog-backdrop.show{display:flex}
.dialog{
vertical-align:middle;
margin:auto;
background-color:white;
color:#999;
box-sizing:border-box;
box-shadow:rgba(0,0,0,.7) 0 0 24px;
padding:20px;
border-radius:3px;
display:none;
}
.dialog.show{display:block}
/* settings dialog */
#settings-dialog{
min-width:400px;
max-width:90%;
}
#settings-close-dialog{
padding:8px;
border-radius:30px;
margin-right:-8px;
margin-top:-8px;
transition:background-color .2s;
height:24px;
}
#settings-close-dialog:hover{
cursor:pointer;
background-color:#f4f4f4;
}
/* ZIP dialog */
#zip-dialog{
min-width:360px;
}
#zip-dialog-file-list{
list-style:none;
padding:0;
margin: 0;
max-height:300px;
overflow-y:auto;
}
#zip-dialog-file-list li{
color:#3c3c3c;
padding: 2px 8px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
#zip-dialog-file-list li:hover{
background-color:#eee;
cursor:pointer;
color: black;
border-radius: 3px;
}
/* light theme */
body.light{background-color:#eeeceb;}
body.light .button-outer{color:#575b66;}
body.light footer img.icon.settings, body.light footer img.icon.github{filter: invert(0%) brightness(30%);}
body.light .button-outer:hover, body.light .button-outer:focus{background-color: #e6e3e1}
body.light .switch.enabled{background-color:#ff941e;}
body.light #switch-create.disabled{background-color:#cec5bd;}
body.light button:not(.button-outer){background-color:#f9a345;}
body.light button:not(.button-outer):not(:disabled):hover{background-color:#ffbe78;}
body.light header img{filter: hue-rotate(98rad) brightness(97.9%) saturate(160%)}
body.light footer{color:#a89d97}
body.light footer a{color:#8c817c; border-color:#e3d4cc}
body.light footer a:hover{color:#484543; border-color:#ffac41}
/* responsive */
@media only screen and (max-width:641px){
#wrapper{font-size:14px}
#rom-info{font-size:11px}
header img{max-height:96px}
#settings-dialog, #zip-dialog{min-width: auto;}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View file

@ -0,0 +1 @@
<svg width="32" height="32" xmlns="http://www.w3.org/2000/svg"><line stroke="#888" stroke-width="4" x1="1" x2="31" y1="1" y2="31"/><line stroke="#888" stroke-width="4" x1="31" x2="1" y1="1" y2="31"/></svg>

After

Width:  |  Height:  |  Size: 206 B

View file

@ -0,0 +1,2 @@
<!-- from octicon https://github.com/primer/octicons/ -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 760 B

View file

@ -0,0 +1 @@
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><path d="M4.3,2.1C2.816504,2.218309 1.4015208,3.1679153 0.87398438,4.58375 0.34678348,5.8777663 0.60547985,7.4216259 1.4467505,8.5260791 2.428738,9.9126453 3.9007302,10.812811 5.1733475,11.900144 6.1025539,12.652873 7.0572605,13.374847 8.03,14.07 9.8414762,12.741487 11.62015,11.365101 13.338177,9.9179978 14.534069,8.8670814 15.617932,7.3839163 15.42875,5.70625 15.351533,4.1060809 14.135607,2.6806679 12.60355,2.2616895 11.873602,2.0258183 11.076327,2.0638822 10.328633,2.1798242 9.4188311,2.3910696 8.6202638,2.9340558 8.02,3.64 7.2081279,2.6482215 5.9494781,2.0210606 4.6572266,2.0850586 4.5320909,2.0883379 4.4030623,2.0709127 4.28,2.1" fill="#e74c3c" /></svg>

After

Width:  |  Height:  |  Size: 729 B

View file

@ -0,0 +1 @@
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><ellipse cx="8" cy="8" fill="transparent" rx="2.5" ry="2.5" stroke="#ffffff" stroke-width="1.13" style="fill:none" /><path d="M 6.34,15.93 C 5.4588832,15.789025 5.3688301,14.771577 5.1756353,14.070626 4.88526,12.736602 3.4341324,13.644518 2.5971627,13.722372 1.429913,13.634857 1.20405,12.274774 0.57110457,11.527239 0.01256473,10.862766 -0.28025442,9.5563977 0.70078865,9.1307029 1.6881819,8.6864782 1.6795687,7.1576053 0.59919067,6.8587653 -0.3188158,6.2116572 0.07040277,5.0345832 0.70962743,4.3579058 1.1458066,3.5997233 1.4745511,2.6165206 2.3772581,2.2995448 3.2853269,2.2266882 4.6163607,3.3077881 5.1380438,2.0525892 5.3177552,1.1958744 5.5325932,-0.04373646 6.6754583,0.01614659 7.7138044,0.09890156 8.7722596,-0.11077281 9.7963088,0.11827521 10.569368,0.31098193 10.571622,1.2355275 10.750403,1.8610601 c 0.307427,1.3026813 1.685123,0.6224444 2.503868,0.3533436 0.82764,-0.090277 1.050717,0.8516809 1.472505,1.360975 0.38881,0.6166529 0.834189,1.2049045 1.187448,1.8394061 0.330486,1.0831614 -0.991271,1.5371101 -1.414855,2.2998779 -0.2856,1.1308037 1.380075,1.3511924 1.444845,2.3814793 -0.0079,0.927004 -0.692382,1.66038 -1.102823,2.444419 -0.39067,0.912507 -1.296704,1.612005 -2.278176,1.000092 -0.946701,-0.63303 -1.993578,0.112458 -1.907388,1.191742 0.0079,1.047847 -1.1122687,1.390845 -1.9768201,1.270072 C 7.8997219,15.932974 7.1191405,16.008245 6.34,15.93 Z m 3,-1.22 c 0.072192,-1.038342 0.5424188,-2.201536 1.610519,-2.551894 0.862697,-0.55308 2.289047,0.880644 2.758176,-0.364357 0.567891,-0.682315 1.24254,-1.736649 0.133817,-2.2771293 C 12.89432,8.7323482 13.006994,7.194583 13.884539,6.4028471 14.39852,6.1084948 14.833041,5.6436658 14.236602,5.1479432 13.957925,4.5967831 13.628958,4.0709474 13.33,3.53 12.383852,3.9354666 11.178531,4.1915721 10.300674,3.4919254 9.2869972,3.1081771 9.8933516,1.1858595 8.7077559,1.2901448 8.0634396,1.4130361 6.9692111,1.0406335 6.610177,1.4803803 6.5078416,2.4182135 6.0678498,3.4743442 5.0999314,3.7923931 4.2722569,4.3906884 3.3123355,3.3221592 2.4772941,3.8142154 2.2352934,4.5382998 0.87395464,5.4749545 1.8007346,6.1937991 c 1.053982,0.664386 1.302272,2.235122 0.4884086,3.1815951 -0.5474043,0.4278527 -1.2164805,0.9805138 -0.5529679,1.6797018 0.467778,0.565173 0.7706283,1.822385 1.7323918,1.179994 0.7375009,-0.386335 1.6577438,-0.111792 2.2758,0.38501 0.5290179,0.419069 0.7676252,1.08802 0.7930337,1.743226 0.4594894,0.831464 1.6051495,0.156768 2.3667841,0.36409 C 9.0495442,14.724232 9.1948683,14.718824 9.34,14.71 Z" fill="#ffffff" /></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
legacy/style/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

BIN
legacy/style/thumbnail.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB