mirror of
https://github.com/marcrobledo/RomPatcher.js.git
synced 2025-06-27 16:25:54 +00:00
added support for APS (GBA), fixing #58
This commit is contained in:
parent
0769fae972
commit
1ea536ad27
9 changed files with 147 additions and 133 deletions
|
@ -5,7 +5,7 @@ A ROM patcher made in HTML5.
|
||||||
* Supported formats:
|
* Supported formats:
|
||||||
* IPS
|
* IPS
|
||||||
* UPS
|
* UPS
|
||||||
* APS
|
* APS (N64/GBA)
|
||||||
* BPS
|
* BPS
|
||||||
* RUP
|
* RUP
|
||||||
* PPF
|
* PPF
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var PRECACHE_ID='rom-patcher-js';
|
var PRECACHE_ID='rom-patcher-js';
|
||||||
var PRECACHE_VERSION='v27b';
|
var PRECACHE_VERSION='v28';
|
||||||
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',
|
||||||
|
@ -41,7 +41,8 @@ var PRECACHE_URLS=[
|
||||||
'/RomPatcher.js/js/zip.js/inflate.js',
|
'/RomPatcher.js/js/zip.js/inflate.js',
|
||||||
'/RomPatcher.js/js/formats/ips.js',
|
'/RomPatcher.js/js/formats/ips.js',
|
||||||
'/RomPatcher.js/js/formats/ups.js',
|
'/RomPatcher.js/js/formats/ups.js',
|
||||||
'/RomPatcher.js/js/formats/aps.js',
|
'/RomPatcher.js/js/formats/aps_n64.js',
|
||||||
|
'/RomPatcher.js/js/formats/aps_gba.js',
|
||||||
'/RomPatcher.js/js/formats/bps.js',
|
'/RomPatcher.js/js/formats/bps.js',
|
||||||
'/RomPatcher.js/js/formats/rup.js',
|
'/RomPatcher.js/js/formats/rup.js',
|
||||||
'/RomPatcher.js/js/formats/ppf.js',
|
'/RomPatcher.js/js/formats/ppf.js',
|
||||||
|
|
|
@ -40,7 +40,8 @@
|
||||||
<script type="text/javascript" src="./js/formats/zip.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/ips.js"></script>
|
||||||
<script type="text/javascript" src="./js/formats/ups.js"></script>
|
<script type="text/javascript" src="./js/formats/ups.js"></script>
|
||||||
<script type="text/javascript" src="./js/formats/aps.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/bps.js"></script>
|
||||||
<script type="text/javascript" src="./js/formats/rup.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/ppf.js"></script>
|
||||||
|
@ -179,7 +180,7 @@
|
||||||
<button id="button-settings" class="button-outer"><img src="style/icon_settings.svg" class="icon settings" /> <span data-localize="settings">Settings</span></button>
|
<button id="button-settings" class="button-outer"><img src="style/icon_settings.svg" class="icon settings" /> <span data-localize="settings">Settings</span></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
Rom Patcher JS <small>v2.7.1</small> by <a href="/">Marc Robledo</a>
|
Rom Patcher JS <small>v2.8</small> by <a href="/">Marc Robledo</a>
|
||||||
<br />
|
<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_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>
|
<img src="style/icon_heart.svg" class="icon heart" /> <a href="https://www.paypal.me/marcrobledo/5" target="_blank" rel="nofollow">Donate</a>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* Rom Patcher JS v20230202 - Marc Robledo 2016-2023 - http://www.marcrobledo.com/license */
|
/* Rom Patcher JS v20230331 - Marc Robledo 2016-2023 - http://www.marcrobledo.com/license */
|
||||||
|
|
||||||
const TOO_BIG_ROM_SIZE=67108863;
|
const TOO_BIG_ROM_SIZE=67108863;
|
||||||
const HEADERS_INFO=[
|
const HEADERS_INFO=[
|
||||||
|
@ -663,8 +663,10 @@ function _readPatchFile(){
|
||||||
patch=parseIPSFile(patchFile);
|
patch=parseIPSFile(patchFile);
|
||||||
}else if(header.startsWith(UPS_MAGIC)){
|
}else if(header.startsWith(UPS_MAGIC)){
|
||||||
patch=parseUPSFile(patchFile);
|
patch=parseUPSFile(patchFile);
|
||||||
}else if(header.startsWith(APS_MAGIC)){
|
}else if(header.startsWith(APS_N64_MAGIC)){
|
||||||
patch=parseAPSFile(patchFile);
|
patch=parseAPSFile(patchFile);
|
||||||
|
}else if(header.startsWith(APS_GBA_MAGIC)){
|
||||||
|
patch=APSGBA.fromFile(patchFile);
|
||||||
}else if(header.startsWith(BPS_MAGIC)){
|
}else if(header.startsWith(BPS_MAGIC)){
|
||||||
patch=parseBPSFile(patchFile);
|
patch=parseBPSFile(patchFile);
|
||||||
}else if(header.startsWith(RUP_MAGIC)){
|
}else if(header.startsWith(RUP_MAGIC)){
|
||||||
|
|
133
js/crc.js
133
js/crc.js
|
@ -108,126 +108,19 @@ function adler32(marcFile, offset, len){
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* CRC16 */
|
/* CRC16/CCITT-FALSE */
|
||||||
/*
|
function crc16(marcFile, offset, len){
|
||||||
const CRC16_TABLE=(function(){
|
var crc=0xffff;
|
||||||
var c,crcTable=[];
|
|
||||||
for(var n=0;n<256;n++){
|
offset=offset? offset : 0;
|
||||||
c=n;
|
len=len && len>0? len : marcFile.fileSize;
|
||||||
for(var k=0;k<8;k++)
|
|
||||||
c=((c&1)?(0x8408^(c>>>1)):(c>>>1));
|
for(var i=0; i<len; i++){
|
||||||
crcTable[n]=c;
|
crc ^= marcFile._u8array[offset++] << 8;
|
||||||
|
for (j=0; j<8; ++j) {
|
||||||
|
crc = (crc & 0x8000) >>> 0 ? (crc << 1) ^ 0x1021 : crc << 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return crcTable;
|
|
||||||
}());
|
|
||||||
function crc16(marcFile){
|
|
||||||
var crc=0^(-1);
|
|
||||||
|
|
||||||
for(var i=0;i<marcFile._u8array.length;i++)
|
return crc & 0xffff;
|
||||||
crc=((crc>>>8)&0x0ff)^CRC16_TABLE[(crc^marcFile._u8array[i])&0xff];
|
|
||||||
|
|
||||||
return ((crc^(-1))>>>0) & 0xffff;
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* specific ROM checksums */
|
|
||||||
/* this is unused code, might be used in a future so ROM checksums can be fixed after patching */
|
|
||||||
const CONSOLES=[
|
|
||||||
{
|
|
||||||
title:'Sega Mega Drive/Genesis',
|
|
||||||
MEGADRIVE_LOGO:[0x53, 0x45, 0x47, 0x41, 0x20, 0x4d, 0x45, 0x47, 0x41, 0x20, 0x44, 0x52],
|
|
||||||
GENESIS_LOGO:[0x53, 0x45, 0x47, 0x41, 0x20, 0x47, 0x45, 0x4e, 0x45, 0x53, 0x49, 0x53],
|
|
||||||
checkHeader:function(marcFile){
|
|
||||||
var megadrive=true;
|
|
||||||
var genesis=true;
|
|
||||||
for(var i=0; i<12 && (megadrive || genesis); i++){
|
|
||||||
if(marcFile._u8array[0x100+i]!==this.MEGADRIVE_LOGO[i])
|
|
||||||
megadrive=false;
|
|
||||||
if(marcFile._u8array[0x100+i]!==this.GENESIS_LOGO[i])
|
|
||||||
genesis=false;
|
|
||||||
}
|
|
||||||
return megadrive || genesis;
|
|
||||||
},
|
|
||||||
getChecksum:function(marcFile){
|
|
||||||
return (marcFile._u8array[0x018e]<<8) + marcFile._u8array[0x018f];
|
|
||||||
},
|
|
||||||
recalculateChecksum:function(marcFile){
|
|
||||||
var checksum=0;
|
|
||||||
|
|
||||||
for(var i=0x200; i<marcFile.fileSize; i+=2)
|
|
||||||
checksum=(checksum + (((marcFile._u8array[i]<<8) + marcFile._u8array[i+1])>>>0)) & 0xffff;
|
|
||||||
|
|
||||||
return checksum
|
|
||||||
},
|
|
||||||
updateChecksum:function(marcFile, newChecksum){
|
|
||||||
marcFile._u8array[0x18e]=newChecksum>>8;
|
|
||||||
marcFile._u8array[0x18f]=newChecksum & 0xff;
|
|
||||||
}
|
|
||||||
},{
|
|
||||||
title:'Game Boy',
|
|
||||||
NINTENDO_LOGO:[0xce, 0xed, 0x66, 0x66, 0xcc, 0x0d, 0x00, 0x0b, 0x03, 0x73, 0x00, 0x83, 0x00, 0x0c, 0x00, 0x0d],
|
|
||||||
checkHeader:function(marcFile){
|
|
||||||
for(var i=0; i<this.NINTENDO_LOGO.length; i++){
|
|
||||||
if(marcFile._u8array[0x104+i]!==this.NINTENDO_LOGO[i])
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
getChecksum:function(marcFile){
|
|
||||||
return marcFile._u8array[0x14d]
|
|
||||||
},
|
|
||||||
recalculateChecksum:function(marcFile){
|
|
||||||
var checksum=0;
|
|
||||||
|
|
||||||
for(var i=0x134; i<0x014d; i++){
|
|
||||||
checksum=(checksum - marcFile._u8array[i] - 1) & 0xff;
|
|
||||||
}
|
|
||||||
|
|
||||||
return checksum
|
|
||||||
},
|
|
||||||
updateChecksum:function(marcFile, newChecksum){
|
|
||||||
marcFile._u8array[0x014d]=newChecksum;
|
|
||||||
|
|
||||||
|
|
||||||
/* global checksum isn't checked by real hw, but fix it anyway */
|
|
||||||
var globalChecksumOld=(marcFile._u8array[0x014e]<<8) + marcFile._u8array[0x014f];
|
|
||||||
var globalChecksumNew=0;
|
|
||||||
for(var i=0x0000; i<0x014e; i++)
|
|
||||||
globalChecksumNew=((globalChecksumNew + marcFile._u8array[i]) >>> 0) & 0xffff;
|
|
||||||
for(i=0x0150;i<marcFile.fileSize; i++)
|
|
||||||
globalChecksumNew=((globalChecksumNew + marcFile._u8array[i]) >>> 0) & 0xffff;
|
|
||||||
if(globalChecksumOld!==globalChecksumNew){
|
|
||||||
marcFile._u8array[0x014e]=globalChecksumNew>>8;
|
|
||||||
marcFile._u8array[0x014f]=globalChecksumNew & 0xff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
function checkConsole(marcFile){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
function fixConsoleChecksum(marcFile){
|
|
||||||
var system=false;
|
|
||||||
for(var i=0; i<CONSOLES.length && !system; i++)
|
|
||||||
if(CONSOLES[i].checkHeader(marcFile))
|
|
||||||
system=CONSOLES[i];
|
|
||||||
if(!system)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var oldChecksum=console.getChecksum(marcFile);
|
|
||||||
var newChecksum=console.recalculateChecksum(marcFile);
|
|
||||||
|
|
||||||
if(oldChecksum!==newChecksum)
|
|
||||||
if(alert('Fix '+console.title+' checksum?')){
|
|
||||||
console.updateChecksum(marcFile, newChecksum);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
114
js/formats/aps_gba.js
Normal file
114
js/formats/aps_gba.js
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
/* 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;
|
||||||
|
|
||||||
|
var originalEndianness=sourceFile.littleEndian;
|
||||||
|
sourceFile.littleEndian=true;
|
||||||
|
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
|
||||||
|
}
|
||||||
|
sourceFile.littleEndian=originalEndianness;
|
||||||
|
|
||||||
|
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_RECORD_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;
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
/* APS (N64) module for Rom Patcher JS v20180930 - Marc Robledo 2017-2018 - http://www.marcrobledo.com/license */
|
/* APS (N64) module for Rom Patcher JS v20180930 - Marc Robledo 2017-2018 - http://www.marcrobledo.com/license */
|
||||||
/* File format specification: https://github.com/btimofeev/UniPatcher/wiki/APS-(N64) */
|
/* File format specification: https://github.com/btimofeev/UniPatcher/wiki/APS-(N64) */
|
||||||
|
|
||||||
const APS_MAGIC='APS10';
|
const APS_N64_MAGIC='APS10';
|
||||||
const APS_RECORD_RLE=0x0000;
|
const APS_RECORD_RLE=0x0000;
|
||||||
const APS_RECORD_SIMPLE=0x01;
|
const APS_RECORD_SIMPLE=0x01;
|
||||||
const APS_N64_MODE=0x01;
|
const APS_N64_MODE=0x01;
|
||||||
|
@ -61,7 +61,7 @@ APS.prototype.export=function(fileName){
|
||||||
tempFile=new MarcFile(patchFileSize);
|
tempFile=new MarcFile(patchFileSize);
|
||||||
tempFile.littleEndian=true;
|
tempFile.littleEndian=true;
|
||||||
tempFile.fileName=fileName+'.aps';
|
tempFile.fileName=fileName+'.aps';
|
||||||
tempFile.writeString(APS_MAGIC, APS_MAGIC.length);
|
tempFile.writeString(APS_N64_MAGIC, APS_N64_MAGIC.length);
|
||||||
tempFile.writeU8(this.headerType);
|
tempFile.writeU8(this.headerType);
|
||||||
tempFile.writeU8(this.encodingMethod);
|
tempFile.writeU8(this.encodingMethod);
|
||||||
tempFile.writeString(this.description, 50);
|
tempFile.writeString(this.description, 50);
|
|
@ -1,10 +1,11 @@
|
||||||
/* Rom Patcher JS v20200502 - Marc Robledo 2016-2020 - http://www.marcrobledo.com/license */
|
/* Rom Patcher JS v20230331 - Marc Robledo 2016-2023 - http://www.marcrobledo.com/license */
|
||||||
|
|
||||||
self.importScripts(
|
self.importScripts(
|
||||||
'./MarcFile.js',
|
'./MarcFile.js',
|
||||||
'./crc.js',
|
'./crc.js',
|
||||||
'./formats/ips.js',
|
'./formats/ips.js',
|
||||||
'./formats/aps.js',
|
'./formats/aps_n64.js',
|
||||||
|
'./formats/aps_gba.js',
|
||||||
'./formats/ups.js',
|
'./formats/ups.js',
|
||||||
'./formats/bps.js',
|
'./formats/bps.js',
|
||||||
'./formats/rup.js',
|
'./formats/rup.js',
|
||||||
|
@ -26,8 +27,10 @@ self.onmessage = event => { // listen for messages from the main thread
|
||||||
patch=parseIPSFile(patchFile);
|
patch=parseIPSFile(patchFile);
|
||||||
}else if(header.startsWith(UPS_MAGIC)){
|
}else if(header.startsWith(UPS_MAGIC)){
|
||||||
patch=parseUPSFile(patchFile);
|
patch=parseUPSFile(patchFile);
|
||||||
}else if(header.startsWith(APS_MAGIC)){
|
}else if(header.startsWith(APS_N64_MAGIC)){
|
||||||
patch=parseAPSFile(patchFile);
|
patch=parseAPSFile(patchFile);
|
||||||
|
}else if(header.startsWith(APS_GBA_MAGIC)){
|
||||||
|
patch=APSGBA.fromFile(patchFile);
|
||||||
}else if(header.startsWith(BPS_MAGIC)){
|
}else if(header.startsWith(BPS_MAGIC)){
|
||||||
patch=parseBPSFile(patchFile);
|
patch=parseBPSFile(patchFile);
|
||||||
}else if(header.startsWith(RUP_MAGIC)){
|
}else if(header.startsWith(RUP_MAGIC)){
|
||||||
|
|
|
@ -4,7 +4,7 @@ self.importScripts(
|
||||||
'./MarcFile.js',
|
'./MarcFile.js',
|
||||||
'./crc.js',
|
'./crc.js',
|
||||||
'./formats/ips.js',
|
'./formats/ips.js',
|
||||||
'./formats/aps.js',
|
'./formats/aps_n64.js',
|
||||||
'./formats/ups.js',
|
'./formats/ups.js',
|
||||||
'./formats/bps.js',
|
'./formats/bps.js',
|
||||||
'./formats/ppf.js',
|
'./formats/ppf.js',
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue