MetroidNESRecomp
Metroid
OriginNintendo Entertainment System
TechnologyStatic recompilation (native C/C++)
Available forWindows, Linux, macOS
View official projectAbout the project
Metroid es un videojuego del género acción y aventura y la primera entrega de la serie de videojuegos de Metroid. Desarrollado en cooperación por Nintendo Research & Development 1 e Intelligent Systems, y distribuido internacionalmente por Nintendo, el juego fue lanzado inicialmente en 1986 para la Family Computer Disk System en Japón y para la Nintendo Entertainment System en Norteamérica y Europa en 1987.
Official project information
MetroidNESRecomp
This recompilation is a byproduct of developing nesrecomp — the games are the proving ground, the framework is the goal. These are in-development previews, not finished ports — expect rough edges, and depth will keep landing over months, not days. My time for any one title is limited, so I ask for your patience. Contributions are welcome — testing, issues, and PRs to the game or framework all help and will accelerate this game's polish. More on the why at: Recomp + AI: 5 Months Later »
A static recompilation of Metroid (NES) using nesrecomp. The original 6502 machine code is translated to C at build time, then compiled to native x64 for direct execution on modern PCs.
This is NOT an emulator. The game logic runs as native compiled code. NES hardware (PPU, APU, mapper) is simulated by the runner library.
You must supply your own Metroid ROM. No copyrighted game data is included in this repository.
Windows builds record startup diagnostics automatically. After a failed launch,
send the diagnostics folder beside the executable. See
collecting startup diagnostics, including the helper for
Windows loader failures before the launcher appears.
Which ROM
The default cycle backend has separate USA/NTSC and Europe/PAL builds:
| Variant | CRC32 (headerless PRG+CHR) | CRC32 (whole .nes) | m1disasm target |
|---|---|---|---|
| USA / NTSC | 70080810 | A2C89CB9 | NES_NTSC |
| Europe / PAL | 7751588D | A591CC24 | NES_PAL |
Choose the variant at build time. Each executable accepts only its matching ROM. The builds use separate generated cycle sources and content keys; European packages are staged with the European CRC. The legacy backend retains its USA-only generated sources and hints.
Status
Experimental adaptive widescreen support (USA and Europe) is available through the bundled Metroid Widescreen mod. Enable it in the launcher's mod controls and choose Fit window, 16:9, 21:9, or 32:9. The custom renderer shows adjacent room terrain and follows live window resizing in Fit mode.
Optional mod choices add Viewport enemies and item previews, Expanded sprite capacity, and Reduced slowdown. Viewport enemies retain their original distance-based attack triggers. Widescreen is off by default, and each additional enhancement defaults to Original. These features are still experimental; enemy, projectile, and room-transition coverage remains incomplete.

For the command-line options, tested behavior and remaining limitations, see WIDESCREEN.md.
The cycle migration preserves password saves, widescreen, viewport actors, expanded sprites, optional slowdown reduction and first-person voxel rendering. Stock gameplay and the tested enhancements agree between native cycle code and the cycle interpreter in both regions. See CYCLE-MIGRATION.md for build commands, validation and progress migration.
These are still development previews. The short regression route does not establish compatibility across every area, boss or ending. The function-level backend remains available for explicit legacy builds.
Experimental Voxel 3D (first person)
Open Mods in the launcher and enable Voxel 3D (first person). The bundled feature is disabled by default and targets the selected USA or European ROM. It does not patch the ROM or alter password data.
The camera follows Samus's live screen position; Right and Left directly own
its horizontal heading, and holding Up smoothly raises the view toward the
near-vertical shot path. The lens opens during that upward aim so overhead
enemies and Samus's pellets remain visible. The room plane is reconstructed
upright so horizontal movement remains forward/back instead of looking
sideways. Only Samus's reserved OAM card is hidden; projectiles, enemies,
doors, pickups, architecture, and shadows remain visible. The full room is
voxelized behind a HUD-only overlay. The fixed Energy OAM band is excluded
from world geometry and only the EN-## glyph footprint is restored in
screen space, so no flat top strip or room pixels leak into the HUD.
Numpad 8/2 adjusts look pitch, 4/6 offsets yaw from Samus's facing direction, 7/9 rolls the camera, numpad or regular +/- adjusts field of view, and 1/3 changes sprite scale. Numpad 0 toggles the live view and Numpad 5 restores package defaults.
The earlier room-diorama package remains archived under mods/hidden for
development reference but is intentionally not shown in release launchers.
Building
Prerequisites
- Windows 10/11
- Visual Studio 2022 Build Tools (or full VS 2022)
- CMake 3.20+
- A matching USA or European Metroid ROM; see Which ROM.
Steps
git clone https://github.com/mstan/MetroidNESRecomp.git
cd MetroidNESRecomp
# Windows
setup.bat
# Linux / macOS
chmod +x setup.sh && ./setup.sh
This initializes the pinned engine and UI submodules. Cycle code generation runs as part of the CMake build from your supplied ROM:
cmake -S . -B build-cycle-usa -G "Visual Studio 17 2022" -A x64 -DNESRECOMP_ROM=F:/ROMs/metroid.nes -DMETROID_VARIANT=usa
cmake --build build-cycle-usa --config Release
For Europe, use a separate build directory, -DMETROID_VARIANT=europe and
the European ROM path. See CYCLE-MIGRATION.md for details.
An explicit -DNESRECOMP_BACKEND=legacy build uses the checked-in USA
function-level sources. The remaining symbol/hint instructions below describe
that legacy generator workflow.
To create separate local Windows archives for both regions, run
powershell -File tools/make_release.ps1 -UsaRom F:/ROMs/metroid.nes -EuropeRom F:/ROMs/metroid-eu.nes.
Existing regional build caches retain their ROM paths. Use -SkipBuild to
package already-built cycle executables, or -Variant usa|europe to select
one region. Archives contain the selected region's mod packages and no ROM,
settings or player saves. This script does not publish releases.
Symbols and hints come from the disassembly
disasm/m1disasm is a submodule of metroidret/m1disasm,
an annotated WLA-DX disassembly that assembles byte-exact copies of both ROM
revisions from one source tree (out/M1_NES_NTSC.nes, out/M1_NES_PAL.nes) and,
via wlalink -S, a .sym file of labelled addresses for each. Two tools consume it:
tools/gen_symbols.py— turns one target's.symintosymbols.sym(the bank-aware symbol overlaygame.tomlpoints at, so generated functions carry their real names) andmetroid_ram.h(MET_<Name>defines for every RAM/MMIO label, soextras.cand the widescreen renderer never spell an address out). Defaults toNES_NTSC; it refuses to run if the ROM body does not match the target it was asked for.tools/extract_metroid_data_regions.py— walksdisasm/m1disasm/SRCand emits the[[data_region]]blocks that keep the recompiler from mistaking tables for code.--targetselects which.if BUILDTARGETbranch applies.
Retargeting legacy hints to another ROM revision
game.toml's hints are raw addresses, so they are revision-specific.
tools/migrate_hints.py moves them by label name rather than by hand: it
reads the old and new .sym, rewrites [functions], [[data_region]],
[[inline_dispatch]], [[stack_bail_func]], [[cond_bail_func]],
[[merge_range]] and [[mod_function_hook]], and drops anything it cannot
resolve (reporting each one). An address with no label of its own is recovered
only when the move can be proven: the span between the two surrounding matched
labels must be the same length in both builds, and — given --old-rom/--new-rom —
must also hold the same bytes, the same relocated operands, or the same 6502
opcode stream. To retarget this repo at the European ROM:
python tools/gen_symbols.py --target NES_PAL --rom metroid-eu.nes
python tools/extract_metroid_data_regions.py --target NES_PAL # replaces [[data_region]]
python tools/migrate_hints.py --toml game.toml \
--old-sym disasm/m1disasm/out/M1_NES_NTSC.sym \
--new-sym disasm/m1disasm/out/M1_NES_PAL.sym \
--old-rom metroid.nes --new-rom metroid-eu.nes \
--skip data_region --out game.toml --report migration_report.txt
then update the CRC32 in extras.c, CMakeLists.txt and the mod manifests, and
regenerate. The tool is game-agnostic — any nesrecomp title with per-revision
.sym files can use it.
Project Structure
| File | Purpose |
|---|---|
game.toml | Recompiler configuration (dispatch hints, data regions, extra functions) — NTSC/USA addresses |
extras.c | Game-specific runner hooks (frame callbacks, password save system, ROM CRC gate) |
symbols.sym | Bank-aware symbol overlay, generated from the disassembly (do not edit) |
metroid_ram.h | MET_<Name> RAM/MMIO defines, generated from the disassembly (do not edit) |
metroid_ws*.c/h | Opt-in widescreen mod: gating, function hooks, compositor, room decoder |
mods/ | Declarative mod packages + their trusted plugin registration |
recomp_stack.h/c | Recompiled function call stack tracking |
watchdog.h/c | Frame timeout detection |
generated/metroid_full*.c | Auto-generated recompiled game code (do not edit) |
generated/metroid_dispatch.c | Auto-generated dispatch table (do not edit) |
disasm/m1disasm/ | Annotated WLA-DX disassembly (git submodule) — source of symbols and data regions |
tools/gen_symbols.py | .sym → symbols.sym + metroid_ram.h |
tools/extract_metroid_data_regions.py | disassembly → [[data_region]] blocks |
tools/migrate_hints.py | move game.toml addresses between ROM revisions by label name |
nesrecomp/ | Recompiler + runner framework (git submodule) |
How It Works
The nesrecomp recompiler statically analyzes the ROM, discovers functions via BFS from the NMI/RESET/IRQ vectors, and translates each 6502 instruction to equivalent C code. JSR becomes a direct C function call, branches become gotos, and the 6502 stack is maintained as real RAM at g_ram[0x100+S].
Metroid uses the MMC1 mapper with 8 PRG banks. Bank 7 is fixed (always mapped at $C000-$FFFF) and contains the main game loop, NMI handler, and core routines. Banks 0-6 are switchable and contain area-specific code. The recompiler handles bank switching via call_by_address() runtime dispatch.
License
PolyForm Noncommercial 1.0.0 — see LICENSE. The nesrecomp
framework submodule and any other third-party components are licensed
separately in their own repositories.
No game ROM data is included. You must supply your own legally obtained copy of Metroid (USA).
<p align="center"> <sub><b>R.A.I.D. — Retro AI Development</b> · a Discord for AI-assisted retro reverse-engineering, decomp & recomp</sub> </p> <p align="center"> <a href="https://discord.gg/Ad9BwSzctP"><img src=".github/raid-discord.png" alt="Join the Retro AI Development (R.A.I.D.) Discord" width="200"></a> </p>
