Skip to content
PORTSNATIVE.
HomeCatalog
Web GamesEmulatorsToolsNews
PORTSNATIVE.

A catalog for discovering ports and related projects. We link to official sources and do not host original game files.

ExploreCatalogMy list
InformationAbout usSuggest a portSupportDonationsLegal notice
© 2026 PortsNative
Back to catalog
Tombi!
PORTSNATIVE

Tombi!

NATIVE PORTPLATFORMERWINDOWSLINUXMACOSALPHA

Tombi!

OriginPlayStation

TechnologyNative C/C++ port (engine reimplementation)

Available forWindows, Linux, macOS

View official project

About the project

Tombi! 2: Contra los cerdiablos, también conocido en inglés como Tomba! 2: The Evil Swine Return en région NTSC, simplemente Tombi! 2 en la región PAL (europea) o トンバ! ザ・ワイルドアドベンチャー en Japón), es un videojuego de acción y aventuras de plataformas, desarrollado por Whoopee Camp y publicado por Sony Computer Entertainment para la videoconsola PlayStation. Salió a la venta en el año 1999 para Japón y Norteamérica, y en el año 2000 para la región PAL. El juego es una secuela de Tombi!

Official project information

Author's original READMEOpen on GitHub ↗
<p align="center"><img src="docs/logo.png" alt="Tombi!" width="250">&nbsp;<img src="docs/logo_us.png" alt="Tomba!" width="250">&nbsp;<img src="docs/logo_jp.png" alt="Ore! Tomba" width="250"></p>

Tombi! (Tomba!) decomp + native PC port

VersionCodeDataFunctionsNotes
Europe, Spanish (SCES_013.31)CodeDataFunctionsreference version, everything below
Japan, Ore! Tomba (SLPS_011.44)CodeDataFunctionsmatching in progress, see docs/JP.md
Japan demo, taikenban (SLPM_801.65)CodeDataFunctionsshared C reuse + automatic sweep (tools/demo_sweep.py), see docs/JPDEMO.md
Beta 1998-02-03 (BETA_980203)CodeDataFunctionsshared C reuse, see docs/BETA_980203.md
PrePre 8 demo (PCPX_960.84)CodeDataFunctionssymbol pairing, see docs/PREPRE8.md

Report Platform Compiler License

A matching decompilation and native recompilation of Tombi! (known as Tomba! in North America and Ore! Tomba in Japan) for the original PlayStation:

  • Decompilation: finished. The game's code is C source that compiles, with the game's original toolchain, into byte-identical machine code (99.34 %: every function but three memory-card ones, which have functionally equivalent C, see "Status" below). Work on the decompilation is considered complete; the remaining work is naming and documentation.
  • Recompilation: the native PC port. The same C is built as a native Windows/Linux program (port2/): OpenGL or software rendering, SDL2 controllers, the original music, voices and movies, saves, every language of the disc, and a launcher with mods (tombi-port-<ver>-windows-x64.exe / tombi-port-<ver>-linux-x64.tar.gz of the latest release, see docs/PORT.md). The game is fully playable; you supply your own disc image (Tombi! PAL SCES-01331 Rev 1), which the launcher extracts once.

This repository targets the European release (SCES-01331 Rev 1) and works function by function: every function in src/ is verified to produce exactly the same instructions as the retail executable.

This repository contains no game data, executables, disc images or Sony SDK files. You need your own legally obtained copy of the game and of the toolchain.

Status

<!-- progress:start -->

Whole game: 99.34 % of the game's code matches byte for byte (1520132 of 1530244 bytes).

PartCodeMatching bytesMatching
MAIN0.EXE: engine, shared by every area286644 B286644 B🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩 100.0 %
X000.BIN: AREA00 + object code reused by all areas313676 B312708 B🟩🟩🟩🟩🟩🟩🟩🟩🟩🟨 99.7 %
X001..X019.BIN: code specific to the other areas736440 B736440 B🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩 100.0 %
SYSTEM/*.BIN: options, sub-screens, game over, info screens193484 B184340 B🟩🟩🟩🟩🟩🟩🟩🟩🟩🟨 95.3 %
Whole game1530244 B1520132 B🟩🟩🟩🟩🟩🟩🟩🟩🟩🟨 99.34 %
  • Bytes of machine code in game functions, counted exactly like the objdiff report on decomp.dev (tools/units.py is the common list); Sony's Psy-Q library (100904 B) is not counted: 90016 B of it (89.2 %) also matches, from Psy-Q sources ported from psx_tomba and other decompilations (THIRD_PARTY_NOTICES.md), and is tracked apart.
  • Each function counts once. The 16 area overlays share about 4800 functions with MAIN0/X000 or with each other; those copies match automatically and are not added again (counting every copy separately gives 99.7 %).
  • Also tracked for MAIN0 + X000: 95.5 % of the code named by us, 67.3 % typed with the TObj structure.
<!-- progress:end -->

Per-program and per-area breakdown, pending work and what is not counted: docs/PROGRESS.md. The table above is regenerated with python3 tools/progress.py.

Where things stand (October 2026). The code is 99.40 % byte-matched: 2 952 of 2 955 functions. The three left are in the memory card overlays (D0SPSUB func_800E89F8, func_800EFD6C, D0SPSUB2 func_800E999C, plus the D0SPSUB3 twin of the first); each has functionally equivalent C in src/wip/ whose header documents the remaining difference (register allocation and scheduler order, the structure matches), see notes/remaining.md. The D0SPSUB menu dispatcher func_800ED0C4 (now subScreenItemListStep) was matched by michaelm-afk (PR #1) and its D0SPSUB3 twin func_800EC89C followed from the same idioms. Function naming is complete for MAIN0, X000, the SYSTEM overlays and the area overlays X001..X019: 0 functions keep a generic name (notes/unnamed.json; empty stubs are named stubNop<addr> / stubFreeObj<addr>). Data naming is done by usage for MAIN0 (342 globals, notes/data_names_main0.csv), X000 (115) and the eight SYSTEM overlays (266 in their notes/data_names_<overlay>.csv), applied with tools/rename_data.py; what is still spelled by address is animation scripts, elements of named pointer tables, field aliases and Psy-Q internals. The Japanese release is active again (see "Japanese release progress"); the demo is swept automatically (see "Japanese demo progress").

Target

FileDescriptionSHA-1
SCES_013.31Boot executable (PAL, Spanish)f6c383514e5d8367d2eed06400315cf6dd57ef6b
EXE/MAIN0.EXEGame core, loaded at 0x80010000bec8dc5f4116f5e497f2337eef8c523ff6dee8fc
AREA00/X000.BINArea overlay, AREA00 (Village of All Beginnings), loaded at 0x800E8028ce168475f667f2ca8842eabad2aec829e301c5c5
AREA01/X001.BINArea overlay, AREA01 (also AREA07), loaded at 0x800E8028923f812dcfd7da18ee0f7017e5b45a5e38ee3015
AREA02/X002.BINArea overlay, AREA02 (AREA19 ships a copy), loaded at 0x800E8028646aa8b5ad97f70b299b982b3260f9f731a142bb
AREA03/X003.BINArea overlay, AREA03 (Phoenix Mountain), loaded at 0x800E8028440ae76f5012e3778b908743d7d030c350caf7d3
AREA04/X004.BINArea overlay, AREA04 (also AREA12), loaded at 0x800E8028f01c882217efee45bf76aba54ee4fadfa6c25665
AREA05/X005.BINArea overlay, AREA05 (Baccus Village), loaded at 0x800E80280d2778c0d4b3e5b5dfc40fc46053e46d9d785b17
AREA06/X006.BINArea overlay, AREA06 (Dirt Motocross), loaded at 0x800E80281bd5b648feb75a580c14db6715e057cb035c1db1
AREA08/X008.BINArea overlay, AREA08 (Baccus Lake), loaded at 0x800E80285e098dccfd123af09967ca778c83223e8a21716d
AREA09/X009.BINArea overlay, AREA09 (Mushroom Village), loaded at 0x800E80281b44ea24890128c3d3789b0badb252135ba4dec1
AREA10/X010.BINArea overlay, AREA10 (Deep Jungle), loaded at 0x800E80285093e40399319f5987668bfb1d042713216d356e
AREA11/X011.BINArea overlay, AREA11 (Village of Civilization), loaded at 0x800E8028016559470c5a253deb5a38f685ff312c39c2cb46
AREA13/X013.BINArea overlay, AREA13 (Pig Island), loaded at 0x800E8028a4e8668eca85a32d6d0cdfa05ddffd6ad40bfbb0
AREA14/X014.BINArea overlay, AREA14 (Evil Pigs), loaded at 0x800E80284ee916eb3215ccae64773e1a9a547ab8dbcd1ff5
AREA16/X016.BINArea overlay, AREA16 (Village of Civilization: Clock Tower), loaded at 0x800E80287c93915bc943e8d9d8fa5d5efbd02f7e563650e4
AREA17/X017.BINArea overlay, AREA17 (Village of Civilization: Iron Tower), loaded at 0x800E802859c5964cbb4536bc62b1a65262d963fab1ad3d33
AREA18/X018.BINArea overlay, AREA18 (Village of Civilization: Y Crossing), loaded at 0x800E802883eca42eb30944bb0c61056632b2ef7bb9891b34
AREA19/X019.BINArea overlay built for AREA19 (the dwarves: Dwarf Elder's Hut, Hidden Village), never loaded by the retail game: no file table lists it and AREA19 runs AREA19/X002.BIN (= AREA02's X002); see docs/CUT_CONTENT.md94060c10f7b6e3af1adc55a0c9cff4b81890988c

The 17 area overlays (X0nn.BIN, Spanish; X1nn..X8nn are the other languages) are raw code blobs, all loaded at 0x800E8028. Their first 188 KB (0x800E8028..0x80115EA4: header, ~300 shared object handlers and a data block) are byte-identical in every area; the rest is per-area code. X001..X019 are tracked like X000: boundaries in notes/functions_x0nn.csv, sources in src/x0nn/ (// FUNC addr size X0nn), and functions identical to an already matched MAIN0/X000 function are generated by tools/areas.py twins.

MAIN0.EXE corresponds to SCUS_942.36 of the NTSC-U release (same functions, shifted addresses). See notes/disc_layout.md, notes/memory_map_main0.md and notes/overlays.md.

How matching works

Each function lives in its own file, src/<Name>.c, with a small header:

// FUNC 8001fddc 48 MAIN0        address, size, program
// MATCHING 8001fddc 48          added once the bytes match

Optional header lines: // FLAGS -O2 -G0 ... (compiler flags) and // CC gcc-2.8.1 (compiler, by old-gcc release name; the default is gcc-2.7.2). A few MAIN0 library objects (0x8006xxxx) were built with the newer GCC 2.8.1 (Psy-Q 4.4): ncheck.py, build_full.py and build_report.py compile those with /opt/oldgcc/gcc-2.8.1-psx, and matchcheck.py with Psy-Q 4.4 CC1PSX.EXE (/opt/psyq/cc44/).

Two checkers compile the file and compare the result with the retail binary, masking relocations:

ToolToolchainUse
tools/ncheck.pynative gcc-2.7.2-psx cc1 + maspsx + GNU asfast iteration (seconds)
tools/matchcheck.pyoriginal PSY-Q 4.3 CC1PSX.EXE (Wine) + CPPPSX (DOSBox) + ASPSX 2.86reference check

The game was built with the PSY-Q 4.3 C compiler (GCC 2.7.2.SN.1); see notes/compiler.md. Practical recipes for reproducing GCC 2.7.2 output are collected in notes/matching_guide.md.

Getting started

Requirements: Linux, Python 3, binutils-mipsel-linux-gnu. Optional: Wine + DOSBox and the PSY-Q SDK binaries for the reference checker; Ghidra 12.1.4 + JDK 21 for analysis.

# 1. Put your own game files in game/ (ignored by git)
#    game/SCES_013.31, game/MAIN0.EXE, game/AREA00/X000.BIN

# 2. Fast native toolchain (old-gcc + maspsx + binutils)
tools/setup_native.sh

# 3. Check one function, or all of them
python3 tools/ncheck.py src/MulCos.c
python3 tools/ncheck.py src/*.c
python3 tools/ncheck.py src/x0*/*.c       # area overlays X001..X019

# 4. Inspect a function: disassembly + Ghidra pseudo-C
python3 tools/fn.py 8001fddc

Reference checker: copy CC1PSX.EXE (PSY-Q 4.3) to /opt/psyq/cc43/ (and PSY-Q 4.4's to /opt/psyq/cc44/ for the // CC gcc-2.8.1 files), the DOS CPPPSX.EXE to /opt/psyq/new/ and ASPSX.EXE 2.86 to /opt/psyq/46/BIN/, install wine and dosbox, then run python3 tools/matchcheck.py src/<Name>.c.

Full build

tools/build_full.py links both programs from the matched C plus splat assembly for everything else and checks them against the retail files:

python3 tools/build_full.py          # splat split + compile src/*.c + link; prints SHA-1 and OK/FAIL
python3 tools/build_full.py --no-split   # reuse the previous split (build/full/asm)

Run tools/setup_native.sh first (toolchain and the include/tomba/psyq headers; without them the files that include those headers are kept as asm, the output still matches). A full run takes about 1.5 minutes.

Outputs: build/MAIN0.EXE (must equal bec8dc5f…), build/X000.BIN (must equal ce168475…) and the eight SYSTEM overlays (O0PTSUB0.BIN, D0SPSUB.BIN, D0SPSUB2.BIN, D0SPSUB3.BIN, G0OVER.BIN, I0NFO.BIN, I0NFO2.BIN, I0NFO3.BIN, SHA-1 checked the same way); work files in build/full/ (linker scripts *.full.ld, maps, objects). On a mismatch it lists the first differing offsets and the function or splat range that produced them.

  • Each src/*.c replaces the splat lines in [addr, addr+size) of its // FUNC header (the header is the truth, even when splat had cut that range into several functions).
  • String literals, jump tables and static data of a C file are linked at their original addresses (found from the retail bytes of the instructions that reference them). Data a file merely re-defines (globals ported from psx_tomba headers) is dropped in favour of the real data from splat, and every external symbol of a C file gets its retail address (PROVIDE in the linker script, renamed when the name belongs to another address, e.g. NTSC-named symbols).
  • X000 is linked at 0x800E8028; MAIN0 symbols it uses are absolute (build/full/undefined_*_auto.x000.txt).
  • A file that does not compile or cannot be placed is reported as WARN ... kept as asm and the build still links the retail assembly for it. Today 28 MAIN0 files stay as asm (1124 of 1152 src files linked): Psy-Q library objects whose .rodata cannot be placed (several bases, or bytes that differ from the game) and FUN_80068ae4 (a BIOS stub whose inline asm ends in .data; it needs ASPSX). The output still matches.
  • Rules for new C files: a data table shared by several functions must be referenced as an extern symbol in each file (it can only be linked once), and do not emit another function's data with __asm__(".word").

Japanese release progress

<!-- jp-progress:start -->

Ore! Tomba (SLPS-01144): 83.34 % of the code matches byte for byte (1182252 of 1418632 bytes, 2721 of 3095 functions).

PartCodeMatching bytesFunctionsMatching
SLPS_011.44: engine378336 B323360 B1101 / 1269🟩🟩🟩🟩🟩🟩🟩🟩🟨⬜ 85.5 %
X00.BIN: shared object code300480 B285408 B488 / 507🟩🟩🟩🟩🟩🟩🟩🟩🟩🟨 95.0 %
X01.BIN: area code151304 B128436 B214 / 239🟩🟩🟩🟩🟩🟩🟩🟩🟨⬜ 84.9 %
X02.BIN: area code25320 B24352 B32 / 34🟩🟩🟩🟩🟩🟩🟩🟩🟩🟨 96.2 %
X03.BIN: area code122684 B100468 B191 / 222🟩🟩🟩🟩🟩🟩🟩🟩⬜⬜ 81.9 %
X04.BIN: area code102832 B70144 B163 / 197🟩🟩🟩🟩🟩🟩🟨⬜⬜⬜ 68.2 %
X05.BIN: area code6112 B6112 B16 / 16🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩 100.0 %
X06.BIN: area code35844 B28200 B41 / 47🟩🟩🟩🟩🟩🟩🟩🟨⬜⬜ 78.7 %
X08.BIN: area code228 B228 B1 / 1🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩 100.0 %
X09.BIN: area code82896 B58664 B117 / 144🟩🟩🟩🟩🟩🟩🟩⬜⬜⬜ 70.8 %
X10.BIN: area code97636 B69804 B146 / 175🟩🟩🟩🟩🟩🟩🟩⬜⬜⬜ 71.5 %
X11.BIN: area code13856 B10048 B26 / 31🟩🟩🟩🟩🟩🟩🟩🟨⬜⬜ 72.5 %
X13.BIN: area code6336 B4140 B7 / 8🟩🟩🟩🟩🟩🟩🟨⬜⬜⬜ 65.3 %
X14.BIN: area code54472 B36588 B85 / 109🟩🟩🟩🟩🟩🟩🟨⬜⬜⬜ 67.2 %
X16.BIN: area code11688 B10852 B36 / 37🟩🟩🟩🟩🟩🟩🟩🟩🟩🟨 92.8 %
X17.BIN: area code12124 B10988 B27 / 28🟩🟩🟩🟩🟩🟩🟩🟩🟩⬜ 90.6 %
X18.BIN: area code16136 B14112 B29 / 30🟩🟩🟩🟩🟩🟩🟩🟩🟨⬜ 87.5 %
X19.BIN: area code348 B348 B1 / 1🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩 100.0 %

Regenerated with python3 tools/build_report.py --version slps01144 then python3 tools/progress_jp.py. Analysis of JP-only code (Ghidra, PAL names ported by matching, functions with no PAL counterpart): notes/jp_retail.md.

<!-- jp-progress:end -->

Japanese demo progress

<!-- slpm80165-progress:start -->

Ore! Tomba demo (SLPM-80165): 40.72 % of the code matches byte for byte (261320 of 641804 bytes, 1102 of 1832 functions).

PartCodeMatching bytesFunctionsMatching
SLPM_801.65: boot exe348752 B157260 B833 / 1309🟩🟩🟩🟩🟨⬜⬜⬜⬜⬜ 45.1 %
X00.BIN: shared object code293052 B104060 B269 / 523🟩🟩🟩🟨⬜⬜⬜⬜⬜⬜ 35.5 %

Regenerated with python3 tools/build_report.py --version slpm80165 then python3 tools/progress_jp.py --version slpm80165. python3 tools/demo_sweep.py installs the PAL / JP C that matches demo functions as is (see docs/JPDEMO.md).

<!-- slpm80165-progress:end -->

Versions

Every known retail, demo and prototype disc (serials, dates, executable hashes): wiki: List of builds.

VersionSerialStatus
Tombi! PAL (Spanish)SCES-01331matching, everything above
Ore! Tomba (Japan)SLPS-01144split + shared C, matching in progress (figures in the table at the top and in "Japanese release progress"), see docs/JP.md
Ore! Tomba demo (Japan, taikenban)SLPM-80165split + shared C, 40.9 % of code (objdiff; figures in "Japanese demo progress"), tools/demo_sweep.py installs what PAL / JP C matches as is, see docs/JPDEMO.md
Ore! Tomba beta 1998-02-03 (Japan)SLPS-01144 (prototype)split + shared C, 56.5 % of code, see docs/BETA_980203.md
Tomba! PrePre 8 (Japan demo)PCPX-96084split + symbol pairing, 17.1 % of code, see docs/PREPRE8.md

All versions live in this repository and appear on decomp.dev as versions of one project: the CI uploads one <serial>_report artifact per version.

How the versions share code

The repository follows the usual PS1 multi-version layout (as in sotn-decomp and psx_tomba): one src/ tree is compiled for every version, and each version has its own splat configs, symbol tables and asm.

Version--version (old alias)Macrosplat configs / symbolsasmreport
SCES-01331 (PAL, reference)sces01331 (default)VERSION_SCES_01331config/*.yamlasm/<prog>/build/report.json
SLPS-01144 (Japan)slps01144 (jp)VERSION_SLPS_01144config/slps01144/asm/slps01144/<prog>/build/slps01144/report.json
SLPM-80165 (Japan demo)slpm80165 (jpdemo)VERSION_SLPM_80165config/slpm80165/asm/slpm80165/<prog>/build/slpm80165/report.json
  • The C keeps the PAL names. config/<serial>/symbol_addrs_<prog>.txt maps PAL names to that version's addresses (tools/jpdemo_syms.py); names that only exist in a version carry the serial (func_8011F5AC_slps01144).
  • The build defines the version macro (tools/ncheck.py EXTRA_CPP; tools/build_report.py --version <v> sets it). INCLUDE_ASM_V("<prog>/nonmatchings/<prog>", name) (include/tomba/include_asm.h) resolves against the asm dir of the version being built.
  • A version function the PAL C matches as is just reuses the PAL file (pairing from the symbol tables and config/<serial>/reuse.txt). A function that differs gets a branch in the PAL file, plus a // VERSION <SERIAL> <prog> <addr> <size> header line (and optional // FLAGS.<SERIAL> / // CC.<SERIAL> lines):
    // FUNC 800ef490 84 X000
    // MATCHING 800ef490 84
    // VERSION SLPS_01144 x00 800EF5FC 84
    #if defined(VERSION_SLPS_01144)
    ... Japanese C, or INCLUDE_ASM_V(...) while it is not matched ...
    #else
    ... PAL C ...
    #endif
    
  • Version-only functions (the demo's stubs and debug code, JP-only helpers) live in the shared tree too, next to the PAL files of the same program, as <name>_<serial>.c whose whole body is inside #if defined(VERSION_...).
  • tools/version_share.py writes these blocks: fold <version> <file.c> folds a standalone version file into the shared tree, asm <version> adds the INCLUDE_ASM branches for the PAL files whose version counterpart is not matched yet. The PAL build is unaffected (it never defines the other macros).

Releases

Releases of the port are published by hand by .github/workflows/release.yml (Actions > "Publish release" > Run workflow, or gh workflow run release.yml) once the owner approves; there is no automatic release.

  • Versions are vX.Y.Z: tag v0.0.N, title "Tombi port 0.0.N", one release for every platform: tombi-port-0.0.N-windows-x64.exe, tombi-port-0.0.N-linux-x64.tar.gz and tombi-port-0.0.N-android.apk (one APK with both arm64-v8a, the 64-bit wasm2c build, and armeabi-v7a; Android picks the one the device runs), optionally Tombi-Tools-0.0.N-... (translation editor, graphics editor, assets extractor). Each release bumps the patch of the latest v* tag and is marked Latest; the notes list the player-facing commits since the previous v* tag (tools/release_notes.py).
  • To bump the minor/major version run the workflow with the input version (e.g. 0.1.0; must be X.Y.Z and greater than the latest): gh workflow run release.yml -f version=0.1.0. Later automatic releases continue from it (0.1.1, ...).
  • The old build-YYYYMMDD-<sha7> releases are kept as pre-releases titled "dev build YYYYMMDD-<sha7>" (gh workflow run release.yml -f migrate_dev=true re-applies that without publishing anything).

Progress report (objdiff / decomp.dev)

tools/build_report.py produces the objdiff report that decomp.dev displays: splat splits the retail binaries into one assembly file per function (config/main0.yaml, config/x000.yaml, config/x0nn.yaml for the 16 area overlays), each function is assembled as the target object, each src/*.c and src/x0nn/*.c is compiled as the base object, and objdiff-cli compares them (build/report.json). The report and docs/PROGRESS.md count exactly the same bytes: both take their unit list from tools/units.py (every matched source file as one unit, plus the csv functions no file covers), game code only (the Psy-Q library is left out unless --with-sdk is given) and every shared overlay function counted once, so the decomp.dev headline is the whole-game figure to the second decimal. The GitHub Actions workflow .github/workflows/report.yml runs it on every push; it needs a GAME_FILES_URL secret (and GAME_FILES_URL_JP / GAME_FILES_URL_JP_DEMO for the Japanese release / demo) pointing to your own disc image: a direct, Dropbox, Drive or MEGA link to the image or to a zip of it (tools/ci_download.sh + tools/disc_unpack.sh; the downloads stay in the Actions cache), or a zip with just MAIN0.EXE and AREA00/X000.BIN, which limits the report to those two.

Units are marked complete (decomp.dev "fully linked", complete_code) when their object is linked into MAIN0.EXE / X000.BIN by tools/build_full.py and matches 100 % of its code. Overlay units are not (the overlays are not yet linked from source), and no data is claimed: .data/.rodata still come from the retail binary via splat.

Repository layout

src/            matching C, one function per file (MAIN0 and X000)
src/x0nn/       matching C of the other area overlays (X001..X019), mostly generated twins of src/
src/wip/        unfinished attempts (not counted as progress)
include/        shared headers (tobj.h: game object struct)
include/tomba/  headers from psx_tomba (see THIRD_PARTY_NOTICES.md)
notes/          memory maps, structures, function lists and names, guides
docs/           generated progress report and charts
ghidra/scripts/ Ghidra scripts (headless pipeline, struct and name import)
config/         splat configurations for MAIN0.EXE and the X0nn.BIN overlays
.github/        CI workflow that publishes the objdiff report
tools/          checkers, progress report, analysis helpers, setup scripts
game/           your own game files (ignored by git)
game_jp/        your own Japanese disc files (ignored by git; game_jpdemo/ for the demo)
config/slps01144/  splat configs and symbol tables of the Japanese release (config/slpm80165/: the demo)

Tools

ToolPurpose
tools/ncheck.pyFast native compile-and-compare (--score, --asm, --mark)
tools/matchcheck.pyReference compile-and-compare with the original PSY-Q binaries
tools/build_full.pyFull build of MAIN0.EXE and X000.BIN, byte-for-byte check against the retail files
tools/fn.pyDisassembly (capstone) and Ghidra pseudo-C of one function
tools/areas.pyArea overlays: function boundaries (bounds), copies of matched twins (twins)
tools/progress.pyRegenerates docs/PROGRESS.md and the SVG charts
tools/units.pyThe unit list shared by progress.py, gen_symbols.py and build_report.py (prints the totals)
tools/classify.pySeparates complete functions from Ghidra fragments (notes/todo_match3.csv)
port2/tools/audit_pointers.pyNative port: static audit of the retail code pointers of every image the game loads (MAIN0's data, X000, every area, the SYSTEM overlays, the LD0* load lists), walked as the port's relocator walks them: function starts with native code, starts without (stubs), labels inside functions (jump tables vs handler entries), pointers to code the function csv lacks (gap), data pointers that would become another overlay's thunk, pointer tables inside code ranges, the round constants of the C that fall in code; needs build/port2/gen (docs/PORT.md "Pointer audit")
port2/tools/sweep_areas.pyNative port: runs every room of the debug select headless (--skip-movies --poke 900:1F8001B4:1, area/section poked, generic walk/jump/attack pad script) and greps each log for faults, no native code, stubs, watchdog lines; table per room, --out DIR keeps the logs
tools/fixbounds.pyDetects functions Ghidra split incorrectly
tools/permute.pySmall random permuter (CPU only) for near-misses
tools/ghidra_pipeline.shHeadless Ghidra: import, apply names and structs, export pseudo-C
tools/setup_ghidra.sh, tools/setup_native.shToolchain installers
tools/psxexe_info.pyPrints the PS-X EXE header
tools/wfm.pyAssets: WFM font/dialogue files <-> YAML + glyph PNGs, byte-exact round trip (369/369) through a TombaTools fork
tools/wfm_py.pyAssets: pure-Python WFM codec (no Go): dialogues as readable text with tags ({NL} {CLEAR} {WAIT} {BOX w h} {PAUSE n} {COLOR n}...), glyph table, byte-exact round trip on 369/369 files (--check); characters come from tools/wfm_charmap.json (bitmap hashes, made by wfm_charmap_gen.py)
tools/text_editor.pyTranslation editor (Qt/PySide6; catalog in text_catalog.py, checks of check_text_patch.py, box preview of text_preview.py): every text a translation patch can change by category (dialogues, signs, system messages, item names/descriptions, event titles/list names/hints, places, memory card, title/options and pause menus), disc original / improved translation / your text side by side, the other PAL languages and the aligned Japanese line (jp_text.py), live checks, Install in the game (compiles translation.tpatch with tpatch_build.py from your disc and copies it next to the game); Windows build tombi-text.exe (Releases)
tools/gfx_editor.pyGraphics editor (tkinter; logic in gfx_core.py): open the disc image, browse screens, the UiSpriteDef table of MAIN0 (NOW / LOADING), VRAM pages per load list, WPP sprite frames and CLUT palettes with their real colours, rectangle overlay, export/import indexed PNG (RGB(A) PNG is quantised to the palette), palette editing (15 bit, shared-palette warning), undo, in-context preview, composed room views (tree node "Vista compuesta": every room as the game draws it + the sky/far background alone, captured on demand by running the game in tools/psxemu and cached in the user cache folder; needs the .bin image and numpy), save/load mod (mods/<name>/mod.json + PNG, the mod.py format), Build ISO; UI language selector (i18n.py); Windows build tombi-gfx.exe (Releases) (bundles mkpsxiso). Tests: tools/test_gfx.py
tools/tombi_save_editor.pySave editor (Qt/PySide6, PKHeX-style; library + CLI tombi_save.py, tests test_tombi_save.py): memory card images (.mcd/.mcr/.gme/.vgs/.mcs, the port's tombi_mc0.mcd), AP, vitality/HP, lives, exp levels, items/inventory, the 130 events (not started/started/completed, cut ones marked), play time, area; checksum rewritten, Save as / .bak. Names read at run time from your disc (English/Spanish/...). Format: docs/SAVE_FORMAT.md, guide: docs/SAVE_EDITOR.md; Windows build TombiSaveEditor.exe (artifact of save-editor-exe.yml). Editor de partidas: AP, vida, objetos, eventos y tiempo de las partidas de la tarjeta de memoria.
tools/gam_py.pyAssets: pure-Python GAM encoder (the decoder is area000.gam_unpack), valid streams, original bytes kept for unchanged data; used by mod.py and the graphics editor so no Go is needed
tools/unnamed.pyNames: exports to notes/unnamed.json (committed, derived from src/) every function that still has a generic name (func_8xxxxxxx / FUN_8xxxxxxx) in MAIN0, X000, the SYSTEM overlays (D0SPSUB*, O0PTSUB0, I0NFO*, G0OVER) and the own functions of the area overlays X001..X019: address, program, file, size, the // probably: note, callers, the object table slot (pointer scan of MAIN0/X000) and, when it resolves as the "Animaciones" node does (animobj.py), the SPR table / frames of the handler whose scripts reach it. The graphics editor shows the list under Sin nombre / Unnamed (names_panel.py: "Probablemente" = the functions with a note, to review, and "Resto"; note, callers, area, sprite frames and the player for linked scripts; a Nombre + comment per entry, Exportar nombres / Importar names_user.json, autosaved in the TombiTools config folder)
tools/apply_names.pyNames: applies a names_user.json of the editor ([{address, prog, old, new, comment}]) with the mechanics of the names: commits: moves src/<old>.c to src/<new>.c (+ the twins src/x0NN/), the symbol in sources and callers, notes/functions_*.csv rows (twins by X000:addr), notes/names_main0.csv / names_x000.csv (media, the comment), a # User names line in notes/names.md, label keys, and every other mention under src/ include/ notes/ tools/ port2/ docs/ (SYSTEM overlay names only inside their folder, the rest is listed); validates the identifier, refuses collisions, --dry previews. Then tools/build_full.py and regenerate notes/unnamed.json
tools/i18n.pyUI languages of the Windows tools (es, en, fr, de, it, nl, pt, sv, da; the port launcher is complete in all nine, port_launcher_lang.py): catalogs, system language detection, choice saved in %APPDATA%/TombiTools/settings.json
tools/gam.pyAssets: GAM (the game's LZ) unpack/pack; --check runs the whole round trip (816 files)
tools/mkdisc.pyAssets: rebuilds the .bin/.cue from game/disc/fs with mkpsxiso, SHA-1 identical to the original (--check)
tools/cdfiles.pyAssets: the LBA/size file table inside EXE/MAIN0..8.EXE (dump, check against an image, update from a new layout); used by mkdisc.py build --relayout
tools/area000.pyAssets: AREAxx/*.000 and SYSTEM/*.000 (uncompressed VRAM images, WPP/WMD tables, APD grids): info, unpack (+PNG views), pack, --check (286/286 identical); format in notes/formats.md
tools/room.pyAssets: room models (WMD, kind 0x50): flat PNG render (AREA00:1 out.png, --all) and 3D glTF export (--glb AREA00:1 out.glb, --glb-all DIR; texture atlas, vertex colours; opens in Blender / Windows 3D Viewer); also written to rooms/ and rooms3d/ by extract_sprites
tools/anim.pyAssets: Tombi's animations: decodes the 90-entry animation table (overlay 0x800E8404 -> scripts in MAIN0 data) into a catalogue (id, name, frames, ticks, loop; 82 distinct scripts, 36 named), list [--json], export OUT (animated GIF + PNG strip per animation + catalog.json; also step animations of extract_sprites); preview window with play/pause/step/speed/loop/zoom/GIF export in the graphics editor (node "Animaciones", anim_player.py)
tools/wpp.pyAssets: WPP sprite frames (flag-byte RLE, 4 bpp): info, unpack (indexed PNG with the Tombi CLUT), pack, --check (40/40 files identical); area000.py page exports room pages with their CLUT
tools/vram.pyAssets: VRAM simulation of every load list (GRPX pages, CLUTs, GAM images at their LoadImage position) and the SPR frame definitions cut out of it (--all OUT: atlas parts, composed frames, flat pages, 1024x512 snapshots; snapshot AREA00:1 x.png; --stats)
tools/sprclut.pyAssets: per frame texture page offset and CLUT of SPR frames as the game code sets them: item descriptors of initItemObject (DAT_8007B14C: tpage offset w1e, CLUT override w08 when b0d & 1, money bag palettes) and the page that owns a palette strip; used by vram.py (items lists them)
tools/psxemu/clutmap.pyAssets: runs the game in tools/psxemu (title, memory card screen, first room, pause menu, item menu, map), records every textured primitive and resolves texels and palette to disc files; writes tools/ui_cluts.py (generated table, only coordinates/file names), which vram.py / screens.py use for HUD, menus and fonts drawn by code
tools/audio.pyAssets: sound, pure Python: .WVD banks -> every VAB sample as WAV (PS1 ADPCM, loop points as smpl chunk) + tone table, .SEQ (pQES) -> MIDI type 0 (kept in sound/music, useful for editing), software sequencer (numpy) that renders each SEQ with its bank (mapping in SEQ_BANK) to stereo 44.1 kHz audio in sound/music_audio (loop played twice, fade out), XA audio of the .STR movies -> WAV (bit-exact vs ffmpeg); `--audio-format wav
tools/str.pyAssets: .STR movies: demux + pure Python MDEC decoder (BS v2/v3, matches ffmpeg at about 49 dB): info, frames (PNG sequence, ~0.2 s/frame), mp4 (needs ffmpeg in PATH), avi, all (AVI + mp4 when ffmpeg exists)
tools/extract_sprites.pyOne-shot: every graphic of the game as PNG straight from a retail disc image (WPP frames, SPR atlas cuts, pages and VRAM snapshots; no dependencies) plus sound (sound/: WAV, MIDI) and movie AVI/audio (video/); choose what to extract: window with checkboxes per group (Sprites, Screens, VRAM pages, Rooms PNG/3D, Sound banks, Music MIDI, Rendered music, Movie audio, Videos; selector WAV / MP3) or --only sprites,rooms3d,... and `--audio-format wav
tools/mod.pyAssets: mods (mods/<name>/mod.json + PNG patches + mods/load_order.json) applied to a copy of game/disc/fs (GRPX pages/CLUTs, WPP frames; GAM re-packed), then mkdisc.py build --relayout; layout idea from TombaPatcher (US)
tools/overlay_data.pyAssets: data layout of the area overlays X0nn.BIN (summary, dump, header, strings), see notes/overlays.md

Assets. wfm.py, gam.py and mkdisc.py work on your own disc (game/disc/Tombi.bin and the files extracted to game/disc/fs, never committed) and call outside tools: a PAL fork of TombaTools (Go, not vendored; TOMBATOOLS defaults to /opt/psyq/tombatools-pal) and mkpsxiso/dumpsxiso 2.30 (MKPSXISO_DIR). wfm.py --check gives 369/369 identical WFM; gam.py --check reports how many of the 816 GAM files the re-compressor reproduces (743 by itself, 765 with its second variant, 816 when the original bytes are kept for unchanged data); mkdisc.py --check rebuilds the image in /tmp and compares its SHA-1. Details in notes/assets_tombatools.md.

Windows exes (the port is tombi-port-<ver>-windows-x64.exe of the latest release, the tools are in Tombi-Tools-<ver>-windows-x64.zip of the releases that include them; older versions stay on the Releases page; tombi-port.exe is the native port with its launcher; built by GitHub Actions on every change, no Python needed; your own disc image is required, nothing of the game is inside them; the native port extracts it (.bin/.cue/.iso, or a MAME .chd: tools/chd.py) once into a data/ folder and runs from that, see docs/PORT.md "Running without the disc image"):

  • tombi-assets.exe: sprite/sound/video extractor (window with checkboxes).
  • tombi-text.exe: translation editor (Spanish / English interface, light / dark theme). Open your Tombi.bin (or the extracted data/ folder) and start a New translation (from the disc text, or, in Spanish, from the bundled improved translation) or open one. The left tree lists every text the patch system can change by category: dialogues (by area), signs, system messages, item names (dialogues and inventory) and descriptions, event titles, event list names and hints, places (save list, travel list), memory card screen, title / options menus and pause menus. Each entry shows the disc original, the improved translation and your text side by side; search and filters (only changed, only untranslated, different from the improved one, with errors). Other languages shows the same entry in every language of the disc; Japanese reads the Japanese disc (Ore! Tomba) and shows the JP line aligned with it, with the PAL English line as its translation (tools/jp_text.py; JP lines with no PAL counterpart are a category of their own; no online service is used). While you type, the validator checks the entry (missing glyphs, box overflow, lines/pages, control codes, room in the menus) and the box is drawn as the game draws it. Save writes your *.tombipatch.json; Install in the game… compiles it into translation.tpatch (from your disc, a few minutes) and copies translation.tombipatch.json + translation.tpatch next to the game's executable: the port loads it by itself and it wins over the bundled improved translation (docs/PORT.md "Your own translation file"); the launcher's Import translation… does the same from any folder, Android imports the .tpatch (Options → Data → Import translation). More: compile only, build a patched ISO (needs mkpsxiso, bundled), export/import the dialogues as text. Command line of the patches: python tools/text_patch.py make|apply|merge|info (make --ppf for a PPF3), python tools/tpatch_build.py DISC.bin|data/ X.tombipatch.json -o X.tpatch, python tools/data_patch.py apply data X.tombipatch.json.
  • tombi-gfx.exe: graphics editor. Open Tombi.bin; the tree on the left has Screens, Interface/HUD (the three plausible UiSpriteDef entries of MAIN0: 0 unused, 1 NOW, 2 LOADING), Pages by load list, Sprites (WPP frames) and Palettes (CLUT). The middle shows the page with the palette chosen in the right-hand list (x1..x8, optional sprite rectangles; click one or drag a region). Export/Import PNG work on the whole image or the selection: an indexed PNG with the same palette is used as is, an RGB(A) PNG is converted to the nearest palette colour (transparent = index 0 / word 0x0000) with a warning when it has more than 16/256 colours. Clicking a palette swatch opens a colour chooser (15-bit colours, STP bit kept; a warning lists the other images that share the palette). Save mod writes mods/<name>/mod.json + PNGs that python tools/mod.py build --mods <name> also applies; Build ISO writes a new .bin/.cue (about 1.5 GB free; without mkpsxiso or with a folder it writes the changed files only).
  • All three exes have an Idioma / Language selector (Español, English, Français, Deutsch, Italiano, Português; default = system language, saved in %APPDATA%/TombiTools/settings.json) (Linux: ~/.config/TombiTools/settings.json).

Linux binaries (tombi-port-<ver>-linux-x64.tar.gz / Tombi-Tools-<ver>-linux-x64.tar.gz of the releases, executable bits kept, built by the same workflows on ubuntu-24.04, same data policy: nothing of the game inside, except that tombi-port embeds the MAIN0/X000 bytes exactly like tombi-port.exe): tombi-port (launcher with the game tombi-pc and a bundled 32-bit libSDL2-2.0.so.0 found through rpath $ORIGIN; Ubuntu's build links ALSA/PulseAudio/X11 directly, so the 32-bit SDL2 package below is still needed), tombi-text, tombi-gfx, tombi-assets (PyInstaller one-file; text/gfx bundle mkpsxiso/dumpsxiso built from source). The game tombi-pc is an i386 program (port2 builds with -m32), so a 64-bit distro needs the 32-bit runtime: on Debian/Ubuntu sudo dpkg --add-architecture i386 && sudo apt install libc6:i386 libsdl2-2.0-0:i386 (the latter pulls the 32-bit X11/Wayland/GL libraries SDL2 loads at run time); Fedora: glibc.i686 SDL2.i686. The launcher window (Qt, 64-bit, bundled) needs the X11 cursor library many desktops already have: libxcb-cursor0 (Fedora xcb-util-cursor).

Releases. Every push only builds (the binaries stay as Actions artifacts bin-<platform>-<tool>). .github/workflows/release.yml publishes a release build-YYYYMMDD-<sha7> from the newest green port build (tombi-port-<ver>-windows-x64.exe, tombi-port-<ver>-linux-x64.tar.gz): on request (GitHub > Actions > "Publish release" > Run workflow, or gh workflow run release.yml; tick include tools, or -f include_tools=true, to add Tombi-Tools-<ver>-windows-x64.zip / -linux-x64.tar.gz with tombi-text, tombi-gfx and tombi-assets), or automatically after a port build once master has 5 non-CI commits (subject not starting with ci:/dist:) since the last release.

Related projects

  • psx_tomba — decompilation of the NTSC-U release (SCUS_942.36) with a full splat/objdiff build. Its headers, names and some functions are reused here and re-verified against the PAL executable; see THIRD_PARTY_NOTICES.md.

License

Source code and documentation are released under the MIT License. Third-party material is listed in THIRD_PARTY_NOTICES.md.

Legal

This project contains only original source code written to reproduce the behaviour of the game, plus documentation. Tombi! is © Whoopee Camp / Sony Computer Entertainment. No copyrighted game data or SDK files are distributed.

Read official guide
CALIFICACIÓN
—
Loading votes…
Advertisement

PROJECT FILE

Project type
NATIVE PORT
Genre
Platformer
Status
alpha
Original platform
PlayStation
Technology
Native C/C++ port (engine reimplementation)
Version
—
Updated
—

Target platforms 3

Windows
Linux
macOS
View on GitHub
Advertisement