Skip to content

Repository files navigation

Super Mario Kart — Static Recompilation

Static recompilation of Super Mario Kart (SNES, 1992) from WDC 65C816 assembly to native C code, playable on modern hardware via SDL2.

Part of the sp00nznet recompilation portfolio. This is the first SNES (65816 CPU) target in the series.

Status

Playable end-to-end. A cold boot walks the authentic flow from controller input — title → driver select → 50cc class / cup select → a live Mode-7 race — with a Dear ImGui menu bar, save-states, configurable keyboard + gamepad controls for two players, and lockstep netplay. The SNES hardware (PPU Modes 0–7, SPC700 + DSP audio, DMA/HDMA, DSP-1 coprocessor) is fully emulated by the LakeSnes backend.

Two execution paths share the same backend: real-frame mode (the default) runs the genuine ROM through LakeSnes's cycle-accurate frame, so the whole game plays today; the recompiled-shell path (SMK_SHELLS=1) runs hand-written RECOMP_PATCH functions and is where the static-recompilation work grows incrementally. Recompiled functions are defined with RECOMP_PATCH(name, snes_addr) { ... } and auto-register in snesrecomp's dispatch table at static-init time — adding a translated function is a one-line change, no central list.

Title Screen

The full menu flow runs from controller input — title → driver select → class/cup select:

Driver Select

Class / Cup Select

The Mode-7 race renders and plays — perspective track, karts, HUD, split-screen:

Mode-7 Race Mode-7 Race 2

Lockstep netplay — two machines run the same deterministic emulation in sync (host = Player 1, client = Player 2), starting from a shared save-state:

Multiplayer netplay

A Dear ImGui menu bar (File / Graphics / Sound / Controller / Multiplayer / Help) overlays the game — see Menu.

What works

  • Full authentic flow from controller input — title → driver select → 50cc class / cup select → Mode-7 race, all driven by the genuine ROM's state sequencer ($81:E000)
  • Mode-7 race rendering + gameplay: perspective track, karts, HUD, lap timer, split-screen
  • Dear ImGui menu overlaying the game — File / Graphics / Sound / Controller / Multiplayer / Help
  • Save-states — File → Save/Load (and F5/F8), full machine state via LakeSnes; File → New resets
  • Configurable input for both players — rebind keyboard and Xbox-style gamepad per button
  • Lockstep netplay (TCP) with input-delay buffering — host/join, save-state sync on connect, deterministic in-sync emulation (verified by matching WRAM checksums)
  • Full boot chain: reset vector → hardware init → WRAM 0x55 fill → PPU/APU/DSP-1 setup → Mode-7 tables
  • Custom tile/tilemap decompressor ($84:E09E) — all 7 compression modes + E0+ extended counts
  • Joypad/auto-joypad input with edge detection; real palettes ROM → CGRAM; per-scanline HDMA
  • LakeSnes renders all PPU scanlines per frame; SDL2 window (3× scale by default), 60 fps, audio

What's next

  • Grow the recompiled-shell path (SMK_SHELLS=1) to drive gameplay — currently the shells render the menus but the Mode-7 race's multi-frame init/fade-in needs the real frame (the default real-frame path handles it). See docs/smk_flow_re.md.
  • Netplay polish: rollback / over-the-wire desync detection; "drop straight into 2-player on connect"
  • More recompiled functions (the long-term goal: replace real-frame with recompiled C)

Recent (June 2026)

  • Cracked the menu→race flow — root-caused the long-standing menu blocker to a power-on WRAM fill ($2E game-mode read as 2-player on a zero-filled RAM); fixed with a 0x55 fill matching hardware/snes9x. Full RE writeup in docs/smk_flow_re.md, cracked headlessly via a BizHawk + snes9x-core harness (tools/bizhawk/).
  • Real-frame mode — runs the genuine ROM via LakeSnes's full frame so the whole game plays now.
  • ImGui menu, save-states, 2-player input, and lockstep netplay added to snesrecomp (generic).
  • Fixed a real s_pixel_buf overflow (478 vs 480 rows) that silently zeroed adjacent statics.

Earlier

  • Auto-registered dispatch via RECOMP_PATCH(name, snes_addr) { ... } — linker-priority static constructors, no central list. Pattern inspired by N64Recomp's RECOMP_PATCH.
  • Public 65816 op kit<snesrecomp/cpu_ops.h> (op_lda_*, op_sta_*, op_rep, …).

Architecture

┌─────────────────────────────────────────────────┐
│                 smk_launcher                      │
│  ┌──────────────────────────────────────────┐    │
│  │  src/recomp/ — 48 Recompiled functions   │    │
│  │  smk_boot.c  — NMI, state machine, input │    │
│  │  smk_init.c  — Init, transition dispatch │    │
│  │  smk_title.c — Decompressor, PPU, menus  │    │
│  └──────────────────────────────────────────┘    │
│                       │                           │
│              bus_read8 / bus_write8               │
│                       │                           │
│  ┌──────────────────────────────────────────┐    │
│  │  snesrecomp (ext/snesrecomp/)            │    │
│  │  ┌────────────────────────────────────┐  │    │
│  │  │  LakeSnes — Cycle-accurate SNES HW │  │    │
│  │  │  Real PPU (Mode 0-7, sprites, etc) │  │    │
│  │  │  Real SPC700 + DSP audio           │  │    │
│  │  │  Real DMA (GPDMA + HDMA)           │  │    │
│  │  │  Full memory bus routing            │  │    │
│  │  └────────────────────────────────────┘  │    │
│  │  SDL2 platform (window, audio, input)    │    │
│  └──────────────────────────────────────────┘    │
└─────────────────────────────────────────────────┘

In the recompiled-shell path (SMK_SHELLS=1), recompiled game code acts as the CPU — it calls bus_read8(bank, addr) / bus_write8(bank, addr, val), which route through LakeSnes's real memory bus to the actual PPU, APU, DMA, and cartridge hardware. In the default real-frame path, LakeSnes's own CPU executes the genuine ROM each frame (snes_runFrame) — identical to a cycle-accurate emulator — so the full game plays while the recompiled functions are still being written. Either way, all PPU/APU/DMA behaviour is real.

Controls

Default Player 1 keyboard (rebindable in the menu):

Key SNES Button
Arrow keys D-pad
Z B
X Y
A A
S X
Q L
W R
Enter Start
Right Shift Select
F5 / F8 Save / Load state
Escape Quit

Gamepads (Xbox-style, via SDL_GameController) work out of the box for both players — A→B, B→A, X→Y, Y→X, shoulders→L/R, Back→Select, Start→Start, d-pad and left stick steer. Rebind any of it in Controller (P1/P2 tabs).

Menu

A Dear ImGui menu bar (modelled on LinksAwakening) overlays the game:

  • File — New (reset/restart), Save / Load state (smk_state.sav; also F5 / F8), Settings ▸ (save/load smk_config.ini), Quit
  • Graphics — window scale, V-Sync, texture filter, scanlines, show FPS
  • Sound — master volume, mute
  • Controller — rebind keyboard and gamepad for Player 1 and Player 2
  • Multiplayer — Host / Join (IP:port), input-delay, connection status — lockstep netplay
  • Help → About — links back to this repository

The menu auto-disables in headless/scripted runs (SMK_HEADLESS/SMK_SCRIPT).

Building

Prerequisites

  • CMake 3.16+
  • Visual Studio 2022 (MSVC)
  • SDL2 via vcpkg: vcpkg install sdl2:x64-windows
  • Python 3.10+ (for disassembler and analysis tools)

Build

cmake -B build -G "Visual Studio 17 2022" -A x64 \
  -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake

cmake --build build --config Debug

Run

build/Debug/smk_launcher.exe

The ROM file is not included — supply your own US v1.0 copy (MD5: 7f25ce5a283d902694c52fb1152fa61a).

By default the launcher runs in real-frame mode — the genuine ROM via LakeSnes's full cycle-accurate frame — so menus and the Mode-7 race render and play at full speed out of the box. (The recompiled per-frame shells can't yet drive the race's multi-frame setup; as gameplay functions are recompiled they take over.)

For recompilation development, use the recompiled-shell path:

SMK_SHELLS=1 build/Debug/smk_launcher.exe           # recompiled per-frame shells

Decompressor

The custom decompressor at $84:E09E handles SMK's tile/tilemap compression format:

Mode Encoding Description
$00 Raw Copy N bytes from stream
$20 RLE Repeat 1 byte N times
$40 Word fill Alternate 2 bytes for N entries
$60 Inc fill Store incrementing byte N times
$80 Backref Copy from earlier in buffer (abs offset + base)
$A0 Inv backref Copy with XOR $FF (inverted)
$C0 Byte backref Copy from buf_pos - offset (1-byte offset)

Commands $E0$FE use extended 10-bit counts: 1 data byte + cmd bits 0-1 as high bits.

Project Structure

├── include/smk/       functions.h (smk_XXXXXX forward declarations)
├── src/
│   ├── recomp/        Recompiled game functions (smk_boot.c, smk_init.c, smk_title.c)
│   └── main/          main.c — entry point, frame loop
├── ext/snesrecomp/    snesrecomp library (LakeSnes backend + SDL2 platform):
│                        recomp_patch.h    — RECOMP_PATCH auto-registration macro
│                        cpu_ops.h         — 65816 instruction helpers (op_lda_*, op_sta_*, …)
│                        menu_overlay.cpp  — Dear ImGui menu (File/Graphics/Sound/Controller/MP/Help)
│                        mp_session.c      — generic SNES lockstep netplay (TCP)
│                        third_party/imgui — vendored Dear ImGui
├── docs/
│   └── smk_flow_re.md  reverse-engineering of the menu→race flow
└── tools/
    ├── disasm/        65816 disassembler (M/X flag tracking, all addressing modes)
    ├── bizhawk/       headless BizHawk + snes9x-core ground-truth harness
    └── lakesnes_ref.c native LakeSnes runner (scripted input, snapshot/BMP dumps)

ROM Details

Field Value
Title SUPER MARIO KART
System Super Nintendo (SNES)
CPU WDC 65C816 @ 3.58 MHz
Coprocessor DSP-1 (math) + SPC700 (audio)
Mapping HiROM FastROM
Size 512 KB (8 × 64 KB banks, C0–C7)
SRAM 2 KB
Region USA
CRC32 CD80DB86

Key References

License

This project contains no Nintendo copyrighted material. The ROM file is not included and must be legally obtained by the user.

About

Static recompilation of Super Mario Kart (SNES, 1992) from WDC 65C816 assembly to native C code, playable on modern hardware via SDL2.

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages