Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatically remove header + build step for Javascript #90

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
*.log
**/build/
**/include/sym/
web/node_modules
1 change: 1 addition & 0 deletions web/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions web/js/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const VANILLA_ROM_SIZE = 0x300000
export const HEADERED_ROM_SIZE = 3146240
export const VANILLA_CRC32 = 'd63ed5f8'
12 changes: 12 additions & 0 deletions web/js/customizer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import vanillaRom from './rom'

async function main() {
new vanillaRom()
}

window.addEventListener('load', () => {
main()
.catch((err) => {
console.error(err)
})
})
93 changes: 93 additions & 0 deletions web/js/helpers/crc32.ts
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions web/js/helpers/hasFileReader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const hasFileReader = () => (
window.File && window.FileList && window.FileReader
)

export default hasFileReader
23 changes: 23 additions & 0 deletions web/js/helpers/settings.ts
Original file line number Diff line number Diff line change
@@ -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
181 changes: 181 additions & 0 deletions web/js/rom.ts
Original file line number Diff line number Diff line change
@@ -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 = (<HTMLInputElement>evt.target).files?.[0]
if (file) {
useFile(file)
}
})
}

checkForStoredFile(): Promise<boolean> {
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
15 changes: 15 additions & 0 deletions web/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
Loading