TMNT - Arcade game - Native Port

TMNT - Arcade game - Native Port is a C decompilation and native port of the 1989 arcade game Teenage Mutant Ninja Turtles for Windows, Linux, and macOS.

README

Teenage Mutant Ninja Turtles — decompilation and port

A reconstruction of Konami's 1989 arcade game Teenage Mutant Ninja Turtles (a 68000 main board with a Z80 sound board) into platform-agnostic C, not an emulator, following the method of segasonic-decomp and outrunners-decomp: both of the game's programs decompiled one function at a time into readable C, and checked frame by frame against MAME in state, picture and sound.

Apart from the screenshots on this page, this repository contains no game data. The game's programs are here as C, decompiled from the original machine code (src/); as with n64decomp/sm64, you bring your own ROM set (MAME's tmnt) for everything else: the tools verify it, extract the assets, and you build your own executable, which runs from the assets and opens no ROM.

The title screen Four turtles in April's burning apartment Shredder carries April off The street, and April on the TV Krang's android body Shredder and his copies

Frames drawn by the game's own video library (runtime/src/video.c) while the decompiled game plays the scenarios of tests/controls/.

Playing it

Download the package for Windows, Linux or macOS from the releases. Keep tmnt.zip beside the game, or drop it on its window when it asks: the first time, it takes the assets out of it. Two games come in it, the same program:

  • TMNT Arcade: the arcade game as it is: coins, the test mode, four players.
  • TMNT: the console version (see "The console version (TMNT)"): no coins, START brings a player in, Splinter Way's modes (NORMAL, BOSS RUSH, RONIN), LAN for up to four computers (arcade-lan), WIDE 16:9 and 20:9, the pixels' filters, the pause.

The package also has the tools that make the game for your devices, with your ROM set:

  • Nintendo Switch: TMNT NSP makes TMNT.nsp and TMNT Arcade.nsp, for a console with Atmosphere.
  • Android: TMNT APK makes TMNT.apk and TMNT Arcade.apk, signed with a key of yours.
  • Anbernic's H700 handhelds (RG40XX H...): TMNT Anbernic makes the folder for PORTS.

What a tool makes has the game's data in it: it is yours alone, never share it. The package's README.txt (docs/package/README.txt) has the controls and what each tool needs.

TMNT's menu under the title, in 16:9: ARCADE, SPLINTER WAY, LAN, OPTIONS, EXIT Four players on the street in 16:9, the Foot coming in from the right OPTIONS: controls, difficulty, lives, demo sound, region, video, volume, sound test

TMNT, the console version, in WIDE 16:9: its menu, four turtles on the street, OPTIONS.

Status

As of 06-11-2026:

  • ROM set verification (MAME set tmnt, World 4 Players, version X), by CRC32 and SHA-1 of every file
  • A reference: the original programs on MAME's 68000 and Z80 cores, with MAME's K052109, K051960, K007232 and uPD7759 and ymfm's YM2151, against a model of the board in C (kref, in the shared core)
  • 18 scenarios (tests/controls/): the attract mode, the test mode through every page and sound, games of one and four players, the continue and GAME OVER, the story before area 0 with no button pressed to its end, the DIP switches, and a game in each of the ten areas played until its boss falls, the last one through the ending and the staff roll: 513,383 frames, which run 25,526 addresses of the 68000 program and 4,486 of the Z80's
  • A 68000 disassembler identical to MAME's at every offset of the program and of an image of every opcode, and a static analysis that finds, without traces, every address the scenarios run: 28,515 instructions in the 68000 program, and the Z80's the same way
  • Every byte of both programs explained: the 68000's 32.5 % code, 65.9 % data described with its layout, types and a name, 1.6 % fill; the Z80's 35.6 % code, 64.3 % data, 0.1 % fill. Nothing unexplained
  • Both programs decompiled: 2,058 functions of the 68000 program and 381 of the sound program, no goto, every function, variable, field, parameter and local with a name and every function with a comment, the data read by name from the assets, never through an address of the ROM. 2,423 of the 2,439 functions (99.3 %, and 99.9 % of the lines) have nothing in them but the faithful leftovers "Reading the C" lists; each of the other 16 has its reason there too
  • The game matches the reference in state, picture and sound in all 18 scenarios, frame by frame, and its own sound board matches MAME's in every frame (samples and RAM); the structure of every function, the code no scenario runs too, checks against its jumps; no executable holds a stretch of the ROM set
  • The board in C (runtime/): the memory map, the inputs, the interrupts, the video chips drawing what MAME draws pixel for pixel, the YM2151, the K007232, the uPD7759 and the title music sample making the samples MAME makes
  • The assets (see "The assets"): tiles and sprites in sheets by subject with their own colours, the programs' data as text by subject with every piece named, the samples as WAV, the music as Standard MIDI Files; the game loads them, and they build back into the ROM set byte for byte
  • A window port (ports/sdl2/, SDL2): the game with its sound, the keyboard and four game controllers, on Windows, Linux and macOS (the 18 scenarios identical on Linux too)
  • The port's first run in C: the game takes the assets out of your ROM set itself, the same files extract_assets.py writes, byte for byte; and the pack the devices load
  • The packages for Windows, Linux and macOS (see "The Windows package"): TMNT Arcade and TMNT, SDL2 inside them, an icon of the project's own, its README and licenses, the devices' tools; built by the CI, and released on a tag
  • The console version (see "The console version (TMNT)"): its presentation, its menus, no coins, the turtle choice, Splinter Way's NORMAL, BOSS RUSH and RONIN, OPTIONS, WIDE 16:9 and 20:9, the pause
  • LAN: up to four computers in one game, in lockstep through the host (arcade-lan)
  • The devices: the Nintendo Switch, Android (touch controls) and Anbernic's H700 handhelds, each made on the player's computer from the ROM set (ports/PLAN.md); not yet tried on a real Switch or Anbernic
  • Save states, the original turtles as a mode of their own (console/PLAN.md)

Getting started

Requirements: Python 3.10 or newer, CMake 3.20 or newer, a C11 and C++20 compiler (Visual Studio 2026 Build Tools on Windows) and, for the game's window, the SDL2 development files: on Windows, unpack SDL2-devel-2.x.x-VC.zip from SDL's releases into build/downloads/sdl2/; elsewhere, the system's package (libsdl2-dev). Without SDL2 only the headless builds are made.

  1. Clone with the shared core, arcade-decomp, which this repository keeps in tools/core as a git submodule (a clone made without --recursive takes it with git submodule update --init):

    git clone --recursive https://github.com/cheviman/tmntarcade-decomp
    
  2. Put your own copy of MAME's tmnt set in rom/ (the .zip as is, or its files), and check it:

    python tools/verify_rom.py
    
    FileSizeCRC32What it holds
    963-x23.j17, 963-x24.k17128 KB eacha9549004, e5cc906768000 program, 0x00000-0x3FFFF (even, odd bytes)
    963-x21.j15, 963-x22.k1564 KB each5789cf92, 0a74e27768000 program, 0x40000-0x5FFFF (even, odd bytes)
    963e20.g1332 KB1692a6d6Z80 sound program
    963a28.h27, 963a29.k27512 KB eachdb4769a8, 8069cd2etile graphics (K052109)
    963a17.h4, 963a15.k4, 963a18.h6, 963a16.k6512 KB eachb5239a44, 1f324eed, dd51adef, d4bd9984sprite graphics (K051960)
    963a30.g7, 963a31.g19256 bytes eachabd82680, f8004a1csprite address decoder, priority PROMs
    963a26.c13128 KBe2ac3063K007232 samples
    963a27.d18128 KB2dfd674buPD7759 speech
    963a25.d5512 KBfca078c7title music sample
  3. Extract the assets into assets/, and check that they build back into your ROM set byte for byte:

    python tools/extract_assets.py
    
    python tools/extract_assets.py --check
    
  4. Build the game and the development tools into build/bin/:

    cmake -S . -B build/msvc
    
    cmake --build build/msvc --config Release --parallel 6
    
  5. Play:

    "build/bin/TMNT Arcade"
    

    The game reads its assets from --assets FOLDER, or from an assets folder beside the executable or in the current folder; with none, it takes them out of your ROM set itself, once (see "The port's first run"). The executable can be copied anywhere with SDL2's library and font.bmp beside it (the build puts them next to it): the assets come from your ROM set, so they are yours, not to share.

    Or run the game without a window, here with the controls of the attract mode, writing a hash of every frame:

    build/bin/tmnt_game_headless --script tests/controls/attract.txt --hashes build/attract.txt
    

Playing

It is the arcade game for four: each player has a coin slot and no start button; a coin and the attack button bring a turtle in (player 1 is Leonardo, 2 Michelangelo, 3 Donatello, 4 Raphael).

ControlKeys
Move (player 1)the arrow keys
Attack, jumpZ or left Ctrl; X or left Alt
Coins 1-45, 6, 7, 8
Service9
Full screenF11 or Alt+Enter
Smooth or sharp pixelsF3
QuitEscape

Game controllers are players 1 to 4 in the order they are plugged in: the pad or the left stick moves, A attacks, B jumps, Back puts a coin in.

Options of TMNT Arcade and tmnt_game_headless: --script FILE plays a control script (format in runtime/include/tmnt/script.h) instead of the controls (--fast in the window runs it without waiting for each frame's time); --hashes, --video-hashes and --sound-hashes FILE write a hash of every frame's state, picture and sound, as the reference does; --service turns the test mode's DIP switch on; --assets FOLDER says where the assets are (a folder, or a pack: a file whose name ends in .assets); --write-pack FILE writes the pack of the assets loaded and stops. tmnt_game_headless also takes --frames N, --dump FRAME --dump-dir DIR (the board's memories at the end of a frame) and --video-dump FRAME ... --video-dir DIR (those frames' pictures as BMP, as the game's own video draws them: the screenshots above).

The port's first run

A player of the port has no Python: with no assets, TMNT Arcade takes them out of the ROM set itself, once.

  • Where. It loads the pack tmnt.assets or the folder assets beside the executable, in the folder where the game keeps its files (beside the executable on Windows; ~/.local/share/TMNT on Linux, ~/Library/Application Support/TMNT on macOS) or in the current folder; --assets picks one. With none, it looks for the ROM set, tmnt.zip or a folder rom/ with its files (or its .zip), beside the executable or in the game's folder; with none there, a window asks for it to be dropped on it and says what it is doing and what failed. The assets go into assets/ in the game's folder, and the game plays.
  • The same files. The first run (runtime/src/extract.c and the files beside it) writes the folder tools/extract_assets.py writes, every file byte for byte: the sheets' PNGs packed as zlib's level 9 packs them (deflate.c), the WAVs, the data as text, the songs' MIDI files (extract_music.c, tools/sound_music.py's writer in C). What the Python tools work out from the static analysis (which bytes are which piece, their names and formats, the sheets' codes and palettes) it takes from runtime/src/extract_layout.c, which tools/extract_layout.py writes with positions, names and codes only, no byte of the ROM; run it again when symbols.txt, sound_symbols.txt, graphics_subjects.txt or the extractor change. tmnt_assets_check ROM_SET PYTHON_ASSETS [OUT] (built with the reference tools) runs the first run into OUT and compares every file with the folder extract_assets.py --out PYTHON_ASSETS wrote.
  • Versions. The first run writes version.txt last (the version of its tables and writers); a folder of another version is taken out again, and one with no data/layout.txt (a first run that did not finish) is not taken. extract_assets.py's folders have no version.txt and are taken as they are.
  • The pack. tmnt.assets is one file with what the game loads (the video and sound chips' memories and the programs' data, as the assets give them), for the devices, which never take the assets out of a ROM set: "TMNT Arcade" --write-pack tmnt.assets makes it from the assets. It holds the version of the tables the game's C reads by name: a pack of another build says so instead of loading.
  • The window writes with Kenney Mini (CC0, ports/sdl2/font/, font.bmp beside the executable): none of the game's letters are there before its assets.

The Windows package

What a player downloads: tmnt-windows-x64.zip, a folder tmnt with TMNT Arcade.exe, TMNT.exe (the console version) and the font.bmp their first run and TMNT's menus write with, the package's README.txt (docs/package/: what the player needs, their own ROM set, how the first run takes it, the controls), LICENSE.txt, and licenses/ with MAME's and ymfm's terms (the board's video and sound follow them), SDL2's and Kenney's. Nothing of the ROM set: the game takes its assets out of the player's.

  • SDL2 inside. TMNT_STATIC_SDL2 builds SDL2 2.32.10 from its source (CMake's FetchContent, from GitHub) into the executable, and the C runtime with it: it needs no library beside it, nor Visual C++'s redistributable. Without it (the default) the build is the one above, SDL2's library beside the executable.

  • The icon is the project's own drawing, nothing of the game's art (tools/make_icon.py writes ports/sdl2/icon/: a manhole's lid lit from below): a resource of the executable, which Explorer and the window show. A picture of your own takes its place in your builds: local/icon_pc.png (local/ is never committed) or -DTMNT_PC_ICON=PICTURE.

  • Made locally in a build folder of its own, without the reference tools, its executables in build/release/bin (the usual build's in build/bin stay as they are):

    cmake -S . -B build/release -DTMNT_STATIC_SDL2=ON -DTMNT_REFERENCE_TOOLS=OFF -DTMNT_BIN_DIR=build/release/bin
    
    cmake --build build/release --config Release --parallel 6 --target tmnt_package
    

    writes build/package/tmnt-windows-x64.zip, and the folder build/package/tmnt it packs.

  • The CI (.github/workflows/build.yml) builds and packs it the same way on a clean Windows with no ROM set, on every push to main and its pull requests: the zip is the job's artifact, tmnt-windows-x64.

The console version (TMNT)

TMNT (build/bin/TMNT.exe, beside TMNT Arcade) is the same game made for playing at home: the program with the functions console/replaced.txt lists in the console's own versions (console/*.c; the decompiled C writes their definitions as CONSOLE_ORIGINAL(name), so TMNT Arcade runs the program as it is). It starts with a Konami presentation of its own (the fixed layer's Konami logo, large, lit by the four turtles running across it), then a menu under the title's logo: ARCADE (the arcade game with no coins: a player's START puts the coin and the attack in, and the boxes say PRESS START), SPLINTER WAY (the port's own modes: NORMAL, 5 lives and 3 continues; more to come), OPTIONS (over BEST 10 PLAYERS' picture: the cabinet's DIP switches by the test mode's names, the region, the volume, the pixels, WIDE 16:9 and 20:9 for the play and the menus; kept in tmnt.ini beside the executable), KONAMI CODE (the level select the Konami code will open: any of the ten areas, infinite lives, every turtle in a red bandana, everyone the same turtle) and EXIT. A game starts on the coin's screen, each player choosing the turtle he plays (the others come in with START). Its menus write with the game's own letters; what it draws of the game's comes from your assets. Escape (or START of a player in the game) pauses a game. console/PLAN.md has the plan, and what was done.

ControlKeys
Move (player 1)the arrow keys
Attack (in the menus: pick), jump (back)Z or left Ctrl; X or left Alt
STARTEnter, a controller's Start
Back in the menusEscape
Full screen; the next kind of pixelsF11 or Alt+Enter; F3

tmnt_console_headless runs it with no window for scripted checks: the script's controls are the players', --actions FILE gives the menus' actions by frame (FRAME up|down|left|right|accept|back|start [PLAYER]: with a PLAYER, that player's own stick, buttons or START; pause, quit, restart in a game), --wide 1|2 WIDE (--wide-check: each frame, the columns of its sides that are not their map's), and --video-dump FRAME ... --video-dir DIR writes those frames with the console's picture over them (tests/console/ has the menus' actions of a few runs):

build/bin/tmnt_console_headless --actions tests/console/arcade_donatello.txt --script tests/console/skip_intro.txt --video-dump 110 680 700 1200 --video-dir build

Changing the decompilation

src/ is written by the decompiler from your ROM set, symbols.txt and tools/game_profile.py; src/sound/ from the sound program and sound_symbols.txt. Change those (names, types, comments, what the analysis cannot work out alone), not the C, and write it again:

python tools/decompile.py
python tools/decompile_sound.py --checks

decompile_sound.py also writes the Z80's timing table, build/sound/timing.txt, which the reference needs to keep the sound program's time as the game's own sound board does: run it once in a new clone before the checks below.

build/bin/kref runs the original programs on MAME's cores against the same board model. Both it and the game take the control scripts of tests/controls/ (written by tools/make_scenarios.py), and both write hashes of every frame. This runs every script on both, compares them frame by frame in state, picture and sound, and checks that the static analysis alone finds all the code the reference ran (--native-only reuses the reference's results of an earlier run, in build/scenarios_frames/):

python tools/check_scenarios.py --video --sound --jobs 6 --out build/scenarios_frames