Underground 2 Revived
Play Need for Speed: Underground 2 (2004) in Chrome and Firefox with WebGPU graphics, Web Audio, up to 120 fps widescreen, controller support, free roam and Online races with friends. Fan project, not
翻譯準備中
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
Draft prepared on 2026-10-05 from a read-only review of the public repo (main, last commit 2026-09-13), the live site, the project's troubleshooting notes (dosvault-jsdos-troubleshooting.md) and previous working sessions. Nothing in the repo was changed while writing it. Items marked (to confirm) could not be verified from the public sources.檢視完整 README ↗Draft prepared on 2026-10-05 from a read-only review of the public repo (
main, last commit 2026-09-13), the live site, the project's troubleshooting notes (dosvault-jsdos-troubleshooting.md) and previous working sessions. Nothing in the repo was changed while writing it. Items marked (to confirm) could not be verified from the public sources.
DOSVault is a free, browser-based catalog of classic DOS and abandonware games. Visitors pick a game and play it directly in the browser, with no installation, through one of two WebAssembly emulation engines: js-dos (a full DOSBox PC emulated in the browser) or ScummVM (native re-implementation of classic adventure game engines).
The interface imitates a Norton Commander-style DOS file manager: two blue panels (genres on the left, games on the right), a command line, and an F-key bar at the bottom. Games open in draggable, DOS-styled windows inside the page.
| Item | Location |
|---|---|
| Live site | https://dosvault.netlify.app/ |
| Repository | https://github.com/PolZirilli/dosvault (public) |
| Local working copy | /Users/pacata/Documents/Proyectos/dosvault |
| Game bundles | Cloudflare R2, public URL https://pub-13140bd15eda49b4a3f35bc937ab1c58.r2.dev/projects/dosvault/ |
| Owner | Pol Zirilli |
In scope
.jsdos bundles).Out of scope (current decisions)
DOSVault is a static site with no build step: plain HTML, CSS and vanilla JavaScript, served by Netlify straight from the repository. All dynamic behavior happens in the visitor's browser.
Visitor's browser
├─ index.html + css/ + js/ ← served by Netlify (from GitHub repo)
├─ data/games.json, data/genres/* ← catalog, same origin, lazy-loaded per genre
├─ Game bundles (.jsdos / ScummVM) ← Cloudflare R2 (fetched on launch; HEAD for size/date)
├─ js-dos v7 (vendored) ← emulates the DOS PC
├─ ScummVM WASM (vendored, iframe) ← adventure game engines
└─ Third-party lookups (client-side, no keys):
• GitHub API → date of last commit to data/games.json
• Netlify Forms → Help/Contact form submissions
dosvault/
├─ index.html Single page: panels, F-key bar, all modals, script loading
├─ favicon.png
├─ css/
│ ├─ style.css Full site styling (DOS/Norton Commander look)
│ └─ fonts/ PxPlus IBM VGA8 (self-hosted, CC BY-SA 4.0) + license
├─ js/
│ ├─ app.js Main application: catalog, navigation, windows, modals,
│ │ key remapping, gamepad, test-bundle tool (~1,700 lines)
│ ├─ i18n.js ES/EN dictionaries, genre labels, language detection
│ ├─ scummvm-engine.js Wrapper that runs ScummVM inside an iframe
│ └─ vendor/
│ ├─ js-dos/ js-dos 7.1.0 (js-dos.js, wdosbox.js/.wasm, css)
│ └─ scummvm/ ScummVM WASM build + launcher.html + fflate + engine plugins
├─ data/
│ ├─ games.json Genre index (name, count, URL of each genre file)
│ └─ genres/*.json One file per genre with its game entries
└─ .github/workflows/
└─ build-scummvm.yml Manual workflow that compiles ScummVM to WASM
data/games.json — genre index{
"genres": {
"race": { "name": "Carreras", "count": 17, "url": "data/genres/race.json" }
}
}
name: fallback label (the visible label normally comes from I18N_GENRES in js/i18n.js).count: maintained by hand. It is shown in the Files column only until the genre files finish loading; from then on the column shows the number of visible games of the genre (games whose bundle returned 404/410 on R2 are not counted). Genres with 0 games are hidden.url: genre file, loaded only when the visitor opens that genre (lazy loading with in-memory cache).Current genre ids: fps, rts, platformer, avg, rpg, sim, arcade, action, race, sports.
data/genres/<id>.json — game entries{
"id": "race",
"name": "Carreras",
"games": [
{
"id": "deathrally",
"name": "DEATHRALLY",
"genre": "race",
"added": "2026-08-11",
"year": "1996",
"cover": "https://thumbnails.libretro.com/DOS/Named_Boxarts/Death%20Rally.png",
"bundle": "https://pub-…r2.dev/projects/dosvault/deathrally.jsdos"
}
]
}
| Field | Required | Purpose |
|---|---|---|
id | yes | Unique key; also used for window ids and cache keys |
name | yes | Short DOS-style name shown in the list and window title |
title | no | Full display title; preferred over name for sorting and tooltip |
genre | yes | Must match the genre id |
added | yes | YYYY-MM-DD; drives the "New games" popup |
year | yes | Release year, shown in the new-games popup |
cover | yes | Box-art image URL (libretro thumbnails). Not displayed anywhere at the moment (it was only used by the removed Info popup) |
bundle | yes | Absolute URL of the bundle on R2 |
engine | no | "scummvm" to use ScummVM; omitted = js-dos |
lang | no | "es"/"en", shown in the Language column; defaults to EN |
Size and date shown in the right panel are not stored in JSON: they are read live from the bundle with an HTTP HEAD request (Content-Length / Last-Modified), which requires the R2 bucket's CORS policy to allow the site's origin.
The same HEAD decides whether a game is listed. Rule: a game whose bundle is not on R2 must never be visible, not even for an instant. On load, every bundle in the catalog is checked in the background (at most 6 requests at a time, 15 s timeout each; the games of the open genre jump the queue and skip the 6-request limit). A game with a bundle is shown only once its HEAD has finished:
Games without a bundle field are always visible, as before. While the open genre still has unchecked games, the right panel shows "Checking games…" ("Verificando juegos…") instead of a partial list, and Enter/Run do nothing until it fills. The Files count of a genre uses the count from games.json until all its games are checked, then the number of visible games; a genre with 0 visible games is hidden. data/ is never modified: the filter is applied at runtime.
Catalog snapshot at the time of this review: 99 games, 85 on js-dos and 14 on ScummVM.
js/vendor/js-dos/, with emulators.pathPrefix pointing there.Dos(container, {}).run(bundleUrl). No custom extraction logic of our own..jsdos = a regular zip containing the game files plus .jsdos/dosbox.conf (with the [autoexec] that mounts C: and starts the game).[⛶] button requests real browser fullscreen. Trade-off: in real fullscreen, Esc exits fullscreen instead of reaching the game (browser standard, cannot be overridden)..github/workflows/build-scummvm.yml (manual workflow_dispatch, compiles scummvm/scummvm with Emscripten, publishes scummvm-wasm.zip as a GitHub Release; artifacts are then copied by hand into js/vendor/scummvm/).scumm, scumm_7_8, sci, sci32, agi, agos, agos2, sky, queen, drascula, lure, gob, tucker, touche, cine, cruise, kyra, parallaction.js/vendor/scummvm/launcher.html?bundle=<url>) so that relative asset paths resolve correctly and each game gets an isolated JS context.saves, extras, docs, dosbox, etc.), flattens it into /game, pre-loads all engine plugins (data/plugins/*.so), and starts ScummVM with --path=/game auto-detection.<id>.scummvm (the extension is organizational only; fflate ignores it). Existing catalog entries still use .zip (see section 10).[≡] titlebar button simulates Ctrl+F5 to open ScummVM's Global Main Menu (save/load/options). A one-time "how to play with ScummVM" hint appears before the first ScummVM launch, with a "don't show again" option.| Feature | How it works |
|---|---|
| Two-panel navigation | Genres on the left, games on the right; ↑/↓ move, ← goes to the left panel, → to the right panel, Tab switches, Enter opens/runs. The selected row is kept scrolled into view, and switching ES/EN keeps the selected game. While any popup is open (Controls, Help, New games, F9 Test bundle, ScummVM hint) navigation is blocked and Esc closes it (on the ScummVM hint, Esc works like its close button and the game starts). Keyboard goes entirely to the game while a game window has focus; clicking outside returns it to the shell. |
| F-key bar | F1 Controls · F3 Run · F4 Refresh · F5 Help · F9 Test bundle · F10 Close. |
| Game windows | Draggable, maximizable, multiple at once; z-ordering and focus handling in app.js. |
| Key remapping | "Controls" popup: Navigation tab (wired), In-game tab (action1/action2 reserved, not wired yet), Gamepad tab. Uses KeyboardEvent.code. |
| Gamepad support | Browser Gamepad API (tested with Xbox Series X, "standard" mapping). Buttons are mapped to keyboard keys and sent to the game as synthetic key events. Remappable. |
| New games popup | Shown once per load, after all R2 checks finish. Lists only games added (available now, not available on this browser's previous visit). A game is listed only when R2 confirmed its bundle (or it has no bundle field); games that could not be checked (network/CORS) are reported once R2 confirms them, and a game whose bundle is 404/410 is never listed. Removed games are not shown: a game whose bundle is not on R2 must not appear anywhere on the site. First visit: no popup. Visitors with only the old dosvaultLastVisit key get the old added-date criterion once, with the same R2 filter. |
| Help / Contact | Netlify Forms (name="contacto", honeypot field): feedback, game requests, bug reports. |
| Test a local bundle (F9) | Loads a local .jsdos or ScummVM zip through a blob: URL; the file never leaves the browser. Used to test a bundle before uploading it to R2. |
| i18n | Spanish for any es-* browser language, English for everything else; manual ES/EN switch saved per browser. |
| Mobile block | Full-screen notice on phones/tablets. |
| Key | Content |
|---|---|
dosvaultControls | Keyboard remapping |
dosvaultGamepadControls | Gamepad remapping |
dosvaultLang | Chosen UI language |
dosvaultLastVisit | Date of last visit (for "New games") |
dosvaultAvailableGames | Ids of the games available on the last visit: { v: 2, ids: [<id>, ...] } (for "New games"). The older { v: 1, games: { <id>: {...} } } format is still read (only its keys are used) |
dosvaultScummvmHintDismissed | ScummVM hint dismissed |
dosvault-bundle-creator (build_jsdos.py / build_scummvm.py), ordosvault-builder (js/core.js + js/app.js, kept in a separate repo/zip, with feature parity with the skill).projects/dosvault/.data/genres/<genre>.json and update count in data/games.json.main; Netlify redeploys automatically (to confirm).| Rule | Reason |
|---|---|
| Max 300 MB per bundle; builders abort with exit code 6 | Cloudflare hosting limit |
cycles=20000 (fixed), never cycles=auto | auto hangs heavier games (especially DOS4GW) in the browser |
keyboardlayout=us | Consistent key mapping |
| Explicit zip directory entries for every folder level | js-dos v7 freezes on "Extracting" with 2+ nested levels lacking them |
.jsdos for js-dos, .scummvm for ScummVM | Naming convention for the bucket |
| Test on a throwaway copy | Games write config files on first run that would otherwise end up in the bundle |
Drop installers, native Windows .exes and pack extras | Smaller bundles; avoids extraction freezes |
Large bundles (roughly 40–90 MB and above) can freeze during extraction in js-dos v7 even under 300 MB. This is a known upstream issue with no fix on our side; the only mitigation is reducing content (with the owner's approval).
| Item | Purpose |
|---|---|
dosvault-bundle-creator (Claude skill) | Builds .jsdos and .scummvm bundles from game archives |
dosvault-builder (web tool) | Same build engine in the browser (JSZip) |
dosvault-jsdos-troubleshooting.md | Case log of every bundle problem diagnosed and how it was fixed |
build-scummvm.yml | Rebuilds the ScummVM WASM engine when needed |
Skill updates only take effect once the .skill file delivered in a session is saved by the owner; if a skill still shows old behavior (e.g. cycles=auto), it was not saved.
These were noticed while reading the code. None has been modified.
count in games.json totals 69, but the genre files contain 99 games (e.g. sports says 4, has 13; race says 17, has 24). The Files column therefore shows wrong numbers.CONTROL_ACTIONS are F1=Help, F2=Controls, F4=Run, F5=Refresh (F3 unbound). Pressing F1 on the keyboard likely opens a different popup than the bar label says (to confirm in the browser)..zip for its 14 ScummVM bundles and the F9 picker filters to .zip, while the skill and builder now output .scummvm. New .scummvm files will be hidden by default in the F9 file picker.action, but I18N_GENRES uses the key accion, and sports has no entry at all. Both fall back to the Spanish name from games.json, so they won't be translated in English mode.lang field in any entry, so every game shows EN in the Language column, including Spanish releases.dosvault-files-lang-columns.patch (root), and 1-byte placeholder files css/fonts/txt and js/vendor/scummvm/this.r2.dev address. Cloudflare positions these as development endpoints with rate limiting; a custom domain is the usual production setup (worth evaluating).main? Any environment variables, redirects or headers set in the Netlify UI?dosvault-builder repo lives and whether it should be documented here.