Tooling
Two kinds of tools live here. The first are for you: point them at your own Legend of Legaia disc and they extract every asset, re-roll the game from a seed, or translate its text. The rest are the project's reverse-engineering instruments - the Ghidra, emulator-capture and measurement tools the format docs and engine port are built with. Pick a task below and follow the card.
Pick a task
Every tool takes a disc image you supply (the project ships no game data); most read the extracted/ tree the extraction pipeline produces. The groups are in arrival order: get the assets off the disc, then either patch the game or dig into how it works.
- Extract assets - turn the disc into PNGs, meshes and audio; the starting point for everything else.
- Patch a disc - randomizer, ROM patcher, language packs. Shipped deliverables, not side effects.
- Dig in Ghidra - the static path: disassemble the executable, recover the RAM overlays, answer "who calls this?".
- Capture the emulator - the dynamic path: watch the retail game run and record what the code actually does.
- Measure the port - how much of the game is understood, how much is ported, and whether the port matches retail.
- Ship a release - how the per-platform download archives are built from a tag.
- Beyond Legaia - how reimplementation compares with decomp and recomp, how its fidelity is measured, and what carries to other PS1 games.
- More tooling docs - CI gates and triage ledgers, on GitHub only.
Extract assets
Almost everything on the disc lives inside one archive, PROT.DAT, as containers within containers. One command unpacks the whole chain.
./legaia-extract "/path/to/Legend of Legaia (USA).bin" --out extractedextracted/ and where to look next.legaia-engine export-glb writes a scene's textured world, NPCs and animated props as .glb files with a placement manifest, ready for Unity or VRChat. export-glb writes: the world, NPC and prop files, equipment items and the party, the manifest's placement arrays, and the frame and winding the files share.Patch a disc
The patcher edits a copy of your disc in place - same-size edits, sector checksums re-encoded - so the result is an ordinary disc image that boots on an emulator or a console. Nothing Sony-owned is ever committed; you bring the disc.
legaia-patcher translate exports a disc's text as an editable YAML pack and applies a filled pack back as a same-size patch. Built for community translations.Dig in Ghidra
The static path. Ghidra runs headlessly in Docker, driven by scripts; every FUN_80xxxxxx on this site is a function it labelled. One fact shapes all four pages: most of Legaia's game logic lives in overlays - code the game pages into RAM on demand - not in the executable, so "no static caller" is a question, not an answer.
exec workflow, the script catalogue, the LUI+ADDIU workaround, and the decompiler artifacts that have produced false claims.PROT.DAT at its recovered load address, so an overlay's identity comes from the disc rather than a guess. Reproducible from any copy of the disc.Capture the emulator
The dynamic path: load a save state, arm a watch or breakpoint, run a few frames, read back what happened. PCSX-Redux brings a scriptable breakpoint debugger; mednafen brings cheap save states whose RAM can be diffed.
Measure the port
How much of the game is understood and reimplemented (port catalog, disc coverage), how much of one entry's bytes a parser really consumes (byte accounting), whether the port matches retail (the retail comparison corpus against save states, the differential oracle frame by frame), whether it matches itself run twice (determinism), whether it plays from New Game to the credits (the full-game ladder), and what breaks when nobody plays it sensibly (the soak harness).
// PORT: tags in the crates, with feature views and a regenerable open-work dashboard.// PORT: address can name the wrong routine while every other gate passes. Ranked signals, a ratchet on unwaived findings, and a differential as the proof.trace_diff.py reports the first frame each channel diverges.legaia-engine record / replay: capture your inputs to a small text file and play them back frame-perfectly. Same input twice → bit-identical state trace.dev-fast profile, the wasm --fast path, where a test run goes, and full-gate.py, the end-of-work gate as one command.Ship a release
How the downloadable archives are built: one per platform from a pushed tag, programs and licenses only.
Beyond Legaia
Legend of Legaia has been ported twice over the same months: a static recompilation, which translates the executable and models the hardware, and this project, a new engine in Rust written from the format docs and the traced disassembly. The case study puts them side by side, with decompilation as the third route: what each gets up front, what it has to earn, and why reimplementation is more practical than it used to be. A recomp gets you the game. A reimplementation, plus the oracles that measure it, gets you an engine for the game.
- What it gets you
- No PS1 machine model underneath, so the game draws through a wgpu renderer, builds to WebAssembly for the browser, runs in VR, and takes enhancements such as lighting, fog and SSAO with the retail-faithful mode one toggle away (fidelity and enhancements). Modding works by game concept: an expansion pack is TOML naming monsters, maps and quests, and patcher features proved against retail graduate into engine toggles. And the rules are readable: each one is a named Rust kernel carrying the retail address it was read from.
- How fidelity is kept
- Measured, not inherited, since nothing of the original runs. The retail comparison corpus scores the engine against several hundred retail save states; the cast-sequence oracle and the walk film record a cast or a walk on both sides frame by frame; the audio sweep, real retail PCM and the films' cue ratchet score the sound; the retail kernel differential runs a retail routine off your disc in an R3000 interpreter beside its Rust port; the full-game ladder plays New Game to credits on pad input. Beside those, the port is held to itself: the browser page against the native window on one pad script, the same input twice, and counted frame costs. A committed score is a ratchet that may only rise, and a fall needs its reason on record. The coverage map lists every instrument by the gap it closes and what it cannot see.
- What it costs
- Fidelity is a long tail, paid continuously: each divergence has to be found, read and fixed, and what the corpus has not seen is unmeasured. The work rests on decompilation-grade analysis of the disassembly, routine by routine; it does not replace it. And the measuring instruments are a project of their own.
The pages below make the case at length and hand over what carries. The disc reader, the Sony-format parsers, the GTE and SPU models and the emulator save-state readers describe the PlayStation, not this game, and the measurement method serves any route: a decompilation or a recomp can use the same oracles as a regression check.
More tooling docs (on GitHub)
These live in docs/tooling/ only: the parity and playthrough instruments, the CI gates that keep a measurement honest, and the per-row triage ledgers behind the port catalog.
REAL / INTERIOR / SHARED_TAIL / DUPLICATE / VA_ALIASED / ...), so the worklist reads as work rather than addresses.NOT WIRED that the audit finds live: a stale tag or a false edge, the name-resolution mechanisms that invent edges, and the fix each takes.jal target is a property of the bytes, not the load base - and the one dump window whose targets are therefore untrustworthy.0x801C**** / 0x801D**** band: which image and VA a printed address's bytes really occupy, and whether that VA is a function entry.pkill -f matching itself, grep's no-match exit 1 read as failure.#anchors that must resolve.legaia-patcher, module by module: what each randomizer, slider, hook and replacement reads and writes, and the invariants it holds.Disc-gated tests
Tests that touch a real .bin run only when LEGAIA_DISC_BIN is set; without it they skip and pass, which is how CI runs. Source builds put the binaries under target/release/.
LEGAIA_DISC_BIN="/path/to/Legend of Legaia (USA).bin" cargo test --workspace