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:
parent
c59a8cbfd0
commit
94529e1a52
19 changed files with 473 additions and 188 deletions
|
@ -5,7 +5,7 @@ A ROM patcher made in HTML5.
|
|||
* Supported formats:
|
||||
* IPS
|
||||
* UPS
|
||||
* APS (N64)
|
||||
* APS
|
||||
* BPS
|
||||
* RUP
|
||||
* VCDiff (xdelta)
|
||||
|
@ -13,5 +13,6 @@ A ROM patcher made in HTML5.
|
|||
* can patch and create patches
|
||||
* shows ROM CRC32, MD5 and SHA-1 before patching
|
||||
* can remove headers before patching
|
||||
* unzips files automatically
|
||||
* made in Vanilla JS
|
||||
* can be run in any modern web browser, including mobile
|
||||
|
|
|
@ -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/ */
|
||||
|
||||
/* @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}
|
||||
#tab1{display:none}
|
||||
|
||||
.buttons{
|
||||
margin-top:20px;
|
||||
text-align:right
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -291,6 +295,12 @@ button.no-text.with-icon:before{margin-right:0px}
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* responsive */
|
||||
@media only screen and (max-width:641px){
|
||||
#wrapper{font-size:14px}
|
||||
|
@ -303,35 +313,48 @@ button.no-text.with-icon:before{margin-right:0px}
|
|||
|
||||
|
||||
|
||||
/* MarcDialogs */
|
||||
.dialog-overlay,.dialog{visibility:hidden;opacity:0}
|
||||
.dialog-overlay.active,.dialog.active{visibility:visible;opacity:1;transition-delay:0s}/* fixes fade-in/fade-out*/
|
||||
/* ZIP dialog */
|
||||
.zip-overlay{
|
||||
position:fixed;
|
||||
display:flex;
|
||||
top:0;
|
||||
left:0;
|
||||
width:100%;
|
||||
height:100%;
|
||||
background-color:rgba(0,0,0,.65);
|
||||
|
||||
.dialog-overlay{
|
||||
transition:visibility 0s .2s, opacity .2s;
|
||||
|
||||
background-color:black;
|
||||
-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=75)";/* IE8 */
|
||||
background-color:rgba(0,0,0,.7)
|
||||
margin:0;
|
||||
padding:0;
|
||||
}
|
||||
|
||||
.dialog{
|
||||
position:absolute;top:0;left:0; /* fix for reserved space */
|
||||
|
||||
transform:translateY(-10px);
|
||||
transition:visibility 0s .2s, opacity .2s ease-in, transform .2s ease-in;
|
||||
|
||||
background-color:#f9fafa;
|
||||
padding:15px;
|
||||
min-width:360px;
|
||||
max-width:80%;
|
||||
.zip-dialog{
|
||||
vertical-align:middle;
|
||||
color:#999;
|
||||
text-align:center;
|
||||
margin:auto;
|
||||
background-color:white;
|
||||
box-sizing:border-box;
|
||||
padding:4px;
|
||||
border-radius:3px;
|
||||
box-shadow:0 5px 15px 0 rgba(0,0,0,.5);
|
||||
line-height:1.8;
|
||||
min-width:340px;
|
||||
max-width: 90%;
|
||||
}
|
||||
.dialog.active{transform:translateY(0px)}
|
||||
.buttons{
|
||||
margin-top:20px;
|
||||
text-align:right
|
||||
.zipped-files{
|
||||
list-style:none;
|
||||
padding:0;
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
210
RomPatcher.js
210
RomPatcher.js
|
@ -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 HEADERS_INFO=[
|
||||
[/\.nes$/, 16, 1024], //interNES
|
||||
|
@ -10,8 +10,8 @@ const HEADERS_INFO=[
|
|||
|
||||
|
||||
|
||||
const FORCE_HTTPS=true;
|
||||
/* 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)
|
||||
|
@ -28,37 +28,30 @@ var webWorkerApply,webWorkerCreate,webWorkerCrc;
|
|||
try{
|
||||
webWorkerApply=new Worker('./worker_apply.js');
|
||||
webWorkerApply.onmessage = event => { // listen for events from the worker
|
||||
//romFile._u8array=event.data.romFileU8Array;
|
||||
//romFile._dataView=new DataView(romFile._u8array.buffer);
|
||||
//patchFile._u8array=event.data.patchFileU8Array;
|
||||
//patchFile._dataView=new DataView(patchFile._u8array.buffer);
|
||||
//if(patch.file){
|
||||
// patch.file._u8array=patchFile._u8array;
|
||||
// patch.file._dataView=patchFile._dataView;
|
||||
//}
|
||||
//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
|
||||
romFile._u8array=event.data.romFileU8Array;
|
||||
romFile._dataView=new DataView(romFile._u8array.buffer);
|
||||
}
|
||||
patchFile._u8array=event.data.patchFileU8Array;
|
||||
patchFile._dataView=new DataView(patchFile._u8array.buffer);
|
||||
|
||||
|
||||
preparePatchedRom(romFile, new MarcFile(event.data.patchedRomU8Array.buffer), headerSize);
|
||||
|
||||
setTabApplyEnabled(true);
|
||||
};
|
||||
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');
|
||||
setTabApplyEnabled(true);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
webWorkerCreate=new Worker('./worker_create.js');
|
||||
webWorkerCreate.onmessage = event => { // listen for events from the worker
|
||||
//console.log('received_create');
|
||||
//romFile1._u8array=event.data.sourceFileU8Array;
|
||||
//romFile1._dataView=new DataView(romFile1._u8array.buffer);
|
||||
//romFile2._u8array=event.data.modifiedFileU8Array;
|
||||
//romFile2._dataView=new DataView(romFile2._u8array.buffer);
|
||||
|
||||
|
||||
var newPatchFile=new MarcFile(event.data.patchFileU8Array);
|
||||
newPatchFile.fileName=romFile2.fileName.replace(/\.[^\.]+$/,'')+'.'+el('select-patch-type').value;
|
||||
newPatchFile.save();
|
||||
|
@ -73,8 +66,6 @@ try{
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
webWorkerCrc=new Worker('./worker_crc.js');
|
||||
webWorkerCrc.onmessage = event => { // listen for events from the worker
|
||||
//console.log('received_crc');
|
||||
|
@ -105,27 +96,25 @@ function _(str){return userLanguage[str] || str}
|
|||
|
||||
|
||||
|
||||
function selectFetchedPatch(i){
|
||||
patchFile=fetchedPatches[i].slice(0);
|
||||
_readPatchFile();
|
||||
}
|
||||
function fetchPredefinedPatch(i, doNotDisable){
|
||||
if(!doNotDisable)
|
||||
function fetchPatch(uri){
|
||||
setTabApplyEnabled(false);
|
||||
|
||||
var patchFromUrl=PREDEFINED_PATCHES[i].patch;
|
||||
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'){
|
||||
fetch(patchFromUrl)
|
||||
fetch(patchURI)
|
||||
.then(result => result.arrayBuffer()) // Gets the response and returns it as a blob
|
||||
.then(arrayBuffer => {
|
||||
fetchedPatches[i]=new MarcFile(arrayBuffer);
|
||||
fetchedPatches[i].fileName=decodeURIComponent(patchFromUrl);
|
||||
|
||||
if(i===el('input-file-patch').selectedIndex)
|
||||
selectFetchedPatch(i);
|
||||
fetchedPatches[patchURI]=patchFile=new MarcFile(arrayBuffer);
|
||||
fetchedPatches[patchURI].fileName=patchURI.replace(/^(.*?\/)+/g, '');
|
||||
_readPatchFile();
|
||||
})
|
||||
.catch(function(evt){
|
||||
setMessage('apply', _('error_downloading'), 'error');
|
||||
|
@ -133,16 +122,14 @@ function fetchPredefinedPatch(i, doNotDisable){
|
|||
});
|
||||
}else{
|
||||
var xhr=new XMLHttpRequest();
|
||||
xhr.open('GET', patchFromUrl, true);
|
||||
xhr.open('GET', patchURI, true);
|
||||
xhr.responseType='arraybuffer';
|
||||
|
||||
xhr.onload=function(evt){
|
||||
if(this.status===200){
|
||||
fetchedPatches[i]=new MarcFile(xhr.response);
|
||||
fetchedPatches[i].fileName=decodeURIComponent(patchFromUrl);
|
||||
|
||||
if(i===el('input-file-patch').selectedIndex)
|
||||
selectFetchedPatch(i);
|
||||
fetchedPatches[patchURI]=patchFile=new MarcFile(xhr.response);
|
||||
fetchedPatches[patchURI].fileName=patchURI.replace(/^(.*?\/)+/g, '');
|
||||
_readPatchFile();
|
||||
}else{
|
||||
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 */
|
||||
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 */
|
||||
var langCode=(navigator.language || navigator.userLanguage).substr(0,2);
|
||||
|
@ -181,36 +207,38 @@ addEvent(window,'load',function(){
|
|||
|
||||
addEvent(el('input-file-rom'), 'change', function(){
|
||||
setTabApplyEnabled(false);
|
||||
romFile=new MarcFile(this, function(){
|
||||
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);
|
||||
});
|
||||
romFile=new MarcFile(this, _parseROM);
|
||||
});
|
||||
|
||||
|
||||
|
||||
/* 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'){
|
||||
fetchedPatches=new Array(PREDEFINED_PATCHES.length);
|
||||
fetchedPatches={};
|
||||
|
||||
var container=el('input-file-patch').parentElement;
|
||||
container.removeChild(el('input-file-patch'));
|
||||
|
@ -219,22 +247,26 @@ addEvent(window,'load',function(){
|
|||
select.id='input-file-patch';
|
||||
for(var i=0; i<PREDEFINED_PATCHES.length; i++){
|
||||
var option=document.createElement('option');
|
||||
option.value=i;
|
||||
option.value=PREDEFINED_PATCHES[i].patch;
|
||||
option.innerHTML=PREDEFINED_PATCHES[i].name;
|
||||
select.appendChild(option);
|
||||
}
|
||||
container.appendChild(select)
|
||||
container.parentElement.title='';
|
||||
|
||||
|
||||
addEvent(select,'change',function(){
|
||||
if(fetchedPatches[this.selectedIndex])
|
||||
selectFetchedPatch(this.selectedIndex);
|
||||
else{
|
||||
if(fetchedPatches[this.value.replace(/\#.*?$/, '')]){
|
||||
patchFile=fetchedPatches[this.value.replace(/\#.*?$/, '')];
|
||||
patchFile.seek(0);
|
||||
_readPatchFile();
|
||||
}else{
|
||||
patch=null;
|
||||
patchFile=null;
|
||||
fetchPredefinedPatch(this.selectedIndex);
|
||||
fetchPatch(this.value);
|
||||
}
|
||||
});
|
||||
fetchPredefinedPatch(0, true);
|
||||
fetchPatch(select.value);
|
||||
}else{
|
||||
setTabCreateEnabled(true);
|
||||
el('input-file-rom1').value='';
|
||||
|
@ -344,14 +376,6 @@ function validateSource(){
|
|||
el('crc32').className='invalid';
|
||||
setMessage('apply', _('error_crc_input'), 'warning');
|
||||
}
|
||||
}else if(patch && romFile && typeof PREDEFINED_PATCHES!=='undefined' && PREDEFINED_PATCHES[el('input-file-patch').selectedIndex].crc){
|
||||
if(PREDEFINED_PATCHES[el('input-file-patch').selectedIndex].crc===crc32(romFile, el('checkbox-removeheader').checked && hasHeader(romFile))){
|
||||
el('crc32').className='valid';
|
||||
setMessage('apply');
|
||||
}else{
|
||||
el('crc32').className='invalid';
|
||||
setMessage('apply', _('error_crc_input'), 'warning');
|
||||
}
|
||||
}else{
|
||||
el('crc32').className='';
|
||||
setMessage('apply');
|
||||
|
@ -365,6 +389,16 @@ function _readPatchFile(){
|
|||
patchFile.littleEndian=false;
|
||||
|
||||
var header=patchFile.readString(6);
|
||||
if(header.startsWith(ZIP_MAGIC)){
|
||||
if(typeof PREDEFINED_PATCHES !== 'undefined' && /\#/.test(el('input-file-patch').value)){
|
||||
parseZIPFile(patchFile, el('input-file-patch').value.replace(/^.*?\#/, ''));
|
||||
}else{
|
||||
parseZIPFile(patchFile);
|
||||
}
|
||||
patch=false;
|
||||
validateSource();
|
||||
setTabApplyEnabled(false);
|
||||
}else{
|
||||
if(header.startsWith(IPS_MAGIC)){
|
||||
patch=parseIPSFile(patchFile);
|
||||
}else if(header.startsWith(UPS_MAGIC)){
|
||||
|
@ -384,9 +418,15 @@ function _readPatchFile(){
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -451,6 +491,7 @@ function applyPatch(p,r,validateChecksums){
|
|||
if(CAN_USE_WEB_WORKERS){
|
||||
setMessage('apply', _('applying_patch'), 'loading');
|
||||
setTabApplyEnabled(false);
|
||||
|
||||
webWorkerApply.postMessage(
|
||||
{
|
||||
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{
|
||||
setMessage('apply', _('applying_patch'), 'loading');
|
||||
|
||||
try{
|
||||
p.apply(r, validateChecksums);
|
||||
preparePatchedRom(r, p.apply(r, validateChecksums), headerSize);
|
||||
|
||||
}catch(e){
|
||||
|
|
|
@ -30,11 +30,13 @@ caches.keys().then(function(cacheNames){
|
|||
});
|
||||
|
||||
var PRECACHE_ID='rom-patcher-js';
|
||||
var PRECACHE_VERSION='v4';
|
||||
var PRECACHE_VERSION='v6';
|
||||
var PRECACHE_URLS=[
|
||||
'/RomPatcher.js/','/RomPatcher.js/index.html',
|
||||
'/RomPatcher.js/manifest.json',
|
||||
'/RomPatcher.js/favicon.png',
|
||||
'/RomPatcher.js/logo114.png',
|
||||
'/RomPatcher.js/logo144.png',
|
||||
'/RomPatcher.js/logo192.png',
|
||||
'/RomPatcher.js/RomPatcher.css',
|
||||
'/RomPatcher.js/RomPatcher.js',
|
||||
|
@ -42,8 +44,12 @@ var PRECACHE_URLS=[
|
|||
'/RomPatcher.js/worker_apply.js',
|
||||
'/RomPatcher.js/worker_create.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/zip.js',
|
||||
'/RomPatcher.js/ips.js',
|
||||
'/RomPatcher.js/ups.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
|
||||
self.addEventListener('install', evt => {
|
||||
evt.waitUntil(
|
||||
|
|
48
index.html
48
index.html
|
@ -9,11 +9,18 @@
|
|||
<link rel="manifest" href="./manifest.json"/>
|
||||
<link rel="shortcut icon" href="./favicon.png" type="image/png" sizes="16x16"/>
|
||||
<link rel="shortcut icon" href="./logo192.png" type="image/png" sizes="192x192"/>
|
||||
<!-- <link rel="apple-touch-icon" href="./logo192.png" sizes="192x192" /> -->
|
||||
<!-- 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"/>
|
||||
<script type="text/javascript" src="./locale.js"></script>
|
||||
<script type="text/javascript" src="./RomPatcher.js"></script>
|
||||
<script type="text/javascript" src="./MarcFile.js"></script>
|
||||
<script type="text/javascript" src="./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="./ips.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="./ppf.js"></script>
|
||||
<script type="text/javascript" src="./vcdiff.js"></script>
|
||||
<script type="text/javascript" src="./libs/zip.js"></script>
|
||||
|
||||
<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=[
|
||||
{patch:'./_example/SONICDX.xdelta',name:'Sonic 3D Blast Director\'s Cut v1.1',crc:0x44a2ca44},
|
||||
{patch:'./_example/SML2DXv181.ips',name:'Super Mario Land 2 DX v1.81',crc:0xd5ec24e4}
|
||||
//zip includes various patches+every patch file inside specified separately (best method)
|
||||
{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>
|
||||
</head>
|
||||
|
@ -53,28 +76,28 @@
|
|||
<div class="leftcol">SHA-1:</div><div class="rightcol"><span id="sha1"></span></div>
|
||||
</div>
|
||||
<div class="row" id="row-removeheader" style="display:none">
|
||||
<div class="leftcol"><label for="checkbox-removeheader" data-localize="remove_header">Remove header before patching:</label></div>
|
||||
<div class="leftcol"></div>
|
||||
<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 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">
|
||||
<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 class="row" id="row-file-patch">
|
||||
<div class="leftcol"><label for="input-file-patch" data-localize="patch_file">Patch file:</label></div>
|
||||
<div class="rightcol">
|
||||
<input type="file" id="input-file-patch" accept=".ips,.ups,.bps,.aps,.rup,.ppf,.xdelta"/>
|
||||
<input type="file" id="input-file-patch" accept=".ips,.ups,.bps,.aps,.rup,.ppf,.xdelta,.zip"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="buttons">
|
||||
<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>
|
||||
|
||||
|
@ -120,9 +143,10 @@
|
|||
|
||||
|
||||
|
||||
|
||||
<!-- 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 />
|
||||
<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>
|
||||
|
|
36
libs/inflate.js
Normal file
36
libs/inflate.js
Normal file
File diff suppressed because one or more lines are too long
2
libs/z-worker.js
Normal file
2
libs/z-worker.js
Normal 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
28
libs/zip.js
Normal file
File diff suppressed because one or more lines are too long
52
locale.js
52
locale.js
|
@ -1,26 +1,28 @@
|
|||
const LOCALIZATION={
|
||||
const LOCALIZATION={
|
||||
'en':{
|
||||
'creator_mode': 'Creator mode',
|
||||
|
||||
'apply_patch': 'Apply patch',
|
||||
'rom_file': 'ROM file:',
|
||||
'patch_file': 'Patch file:',
|
||||
'remove_header': 'Remove header before patching:',
|
||||
'add_header': 'Add temporary header:',
|
||||
'remove_header': 'Remove header',
|
||||
'add_header': 'Add temporary header',
|
||||
'compatible_formats': 'Compatible formats:',
|
||||
'applying_patch': 'Applying patch...',
|
||||
'downloading': 'Downloading...',
|
||||
'unzipping': 'Unzipping...',
|
||||
|
||||
'create_patch': 'Create patch:',
|
||||
'original_rom': 'Original ROM',
|
||||
'original_rom': 'Original ROM:',
|
||||
'modified_rom': 'Modified ROM:',
|
||||
'patch_type': 'Patch type:',
|
||||
'creating_patch': 'Creating patch...',
|
||||
|
||||
'error_crc_input': 'Invalid input ROM checksum',
|
||||
'error_crc_output': 'Invalid output ROM checksum',
|
||||
'error_crc_patch': 'Invalid patch checksum',
|
||||
'error_crc_input': 'Source ROM checksum mismatch',
|
||||
'error_crc_output': 'Target ROM checksum mismatch',
|
||||
'error_crc_patch': 'Patch checksum mismatch',
|
||||
'error_downloading': 'Error downloading patch',
|
||||
'error_unzipping': 'Error unzipping file',
|
||||
'error_invalid_patch': 'Invalid patch file',
|
||||
'warning_too_big': 'Using big files is not recommended.'
|
||||
},
|
||||
|
@ -30,11 +32,12 @@
|
|||
'apply_patch': 'Aplicar parche',
|
||||
'rom_file': 'Archivo ROM:',
|
||||
'patch_file': 'Archivo parche:',
|
||||
'remove_header': 'Quitar cabecera antes de aplicar:',
|
||||
'add_header': 'Añadir cabecera temporal:',
|
||||
'remove_header': 'Quitar cabecera',
|
||||
'add_header': 'Añadir cabecera temporal',
|
||||
'compatible_formats': 'Formatos compatibles:',
|
||||
'applying_patch': 'Aplicando parche...',
|
||||
'downloading': 'Descargando...',
|
||||
'unzipping': 'Descomprimiendo...',
|
||||
|
||||
'create_patch': 'Crear parche',
|
||||
'original_rom': 'ROM original:',
|
||||
|
@ -46,32 +49,35 @@
|
|||
'error_crc_output': 'Checksum de ROM creada no válida',
|
||||
'error_crc_patch': 'Checksum de parche no válida',
|
||||
'error_downloading': 'Error descargando parche',
|
||||
'error_unzipping': 'Error descomprimiendo archivo',
|
||||
'error_invalid_patch': 'Archivo de parche no válido',
|
||||
'warning_too_big': 'No se recomienda usar archivos muy grandes.'
|
||||
},
|
||||
'ca':{
|
||||
'creator_mode': 'Mode creador',
|
||||
|
||||
'apply_patch': 'Aplicar pegat',
|
||||
'apply_patch': 'Aplicar pedaç',
|
||||
'rom_file': 'Arxiu ROM:',
|
||||
'patch_file': 'Arxiu pegat:',
|
||||
'remove_header': 'El·liminar capçalera abans d\'aplicar:',
|
||||
'add_header': 'Afegir capçalera temporal:',
|
||||
'patch_file': 'Arxiu pedaç:',
|
||||
'remove_header': 'Treure capçalera',
|
||||
'add_header': 'Afegir capçalera temporal',
|
||||
'compatible_formats': 'Formats compatibles:',
|
||||
'applying_patch': 'Aplicant pegat...',
|
||||
'applying_patch': 'Aplicant pedaç...',
|
||||
'downloading': 'Descarregant...',
|
||||
'unzipping': 'Descomprimint...',
|
||||
|
||||
'create_patch': 'Crear pegat',
|
||||
'create_patch': 'Crear pedaç',
|
||||
'original_rom': 'ROM original:',
|
||||
'modified_rom': 'ROM modificada:',
|
||||
'patch_type': 'Tipus de pegat:',
|
||||
'creating_patch': 'Creant pegat...',
|
||||
'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 pegat no vàlida',
|
||||
'error_downloading': 'Error descarregant pegat',
|
||||
'error_invalid_patch': 'Arxiu de pegat no vàlid',
|
||||
'error_crc_patch': 'Checksum de pedaç no vàlida',
|
||||
'error_downloading': 'Error descarregant pedaç',
|
||||
'error_unzipping': 'Error descomprimint arxiu',
|
||||
'error_invalid_patch': 'Arxiu de pedaç no vàlid',
|
||||
'warning_too_big': 'No es recomana usar arxius molt grans.'
|
||||
},
|
||||
'ru':{
|
||||
|
@ -80,11 +86,12 @@
|
|||
'apply_patch': 'Применить патч',
|
||||
'rom_file': 'Файл ROM:',
|
||||
'patch_file': 'Файл патча:',
|
||||
'remove_header': 'Удалить заголовок перед применением:',
|
||||
'add_header': 'Добавить временный заголовок:',
|
||||
'remove_header': 'Удалить заголовок перед применением',
|
||||
'add_header': 'Добавить временный заголовок',
|
||||
'compatible_formats': 'Совместимые форматы:',
|
||||
'applying_patch': 'Применяется патч...',
|
||||
'downloading': 'Загрузка...',
|
||||
'unzipping': 'Unzipping...',
|
||||
|
||||
'create_patch': 'Создать патч:',
|
||||
'original_rom': 'Оригинальный ROM:',
|
||||
|
@ -96,6 +103,7 @@
|
|||
'error_crc_output': 'Неправильная контрольная сумма выходного ROM',
|
||||
'error_crc_patch': 'Неправильная контрольная сумма патча',
|
||||
'error_downloading': 'Ошибка при скачивании патча',
|
||||
'error_unzipping': 'Error unzipping file',
|
||||
'error_invalid_patch': 'Неправильный файл патча',
|
||||
'warning_too_big': 'Не рекомендуется использовать большие файлы.'
|
||||
}
|
||||
|
|
BIN
logo114.png
Normal file
BIN
logo114.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
BIN
logo144.png
Normal file
BIN
logo144.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
|
@ -3,6 +3,16 @@
|
|||
"name":"Rom Patcher JS",
|
||||
"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",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
|
|
29
predefined_patches_example.php
Normal file
29
predefined_patches_example.php
Normal 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 "];";
|
||||
}
|
||||
?>
|
8
rup.js
8
rup.js
|
@ -55,10 +55,14 @@ RUP.prototype.validateSource=function(romFile,headerSize){
|
|||
return false;
|
||||
}
|
||||
RUP.prototype.apply=function(romFile, validate){
|
||||
var validFile=this.validateSource(romFile);
|
||||
var validFile;
|
||||
if(validate){
|
||||
validFile=this.validateSource(romFile);
|
||||
|
||||
if(validate && !validFile){
|
||||
if(!validFile)
|
||||
throw new Error('error_crc_input');
|
||||
}else{
|
||||
validFile=this.files[0];
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* Rom Patcher JS v20181018 - Marc Robledo 2016-2018 - http://www.marcrobledo.com/license */
|
||||
|
||||
self.importScripts(
|
||||
'./MarcFile.js',
|
||||
'./libs/MarcFile.js',
|
||||
'./crc.js',
|
||||
'./ips.js',
|
||||
'./aps.js',
|
||||
|
@ -49,13 +49,13 @@ self.onmessage = event => { // listen for messages from the main thread
|
|||
//console.log('postMessage');
|
||||
self.postMessage(
|
||||
{
|
||||
//romFileU8Array:event.data.romFileU8Array,
|
||||
//patchFileU8Array:event.data.patchFileU8Array,
|
||||
romFileU8Array:event.data.romFileU8Array,
|
||||
patchFileU8Array:event.data.patchFileU8Array,
|
||||
patchedRomU8Array:patchedRom._u8array
|
||||
},
|
||||
[
|
||||
//event.data.romFileU8Array.buffer,
|
||||
//event.data.patchFileU8Array.buffer,
|
||||
event.data.romFileU8Array.buffer,
|
||||
event.data.patchFileU8Array.buffer,
|
||||
patchedRom._u8array.buffer
|
||||
]
|
||||
);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* Rom Patcher JS v20181018 - Marc Robledo 2016-2018 - http://www.marcrobledo.com/license */
|
||||
|
||||
self.importScripts(
|
||||
'./MarcFile.js',
|
||||
'./libs/MarcFile.js',
|
||||
'./crc.js'
|
||||
);
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* Rom Patcher JS v20181018 - Marc Robledo 2016-2018 - http://www.marcrobledo.com/license */
|
||||
|
||||
self.importScripts(
|
||||
'./MarcFile.js',
|
||||
'./libs/MarcFile.js',
|
||||
'./crc.js',
|
||||
'./ips.js',
|
||||
'./aps.js',
|
||||
|
|
83
zip.js
Normal file
83
zip.js
Normal 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);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue