-
Notifications
You must be signed in to change notification settings - Fork 0
Emulators
Eight emulators are copied under emulators/ with bundled local ROMs/discs/cartridges. Once python3 -m http.server is running, they need no network — every byte is on the same origin.
| Folder | System | Source | Runtime | ROMs / discs |
|---|---|---|---|---|
emulators/jsbeeb/ |
BBC Micro | mattgodbolt/jsbeeb v1.12.0 | Vite-built dist/
|
10× .ssd in dist/discs/<publisher>/
|
emulators/archimedes-live/ |
Acorn Archimedes (RISC OS) | pdjstone/archimedes-live | Arculator WASM bundle (mirror of archi.medes.live) | 10× zipped discs in software/ + 13 ROM images under emu/roms/
|
emulators/jsnes/ |
NES | bfirsh/jsnes v2.1.0 |
dist/jsnes.min.js (~135 KB) |
10× .nes in roms/
|
emulators/genesis/ |
Sega Genesis | lrusso/Genesis (Emscripten port of PicoDrive) |
Genesis.min.js (~2.1 MB) |
10× .bin in roms/
|
emulators/javatari/ |
Atari 2600 | ppeccin/javatari.js v5.0.4 | javatari.js |
10× .a26 in roms/
|
emulators/jsdos/ |
MS-DOS | caiiiycuk/js-dos v7.0.0 |
js-dos.js + wdosbox.wasm
|
10 flat titles + DUKE submenu (3) + KEEN submenu (7) — 20 .jsdos bundles in bundles/
|
emulators/webmsx/ |
MSX / MSX2 / MSX2+ | ppeccin/WebMSX (mirror of webmsx.org) | Single 5 MB index.html (engine + UI + BIOSes inlined) |
10× MSX1 zips in games/msx1/ + 10× MSX2 zips in games/msx2/
|
emulators/jsvecx/ |
GCE Vectrex (Smith Engineering, 1982) | DrSnuggles/jsvecx (raz0red's JS port of Valavan Manohararajah's VecX) |
index.html + js/jsvecx.js (~31 MB total) |
~100 commercial+homebrew .bin organised by publisher/author folder under roms/
|
- The user picks a number in a
GAMESmenu. -
N.batruns the named launcher, e.g.sonic1. -
sonic1.batis alink:entry pointing at the emulator's URL. - The terminal opens that URL in a new tab via
window.open(link, "_blank").
URL patterns vary by emulator:
| Emulator | URL pattern |
|---|---|
jsnes, genesis, javatari, jsdos
|
../emulators/<name>/play.html?game=<key> — wrapper reads ?game=<key>, looks up {title, rom} in games.json, fetches roms/<key>.<ext> (or bundles/<key>.jsdos), starts the emulator |
jsbeeb |
../emulators/jsbeeb/dist/?disc1=<publisher>/<title>.ssd&autoboot — ?autoboot triggers *EXEC !BOOT; drop it to land at the BASIC > prompt |
archimedes-live |
../emulators/archimedes-live/#disc=<catalogue-id>&autoboot — uses URL hash (not query string); <catalogue-id> is looked up in the bundled software/software.json and per-title ff-ms is honoured automatically so games skip the RISC OS boot |
webmsx |
../emulators/webmsx/?ROM=games/<sub>/<name>.zip[&M=MSX1] — WebMSX auto-detects the cartridge mapper; MSX1 menu adds &M=MSX1 for authentic 8KB BIOS; SD Snatcher uses ?ANY=… because its zip contains 4 .DSK images, not a cart ROM |
jsvecx |
../emulators/jsvecx/?rom=<dir>/<title> — note: use index.html (the default /), never seamless.html. seamless is iframe-embed-only; standalone it loads the ROM but renders the canvas at zero size due to a sizer/init race |
Each self-hosted emulator's GAMES menu has a "boot a clean machine" entry as item N+1:
-
PROMPT for command-line systems — labels match what the user actually sees:
BBCboots to BBC BASIC>,MSX1/MSX2boots to MSX BASICOk. -
DESKTOP for GUI systems — only
archimedes-liveso far, boots a stock A3000 to the RISC OS desktop.
Convention going forward: PROMPT for > / Ok style command-line, DESKTOP for graphical OS shells.
Same recipe each time:
-
Copy the emulator's deployed bundle into
emulators/<name>/(mirror withwgetif it's hosted, or build from source if you must — archimedes-live and webmsx were both straightforwardwgetmirrors of the public deployment). Drop the upstreamnode_modules, the inner.git, and any.gitignorethat excludes deploy artefacts (e.g.*.ssd,/dist). -
Strip telemetry from the source's
index.htmlbefore deploying (jsbeeb and webmsx both shipped Google Analytics; we removed both). -
Add the standard
<noscript>overlay to every new HTML entry point — copy the block verbatim from any existing emulator page; it's identical across the project. -
Write
play.htmlif the emulator needs a key-based launcher (NES, Genesis, Atari 2600, jsdos pattern). Skip this if the emulator's own page already accepts URL params (BBC, Archimedes, WebMSX patterns). For wrappers:- reads
?game=<key>, - fetches
games.json, - looks up
{title, rom}, - boots the emulator with that ROM.
Use absolute paths via
location.pathname.replace(/[^/]*$/, "")for any framework that does its own path mangling (EmulatorJS's loader does — bit us once). - reads
-
Bundle the ROMs under
emulators/<name>/<roms-dir>/. archive.org and most preservation sites (e.g.file-hunter.comfor MSX,files-archi.medes.livefor Archimedes) don't set CORS for direct browser fetch, so wrappers can't pull at runtime — every byte must be on the same origin. -
Wire
fs.js: each game's.batgets alink:field pointing at the URL pattern above. Per-systemGAMESmenu follows the gold-standard 45-char CP437 box format with 10 titles + a PROMPT/DESKTOP entry.
Some platforms have multiple machines under the same vendor — e.g. ATARI has 800XL and ST as siblings, ACORN has BBC and ARCHIMDS, MSX has MSX1 and MSX2. The hierarchical pattern: a parent menu with 1.<MODEL1> / 2.<MODEL2> rows, each pointing into its own GAMES dir. See prompt/javascript/fs.js for the exact tree shape. SMITH-ENG currently has only one entry (VECTREX) but keeps the parent-menu shape against future expansion.
Same hierarchical idea applied to a single emulator: a single menu entry can promote into a submenu of related titles. DOS uses this for DUKE (Duke Nukem 1/2/3D) and KEEN (Commander Keen 1-6 + Keen Dreams) — each is a [GAMES] entry on the parent DOS menu that routes into its own subdir. The launcher recipe is unchanged from the flat case (each game still resolves to ?game=<key> in the same bundles/ directory); only the navigation layer adds a level.
jsbeeb's empty-schema disc URLs don't unzip.
?disc1=path/file.zip falls through to the default: branch in loadDiscImage() (main.js), which calls fdc.load("discs/" + path) and hands raw bytes to discFor(). Only the sth: / http: / https: schemas unzip. Workaround: extract the inner .ssd from each upstream .zip and serve that raw — URL becomes ?disc1=<path>.ssd&autoboot.
Copied .gitignore files silently exclude deploy artefacts.
emulators/jsbeeb/.gitignore had /dist (upstream's build-artefact rule); dist/discs/.gitignore had *.ssd (anti-piracy guard). Both meant the deployed jsbeeb on GitHub was actually broken for several commits — only worked locally where the files existed on disk. When copying an upstream tree, audit every .gitignore it ships.
Cross-origin ROM fetch is consistently blocked.
Tested for archi.medes.live's files-archi.medes.live (no CORS), file-hunter's file-hunter.com/MSX/ (no CORS), archive.org direct downloads (varies — some allow, some 401). Always mirror locally; don't rely on runtime CORS fetches. WebMSX's local-domain fallback (SOFTWARE_BASE_DEV='software/', ROM_BASE_DEV='emu/roms/') and Arculator's equivalent are both lifesavers — neither needed a source patch when self-hosted off-domain.
EmulatorJS was tried first and dropped.
Its data/loader.js prepends "../" to relative EJS_pathtodata, which conflicts with how dynamic-import vs. document-relative paths resolve, blank-screening silently. JSNES's pure-JS API was a half-hour drop-in instead.
archive.org collection access varies.
nintendo-entertainment-system-nes-roms-europeusa and sega-genesis-romset-ultra-usa allow direct file download via https://archive.org/download/<item>/<path>. Some other items return 401/403 (auth-gated). Probe with curl -I before scripting bulk downloads.
WebMSX bundles everything inline.
The deployed index.html is ~5 MB with the engine, UI, and commercial MSX/MSX2/MSX2+ BIOSes all baked in as JS. Mirror is just that one file plus the manifest, cache.manifest, and two icons — no separate .wasm to chase. Strip the inline Google Analytics block (find the (function(i,s,o,g,r,a,m){…}) snippet and replace with a /* GA stripped */ comment).
MSX2 disk titles need ?ANY= not ?ROM=.
SD Snatcher's zip contains 4 .DSK images (3 game disks + USERDISK), not a cart ROM. WebMSX's ?ROM= parameter assumes cartridges; ?ANY= (alias for AUTODETECT_URL) inspects the contents and routes multi-disk zips to the Drive Stack so the user can swap mid-game.
JSVecX seamless.html only works inside an iframe.
The seamless launcher does load ROMs correctly (HTTP fetch succeeds, switchRom runs, loadRom("roms/<path>.bin") is called) but renders the canvas at zero size in a standalone tab — the sizer races with init and ends up shrinking the canvas to nothing. Always link index.html?rom=… (the bundle's full UI) instead. Validated headlessly: index.html with ?rom=Commercial/Mine Storm_1982 loads canvas at 324×403 with the Mine Storm overlay PNG; same URL on seamless.html shows blank.
Microvision attempted and dropped. The bundled JS Microvision emulators all only emulate ONE of the two CPUs the system shipped with: Berrry-app does Intel 8021, Paul Robson's MVEM does TMS1100. Picking one strands the other half of the library. Berrry plus Robson's homebrew was tried first — the bundled ROMs are TMS1100, the bundled emulator only does 8021, so the CPU "ran" garbage opcodes and nothing reached the LCD. Until a JS emulator covers both CPUs (or until a TMS1100 JS port is built and Robson's ROMs are paired with it), Microvision sits out. SMITH-ENG keeps a single-system parent menu shape (one VECTREX row) for if/when this changes.
python3 -m http.server 8765After editing fs.js or any emulator wrapper, hard-refresh the prompt page (Ctrl+Shift+R). Browsers aggressively cache fs.js between visits.
For BBC Micro there's a headless test harness from upstream at emulators/jsbeeb/.claude/skills/emulator/SKILL.md. It needs the upstream dev tree (src/, plus npm install sharp) which we strip in the deploy build, so re-clone fresh if you want to drive the BBC headlessly.