# MetroidNESRecomp > _This recompilation is a **byproduct of developing > [nesrecomp](https://github.com/mstan/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 »](https://1379.tech/recomp-ai-5-months-later/)_ A static recompilation of Metroid (NES) using [nesrecomp](https://github.com/mstan/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. ### 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. ![Experimental 32:9 widescreen showing Samus, enemies and the Morph Ball in the expanded viewport](docs/screenshots/metroid-widescreen.png) For the command-line options, tested behavior and remaining limitations, see [WIDESCREEN.md](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](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](#which-rom). ### Steps ```bash 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: ```powershell 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](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](https://github.com/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 `.sym` into `symbols.sym` (the bank-aware symbol overlay `game.toml` points at, so generated functions carry their real names) and `metroid_ram.h` (`MET_` defines for every RAM/MMIO label, so `extras.c` and the widescreen renderer never spell an address out). Defaults to `NES_NTSC`; it refuses to run if the ROM body does not match the target it was asked for. - **`tools/extract_metroid_data_regions.py`** — walks `disasm/m1disasm/SRC` and emits the `[[data_region]]` blocks that keep the recompiler from mistaking tables for code. `--target` selects which `.if BUILDTARGET` branch 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: ```bash 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_` 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`](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). ---

R.A.I.D. — Retro AI Development · a Discord for AI-assisted retro reverse-engineering, decomp & recomp

Join the Retro AI Development (R.A.I.D.) Discord