Asset extraction
Point one command at your own Legend of Legaia disc image and get back every texture as a PNG, every 3D model as an OBJ, every sound bank and voice line as a WAV - the game's whole asset library as ordinary files. The project ships no game data, so the tools always run against a disc you supply.
What this solves
Almost everything on the disc lives inside PROT.DAT, one archive of 1233 numbered entries, and most entries are containers within containers: compressed streams, texture packs, mesh bundles, sound banks. Nothing on the disc is a file you can open directly. The extraction tools walk that whole chain so you never have to. Use them to browse the art, models and audio; to feed the engine, the asset viewer and the site's 3D pages (all read the extracted/ tree); or to drive one stage while reverse-engineering.
At a glance
- Input
- A raw Mode2/2352
.bindump, or a.cueresolved to its BINARY track. - Output
./extracted/(gitignored): PROT entries, streaming sub-assets, PNG / OBJ / WAV exports, TIM catalogs, dialog font.- Driver
legaia-extract(crates/extract) runs every stage; each stage is also its own binary.- Stages
- verify → disc → PROT → categorize → streaming extract → PNG → CD-XA demux → TIM catalogs → dialog font.
- Consumers
legaia-engine,asset-viewer, the web viewer, Ghidra (mountsextracted/read-only).- Tests
- Disc-gated on
LEGAIA_DISC_BIN; skip and pass without it.
Quick start: the one-command pipeline
./legaia-extract "/path/to/Legend of Legaia (USA).bin" --out extractedtarget/release/; legaia-extract chains them.| Flag | Skips |
|---|---|
--skip-verify | SHA verification of the disc's key files. |
--skip-png | Streaming-container TIM → PNG (a handful of files; the bulk inventory is the catalogs). |
--skip-xa | The CD-XA audio demux (voice lines, streamed audio). |
--skip-catalog | prot_tim_catalog.tsv + prot_tim_deep_catalog.tsv. |
--skip-font | The font/ dialog-font artifacts. |
What lands in extracted/
| Path | Contents |
|---|---|
PROT.DAT CDNAME.TXT SCUS_942.54 | raw archive, entry name map, executable |
PROT/####_<name>.BIN | one file per entry - its whole sector footprint - plus categorize.json |
streaming/####_<name>/chunk##_<TYPE>/ | TMD chunks split into numbered meshes, TIM lists (+ PNG), opaque blobs (MAN, MES, MOVE, VDF) |
prot_tim_catalog.tsv prot_tim_deep_catalog.tsv | flat TIM inventory; TIMs inside LZS-compressed sections |
font/ | dialog-font atlas, width table, metadata |
XA/ | raw Form-1 dumps - truncated, not listenable |
XA_WAV/XAn_fileN_chM.wav | correctly paced per-channel voice / streamed audio |
Getting all the textures
The PNG step covers only the few TIMs the streaming containers carry. For the full set, export the catalogued hits and convert them - the two commands the pipeline's summary prints:
asset tim-scan extracted/PROT --out extracted/tim_scantim convert-dir extracted/tim_scanDetails: why voice audio is demuxed from the raw disc
The ISO walk writes *.XA as Form-1 files, which truncates every Form-2 audio sector (2324 → 2048 bytes) and collapses a file's multiplexed channels into one shuffled stream. The CD-XA step instead reads raw 2352-byte sectors and writes one WAV per (file_no, ch_no), at the rate and stereo mode each sector's subheader declares. The NA corpus is 34 files / 316 channels, all 4-bit 37.8 kHz; the decoder is bit-exact.
Per-stage tools
| Binary | Stage | Key commands | Format |
|---|---|---|---|
disc-extract | disc → files | extract DISC.bin extracted/ | disc |
prot-extract | PROT.DAT → entries | extract, list, locate, retail-names | PROT, CDNAME |
lzs-decode | decompress | raw --size N, container | LZS |
asset | classify + sub-assets | categorize, extract, stream, describe, tim-scan, tmd-scan, tim-catalog, tim-deep-catalog | asset type |
tim | texture → PNG | info, convert [--clut N | --all-cluts], convert-dir | TIM |
tmd | mesh → OBJ | info, dump-obj, validate-prims | TMD |
vab | sound bank → WAV | list, extract [--wav] | VAB |
xa | streamed audio → WAV | demux-disc-all, demux-disc --lba --size, info, convert | XA |
mes | dialog containers | info, disasm, json | MES |
mdt | move tables | classify, records, slots | MDT |
font-extract | dialog font | --disc <bin-or-PROT.DAT> | dialog font |
- Entry size is the sector gap. Each
PROT/*.BINholds exactly its entry's sectors, so the files tile the archive with no gaps or overlaps.prot-extract locatemaps any offset to the entry that owns it. - LZS "decodes without error" proves nothing. The ring buffer starts zeroed, so random input decodes to plausible bytes. Magic-check the output.
- Prefer
xa demux-disc-alloverconvert: it takes rate and channel mode from each sector instead of guessing. - Legaia TMDs are a custom variant (magic
0x80000002); a stock PSX TMD tool will not read them.
Details: prot-extract subcommands and the two TIM catalogs
prot-extract extract extracted/PROT.DAT extracted/PROT/ --cdname extracted/CDNAME.TXT
prot-extract list extracted/PROT.DAT --cdname extracted/CDNAME.TXT
prot-extract locate extracted/PROT.DAT 0x17855 --in-entry 866 --cdname extracted/CDNAME.TXT
prot-extract retail-names extracted/CDNAME.TXT
extractalso writesmanifest.jsonand an unpackedtim/<entry>/dir per TIM-pack entry.listprints each size next todecl_span, the superseded over-reading expression, flaggingovrwhere it overshoots;locatere-keys coordinates measured under it.retail-namesshows what the retail loader reads out ofCDNAME.TXT. Its#definenumbers are raw TOC indices, shifted +2 from the extraction filenames.--clamp-footprintis a deprecated no-op.
asset tim-catalog scans the whole PROT.DAT image, strict-validates each TIM and maps it to its owning entry + offset (or the unindexed system-UI gap); it recovers the same set an independent reference decoder reports, and --rollup prints the count + digest a disc-gated test pins. asset tim-deep-catalog decompresses every entry and catalogs the TIMs inside each section, keyed (entry, section, offset); a hit is admitted only when it strict-parses and decodes to RGBA.
Details: full asset and xa command lists
asset categorize <DIR> [--out categorize.json] # per-class breakdown
asset extract <file> --out <out_dir> # streaming chunks -> files
asset stream <file> # walk DATA_FIELD chunks, no extraction
asset describe <file> # container TOC
asset effect-bundle <file>
asset tmd-scan <DIR> # byte-search for TMD magic
asset tim-scan <DIR> # byte-search for TIM magic (lenient)
asset tim-catalog <PROT.DAT> [--out f.tsv|f.json] # strict flat catalog
asset tim-deep-catalog <PROT.DAT> [--out f.tsv|f.json] # TIMs inside LZS sections
xa demux-disc-all <disc.bin> --out extracted/XA_WAV # every .XA -> per-channel WAV
xa demux-disc <disc.bin> --lba L --size S --out <dir> # one .XA by LBA/size
xa info <file.xa> [--channels stereo] [--sample-rate 37800]
xa convert <file.xa> [-o out.wav] # single Form-1 dump (guesses rate)
Browsing the result
Once extracted, browse interactively with the native asset-viewer (the browser equivalent is the web viewer).
| Command | Shows |
|---|---|
asset-viewer prot extracted/PROT.DAT --cdname extracted/CDNAME.TXT | PROT entry browser, format auto-detected per entry |
asset-viewer tim <file.tim> | one texture; --offset 0x… --clut N reaches TIMs in the unindexed gap of PROT.DAT |
asset-viewer tmd <file.tmd | dir> | a mesh, or a directory to cycle (N / P / PgDn / PgUp); --bundle battle pairs the TIMs for correct CLUTs |
asset-viewer vab <entry.BIN> --offset 0xN --sample N | one sound-bank sample |
How we know
The pipeline is pinned by disc-gated tests that run only when LEGAIA_DISC_BIN points at a valid .bin; without it every one skips and passes, so CI needs no Sony data. Do not change that gating.
LEGAIA_DISC_BIN="/path/to/Legend of Legaia (USA).bin" cargo test --workspace| Test | What it proves |
|---|---|
crates/iso/tests/disc_pipeline.rs | Disc walk, file count, key-file SHA-256s. |
crates/iso/tests/ecc_real.rs | Sector write-back keeps EDC/ECC valid. |
crates/prot/tests/archive_tiling_real.rs | Entry footprints tile PROT.DAT exactly - the sector-gap size matches retail's own span routine (FUN_8003E68C). |
crates/extract/tests/validation_suite.rs | Full pipeline: entry count, sub-asset totals, TIM round-trip, catalog digests. |
Find every disc-gated test with grep -rl LEGAIA_DISC_BIN crates/*/tests.