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 extracted

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.

ROM patcher (in browser)
Run the randomizer client-side: supply your own disc image, toggle the settings, download a patched image. The patch runs in WASM in your tab.
Randomizer and disc patcher
The full CLI reference: loot, encounters, monster stats, shops, doors, arts, the starting party, tuning sliders, and the code-hook features retail has no table for.
Companion to Randomizer
Randomizer: internals
How an edit travels from a PROT entry to a rewritten disc sector, the techniques every feature is built from, and where injected MIPS code is allowed to live.
Companion to Randomizer
Delilas Challenge and party swap
The two Delilas mods from the inside: the Muscle Dome course and its reward, and the battle-model, voice, move-set and cast-route rebuilds that put a Delilas sibling in a party slot.
Translation packs
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.
Translation workbench
Edit a language pack line by line in the browser, with live byte, on-screen-width and scene-fit feedback against your own disc.
Expansion workbench
Build, check and export an expansion pack for the port in the browser: battles, progression, quests, dialogue, cards, modes and a tile painter for custom maps, starting from the sample pack if you like.
Companion to Translation packs
Official PAL localizations
How the official French / German / Italian discs align to the USA coordinate space, and how their text is lifted onto it.

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.

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).

Measuring against retail
Start here: the four instruments side by side - one routine, one instant, a stretch of time, sound - what each reads off retail and what it cannot see.
The verification ladder
Every check in one stack - format docs, port provenance, the kernel differential, stills, films, the full-game ladder - with the question each answers and what it cannot see.
Ratchet integrity
What keeps a ratchet honest: a committed baseline that moved the wrong way needs a ledger entry with a reason, and a disc-gated test that skipped is counted, not passed.
Port catalog
Per-function status - dumped × documented × ported × ignored - from // PORT: tags in the crates, with feature views and a regenerable open-work dashboard.
Port provenance
A // 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.
Field-op census
Disc-wide field-VM opcode census: does any shipped scene issue op X, and where. Its zeros separate "no fixture drives this" from "the disc has nothing to drive it".
Disc coverage
The same question with the denominator taken from the disc: byte-exact coverage of each code image by dumped functions, plus PROT format recognition by bytes.
Byte accounting
One entry at a time, and then all 1233: which bytes any parser here actually consumes, and what shape the rest has - so an unwalked format is a run at the top of a list.
Recomp differential oracle
Frame-tagged parity between the from-scratch engine and a static recomp of retail: both emit the same state trace and trace_diff.py reports the first frame each channel diverges.
Retail kernel differential
Run a retail routine off the disc in an R3000 interpreter beside the Rust kernel that claims to port it, over generated inputs, and compare whole regions of what each wrote.
Determinism + replay
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.
Retail comparison corpus
Seed the engine from every catalogued retail save state and score scene, camera, BGM, party, flags, battle state and the frame against the state's own RAM and VRAM, worst first.
Cast-sequence oracle
The corpus's time-axis sibling: one spell, summon or monster cast recorded on both sides from cast begin to past its Done band, and scored against retail frame by frame.
Field walk film
One field state and one pad schedule on both sides, paired pass by pass: the player, the follow camera, the staging pose and the placements. What a single scored frame cannot show about how a walk unfolds. Reports only.
Gate replay
The port's own pad played on retail beat by beat, lined back up at every state gate: per-beat frame gaps, positions and random draws, with every input the log did not supply counted. Reports only.
Capture coverage
The corpus's denominator from the disc: per scene, monster, enemy attack, player cast, cast module and screen, does some retail state show it - and a ranked worklist of what to capture next.
Host drift
The port ships three hosts on one engine (native window, browser play page, browser minigames), so a feature wired into one is invisible in a diff. The ladder of gates that catches it.
Full-game ladder
New Game to credits as segmented story milestones, each segment driven from its anchor by pad input alone (a scripted walker and fighter press the buttons); a stall names the parked script op, the failing door gates and the unset flags with their disc setters.
Soak harness
Seeded, game-shaped random pad input over every playable scene and minigame - optionally seeded from a library memory-card save, and with a save / load round trip - with panic / softlock / stall detectors and reduced replay repros.
Frame benchmark
What the engine costs a player on a fixed scene set and on the way into it, native and in the browser: frame time, allocations, per-export cost, scene entry. Prints tables, asserts nothing.
Browser frame hash
Did this bundle draw what that bundle drew? The play page stepped tick by tick on a scripted pad, every frame hashed, two bundles compared line for line in retail and enhanced modes.
Build and test speed
The 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.

Decompilation STARTS FROM The executable rewritten as source, function by function PRODUCES Source that rebuilds the game matching: compiles to the retail bytes FIDELITY Fidelity is proved by the byte match, per function GOOD AT Exact source-level truth; source mods; retarget it later Static recompilation STARTS FROM The executable's machine code translated whole into host code PRODUCES The original game, running over a model of the PS1 hardware FIDELITY Fidelity is inherited its code runs, translated GOOD AT Retail behaviour early, with its quirks and timing included Reimplementation this repository STARTS FROM Format docs + traced behaviour the disassembly read, never translated PRODUCES A new engine for the game loads the game data off your disc FIDELITY Fidelity is measured against retail states, ratcheted GOOD AT New renderers, hosts, mechanics; modding by game concept A recomp gets you the game. A reimplementation, plus the oracles that measure it, gets you an engine for the game.
Three routes to a port: what each starts from, what it produces, and where its fidelity comes from.
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.

Worklist classification
Whether a missing-port row is a portable function entry at all (REAL / INTERIOR / SHARED_TAIL / DUPLICATE / VA_ALIASED / ...), so the worklist reads as work rather than addresses.
Live-audit triage
Per-anchor verdicts for the "undisclosed inert ports" the catalog's live audit flags, keyed by address + call site.
Stale NOT-WIRED triage
The sibling ledger: rows tagged 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.
Reach triage
Verdicts for ports that are statically live but never entered by a replayed playthrough - the runtime gap the static audits are silent about.
Call-target integrity
Why a decoded jal target is a property of the bytes, not the load base - and the one dump window whose targets are therefore untrustworthy.
Dump-corpus integrity
A dump's printed addresses depend on its load base; only the header tag is evidence. Census of mis-based and truncated dumps plus the checker.
Phantom print index
Address by address in the 0x801C**** / 0x801D**** band: which image and VA a printed address's bytes really occupy, and whether that VA is a function entry.
Shipped-bundle freshness
The site's WASM bundle is uncommitted build output whose source closure is most of the workspace; a content-addressed stamp says when a local bundle is stale.
Site shell
The static site's app shell across breakpoints, the silent-shadowing traps and their gates (CSS cascade order, split case folds, module syntax in classic scripts), and the page layer's delivery rules.
Shell observer traps
Three shell defects where the observer sits inside what it observes: pipe exit status off the wrong stage, pkill -f matching itself, grep's no-match exit 1 read as failure.
Doc density + link gates
The two pre-commit doc checks: over-long lines and table cells, and relative links + #anchors that must resolve.
Dependency policy
Every third-party input pinned to an exact version and taken only after a publish cooldown: the lockfile, action SHAs, CI tools, the Ghidra image digest, and the RustSec advisory gate.
Patcher module reference
The library side of 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