diff --git a/.gitignore b/.gitignore index 1d794651c..87524e45f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ *.log **/build/ **/include/sym/ +web/node_modules diff --git a/web/install.sh b/web/install.sh index 7a67f960b..43a8d83d3 100755 --- a/web/install.sh +++ b/web/install.sh @@ -79,6 +79,7 @@ find ~/web2py/applications/solver/static -xtype l -exec rm -f {} \; [ -L ~/web2py/applications/solver/static/js/localforage.nopromises.min.js ] || ln -s ~/RandomMetroidSolver/web/static/localforage.nopromises.min.js ~/web2py/applications/solver/static/js/localforage.nopromises.min.js [ -L ~/web2py/applications/solver/static/js/spc_snes.js ] || ln -s ~/RandomMetroidSolver/web/static/spc_js/spc_snes.js ~/web2py/applications/solver/static/js/spc_snes.js [ -L ~/web2py/applications/solver/static/js/spc_snes.js.mem ] || ln -s ~/RandomMetroidSolver/web/static/spc_js/spc_snes.js.mem ~/web2py/applications/solver/static/js/spc_snes.js.mem +[ -L ~/web2py/applications/solver/static/js/customizer.js ] || ln -s ~/RandomMetroidSolver/web/static/customizer.js ~/web2py/applications/solver/static/js/customizer.js mkdir -p ~/web2py/applications/solver/static/images/common/ [ -L ~/web2py/applications/solver/static/images/common/area_map_20200112.png ] || ln -s ~/RandomMetroidSolver/web/static/area_map.png ~/web2py/applications/solver/static/images/common/area_map_20200112.png diff --git a/web/js/constants.ts b/web/js/constants.ts new file mode 100644 index 000000000..677a81387 --- /dev/null +++ b/web/js/constants.ts @@ -0,0 +1,3 @@ +export const VANILLA_ROM_SIZE = 0x300000 +export const HEADERED_ROM_SIZE = 3146240 +export const VANILLA_CRC32 = 'd63ed5f8' diff --git a/web/js/customizer.ts b/web/js/customizer.ts new file mode 100644 index 000000000..9d1d35875 --- /dev/null +++ b/web/js/customizer.ts @@ -0,0 +1,12 @@ +import vanillaRom from './rom' + +async function main() { + new vanillaRom() +} + +window.addEventListener('load', () => { + main() + .catch((err) => { + console.error(err) + }) +}) diff --git a/web/js/helpers/crc32.ts b/web/js/helpers/crc32.ts new file mode 100644 index 000000000..4dc584503 --- /dev/null +++ b/web/js/helpers/crc32.ts @@ -0,0 +1,93 @@ +'use strict' + +class Crc32 { + crc: number + + constructor() { + this.crc = -1 >>> 0 + } + + update(data) { + var dataView = new Uint8Array(data, 0) + var length = dataView.length + for (var i = 0; i < length; i++) { + this.crc = (this.crc >>> 8) ^ LOOKUP[(this.crc ^ dataView[i]) & 0xFF] + } + } + + digest(size = 16) { + var buffer = new ArrayBuffer(4) + var dataView = new DataView(buffer) + dataView.setUint32(0, ~this.crc >>> 0, false) + return dataView.getUint32(0).toString(size) + } +} + +const LOOKUP = [ + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, + 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, + 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, + 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, + 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, + 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, + 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, + 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, + 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, + 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, + 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, + 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, + 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, + 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, + 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, + 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, + 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, + 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, + 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, + 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, + 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, + 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, + 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, + 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, + 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, + 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, + 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, + 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, + 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, + 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, + 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, + 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, + 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D +] + +export default Crc32 diff --git a/web/js/helpers/hasFileReader.ts b/web/js/helpers/hasFileReader.ts new file mode 100644 index 000000000..d15181e73 --- /dev/null +++ b/web/js/helpers/hasFileReader.ts @@ -0,0 +1,5 @@ +const hasFileReader = () => ( + window.File && window.FileList && window.FileReader +) + +export default hasFileReader diff --git a/web/js/helpers/settings.ts b/web/js/helpers/settings.ts new file mode 100644 index 000000000..4ece8e0a6 --- /dev/null +++ b/web/js/helpers/settings.ts @@ -0,0 +1,23 @@ +let SETTINGS + +declare global { + interface Window { + VARIA_SETTINGS: VariaSettings + } +} + +export type VariaSettings = { + permalink?: boolean +} + +const DEFAULTS: VariaSettings = { + permalink: false +}; + +(() => { + SETTINGS = Object.assign({}, DEFAULTS, window.VARIA_SETTINGS) +})() + +const getSettings = () => SETTINGS + +export default getSettings diff --git a/web/js/rom.ts b/web/js/rom.ts new file mode 100644 index 000000000..0c772c29f --- /dev/null +++ b/web/js/rom.ts @@ -0,0 +1,181 @@ +import hasFileReader from './helpers/hasFileReader' +import Settings from './helpers/settings' +import Crc32 from './helpers/crc32' +import { VANILLA_CRC32 } from './constants' +import { del, get, set } from 'idb-keyval' + +const VALID_EXTENSIONS = ['sfc', 'smc'] +const VANILLA_ROM_KEY = 'vanillaROM' + +declare global { + interface Window { + // the /randomizer route uses the variable `vanillaROM` + vanillaROM: Uint8Array | null + // the /customizer route uses the variable `vanillaROMBytes` + vanillaROMBytes: Uint8Array | null + } +} + +class VanillaROM { + el: HTMLElement | null + + constructor() { + if (!hasFileReader()) { + alert('This website requires the HTML5 File API, please upgrade your browser to a newer version.') + return + } + + const checkForStoredFile = this.checkForStoredFile.bind(this) + const bindEvents = this.bindEvents.bind(this) + const broadcastROMStatus = this.broadcastROMStatus.bind(this) + checkForStoredFile() + .then((hasFile) => { + if (hasFile) { + console.log('Vanilla ROM loaded from storage') + } else { + broadcastROMStatus(null) + bindEvents() + } + }) + } + + bindEvents() { + const settings = Settings() + const selector = settings.permalink ? 'vanillaUploadFile' : 'uploadFile' + this.el = document.getElementById(selector) + const useFile = this.useFile.bind(this) + this.el?.addEventListener('change', (evt: Event) => { + const file = (evt.target).files?.[0] + if (file) { + useFile(file) + } + }) + } + + checkForStoredFile(): Promise { + return new Promise((resolve, _reject) => { + this.getROM() + .then((value) => { + if (!value) { + resolve(false) + return + } + const validated = this.validateChecksum(value) + if (validated) { + this.broadcastROMStatus(value) + resolve(true) + return + } + throw Error('Invalid Vanilla ROM value stored') + }) + .catch((err) => { + console.error(err) + del(VANILLA_ROM_KEY) + resolve(false) + }) + }) + } + + displayStatus(hasROM = false) { + const formEl = document.getElementById('vanillaROMVisibility') + const okEl = document.getElementById('vanillaROMOKVisibility') + if (!formEl || !okEl) { + return + } + if (hasROM) { + formEl.style.display = 'none' + okEl.style.display = 'block' + } else { + formEl.style.display = 'block' + okEl.style.display = 'none' + } + + } + + getUnheaderedContent(content) { + const fileSize = content.byteLength + const isHeadered = fileSize === 3146240 + return isHeadered ? content.slice(512) : content + } + + broadcastROMStatus(content: Uint8Array | null) { + const hasROM = content !== null && content.byteLength > 0 + this.displayStatus(hasROM) + this.setLegacyROMToBrowser(content) + } + + validateChecksum(content) { + const fileSize = content.byteLength + const isTooLarge = fileSize > 4*1024*1024 + if (isTooLarge) { + console.warn(`Filesize is too big: ${content.size.toString()}`) + return false + } + + const crc32 = new Crc32() + crc32.update(content) + const checksum = crc32.digest() + + if (checksum === VANILLA_CRC32) { + return true + } + + console.warn('Non-Vanilla ROM detected') + return false + } + + validateFileExtension(name: string) { + const lastDot = name.lastIndexOf('.') + const extension = name.substring(lastDot + 1).toLowerCase() + if (VALID_EXTENSIONS.includes(extension)) { + return true + } + throw Error(`Unsupported file extension: ${extension}`) + } + + async readFile(evt) { + let content = this.getUnheaderedContent(evt.target.result) + const validated = this.validateChecksum(content) + if (!validated) { + return alert('The file you have provided is not a valid Vanilla ROM.') + } + const data = new Uint8Array(content) + const saved = await this.setROM(data) + if (saved) { + this.broadcastROMStatus(data) + } + } + + useFile(file: File) { + this.validateFileExtension(file.name) + + const reader = new FileReader() + const onLoad = this.readFile.bind(this) + reader.addEventListener('load', onLoad) + reader.readAsArrayBuffer(file) + } + + getROM() { + return get(VANILLA_ROM_KEY) + } + + setROM(content: Uint8Array) { + try { + set(VANILLA_ROM_KEY, content) + return true + } catch (err) { + console.error('Could not set Vanilla ROM', err) + return false + } + + } + + setLegacyROMToBrowser(content) { + // Inline scripts on the page uses these variables depending on the route. + // This method sets both values for both variables to work with the inline scripts. + window.vanillaROMBytes = content + window.vanillaROM = content + } +} + +export default VanillaROM diff --git a/web/package.json b/web/package.json new file mode 100644 index 000000000..d28609961 --- /dev/null +++ b/web/package.json @@ -0,0 +1,15 @@ +{ + "name": "varia-web", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "scripts": { + "build": "esbuild js/customizer.ts --bundle --outdir=static" + }, + "devDependencies": { + "esbuild": "^0.14.48" + }, + "dependencies": { + "idb-keyval": "^6.2.0" + } +} diff --git a/web/static/customizer.js b/web/static/customizer.js new file mode 100644 index 000000000..4d8185b71 --- /dev/null +++ b/web/static/customizer.js @@ -0,0 +1,484 @@ +(() => { + // js/helpers/hasFileReader.ts + var hasFileReader = () => window.File && window.FileList && window.FileReader; + var hasFileReader_default = hasFileReader; + + // js/helpers/settings.ts + var SETTINGS; + var DEFAULTS = { + permalink: false + }; + (() => { + SETTINGS = Object.assign({}, DEFAULTS, window.VARIA_SETTINGS); + })(); + var getSettings = () => SETTINGS; + var settings_default = getSettings; + + // js/helpers/crc32.ts + var Crc32 = class { + constructor() { + this.crc = -1 >>> 0; + } + update(data) { + var dataView = new Uint8Array(data, 0); + var length = dataView.length; + for (var i = 0; i < length; i++) { + this.crc = this.crc >>> 8 ^ LOOKUP[(this.crc ^ dataView[i]) & 255]; + } + } + digest(size = 16) { + var buffer = new ArrayBuffer(4); + var dataView = new DataView(buffer); + dataView.setUint32(0, ~this.crc >>> 0, false); + return dataView.getUint32(0).toString(size); + } + }; + var LOOKUP = [ + 0, + 1996959894, + 3993919788, + 2567524794, + 124634137, + 1886057615, + 3915621685, + 2657392035, + 249268274, + 2044508324, + 3772115230, + 2547177864, + 162941995, + 2125561021, + 3887607047, + 2428444049, + 498536548, + 1789927666, + 4089016648, + 2227061214, + 450548861, + 1843258603, + 4107580753, + 2211677639, + 325883990, + 1684777152, + 4251122042, + 2321926636, + 335633487, + 1661365465, + 4195302755, + 2366115317, + 997073096, + 1281953886, + 3579855332, + 2724688242, + 1006888145, + 1258607687, + 3524101629, + 2768942443, + 901097722, + 1119000684, + 3686517206, + 2898065728, + 853044451, + 1172266101, + 3705015759, + 2882616665, + 651767980, + 1373503546, + 3369554304, + 3218104598, + 565507253, + 1454621731, + 3485111705, + 3099436303, + 671266974, + 1594198024, + 3322730930, + 2970347812, + 795835527, + 1483230225, + 3244367275, + 3060149565, + 1994146192, + 31158534, + 2563907772, + 4023717930, + 1907459465, + 112637215, + 2680153253, + 3904427059, + 2013776290, + 251722036, + 2517215374, + 3775830040, + 2137656763, + 141376813, + 2439277719, + 3865271297, + 1802195444, + 476864866, + 2238001368, + 4066508878, + 1812370925, + 453092731, + 2181625025, + 4111451223, + 1706088902, + 314042704, + 2344532202, + 4240017532, + 1658658271, + 366619977, + 2362670323, + 4224994405, + 1303535960, + 984961486, + 2747007092, + 3569037538, + 1256170817, + 1037604311, + 2765210733, + 3554079995, + 1131014506, + 879679996, + 2909243462, + 3663771856, + 1141124467, + 855842277, + 2852801631, + 3708648649, + 1342533948, + 654459306, + 3188396048, + 3373015174, + 1466479909, + 544179635, + 3110523913, + 3462522015, + 1591671054, + 702138776, + 2966460450, + 3352799412, + 1504918807, + 783551873, + 3082640443, + 3233442989, + 3988292384, + 2596254646, + 62317068, + 1957810842, + 3939845945, + 2647816111, + 81470997, + 1943803523, + 3814918930, + 2489596804, + 225274430, + 2053790376, + 3826175755, + 2466906013, + 167816743, + 2097651377, + 4027552580, + 2265490386, + 503444072, + 1762050814, + 4150417245, + 2154129355, + 426522225, + 1852507879, + 4275313526, + 2312317920, + 282753626, + 1742555852, + 4189708143, + 2394877945, + 397917763, + 1622183637, + 3604390888, + 2714866558, + 953729732, + 1340076626, + 3518719985, + 2797360999, + 1068828381, + 1219638859, + 3624741850, + 2936675148, + 906185462, + 1090812512, + 3747672003, + 2825379669, + 829329135, + 1181335161, + 3412177804, + 3160834842, + 628085408, + 1382605366, + 3423369109, + 3138078467, + 570562233, + 1426400815, + 3317316542, + 2998733608, + 733239954, + 1555261956, + 3268935591, + 3050360625, + 752459403, + 1541320221, + 2607071920, + 3965973030, + 1969922972, + 40735498, + 2617837225, + 3943577151, + 1913087877, + 83908371, + 2512341634, + 3803740692, + 2075208622, + 213261112, + 2463272603, + 3855990285, + 2094854071, + 198958881, + 2262029012, + 4057260610, + 1759359992, + 534414190, + 2176718541, + 4139329115, + 1873836001, + 414664567, + 2282248934, + 4279200368, + 1711684554, + 285281116, + 2405801727, + 4167216745, + 1634467795, + 376229701, + 2685067896, + 3608007406, + 1308918612, + 956543938, + 2808555105, + 3495958263, + 1231636301, + 1047427035, + 2932959818, + 3654703836, + 1088359270, + 936918e3, + 2847714899, + 3736837829, + 1202900863, + 817233897, + 3183342108, + 3401237130, + 1404277552, + 615818150, + 3134207493, + 3453421203, + 1423857449, + 601450431, + 3009837614, + 3294710456, + 1567103746, + 711928724, + 3020668471, + 3272380065, + 1510334235, + 755167117 + ]; + var crc32_default = Crc32; + + // js/constants.ts + var VANILLA_CRC32 = "d63ed5f8"; + + // node_modules/idb-keyval/dist/index.js + function promisifyRequest(request) { + return new Promise((resolve, reject) => { + request.oncomplete = request.onsuccess = () => resolve(request.result); + request.onabort = request.onerror = () => reject(request.error); + }); + } + function createStore(dbName, storeName) { + const request = indexedDB.open(dbName); + request.onupgradeneeded = () => request.result.createObjectStore(storeName); + const dbp = promisifyRequest(request); + return (txMode, callback) => dbp.then((db) => callback(db.transaction(storeName, txMode).objectStore(storeName))); + } + var defaultGetStoreFunc; + function defaultGetStore() { + if (!defaultGetStoreFunc) { + defaultGetStoreFunc = createStore("keyval-store", "keyval"); + } + return defaultGetStoreFunc; + } + function get(key, customStore = defaultGetStore()) { + return customStore("readonly", (store) => promisifyRequest(store.get(key))); + } + function set(key, value, customStore = defaultGetStore()) { + return customStore("readwrite", (store) => { + store.put(value, key); + return promisifyRequest(store.transaction); + }); + } + function del(key, customStore = defaultGetStore()) { + return customStore("readwrite", (store) => { + store.delete(key); + return promisifyRequest(store.transaction); + }); + } + + // js/rom.ts + var VALID_EXTENSIONS = ["sfc", "smc"]; + var VANILLA_ROM_KEY = "vanillaROM"; + var VanillaROM = class { + constructor() { + if (!hasFileReader_default()) { + alert("This website requires the HTML5 File API, please upgrade your browser to a newer version."); + return; + } + const checkForStoredFile = this.checkForStoredFile.bind(this); + const bindEvents = this.bindEvents.bind(this); + const broadcastROMStatus = this.broadcastROMStatus.bind(this); + checkForStoredFile().then((hasFile) => { + if (hasFile) { + console.log("Vanilla ROM loaded from storage"); + } else { + broadcastROMStatus(null); + bindEvents(); + } + }); + } + bindEvents() { + const settings = settings_default(); + const selector = settings.permalink ? "vanillaUploadFile" : "uploadFile"; + this.el = document.getElementById(selector); + const useFile = this.useFile.bind(this); + this.el?.addEventListener("change", (evt) => { + const file = evt.target.files?.[0]; + if (file) { + useFile(file); + } + }); + } + checkForStoredFile() { + return new Promise((resolve, _reject) => { + this.getROM().then((value) => { + if (!value) { + resolve(false); + return; + } + const validated = this.validateChecksum(value); + if (validated) { + this.broadcastROMStatus(value); + resolve(true); + return; + } + throw Error("Invalid Vanilla ROM value stored"); + }).catch((err) => { + console.error(err); + del(VANILLA_ROM_KEY); + resolve(false); + }); + }); + } + displayStatus(hasROM = false) { + const formEl = document.getElementById("vanillaROMVisibility"); + const okEl = document.getElementById("vanillaROMOKVisibility"); + if (!formEl || !okEl) { + return; + } + if (hasROM) { + formEl.style.display = "none"; + okEl.style.display = "block"; + } else { + formEl.style.display = "block"; + okEl.style.display = "none"; + } + } + getUnheaderedContent(content) { + const fileSize = content.byteLength; + const isHeadered = fileSize === 3146240; + return isHeadered ? content.slice(512) : content; + } + broadcastROMStatus(content) { + const hasROM = content !== null && content.byteLength > 0; + this.displayStatus(hasROM); + this.setLegacyROMToBrowser(content); + } + validateChecksum(content) { + const fileSize = content.byteLength; + const isTooLarge = fileSize > 4 * 1024 * 1024; + if (isTooLarge) { + console.warn(`Filesize is too big: ${content.size.toString()}`); + return false; + } + const crc32 = new crc32_default(); + crc32.update(content); + const checksum = crc32.digest(); + if (checksum === VANILLA_CRC32) { + return true; + } + console.warn("Non-Vanilla ROM detected"); + return false; + } + validateFileExtension(name) { + const lastDot = name.lastIndexOf("."); + const extension = name.substring(lastDot + 1).toLowerCase(); + if (VALID_EXTENSIONS.includes(extension)) { + return true; + } + throw Error(`Unsupported file extension: ${extension}`); + } + async readFile(evt) { + let content = this.getUnheaderedContent(evt.target.result); + const validated = this.validateChecksum(content); + if (!validated) { + return alert("The file you have provided is not a valid Vanilla ROM."); + } + const data = new Uint8Array(content); + const saved = await this.setROM(data); + if (saved) { + this.broadcastROMStatus(data); + } + } + useFile(file) { + this.validateFileExtension(file.name); + const reader = new FileReader(); + const onLoad = this.readFile.bind(this); + reader.addEventListener("load", onLoad); + reader.readAsArrayBuffer(file); + } + getROM() { + return get(VANILLA_ROM_KEY); + } + setROM(content) { + try { + set(VANILLA_ROM_KEY, content); + return true; + } catch (err) { + console.error("Could not set Vanilla ROM", err); + return false; + } + } + setLegacyROMToBrowser(content) { + window.vanillaROMBytes = content; + window.vanillaROM = content; + } + }; + var rom_default = VanillaROM; + + // js/customizer.ts + async function main() { + new rom_default(); + } + window.addEventListener("load", () => { + main().catch((err) => { + console.error(err); + }); + }); +})(); diff --git a/web/views/customizer.html b/web/views/customizer.html index d17661dd1..ac4ff8b07 100644 --- a/web/views/customizer.html +++ b/web/views/customizer.html @@ -4,7 +4,23 @@ + +{{ if seedInfo is not None: }} + +{{ else:}} + +{{ pass }} + + @@ -20,7 +36,6 @@ - @@ -928,99 +943,37 @@ pass }} window.onload = function(){ - //Check File API support - if(window.File && window.FileList && window.FileReader) { - if(permalink == true) { - var inputName = "vanillaUploadFile"; - } else { - var inputName = "uploadFile"; - } - var filesInput = document.getElementById(inputName); - filesInput.addEventListener("change", function(event){ - var files = event.target.files; //It returns a FileList object - var file = files[0]; - - var reader = new FileReader(); - reader.onload = function(e) { - // check sfc or smc extention - var re = /(?:\.([^.]+))?$/; - var ext = re.exec(file.name)[1]; - console.log("ext: "+ext) - if( ! (ext === "sfc" || ext === "smc" || ext === "SFC" || ext === "SMC") ) { - document.getElementById("uploadFile").value = ""; - alert("wrong extension: "+ext); - return false; - } - - romSize = file.size; - if( romSize > 4*1024*1024 ) { - document.getElementById("uploadFile").value = ""; - alert("wrong rom file size: "+romSize.toString()); - return false; - } - - var crc32 = new Crc32(); - crc32.update(e.target.result); - var digest = crc32.digest(); - console.log("crc32: "+digest); - var isVanilla = digest == "d63ed5f8"; - - if(permalink == true) { - if(! isVanilla) { - alert("Non vanilla ROM detected"); - document.getElementById(inputName).value = ""; - return false; - } - - // store file in localforage - localforage.setItem('vanillaROM', e.target.result, function(err, value) { - if(err != null) { - console.log("error detected with local storage: "+err); - clearLocalStorage(); - } else { - console.log("vanilla ROM stored in local storage"); - vanillaROMBytes = new Uint8Array(value); - displayROMInput(false); - } - }); - - } else { - // detect race mode patch - isRace = false; - var bytes = new Uint8Array(e.target.result); - for(var addr=0x1C0200; addr<0x1C0210; addr++) { - if(bytes[addr] != 0xff) { - isRace = true; - console.log("race mode detected"); - break; - } - } - - // detect varia default patches - isVARIA = false; - if(bytes[0x175ca] == 0x60 && bytes[0x19E1] == 0xEA && bytes[0xF27] == 0x20) { - isVARIA = true; - } - + // detect beam doors patch from seed to customize - beamDoorsPatch = bytes[0x226e5] == 0x0D; + // beamDoorsPatch = bytes[0x226e5] == 0x0D; // detect area layout patch - isArea = bytes[0x788A0] == 0x2b; + // isArea = bytes[0x788A0] == 0x2b; // detect Phantoon_Eye_Door patch - isBoss = bytes[0x7ccaf] == 0x91; + // isBoss = bytes[0x7ccaf] == 0x91; - console.log("isArea: "+isArea+" isBoss: "+isBoss+" isVARIA: "+isVARIA+" isRace: "+isRace); + // console.log("isArea: "+isArea+" isBoss: "+isBoss+" isVARIA: "+isVARIA+" isRace: "+isRace); - greyOutPatches(isRace, isVARIA, isVanilla); - } - } + // // detect varia default patches + // isVARIA = false; + // if(bytes[0x175ca] == 0x60 && bytes[0x19E1] == 0xEA && bytes[0xF27] == 0x20) { + // isVARIA = true; + // } - reader.readAsArrayBuffer(file) + // // detect beam doors patch from seed to customize + // beamDoorsPatch = bytes[0x226e5] == 0x0D; + // isArea = bytes[0x788A0] == 0x2b; + // isBoss = bytes[0x1a2b6] != 0x00; - }, false); - } else { - alert("This website requires the HTML5 File API, please upgrade your browser to a newer version."); - } + // greyOutPatches(isRace, isVARIA, isVanilla); + // } + // } + + // reader.readAsArrayBuffer(file) + + // }, false); + // } else { + // alert("This website requires the HTML5 File API, please upgrade your browser to a newer version."); + // } var inputs = document.getElementsByTagName("input"); @@ -1121,21 +1074,6 @@ pass }} - if(permalink == true) { - // check if a vanilla ROM is already in local storage - localforage.getItem('vanillaROM', function(err, value) { - if(err != null) { - clearLocalStorage(); - } else { - if(value != null) { - displayROMInput(false); - vanillaROMBytes = new Uint8Array(value); - } else { - clearLocalStorage(); - } - } - }); - } } function switchGamepadMapping() { @@ -1161,12 +1099,6 @@ } } -function clearLocalStorage() { - displayROMInput(true); - localforage.clear(); - vanillaROMBytes = null; -} - function permalinkCustomization(permalink) { if(permalink == true) { document.getElementById("uploadFileVisibility").style.display = "none"; @@ -2134,7 +2066,7 @@

Download seed {{=seedInfo["filename"]}}

Vanilla ROM:
- {{=INPUT(_type="file", _name="vanillaUploadFile", _id="vanillaUploadFile", _class="full")}} + {{=INPUT(_type="file", _accept=".smc,.sfc", _name="vanillaUploadFile", _id="vanillaUploadFile", _class="full")}}
Vanilla ROM already loaded diff --git a/web/yarn.lock b/web/yarn.lock new file mode 100644 index 000000000..6e5d3d44f --- /dev/null +++ b/web/yarn.lock @@ -0,0 +1,141 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +esbuild-android-64@0.14.48: + version "0.14.48" + resolved "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.48.tgz#7e6394a0e517f738641385aaf553c7e4fb6d1ae3" + integrity sha512-3aMjboap/kqwCUpGWIjsk20TtxVoKck8/4Tu19rubh7t5Ra0Yrpg30Mt1QXXlipOazrEceGeWurXKeFJgkPOUg== + +esbuild-android-arm64@0.14.48: + version "0.14.48" + resolved "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.48.tgz#6877566be0f82dd5a43030c0007d06ece7f7c02f" + integrity sha512-vptI3K0wGALiDq+EvRuZotZrJqkYkN5282iAfcffjI5lmGG9G1ta/CIVauhY42MBXwEgDJkweiDcDMRLzBZC4g== + +esbuild-darwin-64@0.14.48: + version "0.14.48" + resolved "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.48.tgz#ea3caddb707d88f844b1aa1dea5ff3b0a71ef1fd" + integrity sha512-gGQZa4+hab2Va/Zww94YbshLuWteyKGD3+EsVon8EWTWhnHFRm5N9NbALNbwi/7hQ/hM1Zm4FuHg+k6BLsl5UA== + +esbuild-darwin-arm64@0.14.48: + version "0.14.48" + resolved "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.48.tgz#4e5eaab54df66cc319b76a2ac0e8af4e6f0d9c2f" + integrity sha512-bFjnNEXjhZT+IZ8RvRGNJthLWNHV5JkCtuOFOnjvo5pC0sk2/QVk0Qc06g2PV3J0TcU6kaPC3RN9yy9w2PSLEA== + +esbuild-freebsd-64@0.14.48: + version "0.14.48" + resolved "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.48.tgz#47b5abc7426eae66861490ffbb380acc67af5b15" + integrity sha512-1NOlwRxmOsnPcWOGTB10JKAkYSb2nue0oM1AfHWunW/mv3wERfJmnYlGzL3UAOIUXZqW8GeA2mv+QGwq7DToqA== + +esbuild-freebsd-arm64@0.14.48: + version "0.14.48" + resolved "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.48.tgz#e8c54c8637cd44feed967ea12338b0a4da3a7b11" + integrity sha512-gXqKdO8wabVcYtluAbikDH2jhXp+Klq5oCD5qbVyUG6tFiGhrC9oczKq3vIrrtwcxDQqK6+HDYK8Zrd4bCA9Gw== + +esbuild-linux-32@0.14.48: + version "0.14.48" + resolved "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.48.tgz#229cf3246de2b7937c3ac13fac622d4d7a1344c5" + integrity sha512-ghGyDfS289z/LReZQUuuKq9KlTiTspxL8SITBFQFAFRA/IkIvDpnZnCAKTCjGXAmUqroMQfKJXMxyjJA69c/nQ== + +esbuild-linux-64@0.14.48: + version "0.14.48" + resolved "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.48.tgz#7c0e7226c02c42aacc5656c36977493dc1e96c4f" + integrity sha512-vni3p/gppLMVZLghI7oMqbOZdGmLbbKR23XFARKnszCIBpEMEDxOMNIKPmMItQrmH/iJrL1z8Jt2nynY0bE1ug== + +esbuild-linux-arm64@0.14.48: + version "0.14.48" + resolved "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.48.tgz#0af1eda474b5c6cc0cace8235b74d0cb8fcf57a7" + integrity sha512-3CFsOlpoxlKPRevEHq8aAntgYGYkE1N9yRYAcPyng/p4Wyx0tPR5SBYsxLKcgPB9mR8chHEhtWYz6EZ+H199Zw== + +esbuild-linux-arm@0.14.48: + version "0.14.48" + resolved "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.48.tgz#de4d1fa6b77cdcd00e2bb43dd0801e4680f0ab52" + integrity sha512-+VfSV7Akh1XUiDNXgqgY1cUP1i2vjI+BmlyXRfVz5AfV3jbpde8JTs5Q9sYgaoq5cWfuKfoZB/QkGOI+QcL1Tw== + +esbuild-linux-mips64le@0.14.48: + version "0.14.48" + resolved "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.48.tgz#822c1778495f7868e990d4da47ad7281df28fd15" + integrity sha512-cs0uOiRlPp6ymknDnjajCgvDMSsLw5mST2UXh+ZIrXTj2Ifyf2aAP3Iw4DiqgnyYLV2O/v/yWBJx+WfmKEpNLA== + +esbuild-linux-ppc64le@0.14.48: + version "0.14.48" + resolved "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.48.tgz#55de0a9ec4a48fedfe82a63e083164d001709447" + integrity sha512-+2F0vJMkuI0Wie/wcSPDCqXvSFEELH7Jubxb7mpWrA/4NpT+/byjxDz0gG6R1WJoeDefcrMfpBx4GFNN1JQorQ== + +esbuild-linux-riscv64@0.14.48: + version "0.14.48" + resolved "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.48.tgz#cd2b7381880b2f4b21a5a598fb673492120f18a5" + integrity sha512-BmaK/GfEE+5F2/QDrIXteFGKnVHGxlnK9MjdVKMTfvtmudjY3k2t8NtlY4qemKSizc+QwyombGWTBDc76rxePA== + +esbuild-linux-s390x@0.14.48: + version "0.14.48" + resolved "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.48.tgz#4b319eca2a5c64637fc7397ffbd9671719cdb6bf" + integrity sha512-tndw/0B9jiCL+KWKo0TSMaUm5UWBLsfCKVdbfMlb3d5LeV9WbijZ8Ordia8SAYv38VSJWOEt6eDCdOx8LqkC4g== + +esbuild-netbsd-64@0.14.48: + version "0.14.48" + resolved "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.48.tgz#c27cde8b5cb55dcc227943a18ab078fb98d0adbf" + integrity sha512-V9hgXfwf/T901Lr1wkOfoevtyNkrxmMcRHyticybBUHookznipMOHoF41Al68QBsqBxnITCEpjjd4yAos7z9Tw== + +esbuild-openbsd-64@0.14.48: + version "0.14.48" + resolved "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.48.tgz#af5ab2d1cb41f09064bba9465fc8bf1309150df1" + integrity sha512-+IHf4JcbnnBl4T52egorXMatil/za0awqzg2Vy6FBgPcBpisDWT2sVz/tNdrK9kAqj+GZG/jZdrOkj7wsrNTKA== + +esbuild-sunos-64@0.14.48: + version "0.14.48" + resolved "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.48.tgz#db3ae20526055cf6fd5c4582676233814603ac54" + integrity sha512-77m8bsr5wOpOWbGi9KSqDphcq6dFeJyun8TA+12JW/GAjyfTwVtOnN8DOt6DSPUfEV+ltVMNqtXUeTeMAxl5KA== + +esbuild-windows-32@0.14.48: + version "0.14.48" + resolved "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.48.tgz#021ffceb0a3f83078262870da88a912293c57475" + integrity sha512-EPgRuTPP8vK9maxpTGDe5lSoIBHGKO/AuxDncg5O3NkrPeLNdvvK8oywB0zGaAZXxYWfNNSHskvvDgmfVTguhg== + +esbuild-windows-64@0.14.48: + version "0.14.48" + resolved "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.48.tgz#a4d3407b580f9faac51f61eec095fa985fb3fee4" + integrity sha512-YmpXjdT1q0b8ictSdGwH3M8VCoqPpK1/UArze3X199w6u8hUx3V8BhAi1WjbsfDYRBanVVtduAhh2sirImtAvA== + +esbuild-windows-arm64@0.14.48: + version "0.14.48" + resolved "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.48.tgz#762c0562127d8b09bfb70a3c816460742dd82880" + integrity sha512-HHaOMCsCXp0rz5BT2crTka6MPWVno121NKApsGs/OIW5QC0ggC69YMGs1aJct9/9FSUF4A1xNE/cLvgB5svR4g== + +esbuild@^0.14.48: + version "0.14.48" + resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.14.48.tgz#da5d8d25cd2d940c45ea0cfecdca727f7aee2b85" + integrity sha512-w6N1Yn5MtqK2U1/WZTX9ZqUVb8IOLZkZ5AdHkT6x3cHDMVsYWC7WPdiLmx19w3i4Rwzy5LqsEMtVihG3e4rFzA== + optionalDependencies: + esbuild-android-64 "0.14.48" + esbuild-android-arm64 "0.14.48" + esbuild-darwin-64 "0.14.48" + esbuild-darwin-arm64 "0.14.48" + esbuild-freebsd-64 "0.14.48" + esbuild-freebsd-arm64 "0.14.48" + esbuild-linux-32 "0.14.48" + esbuild-linux-64 "0.14.48" + esbuild-linux-arm "0.14.48" + esbuild-linux-arm64 "0.14.48" + esbuild-linux-mips64le "0.14.48" + esbuild-linux-ppc64le "0.14.48" + esbuild-linux-riscv64 "0.14.48" + esbuild-linux-s390x "0.14.48" + esbuild-netbsd-64 "0.14.48" + esbuild-openbsd-64 "0.14.48" + esbuild-sunos-64 "0.14.48" + esbuild-windows-32 "0.14.48" + esbuild-windows-64 "0.14.48" + esbuild-windows-arm64 "0.14.48" + +idb-keyval@^6.2.0: + version "6.2.0" + resolved "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.0.tgz#3af94a3cc0689d6ee0bc9e045d2a3340ea897173" + integrity sha512-uw+MIyQn2jl3+hroD7hF8J7PUviBU7BPKWw4f/ISf32D4LoGu98yHjrzWWJDASu9QNrX10tCJqk9YY0ClWm8Ng== + dependencies: + safari-14-idb-fix "^3.0.0" + +safari-14-idb-fix@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/safari-14-idb-fix/-/safari-14-idb-fix-3.0.0.tgz#450fc049b996ec7f3fd9ca2f89d32e0761583440" + integrity sha512-eBNFLob4PMq8JA1dGyFn6G97q3/WzNtFK4RnzT1fnLq+9RyrGknzYiM/9B12MnKAxuj1IXr7UKYtTNtjyKMBog==