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

added zip support

This commit is contained in:
Marc Robledo 2019-05-31 21:00:39 +02:00
parent c59a8cbfd0
commit 94529e1a52
19 changed files with 473 additions and 188 deletions

View file

@ -5,7 +5,7 @@ A ROM patcher made in HTML5.
* Supported formats: * Supported formats:
* IPS * IPS
* UPS * UPS
* APS (N64) * APS
* BPS * BPS
* RUP * RUP
* VCDiff (xdelta) * VCDiff (xdelta)
@ -13,5 +13,6 @@ A ROM patcher made in HTML5.
* can patch and create patches * can patch and create patches
* shows ROM CRC32, MD5 and SHA-1 before patching * shows ROM CRC32, MD5 and SHA-1 before patching
* can remove headers before patching * can remove headers before patching
* unzips files automatically
* made in Vanilla JS * made in Vanilla JS
* can be run in any modern web browser, including mobile * can be run in any modern web browser, including mobile

View file

@ -1,4 +1,4 @@
/* WebApps CSS template by Marc Robledo v20180428 */ /* WebApps CSS template by Marc Robledo v20190531 */
/* minify at https://cssminifier.com/ + https://www.base64-image.de/ */ /* minify at https://cssminifier.com/ + https://www.base64-image.de/ */
/* @FONT-FACES */ /* @FONT-FACES */
@ -147,6 +147,10 @@ hr{border:none;border-top:1px dotted #bbb;margin:15px 0}
.tab{background-color:#f9fafa;padding:30px 15px;border-radius: 3px} .tab{background-color:#f9fafa;padding:30px 15px;border-radius: 3px}
#tab1{display:none} #tab1{display:none}
.buttons{
margin-top:20px;
text-align:right
}
@ -291,6 +295,12 @@ button.no-text.with-icon:before{margin-right:0px}
/* responsive */ /* responsive */
@media only screen and (max-width:641px){ @media only screen and (max-width:641px){
#wrapper{font-size:14px} #wrapper{font-size:14px}
@ -303,35 +313,48 @@ button.no-text.with-icon:before{margin-right:0px}
/* MarcDialogs */ /* ZIP dialog */
.dialog-overlay,.dialog{visibility:hidden;opacity:0} .zip-overlay{
.dialog-overlay.active,.dialog.active{visibility:visible;opacity:1;transition-delay:0s}/* fixes fade-in/fade-out*/ position:fixed;
display:flex;
top:0;
left:0;
width:100%;
height:100%;
background-color:rgba(0,0,0,.65);
.dialog-overlay{ margin:0;
transition:visibility 0s .2s, opacity .2s; padding:0;
background-color:black;
-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=75)";/* IE8 */
background-color:rgba(0,0,0,.7)
} }
.zip-dialog{
.dialog{ vertical-align:middle;
position:absolute;top:0;left:0; /* fix for reserved space */ color:#999;
text-align:center;
transform:translateY(-10px); margin:auto;
transition:visibility 0s .2s, opacity .2s ease-in, transform .2s ease-in; background-color:white;
box-sizing:border-box;
background-color:#f9fafa; padding:4px;
padding:15px;
min-width:360px;
max-width:80%;
border-radius:3px; border-radius:3px;
box-shadow:0 5px 15px 0 rgba(0,0,0,.5); min-width:340px;
line-height:1.8; max-width: 90%;
} }
.dialog.active{transform:translateY(0px)} .zipped-files{
.buttons{ list-style:none;
margin-top:20px; padding:0;
text-align:right margin: 0;
max-height:300px;
overflow-y:auto;
} }
.zipped-files li{
color:#3c3c3c;
padding: 2px 8px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.zipped-files li:hover{
background-color:#eee;
cursor:pointer;
color: black;
border-radius: 3px;
}

View file

@ -1,4 +1,4 @@
/* Rom Patcher JS v20190411 - Marc Robledo 2016-2018 - http://www.marcrobledo.com/license */ /* Rom Patcher JS v20190531 - Marc Robledo 2016-2019 - http://www.marcrobledo.com/license */
const TOO_BIG_ROM_SIZE=67108863; const TOO_BIG_ROM_SIZE=67108863;
const HEADERS_INFO=[ const HEADERS_INFO=[
[/\.nes$/, 16, 1024], //interNES [/\.nes$/, 16, 1024], //interNES
@ -10,8 +10,8 @@ const HEADERS_INFO=[
const FORCE_HTTPS=true;
/* service worker */ /* service worker */
const FORCE_HTTPS=true;
if(FORCE_HTTPS && location.protocol==='http:') if(FORCE_HTTPS && location.protocol==='http:')
location.href=window.location.href.replace('http:','https:'); location.href=window.location.href.replace('http:','https:');
else if(location.protocol==='https:' && 'serviceWorker' in navigator) else if(location.protocol==='https:' && 'serviceWorker' in navigator)
@ -28,37 +28,30 @@ var webWorkerApply,webWorkerCreate,webWorkerCrc;
try{ try{
webWorkerApply=new Worker('./worker_apply.js'); webWorkerApply=new Worker('./worker_apply.js');
webWorkerApply.onmessage = event => { // listen for events from the worker webWorkerApply.onmessage = event => { // listen for events from the worker
//romFile._u8array=event.data.romFileU8Array; //retrieve arraybuffers back from webworker
//romFile._dataView=new DataView(romFile._u8array.buffer); if(!el('checkbox-removeheader').checked && !el('checkbox-addheader').checked){ //when adding/removing header we don't need the arraybuffer back
//patchFile._u8array=event.data.patchFileU8Array; romFile._u8array=event.data.romFileU8Array;
//patchFile._dataView=new DataView(patchFile._u8array.buffer); romFile._dataView=new DataView(romFile._u8array.buffer);
//if(patch.file){ }
// patch.file._u8array=patchFile._u8array; patchFile._u8array=event.data.patchFileU8Array;
// patch.file._dataView=patchFile._dataView; patchFile._dataView=new DataView(patchFile._u8array.buffer);
//}
preparePatchedRom(romFile, new MarcFile(event.data.patchedRomU8Array.buffer), headerSize); preparePatchedRom(romFile, new MarcFile(event.data.patchedRomU8Array.buffer), headerSize);
setTabApplyEnabled(true); setTabApplyEnabled(true);
}; };
webWorkerApply.onerror = event => { // listen for events from the worker webWorkerApply.onerror = event => { // listen for events from the worker
romFile=new MarcFile(el('input-file-rom'), _parseROM);
patchFile=new MarcFile(el('input-file-patch'), _readPatchFile);
setMessage('apply', _(event.message.replace('Error: ','')), 'error'); setMessage('apply', _(event.message.replace('Error: ','')), 'error');
setTabApplyEnabled(true); setTabApplyEnabled(true);
}; };
webWorkerCreate=new Worker('./worker_create.js'); webWorkerCreate=new Worker('./worker_create.js');
webWorkerCreate.onmessage = event => { // listen for events from the worker webWorkerCreate.onmessage = event => { // listen for events from the worker
//console.log('received_create');
//romFile1._u8array=event.data.sourceFileU8Array;
//romFile1._dataView=new DataView(romFile1._u8array.buffer);
//romFile2._u8array=event.data.modifiedFileU8Array;
//romFile2._dataView=new DataView(romFile2._u8array.buffer);
var newPatchFile=new MarcFile(event.data.patchFileU8Array); var newPatchFile=new MarcFile(event.data.patchFileU8Array);
newPatchFile.fileName=romFile2.fileName.replace(/\.[^\.]+$/,'')+'.'+el('select-patch-type').value; newPatchFile.fileName=romFile2.fileName.replace(/\.[^\.]+$/,'')+'.'+el('select-patch-type').value;
newPatchFile.save(); newPatchFile.save();
@ -73,8 +66,6 @@ try{
webWorkerCrc=new Worker('./worker_crc.js'); webWorkerCrc=new Worker('./worker_crc.js');
webWorkerCrc.onmessage = event => { // listen for events from the worker webWorkerCrc.onmessage = event => { // listen for events from the worker
//console.log('received_crc'); //console.log('received_crc');
@ -105,27 +96,25 @@ function _(str){return userLanguage[str] || str}
function selectFetchedPatch(i){ function fetchPatch(uri){
patchFile=fetchedPatches[i].slice(0); setTabApplyEnabled(false);
_readPatchFile();
}
function fetchPredefinedPatch(i, doNotDisable){
if(!doNotDisable)
setTabApplyEnabled(false);
var patchFromUrl=PREDEFINED_PATCHES[i].patch;
setMessage('apply', _('downloading'), 'loading'); setMessage('apply', _('downloading'), 'loading');
var isCompressed=/\#/.test(uri);
var patchURI=decodeURI(uri.replace(/\#.*?$/, ''));
//console.log(patchURI);
var compressedName=uri.replace(/^.*?\#/,'');
//console.log(compressedName);
if(typeof window.fetch==='function'){ if(typeof window.fetch==='function'){
fetch(patchFromUrl) fetch(patchURI)
.then(result => result.arrayBuffer()) // Gets the response and returns it as a blob .then(result => result.arrayBuffer()) // Gets the response and returns it as a blob
.then(arrayBuffer => { .then(arrayBuffer => {
fetchedPatches[i]=new MarcFile(arrayBuffer); fetchedPatches[patchURI]=patchFile=new MarcFile(arrayBuffer);
fetchedPatches[i].fileName=decodeURIComponent(patchFromUrl); fetchedPatches[patchURI].fileName=patchURI.replace(/^(.*?\/)+/g, '');
_readPatchFile();
if(i===el('input-file-patch').selectedIndex)
selectFetchedPatch(i);
}) })
.catch(function(evt){ .catch(function(evt){
setMessage('apply', _('error_downloading'), 'error'); setMessage('apply', _('error_downloading'), 'error');
@ -133,16 +122,14 @@ function fetchPredefinedPatch(i, doNotDisable){
}); });
}else{ }else{
var xhr=new XMLHttpRequest(); var xhr=new XMLHttpRequest();
xhr.open('GET', patchFromUrl, true); xhr.open('GET', patchURI, true);
xhr.responseType='arraybuffer'; xhr.responseType='arraybuffer';
xhr.onload=function(evt){ xhr.onload=function(evt){
if(this.status===200){ if(this.status===200){
fetchedPatches[i]=new MarcFile(xhr.response); fetchedPatches[patchURI]=patchFile=new MarcFile(xhr.response);
fetchedPatches[i].fileName=decodeURIComponent(patchFromUrl); fetchedPatches[patchURI].fileName=patchURI.replace(/^(.*?\/)+/g, '');
_readPatchFile();
if(i===el('input-file-patch').selectedIndex)
selectFetchedPatch(i);
}else{ }else{
setMessage('apply', _('error_downloading')+' ('+this.status+')', 'error'); setMessage('apply', _('error_downloading')+' ('+this.status+')', 'error');
} }
@ -157,9 +144,48 @@ function fetchPredefinedPatch(i, doNotDisable){
} }
function _parseROM(){
el('checkbox-addheader').checked=false;
el('checkbox-removeheader').checked=false;
if(romFile.readString(4).startsWith(ZIP_MAGIC)){
parseZIPFile(romFile);
setTabApplyEnabled(false);
}else{
if(headerSize=canHaveFakeHeader(romFile)){
el('row-addheader').style.display='flex';
if(headerSize<1024){
el('headersize').innerHTML=headerSize+'b';
}else{
el('headersize').innerHTML=parseInt(headerSize/1024)+'kb';
}
el('row-removeheader').style.display='none';
}else if(headerSize=hasHeader(romFile)){
el('row-addheader').style.display='none';
el('row-removeheader').style.display='flex';
}else{
el('row-addheader').style.display='none';
el('row-removeheader').style.display='none';
}
updateChecksums(romFile, 0);
}
}
/* initialize app */ /* initialize app */
addEvent(window,'load',function(){ addEvent(window,'load',function(){
/* zip-js web worker */
if(CAN_USE_WEB_WORKERS){
zip.useWebWorkers=true;
zip.workerScriptsPath='./libs/';
}else{
zip.useWebWorkers=false;
var script=document.createElement('script');
script.src='./libs/inflate.js';
document.getElementsByTagName('head')[0].appendChild(script);
}
/* language */ /* language */
var langCode=(navigator.language || navigator.userLanguage).substr(0,2); var langCode=(navigator.language || navigator.userLanguage).substr(0,2);
@ -181,36 +207,38 @@ addEvent(window,'load',function(){
addEvent(el('input-file-rom'), 'change', function(){ addEvent(el('input-file-rom'), 'change', function(){
setTabApplyEnabled(false); setTabApplyEnabled(false);
romFile=new MarcFile(this, function(){ romFile=new MarcFile(this, _parseROM);
el('checkbox-addheader').checked=false;
el('checkbox-removeheader').checked=false;
if(headerSize=canHaveFakeHeader(romFile)){
el('row-addheader').style.display='flex';
if(headerSize<1024){
el('headersize').innerHTML=headerSize+'b';
}else{
el('headersize').innerHTML=parseInt(headerSize/1024)+'kb';
}
el('row-removeheader').style.display='none';
}else if(headerSize=hasHeader(romFile)){
el('row-addheader').style.display='none';
el('row-removeheader').style.display='flex';
}else{
el('row-addheader').style.display='none';
el('row-removeheader').style.display='none';
}
updateChecksums(romFile, 0);
});
}); });
/* predefined patches: parse URL parameter */
/*if(/\?.*?patch=[^=]+/.test(window.location.href)){
var patchUri=decodeURI(window.location.href.match(/\?.*?patch=([^=]+)/)[1]);
var patchInfo={
patch:patchUri,
name:patchUri
};
if(/\?.*?name=[^=]+/.test(window.location.href)){
patchInfo.name=decodeURI(window.location.href.match(/\?.*?name=([^=]+)/)[1]);
}
if(/\?.*?crc=[0-9a-f]{8}/i.test(window.location.href)){
patchInfo.crc=parseInt(window.location.href.match(/\?.*?patch=([0-9a-f]{8})/)[1], 16);
}
if(typeof PREDEFINED_PATCHES === 'undefined'){
PREDEFINED_PATCHES=[patchInfo];
}else{
PREDEFINED_PATCHES.push(patchInfo);
}
}*/
/* predefined patches */
if(typeof PREDEFINED_PATCHES!=='undefined'){ if(typeof PREDEFINED_PATCHES!=='undefined'){
fetchedPatches=new Array(PREDEFINED_PATCHES.length); fetchedPatches={};
var container=el('input-file-patch').parentElement; var container=el('input-file-patch').parentElement;
container.removeChild(el('input-file-patch')); container.removeChild(el('input-file-patch'));
@ -219,22 +247,26 @@ addEvent(window,'load',function(){
select.id='input-file-patch'; select.id='input-file-patch';
for(var i=0; i<PREDEFINED_PATCHES.length; i++){ for(var i=0; i<PREDEFINED_PATCHES.length; i++){
var option=document.createElement('option'); var option=document.createElement('option');
option.value=i; option.value=PREDEFINED_PATCHES[i].patch;
option.innerHTML=PREDEFINED_PATCHES[i].name; option.innerHTML=PREDEFINED_PATCHES[i].name;
select.appendChild(option); select.appendChild(option);
} }
container.appendChild(select) container.appendChild(select)
container.parentElement.title=''; container.parentElement.title='';
addEvent(select,'change',function(){ addEvent(select,'change',function(){
if(fetchedPatches[this.selectedIndex]) if(fetchedPatches[this.value.replace(/\#.*?$/, '')]){
selectFetchedPatch(this.selectedIndex); patchFile=fetchedPatches[this.value.replace(/\#.*?$/, '')];
else{ patchFile.seek(0);
_readPatchFile();
}else{
patch=null; patch=null;
patchFile=null; patchFile=null;
fetchPredefinedPatch(this.selectedIndex); fetchPatch(this.value);
} }
}); });
fetchPredefinedPatch(0, true); fetchPatch(select.value);
}else{ }else{
setTabCreateEnabled(true); setTabCreateEnabled(true);
el('input-file-rom1').value=''; el('input-file-rom1').value='';
@ -344,14 +376,6 @@ function validateSource(){
el('crc32').className='invalid'; el('crc32').className='invalid';
setMessage('apply', _('error_crc_input'), 'warning'); setMessage('apply', _('error_crc_input'), 'warning');
} }
}else if(patch && romFile && typeof PREDEFINED_PATCHES!=='undefined' && PREDEFINED_PATCHES[el('input-file-patch').selectedIndex].crc){
if(PREDEFINED_PATCHES[el('input-file-patch').selectedIndex].crc===crc32(romFile, el('checkbox-removeheader').checked && hasHeader(romFile))){
el('crc32').className='valid';
setMessage('apply');
}else{
el('crc32').className='invalid';
setMessage('apply', _('error_crc_input'), 'warning');
}
}else{ }else{
el('crc32').className=''; el('crc32').className='';
setMessage('apply'); setMessage('apply');
@ -365,28 +389,44 @@ function _readPatchFile(){
patchFile.littleEndian=false; patchFile.littleEndian=false;
var header=patchFile.readString(6); var header=patchFile.readString(6);
if(header.startsWith(IPS_MAGIC)){ if(header.startsWith(ZIP_MAGIC)){
patch=parseIPSFile(patchFile); if(typeof PREDEFINED_PATCHES !== 'undefined' && /\#/.test(el('input-file-patch').value)){
}else if(header.startsWith(UPS_MAGIC)){ parseZIPFile(patchFile, el('input-file-patch').value.replace(/^.*?\#/, ''));
patch=parseUPSFile(patchFile); }else{
}else if(header.startsWith(APS_MAGIC)){ parseZIPFile(patchFile);
patch=parseAPSFile(patchFile); }
}else if(header.startsWith(BPS_MAGIC)){ patch=false;
patch=parseBPSFile(patchFile); validateSource();
}else if(header.startsWith(RUP_MAGIC)){ setTabApplyEnabled(false);
patch=parseRUPFile(patchFile);
}else if(header.startsWith(PPF_MAGIC)){
patch=parsePPFFile(patchFile);
}else if(header.startsWith(VCDIFF_MAGIC)){
patch=parseVCDIFF(patchFile);
}else{ }else{
patch=null; if(header.startsWith(IPS_MAGIC)){
setMessage('apply', _('error_invalid_patch'), 'error'); patch=parseIPSFile(patchFile);
}else if(header.startsWith(UPS_MAGIC)){
patch=parseUPSFile(patchFile);
}else if(header.startsWith(APS_MAGIC)){
patch=parseAPSFile(patchFile);
}else if(header.startsWith(BPS_MAGIC)){
patch=parseBPSFile(patchFile);
}else if(header.startsWith(RUP_MAGIC)){
patch=parseRUPFile(patchFile);
}else if(header.startsWith(PPF_MAGIC)){
patch=parsePPFFile(patchFile);
}else if(header.startsWith(VCDIFF_MAGIC)){
patch=parseVCDIFF(patchFile);
}else{
patch=null;
setMessage('apply', _('error_invalid_patch'), 'error');
}
if(patch && typeof PREDEFINED_PATCHES!=='undefined' && PREDEFINED_PATCHES[el('input-file-patch').selectedIndex].crc){
patch.validateSource=function(romFile, headerSize){
return PREDEFINED_PATCHES[el('input-file-patch').selectedIndex].crc===crc32(romFile, headerSize)
}
}
validateSource();
setTabApplyEnabled(true);
} }
validateSource();
setTabApplyEnabled(true);
} }
@ -451,6 +491,7 @@ function applyPatch(p,r,validateChecksums){
if(CAN_USE_WEB_WORKERS){ if(CAN_USE_WEB_WORKERS){
setMessage('apply', _('applying_patch'), 'loading'); setMessage('apply', _('applying_patch'), 'loading');
setTabApplyEnabled(false); setTabApplyEnabled(false);
webWorkerApply.postMessage( webWorkerApply.postMessage(
{ {
romFileU8Array:r._u8array, romFileU8Array:r._u8array,
@ -462,20 +503,11 @@ function applyPatch(p,r,validateChecksums){
] ]
); );
romFile=new MarcFile(el('input-file-rom'));
if(typeof PREDEFINED_PATCHES==='undefined'){
patchFile=new MarcFile(el('input-file-patch'));
}else{
patchFile=fetchedPatches[el('input-file-patch').selectedIndex].slice(0);
}
if(patch.file) //VCDiff does not parse patch file, it just keeps a copy of original patch, so we retrieve arraybuffer again
patch.file=patchFile;
}else{ }else{
setMessage('apply', _('applying_patch'), 'loading'); setMessage('apply', _('applying_patch'), 'loading');
try{ try{
p.apply(r, validateChecksums);
preparePatchedRom(r, p.apply(r, validateChecksums), headerSize); preparePatchedRom(r, p.apply(r, validateChecksums), headerSize);
}catch(e){ }catch(e){

View file

@ -30,11 +30,13 @@ caches.keys().then(function(cacheNames){
}); });
var PRECACHE_ID='rom-patcher-js'; var PRECACHE_ID='rom-patcher-js';
var PRECACHE_VERSION='v4'; var PRECACHE_VERSION='v6';
var PRECACHE_URLS=[ var PRECACHE_URLS=[
'/RomPatcher.js/','/RomPatcher.js/index.html', '/RomPatcher.js/','/RomPatcher.js/index.html',
'/RomPatcher.js/manifest.json', '/RomPatcher.js/manifest.json',
'/RomPatcher.js/favicon.png', '/RomPatcher.js/favicon.png',
'/RomPatcher.js/logo114.png',
'/RomPatcher.js/logo144.png',
'/RomPatcher.js/logo192.png', '/RomPatcher.js/logo192.png',
'/RomPatcher.js/RomPatcher.css', '/RomPatcher.js/RomPatcher.css',
'/RomPatcher.js/RomPatcher.js', '/RomPatcher.js/RomPatcher.js',
@ -42,8 +44,12 @@ var PRECACHE_URLS=[
'/RomPatcher.js/worker_apply.js', '/RomPatcher.js/worker_apply.js',
'/RomPatcher.js/worker_create.js', '/RomPatcher.js/worker_create.js',
'/RomPatcher.js/worker_crc.js', '/RomPatcher.js/worker_crc.js',
'/RomPatcher.js/MarcFile.js', '/RomPatcher.js/libs/MarcFile.js',
'/RomPatcher.js/libs/zip.js',
'/RomPatcher.js/libs/z-worker.js',
'/RomPatcher.js/libs/inflate.js',
'/RomPatcher.js/crc.js', '/RomPatcher.js/crc.js',
'/RomPatcher.js/zip.js',
'/RomPatcher.js/ips.js', '/RomPatcher.js/ips.js',
'/RomPatcher.js/ups.js', '/RomPatcher.js/ups.js',
'/RomPatcher.js/aps.js', '/RomPatcher.js/aps.js',
@ -55,7 +61,6 @@ var PRECACHE_URLS=[
//PRECACHE_ID='precache-'+PRECACHE_ID+'-'+PRECACHE_VERSION;
// install event (fired when sw is first installed): opens a new cache // install event (fired when sw is first installed): opens a new cache
self.addEventListener('install', evt => { self.addEventListener('install', evt => {
evt.waitUntil( evt.waitUntil(

View file

@ -9,11 +9,18 @@
<link rel="manifest" href="./manifest.json"/> <link rel="manifest" href="./manifest.json"/>
<link rel="shortcut icon" href="./favicon.png" type="image/png" sizes="16x16"/> <link rel="shortcut icon" href="./favicon.png" type="image/png" sizes="16x16"/>
<link rel="shortcut icon" href="./logo192.png" type="image/png" sizes="192x192"/> <link rel="shortcut icon" href="./logo192.png" type="image/png" sizes="192x192"/>
<!-- <link rel="apple-touch-icon" href="./logo192.png" sizes="192x192" /> --> <!-- iOS icons -->
<link rel="apple-touch-icon" sizes="57x57" href="./logo114.png" />
<link rel="apple-touch-icon" sizes="114x114" href="./logo114.png" />
<link rel="apple-touch-icon" sizes="72x72" href="./logo144.png" />
<link rel="apple-touch-icon" sizes="144x144" href="./logo144.png" />
<link rel="apple-touch-icon" href="./logo192.png" />
<link type="text/css" rel="stylesheet" href="./RomPatcher.css" media="all"/> <link type="text/css" rel="stylesheet" href="./RomPatcher.css" media="all"/>
<script type="text/javascript" src="./locale.js"></script> <script type="text/javascript" src="./locale.js"></script>
<script type="text/javascript" src="./RomPatcher.js"></script> <script type="text/javascript" src="./RomPatcher.js"></script>
<script type="text/javascript" src="./MarcFile.js"></script> <script type="text/javascript" src="./libs/MarcFile.js"></script>
<script type="text/javascript" src="./zip.js"></script>
<script type="text/javascript" src="./crc.js"></script> <script type="text/javascript" src="./crc.js"></script>
<script type="text/javascript" src="./ips.js"></script> <script type="text/javascript" src="./ips.js"></script>
<script type="text/javascript" src="./ups.js"></script> <script type="text/javascript" src="./ups.js"></script>
@ -22,11 +29,27 @@
<script type="text/javascript" src="./rup.js"></script> <script type="text/javascript" src="./rup.js"></script>
<script type="text/javascript" src="./ppf.js"></script> <script type="text/javascript" src="./ppf.js"></script>
<script type="text/javascript" src="./vcdiff.js"></script> <script type="text/javascript" src="./vcdiff.js"></script>
<script type="text/javascript" src="./libs/zip.js"></script>
<script type="text/javascript"><!-- <script type="text/javascript"><!--
//uncomment the following lines to enable predefined patches, example should be self explanatory // PREDEFINED PATCHES EXAMPLE
// uncomment this to enable predefined patches, Rom Patcher JS will fetch patches hosted in your server
// format should be self explanatory
// you can add validation to patch files that have no validation (like IPS or xdelta) by providing a crc
// this object could be also dynamically generated by your server backend
/*var PREDEFINED_PATCHES=[ /*var PREDEFINED_PATCHES=[
{patch:'./_example/SONICDX.xdelta',name:'Sonic 3D Blast Director\'s Cut v1.1',crc:0x44a2ca44}, //zip includes various patches+every patch file inside specified separately (best method)
{patch:'./_example/SML2DXv181.ips',name:'Super Mario Land 2 DX v1.81',crc:0xd5ec24e4} {patch:'./_example/SML2DXv181.zip#SML2DXv181.ips',name:'Super Mario Land 2 DX v1.8.1 (USA/Europe)',crc:0xd5ec24e4},
{patch:'./_example/SML2DXv181.zip#SML2DXv181_jap.ips',name:'Super Mario Land 2 DX v1.8.1 (Japan)',crc:0xa715daf5},
//zip includes various patches but no patch file inside specified, user will be able to choose the desired patch file in a popup (not recommended, also only a single crc for all patches could be provided)
{patch:'./_example/SML2DXv181.zip',name:'Super Mario Land 2 DX v1.8.1 (All regions)'},
//zip includes a single patch (recommended)
{patch:'./_example/SONICDX.zip',name:'Sonic 3D Blast Director\'s Cut v1.1',crc:0x44a2ca44},
//uncompressed patch (not recommended because of bandwidth purposes)
{patch:'./_example/MarvelousATI_EN_v1.07.xdelta',name:'Marvelous (SFC) ENG Translation v1.07',crc:0xcedf3ba7}
];*/ ];*/
--></script> --></script>
</head> </head>
@ -53,28 +76,28 @@
<div class="leftcol">SHA-1:</div><div class="rightcol"><span id="sha1"></span></div> <div class="leftcol">SHA-1:</div><div class="rightcol"><span id="sha1"></span></div>
</div> </div>
<div class="row" id="row-removeheader" style="display:none"> <div class="row" id="row-removeheader" style="display:none">
<div class="leftcol"><label for="checkbox-removeheader" data-localize="remove_header">Remove header before patching:</label></div> <div class="leftcol"></div>
<div class="rightcol"> <div class="rightcol">
<input type="checkbox" id="checkbox-removeheader" /> <input type="checkbox" id="checkbox-removeheader" /> <label for="checkbox-removeheader" data-localize="remove_header">Remove header before patching</label>
</div> </div>
</div> </div>
<div class="row" id="row-addheader" style="display:none"> <div class="row" id="row-addheader" style="display:none">
<div class="leftcol"><label for="checkbox-addheader" data-localize="add_header">Add temporary header:</label></div> <div class="leftcol"></div>
<div class="rightcol"> <div class="rightcol">
<input type="checkbox" id="checkbox-addheader" /> <label id="headersize" for="checkbox-addheader"></label> <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> </div>
<div class="row" id="row-file-patch"> <div class="row" id="row-file-patch">
<div class="leftcol"><label for="input-file-patch" data-localize="patch_file">Patch file:</label></div> <div class="leftcol"><label for="input-file-patch" data-localize="patch_file">Patch file:</label></div>
<div class="rightcol"> <div class="rightcol">
<input type="file" id="input-file-patch" accept=".ips,.ups,.bps,.aps,.rup,.ppf,.xdelta"/> <input type="file" id="input-file-patch" accept=".ips,.ups,.bps,.aps,.rup,.ppf,.xdelta,.zip"/>
</div> </div>
</div> </div>
<div class="buttons"> <div class="buttons">
<span id="message-apply" class="message"></span> <span id="message-apply" class="message"></span>
<button id="button-apply" data-localize="apply_patch" class="disabled" disabled onclick="applyPatch(patch, romFile, !/error/.test(el('message-apply').className))">Apply patch</button> <button id="button-apply" data-localize="apply_patch" class="disabled" disabled onclick="applyPatch(patch, romFile, false)">Apply patch</button>
</div> </div>
</div> </div>
@ -120,9 +143,10 @@
<!-- FOOTER --> <!-- FOOTER -->
<footer> <footer>
Rom Patcher JS <small>v2.0 RC1f</small> by <a href="/">Marc Robledo</a> Rom Patcher JS <small>v2.0 RC2</small> by <a href="/">Marc Robledo</a>
<br /> <br />
<i class="icon github"></i> <a href="https://github.com/marcrobledo/RomPatcher.js/" target="_blank">See on GitHub</a> <i class="icon github"></i> <a href="https://github.com/marcrobledo/RomPatcher.js/" target="_blank">See on GitHub</a>
<i class="icon heart"></i> <a href="https://www.paypal.me/marcrobledo/5" target="_blank" rel="nofollow">Donate</a> <i class="icon heart"></i> <a href="https://www.paypal.me/marcrobledo/5" target="_blank" rel="nofollow">Donate</a>

36
libs/inflate.js Normal file

File diff suppressed because one or more lines are too long

2
libs/z-worker.js Normal file
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
libs/zip.js Normal file

File diff suppressed because one or more lines are too long

View file

@ -1,26 +1,28 @@
const LOCALIZATION={ const LOCALIZATION={
'en':{ 'en':{
'creator_mode': 'Creator mode', 'creator_mode': 'Creator mode',
'apply_patch': 'Apply patch', 'apply_patch': 'Apply patch',
'rom_file': 'ROM file:', 'rom_file': 'ROM file:',
'patch_file': 'Patch file:', 'patch_file': 'Patch file:',
'remove_header': 'Remove header before patching:', 'remove_header': 'Remove header',
'add_header': 'Add temporary header:', 'add_header': 'Add temporary header',
'compatible_formats': 'Compatible formats:', 'compatible_formats': 'Compatible formats:',
'applying_patch': 'Applying patch...', 'applying_patch': 'Applying patch...',
'downloading': 'Downloading...', 'downloading': 'Downloading...',
'unzipping': 'Unzipping...',
'create_patch': 'Create patch:', 'create_patch': 'Create patch:',
'original_rom': 'Original ROM', 'original_rom': 'Original ROM:',
'modified_rom': 'Modified ROM:', 'modified_rom': 'Modified ROM:',
'patch_type': 'Patch type:', 'patch_type': 'Patch type:',
'creating_patch': 'Creating patch...', 'creating_patch': 'Creating patch...',
'error_crc_input': 'Invalid input ROM checksum', 'error_crc_input': 'Source ROM checksum mismatch',
'error_crc_output': 'Invalid output ROM checksum', 'error_crc_output': 'Target ROM checksum mismatch',
'error_crc_patch': 'Invalid patch checksum', 'error_crc_patch': 'Patch checksum mismatch',
'error_downloading': 'Error downloading patch', 'error_downloading': 'Error downloading patch',
'error_unzipping': 'Error unzipping file',
'error_invalid_patch': 'Invalid patch file', 'error_invalid_patch': 'Invalid patch file',
'warning_too_big': 'Using big files is not recommended.' 'warning_too_big': 'Using big files is not recommended.'
}, },
@ -30,11 +32,12 @@
'apply_patch': 'Aplicar parche', 'apply_patch': 'Aplicar parche',
'rom_file': 'Archivo ROM:', 'rom_file': 'Archivo ROM:',
'patch_file': 'Archivo parche:', 'patch_file': 'Archivo parche:',
'remove_header': 'Quitar cabecera antes de aplicar:', 'remove_header': 'Quitar cabecera',
'add_header': 'Añadir cabecera temporal:', 'add_header': 'Añadir cabecera temporal',
'compatible_formats': 'Formatos compatibles:', 'compatible_formats': 'Formatos compatibles:',
'applying_patch': 'Aplicando parche...', 'applying_patch': 'Aplicando parche...',
'downloading': 'Descargando...', 'downloading': 'Descargando...',
'unzipping': 'Descomprimiendo...',
'create_patch': 'Crear parche', 'create_patch': 'Crear parche',
'original_rom': 'ROM original:', 'original_rom': 'ROM original:',
@ -46,32 +49,35 @@
'error_crc_output': 'Checksum de ROM creada no válida', 'error_crc_output': 'Checksum de ROM creada no válida',
'error_crc_patch': 'Checksum de parche no válida', 'error_crc_patch': 'Checksum de parche no válida',
'error_downloading': 'Error descargando parche', 'error_downloading': 'Error descargando parche',
'error_unzipping': 'Error descomprimiendo archivo',
'error_invalid_patch': 'Archivo de parche no válido', 'error_invalid_patch': 'Archivo de parche no válido',
'warning_too_big': 'No se recomienda usar archivos muy grandes.' 'warning_too_big': 'No se recomienda usar archivos muy grandes.'
}, },
'ca':{ 'ca':{
'creator_mode': 'Mode creador', 'creator_mode': 'Mode creador',
'apply_patch': 'Aplicar pegat', 'apply_patch': 'Aplicar pedaç',
'rom_file': 'Arxiu ROM:', 'rom_file': 'Arxiu ROM:',
'patch_file': 'Arxiu pegat:', 'patch_file': 'Arxiu pedaç:',
'remove_header': 'El·liminar capçalera abans d\'aplicar:', 'remove_header': 'Treure capçalera',
'add_header': 'Afegir capçalera temporal:', 'add_header': 'Afegir capçalera temporal',
'compatible_formats': 'Formats compatibles:', 'compatible_formats': 'Formats compatibles:',
'applying_patch': 'Aplicant pegat...', 'applying_patch': 'Aplicant pedaç...',
'downloading': 'Descarregant...', 'downloading': 'Descarregant...',
'unzipping': 'Descomprimint...',
'create_patch': 'Crear pegat', 'create_patch': 'Crear pedaç',
'original_rom': 'ROM original:', 'original_rom': 'ROM original:',
'modified_rom': 'ROM modificada:', 'modified_rom': 'ROM modificada:',
'patch_type': 'Tipus de pegat:', 'patch_type': 'Tipus de pedaç:',
'creating_patch': 'Creant pegat...', 'creating_patch': 'Creant pedaç...',
'error_crc_input': 'Checksum de ROM original no vàlida', 'error_crc_input': 'Checksum de ROM original no vàlida',
'error_crc_output': 'Checksum de ROM creada no vàlida', 'error_crc_output': 'Checksum de ROM creada no vàlida',
'error_crc_patch': 'Checksum de pegat no vàlida', 'error_crc_patch': 'Checksum de pedaç no vàlida',
'error_downloading': 'Error descarregant pegat', 'error_downloading': 'Error descarregant pedaç',
'error_invalid_patch': 'Arxiu de pegat no vàlid', 'error_unzipping': 'Error descomprimint arxiu',
'error_invalid_patch': 'Arxiu de pedaç no vàlid',
'warning_too_big': 'No es recomana usar arxius molt grans.' 'warning_too_big': 'No es recomana usar arxius molt grans.'
}, },
'ru':{ 'ru':{
@ -80,11 +86,12 @@
'apply_patch': 'Применить патч', 'apply_patch': 'Применить патч',
'rom_file': 'Файл ROM:', 'rom_file': 'Файл ROM:',
'patch_file': 'Файл патча:', 'patch_file': 'Файл патча:',
'remove_header': 'Удалить заголовок перед применением:', 'remove_header': 'Удалить заголовок перед применением',
'add_header': 'Добавить временный заголовок:', 'add_header': 'Добавить временный заголовок',
'compatible_formats': 'Совместимые форматы:', 'compatible_formats': 'Совместимые форматы:',
'applying_patch': 'Применяется патч...', 'applying_patch': 'Применяется патч...',
'downloading': 'Загрузка...', 'downloading': 'Загрузка...',
'unzipping': 'Unzipping...',
'create_patch': 'Создать патч:', 'create_patch': 'Создать патч:',
'original_rom': 'Оригинальный ROM:', 'original_rom': 'Оригинальный ROM:',
@ -96,6 +103,7 @@
'error_crc_output': 'Неправильная контрольная сумма выходного ROM', 'error_crc_output': 'Неправильная контрольная сумма выходного ROM',
'error_crc_patch': 'Неправильная контрольная сумма патча', 'error_crc_patch': 'Неправильная контрольная сумма патча',
'error_downloading': 'Ошибка при скачивании патча', 'error_downloading': 'Ошибка при скачивании патча',
'error_unzipping': 'Error unzipping file',
'error_invalid_patch': 'Неправильный файл патча', 'error_invalid_patch': 'Неправильный файл патча',
'warning_too_big': 'Не рекомендуется использовать большие файлы.' 'warning_too_big': 'Не рекомендуется использовать большие файлы.'
} }

BIN
logo114.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
logo144.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -3,6 +3,16 @@
"name":"Rom Patcher JS", "name":"Rom Patcher JS",
"icons":[ "icons":[
{ {
"src": "logo114.png",
"sizes": "114x114",
"type": "image/png",
"density": "1.0"
},{
"src": "logo144.png",
"sizes": "144x144",
"type": "image/png",
"density": "1.0"
},{
"src": "logo192.png", "src": "logo192.png",
"sizes": "192x192", "sizes": "192x192",
"type": "image/png", "type": "image/png",

View file

@ -0,0 +1,29 @@
<?php
/*
this template code should build a valid PREDEFINED_PATCHES object structure for Rom Patcher JS
it's intended for sites that host multiple patches (like www.romhacking.net)
note: this has not been tested!
*/
if(isset($_GET["patch"])){
$patchFile=$_GET["patch"];
if(isset($_GET["compressed"])){
$patchFile.="#".$_GET["compressed"];
}
if(isset($_GET["name"])){
$patchName=addslashes($_GET["name"]);
}else{
$patchName=$_GET["patch"];
}
echo "var PREDEFINED_PATCHES=[";
echo "{patch:'".$patchFile."',name:'".$patchName."'";
if(isset($_GET["crc"]) && preg_match("/^[0-9a-f]{1,8}$/i", $_GET["crc"])){
echo ", crc:0x".$_GET["crc"];
}
echo "}";
echo "];";
}
?>

12
rup.js
View file

@ -55,10 +55,14 @@ RUP.prototype.validateSource=function(romFile,headerSize){
return false; return false;
} }
RUP.prototype.apply=function(romFile, validate){ RUP.prototype.apply=function(romFile, validate){
var validFile=this.validateSource(romFile); var validFile;
if(validate){
if(validate && !validFile){ validFile=this.validateSource(romFile);
throw new Error('error_crc_input');
if(!validFile)
throw new Error('error_crc_input');
}else{
validFile=this.files[0];
} }

View file

@ -1,7 +1,7 @@
/* Rom Patcher JS v20181018 - Marc Robledo 2016-2018 - http://www.marcrobledo.com/license */ /* Rom Patcher JS v20181018 - Marc Robledo 2016-2018 - http://www.marcrobledo.com/license */
self.importScripts( self.importScripts(
'./MarcFile.js', './libs/MarcFile.js',
'./crc.js', './crc.js',
'./ips.js', './ips.js',
'./aps.js', './aps.js',
@ -49,13 +49,13 @@ self.onmessage = event => { // listen for messages from the main thread
//console.log('postMessage'); //console.log('postMessage');
self.postMessage( self.postMessage(
{ {
//romFileU8Array:event.data.romFileU8Array, romFileU8Array:event.data.romFileU8Array,
//patchFileU8Array:event.data.patchFileU8Array, patchFileU8Array:event.data.patchFileU8Array,
patchedRomU8Array:patchedRom._u8array patchedRomU8Array:patchedRom._u8array
}, },
[ [
//event.data.romFileU8Array.buffer, event.data.romFileU8Array.buffer,
//event.data.patchFileU8Array.buffer, event.data.patchFileU8Array.buffer,
patchedRom._u8array.buffer patchedRom._u8array.buffer
] ]
); );

View file

@ -1,7 +1,7 @@
/* Rom Patcher JS v20181018 - Marc Robledo 2016-2018 - http://www.marcrobledo.com/license */ /* Rom Patcher JS v20181018 - Marc Robledo 2016-2018 - http://www.marcrobledo.com/license */
self.importScripts( self.importScripts(
'./MarcFile.js', './libs/MarcFile.js',
'./crc.js' './crc.js'
); );

View file

@ -1,7 +1,7 @@
/* Rom Patcher JS v20181018 - Marc Robledo 2016-2018 - http://www.marcrobledo.com/license */ /* Rom Patcher JS v20181018 - Marc Robledo 2016-2018 - http://www.marcrobledo.com/license */
self.importScripts( self.importScripts(
'./MarcFile.js', './libs/MarcFile.js',
'./crc.js', './crc.js',
'./ips.js', './ips.js',
'./aps.js', './aps.js',

83
zip.js Normal file
View file

@ -0,0 +1,83 @@
/* ZIP module for Rom Patcher JS v20190531 - Marc Robledo 2016-2019 - http://www.marcrobledo.com/license */
const ZIP_MAGIC='\x50\x4b\x03\x04';
function parseZIPFile(zipFile, unzipEntryName){
var regex=(zipFile===patchFile)? /\.(ips|ups|bps|aps|rup|ppf|xdelta)$/i : /\.(\w+)$/i;
setMessage('apply', _('unzipping'), 'loading');
var arrayBuffer=zipFile._u8array.buffer;
zip.createReader(
new zip.BlobReader(new Blob([arrayBuffer])),
function(zipReader){
zipReader.getEntries(function(zipEntries){
var zippedFiles=[];
for(var i=0; i<zipEntries.length; i++){
if(typeof unzipEntryName==='string' && unzipEntryName === zipEntries[i].filename){
zippedFiles=[zipEntries[i]];
break;
}else if(regex.test(zipEntries[i].filename)){
zippedFiles.push(zipEntries[i]);
}
}
if(zippedFiles.length>1){
var zipOverlay=document.createElement('div');
zipOverlay.className='zip-overlay';
var zipDialog=document.createElement('div');
zipDialog.className='zip-dialog';
var zipList=document.createElement('ul');
zipList.className='zipped-files'
for(var i=0; i<zippedFiles.length; i++){
var li=document.createElement('li');
li.zipEntry=zippedFiles[i];
li.zipEntry.originalZipFile=zipFile;
li.innerHTML=zippedFiles[i].filename;
addEvent(li, 'click', _evtClickZipEntry);
zipList.appendChild(li);
}
zipDialog.innerHTML=_('patch_file');
zipDialog.appendChild(zipList);
zipOverlay.appendChild(zipDialog);
document.body.appendChild(zipOverlay);
}else if(zippedFiles.length===1){
zippedFiles[0].originalZipFile=zipFile;
unzipEntry(zippedFiles[0]);
}else{
if(zipFile===romFile){
romFile=null;
}else{
patchFile=null;
}
}
setTabApplyEnabled(true);
});
},
function(zipReader){
setTabApplyEnabled(true);
setMessage('apply', _('error_unzipping'), 'error');
}
);
}
function unzipEntry(zipEntry){
zipEntry.getData(new zip.BlobWriter(), function(blob){
var fileReader=new FileReader();
fileReader.onload=function(){
var unzippedFile=new MarcFile(this.result);
unzippedFile.fileName=zipEntry.filename;
if(zipEntry.originalZipFile===romFile){
romFile=unzippedFile;
_parseROM();
}else if(zipEntry.originalZipFile===patchFile){
patchFile=unzippedFile;
_readPatchFile();
}
};
fileReader.readAsArrayBuffer(blob);
});
}
function _evtClickZipEntry(evt){
document.body.removeChild(this.parentElement.parentElement.parentElement);
unzipEntry(this.zipEntry);
}