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 .bin dump, or a .cue resolved 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 (mounts extracted/ read-only).

Quick start: the one-command pipeline

./legaia-extract "/path/to/Legend of Legaia (USA).bin" --out extracted
  1. Disc imageISO9660 walk, fingerprint check. disc-extract
  2. PROT.DAT1233 entries, named via CDNAME.TXT. prot-extract
  3. ClassifyEach entry's format class, by magic bytes. asset categorize
  4. DecompressLZS sections, output magic-checked. lzs-decode
  5. Sub-assetsDATA_FIELD chunks split out. asset extract
  6. ExportsPNG, OBJ, WAV, TIM catalogs, dialog font. tim tmd vab xa
disc .binverify SHA-256 ISO 9660 walkcopy every file split PROT.DAT1233 entries categorizeclass per entry streamingDATA_FIELD chunks TIM to PNG--skip-png CD-XA demuxraw 2352-byte sectors XA_WAV/per (file, channel) TIM catalogstwo .tsv inventories dialog fontatlas + width table
What legaia-extract runs. Each later stage reads what an earlier one wrote; the CD-XA branch reads the raw image, because the ISO walk's Form-1 copy truncates every audio sector. Green nodes are outputs, each behind its own --skip-* flag.

Each stage is a standalone binary; legaia-extract chains them. CD-XA audio skips the walk entirely and is demuxed straight from the raw disc (see below).

FlagSkips
--skip-verifySHA verification of the disc's key files.
--skip-pngStreaming-container TIM → PNG (a handful of files; the bulk inventory is the catalogs).
--skip-xaThe CD-XA audio demux (voice lines, streamed audio).
--skip-catalogprot_tim_catalog.tsv + prot_tim_deep_catalog.tsv.
--skip-fontThe font/ dialog-font artifacts.

--verbose prints one line per file written. --all-textures adds one stage: every TIM in every PROT entry, raw or LZS-compressed, written as .tim + .png under tim_scan/ (asset tim-scan and tim convert-dir in one step).

What lands in extracted/

PathContents
PROT.DAT CDNAME.TXT SCUS_942.54raw archive, entry name map, executable
PROT/####_<name>.BINone 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.tsvflat 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.wavcorrectly 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_scan
tim convert-dir extracted/tim_scan
Details: 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

BinaryStageKey commandsFormat
disc-extractdisc → filesextract DISC.bin extracted/disc
prot-extractPROT.DAT → entriesextract, list, locate, retail-namesPROT, CDNAME
lzs-decodedecompressprobe, container, raw --size NLZS
assetclassify + sub-assetscategorize, extract, stream, describe, tim-scan, tmd-scan, tim-catalog, tim-deep-catalogasset type
timtexture → PNGinfo, convert [--clut N | --all-cluts], convert-dirTIM
tmdmesh → OBJinfo, dump-obj, validate-primsTMD
vabsound bank → WAVlist, extract [--wav]VAB
xastreamed audio → WAVdemux-disc-all, demux-disc --lba --size, info, convertXA
mesdialog containersinfo, disasm, jsonMES
mdtmove tablesclassify, records, slotsMDT
font-extractdialog font--disc <bin-or-PROT.DAT>dialog font
  • Entry size is the sector gap. Each PROT/*.BIN holds exactly its entry's sectors, so the files tile the archive with no gaps or overlaps. prot-extract locate maps 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-all over convert: 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
  • extract also writes manifest.json and an unpacked tim/<entry>/ dir per TIM-pack entry.
  • list prints each size next to decl_span, the superseded over-reading expression, flagging ovr where it overshoots; locate re-keys coordinates measured under it - such a coordinate may name the wrong owning entry.
  • retail-names shows what the retail loader reads out of CDNAME.TXT. Its #define numbers are raw TOC indices, shifted +2 from the extraction filenames.
  • --clamp-footprint is 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)

Beyond these, asset carries the analysis subcommands documented on their own pages: asset account (byte accounting), asset overlay (static overlay pipeline) and asset field-op-census (field-op census, GitHub).

Browsing the result

Once extracted, browse interactively with the native asset-viewer (the browser equivalent is the web viewer).

CommandShows
asset-viewer prot extracted/PROT.DAT --cdname extracted/CDNAME.TXTPROT 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 None sound-bank sample

How we know

The pipeline is pinned by 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
  • The disc walk - file count and the key files' SHA-256s.
  • Sector write-back - re-encoded sectors keep valid EDC/ECC.
  • Archive tiling - entry footprints tile PROT.DAT exactly, and the sector-gap size matches retail's own span routine (FUN_8003E68C).
  • The 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.

See also