Skip to content
nathsouPublic

About

Super Mario Bros. static recompilation in C

Resources

Stars

14 stars

Watchers

2 watching

Forks

Repository files navigation

SMB

Play it online

Static recompilation of Super Mario Bros. from the ROM into C and WebAssembly. The repository contains no game code or data: the recompiler reads your own copy of the ROM, and maps/smb.map gives its routines, tables and variables this project's own names. The page recompiles the ROM in the browser and shows the generated C next to the game.

SMB C port running in the browser

Controls

  • D-Pad: WASD
  • B: K
  • A: L
  • start: Enter
  • select: Space
  • z: Save state
  • x: Load state

Checkpoints

  • Static translation of the disassembly to low-level C
  • PPU & APU emulation layers
  • Convert subroutines to C functions
  • Convert most gotos to if statements
  • Remove unused flag updates
  • Replace PPU with direct draw calls (display state written directly, GPU renderer)
  • Emit instructions as C instead of instruction-emulation helpers
  • Infer routine interfaces (registers and flags read and produced)
  • Recompile from the ROM with our own names (no disassembly in the repository)
  • An IR shared by a C and a WebAssembly backend, with optimisation passes
  • Recompile in the browser, with a code viewer
  • Synthesise sound on the audio thread, band-limited, from per-frame register writes
  • Registers as C locals and parameters
  • Manually rewrite portions of the code to higher level C

See toward a modern C program for how these steps are done and checked.

Building

Linux & MacOS

  1. Fetch the submodules:
$ git submodule update --init --recursive
  1. Build raylib, follow the instructions here

  2. Install MoonBit (see Codegen), place a legally obtained dump of SMB called smb.nes in the root folder, then recompile it and build:

$ make codegen
$ make build
  1. Run ./smb from the root folder (it loads web/video.frag, the GPU renderer's shader; ./smb --software uses the software renderer; --debug-chr draws with generated placeholder tiles)

WebAssembly

The page recompiles your ROM in the browser: the recompiler (MoonBit compiled to JavaScript) turns it into a WebAssembly module and C, which a code viewer shows next to the game (SHOW CODE; the map tab is editable and recompiles).

  1. Install a recent clang with the wasm32 target and MoonBit (see Codegen)
  2. Run make web
  3. Serve the web/ folder over HTTP and open index.html
  4. Load a legally obtained dump of SMB, an iNES file or a raw dump of the cartridge; it stays in your browser. An unrecognised dump (by SHA-256) still loads, with a warning

DEBUG TILES draws with generated placeholder tiles instead of the ROM's, and ?software in the URL selects the software renderer instead of WebGL2.

Codegen

  1. Install Moonbit:
$ curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash -s '0.10.9+6e6c44045'
  1. Place your smb.nes in the root folder and run make codegen; the C goes to codegen/game/ (not in the repository). A raw dump of the cartridge (no iNES header) works too: make codegen ROM=smb.bin

The game is recompiled from the ROM image and maps/smb.map, which gives the routines, tables and variables this project's own names (see recompiling from the ROM). The generator recovers reachable instructions and callable regions from the vectors, builds an IR, optimises it and emits structured C and, with --wasm, WebAssembly. The hand-written display routines (codegen/native/display.c) use codegen/native/smb.h, the map's names as a C header (make header, no ROM needed). See the analysis and migration notes and toward a modern C program.

Run python3 tests/check_codegen.py for the ROM-free suite (as CI does): generator tests, and the independent NROM fixtures (from assembly and from an iNES image) through both backends, natively and in WebAssembly. With your smb.nes, append --rom to also recompile the game, replay the recorded 7,987 frames natively against tests/golden/warpless.trace (debug and ROM tiles), run the differential test of every generated routine (tests/check_routines.py) and replay the WebAssembly build (tests/web/replay_split.mjs). These checks do not require Raylib.

The GPU renderers can be compared with the software renderer pixel for pixel: make gpu-check && xvfb-run ./gpu_check --debug-chr (desktop) and NODE_PATH=$(npm root -g) node tests/web/gpu_check.cjs (WebGL2 in headless Chromium, needs Playwright).

Calls are plain C calls. Return addresses are not kept on the emulated stack, except by the inline-table dispatchers, which read their table through it.

To compile another compatible assembly input into a separate directory:

moon run src/main -- --input tests/nrom128.asm --output /tmp/nrom-codegen

References & Resources

About

Super Mario Bros. static recompilation in C

Resources

Stars

14 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages