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
HTML + vanilla-JS browser port of the original MS-DOS Duke Nukem (1991) — decodes the real game data files at runtime, no emulator.
GamesJavaScript · CSSGitHub @VorticonCmdrOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Loading the full README. The saved excerpt is shown below.
Duke Nukem (1991) — a browser port
An HTML + vanilla-JS port of the original MS-DOS Duke Nukem (1991, Apogee/id Software — not
Duke Nukem II or Duke Nukem 3D). It decodes the real game's original data files at runtime
and reimplements the engine in JavaScript: no emulator, no transcoded assets, just the same
worldalX.dnY levels, sprites, sounds and menus the DOS game shipped, read directly.
Play it in your browser → (needs your own copy of
the game data — see What you need below). The same deployment also hosts the
Asset Viewer.Read the full README ↗An HTML + vanilla-JS port of the original MS-DOS Duke Nukem (1991, Apogee/id Software — not
Duke Nukem II or Duke Nukem 3D). It decodes the real game's original data files at runtime
and reimplements the engine in JavaScript: no emulator, no transcoded assets, just the same
worldalX.dnY levels, sprites, sounds and menus the DOS game shipped, read directly.
Play it in your browser → (needs your own copy of the game data — see What you need below). The same deployment also hosts the Asset Viewer.
This README covers using and playing it. For how the port was built and verified, see
PLAN.md; for the reverse-engineered file formats, see FORMATS.md.
This port would not have been possible without the reverse-engineering work of others, used as
the starting hypothesis for every decoder here (then verified against the real files — see
CLAUDE.md and FORMATS.md):
.exe files ship with.And, of course, Apogee Software and Todd Replogle for the original game.
This repository does not include the game data, and never will — Duke Nukem is copyrighted software. You need your own legally obtained copy of the original DOS game: Episode 1 ("Shrapnel City") was released as freely distributable shareware, so it's the easiest to obtain and try first; Episodes 2 and 3 ("Mission: Moonbase" and "Trapped in the Future!") were the commercial registered version.
The port reads the game's data files only — worldalX.dnY levels, .dn1/.dn2/.dn3
graphics/sound/tileset files, keys.dnY, and so on. It never reads or runs the .exe files, so
you don't need DOS or an emulator, just the data.
npm install
npm run dev
Then open the URL Vite prints (typically http://localhost:5173). This starts a local dev
server; nothing is uploaded anywhere, and none of your game files ever leave your machine.
Two pages are available:
index.html (the root URL) — the actual game.viewer.html ("Asset Viewer →" link in the game's header) — a browsable inspector for
every file the game ships, plus a level editor. See Asset Viewer below. Not
needed to play.The page needs to find your copy of the data files. Two ways, depending on how you're running it:
npm run dev server above): drop your game files straight into a
game/ folder at the project root (create it if it doesn't exist). The dev server serves it
directly and the page picks it up automatically — no folder picker needed. game/ is
git-ignored, so this never risks committing copyrighted files.npm run build, or someone else's hosted instance): the production
build ships no game data at all, so click "Open game folder…" in the header and select the
folder containing your files. This uses the browser's native folder picker (Chrome/Edge, and
any other browser with File System Access API support — the button stays hidden on browsers
that lack it, e.g. Firefox and Safari as of this writing). Your files are read locally by the
browser; they are never uploaded.Either way, point it at one folder containing all the files for whichever episode(s) you own
— the same flat layout the original DOS install used (e.g. worldal1.dn1, duke1.dn1,
anim0.dn1, keys.dn1, ... alongside worldal1.dn2, keys.dn2, ... if you also own Episode 2).
The Episode dropdown in the header switches which episode's files it reads from that same
folder — you don't need a separate folder per episode.
Movement defaults to the arrow keys; Fire and Jump come from whatever your copy's own
keys.dnY file specifies (on the shipped shareware copy, that's Z to fire and X to
jump). You can rebind any of the six actions in-game: F1 → G)ame Setup → K)eyboard mode, pick
a slot (1–6), then press the new key. A gamepad also works out of the box — same menu, J for
joystick mode, which reports connection status and lets you swap which button fires vs. jumps.
The game boots through the same sequence as the original: the Apogee splash screen, the DUKE title screen (any key advances), then the DUKE MAIN MENU:
| Key | Does |
|---|---|
| S | Start a new game (episode 1 shows the Dr. Proton taunt intro first) |
| R | Restore a saved game |
| I | Instructions |
| O | Ordering information |
| G | Game setup — sound, hints, keyboard rebind, joystick |
| H | High scores |
| P | Previews / Main Demo — watch the attract-mode demo play itself |
| V | View your own recorded demo (unlocks after you finish a level once) |
| T | Back to the title screen |
| C | Credits |
| Q | Quit |
| Key | Does |
|---|---|
| F1 | Help menu — S)ave, I)nstructions, G)ame Setup, H)igh scores, F10) Restart |
| P | Pause |
| S | Toggle sound |
| H | Toggle hint mode (re-arms or dismisses the one-shot "Secret Tip" hint boxes) |
| F10 | Restart the current level from your last checkpoint (with a Y/N confirm) |
| Esc / Q | Quit to the main menu (with a confirm) |
, / . (< / >) | Decrease / increase game speed — hold to sweep the full range |
Saving is only offered at a hallway checkpoint between levels (F1 → S — matching the original, which only allowed saving there too), and the game auto-saves a checkpoint at every level transition regardless. Save data, key bindings, joystick swap, and high scores all live in your browser's local storage, keyed per episode — they persist across reloads on the same browser and device, but don't sync anywhere and don't touch your actual game files.
Beat one of the ten scores on the in-game High Scores table and you'll be prompted to enter your name, exactly like the original DOS game's name-entry screen.
The original game shipped with hidden developer cheats. Two of them needed a secret argument
typed after duke1/duke2/duke3 at the DOS prompt; this port's equivalent of that argument is
a ?asp query string on the URL — e.g. http://localhost:5173/?asp.
Always available, during play, no ?asp needed:
| Combo | Does |
|---|---|
| Backspace + Page Down (held together) | Full firepower and all keys |
| Backspace + G (held together) | Freezes the game — a leftover developer debug pause |
Needs ?asp on the URL:
| Combo | Does |
|---|---|
| G + O + D (held together, during play) | All items, full health, all keys, full firepower |
| G + O + W (held together, during play) | Instantly finish the current level |
| F6 while holding Fire, at the title screen | Start recording a new attract-mode demo tape |
The F6 gesture is the same tool Apogee used to record the demo the game ships with — it starts a
fresh game on the level the shipped demo plays out on. While recording, a "⏺ Save" button
appears in the header; click it any time to download the tape as a real
MY_DEMO.DN?/USERDEMO.DN? file, in the exact format the original game reads.
viewer.html browses every file format the original game uses, decoded and rendered directly —
not a screenshot gallery, the actual bytes. Pick an episode, then pick an asset from the dropdown
(grouped by category: Engine, Actors, Level Editor, Tilesets, Fonts, Backdrops, Full-screen art,
Levels, Sound banks, Saves & config). Each asset is also deep-linkable — viewer.html#worldal1.dn1
opens that entry directly, so you can link straight to a specific level or sprite sheet.
It uses the same two ways of finding your game files as the game page (the local game/ folder
in dev, or "Open game folder…" in a built copy) — plus one more, only here: drag and drop
your game files (or the whole folder) straight onto the page. That works in every browser,
including ones without the File System Access API, but read-only — dropped files can't be saved
back to, only downloaded as copies (see the level editor below).
What's in it, briefly:
Select Level Editor for an episode to open a real level editor for the worldalX.dnY format
— paint the same map codes the game reads, then save out a file the game (real DOS or this port)
can load. The byte round-trip is checked by this project's own test suite against every shipped
level, so what you export is a genuine level file, not an approximation.
.dnY file from your computer.Playing works in any reasonably modern browser. The one feature that's Chromium-only (File System
Access API) is the folder picker for a deployed/built copy — everything else, including the
local-dev game/ folder approach above, works everywhere.
This is a from-scratch reimplementation, verified level by level against the original executable
(disassembled) and against real captured frames from the DOS game running under emulation — not
a guess at "close enough." It isn't a byte-for-byte guarantee against every possible edge case,
but every format decoder and engine behaviour here was checked against the original, not assumed.
See PLAN.md if you want the receipts.