Three classes

The classification is read off each crate's source, not its README.

Generic PS1
Works on any PSX disc, or any file of that format.
Generic, Legaia quirks
Sony's format or hardware, but the code bakes in something Legaia does. Each quirk is named below.
Legaia-only
Legaia's own containers, data or runtime. A worked example, not a dependency.

The package name follows the class: the standalone generic crates are named psx-* (lib psx_*), everything else legaia-*. The two engine crates with a generic core (legaia-engine-audio, legaia-render-kernels) keep the engine's prefix. The directory under crates/ carries no prefix either way.

Take it as it isany PSX disc, or any file of that format
isoxabytesretail-exec
Take it, and check the quirkSony's format or hardware, with something of Legaia's baked in
timvabseqlzsmdecmednafenpcsx-reduxram-imageengine-audiorender-kernels::gte
Read it as a worked exampleLegaia's own containers, data and runtime
tmdcontainer-formatsprotformat familiesengine-*
The bottom of the crate stack, sorted by how far it carries. The method transfers more completely than any band here.

Depending on a crate

Nothing here is on crates.io. Take a crate as a git dependency and turn off its default cli feature, which adds the command-line binary and its dependencies (for some crates, other Legaia crates):

psx-tim = { git = "https://github.com/AndrewAltimit/legend-of-legaia-re", default-features = false }

Every crate is MIT OR Unlicense. The binaries build with cargo build --release -p <crate>. None embeds Sony bytes, and none needs a Legaia disc to run on another game's files unless noted.

The crates at a glance

CrateClassEntry pointCLI
psx-isoGenericRawDisc::open, iso9660::walk_files, write::patch_file_logicaldisc-extract
psx-xaGenericdemux::demux_raw_sectors, decode, StreamingDecoderxa
psx-retail-execGenericCpu::call, Cpu::hook, Cpu::calls, gte::Gte (the hardware-exact GTE); the Legaia comparisons are its tests alone (method)-
psx-bytesGenericu16_le / u32_le returning Option-
psx-timQuirksparse, parse_strict, decode_rgba8, encode::encode_replacementtim
psx-vabQuirksparse, find_vabs, decode_vagvab
psx-seqQuirksSeq::parseseq
psx-lzsQuirksdecompress, compresslzs-decode
psx-mdecQuirksstr_av::demux_str_av, MdecDecodermdec
psx-mednafenQuirksSaveState::main_ram_structured, gpu::PsxGpu, spu::PsxSpumednafen-state
psx-pcsx-reduxQuirksSaveState::from_path_structural, gpu::PcsxGpu, fixture::SyntheticStatepcsxr-state --game generic
psx-ram-imageQuirksdiff::diff_ram, bisect::bisect_first_bad, prim_pool::decode, GameProfile-
legaia-engine-audioQuirksspu::Spu, Sequencer::new, vab_bind::VabBank::uploadnote-trace
legaia-render-kernelsGeneric modulegte::Gte, gte::gte_divide, vram_capture-
legaia-tmdLegaia-onlyparse (Legaia variant)tmd
legaia-container-formatsLegaia-onlyparse_streaming, pack; tim_scan is generic-

Four more generic modules sit inside Legaia crates: the memory-card walker legaia_save::card, the PPF 3.0 writer and applier legaia_disc_patch::ppf, the R3000 encoders and small simulator legaia_code_hooks::{mips, mips_sim}, and the GameShark and Mednafen .cht parsers in legaia-cheats. For disc-patch and code-hooks, which depend on Legaia format crates, copying the module is lighter than depending on the crate.

Generic PS1 crates

psx-iso

A raw Mode 2/2352 .bin (or .cue) reader, an ISO9660 walker that resolves names the way the console does (case-insensitive, ;1 stripped), and the write side: EDC and P/Q ECC re-encode by the ECMA-130 math, and patch_file_logical to rewrite bytes inside a file. Four things name Legaia:

  • extract_prot_dat, extract_cdname_txt and extract_scus are shorthands for Legaia's root files; use read_file_in_image or find_path_in_image.
  • disc-extract verify knows only Legaia's image hash; pass --expected.
  • relayout::grow_prot_dat grows one file and renumbers every reference after it. Its safety rests on a fact proved for Legaia only - no executable seeks a later file by absolute LBA (relayout). Check another game's executable first.
  • grow_prot_dat_by_name looks up PROT.DAT by name.

psx-xa

CD-XA ADPCM in 4- and 8-bit, mono and stereo, decoded bit-identically to jPSXdec's reference. The one Legaia-shaped helper, demux_disc_all, visits only files named *.XA; for XA interleaved in .STR files or an archive, feed demux_raw_sectors the raw sectors.

psx-bytes

Bounds-checked little-endian readers returning Option; no dependencies.

Generic crates with Legaia quirks

CrateThe quirkWhat to do on another game
timVram::upload_tim lands a CLUT block as a linear run along row fb_y (palette i at fb_x + 16*i), as Legaia's loader does - not the rectangle a stock LoadImage writes.Upload CLUTs with Vram::write_block until a capture shows the loader's layout.
vabLegaia carries every VAB as two chunks of a stream, with a 4-byte header between the header part and the bodies.parse on a standalone VAB; concatenate split .VH / .VB files first.
sequ32 big-endian version (15-byte header); meta events with no length byte (FF 51 tt tt tt, FF 2F). Both are what the statically linked libsnd reader consumes, so other libsnd titles most likely match - untested.Check one tempo event by hand; SMF-style length bytes would misparse.
lzsCommon LZSS parameters (4 KB ring, cursor at 0xFEE, 18-byte match), but the ring starts zero-filled, not with spaces; no size header; the .lzs container is Legaia's.Compare the game's decompressor; magic-check decoded bytes, since junk decodes without error.
mdecThe decoder is the rare Iki bitstream. A STRv2 frame shares its 0x3800 word and comes back blank or garbage with no error. The STRv2/v3 path needs a VLC table unpacked from Legaia's STR overlay.Reuse the sector parser and A/V demux; check frame[6..8] before decoding.
mednafenLibrary generic; a Legaia string search is only the fallback, and main_ram_with_anchors takes another game's. The CLI pulls in Legaia crates, and identify, display-list, clut-trace, prim-dispatch-* read Legaia structures.Use the library, or extract / diff / bisect / vram-dump / spu.
pcsx-reduxMain RAM, VRAM and scratchpad are all found by walking the state's protobuf; only the scene / mode / player accessors read Legaia's globals, and Legaia's string search is the fallback for a payload the walk does not recognise.SaveState::from_path_structural or pcsxr-state --game generic; identity_with takes your own profile.
ram-imageGame knowledge enters as a GameProfile (anchor strings, identity globals, manifest defaults); the Legaia profile is the default of every older API. prim_dispatch and source_hunt are Legaia's.Implement GameProfile or use GenericGame; diff, bisect, ram_slice, prim_pool take any 2 MiB image.
engine-audioThe SPU has no pitch modulation, noise or FM (Legaia never enables them); battle-voice, duck and slot modules are Legaia's runtime; legaia-prot is a plain dependency though only note-trace uses it.spu::Spu plus Sequencer play any libsnd SEQ + VAB pair.
render-kernelsgte is a register-level GTE (every command, FLAG bits, cycle costs, the UNR divide) inside a crate that depends on Legaia's engine. Its divide, NCLIP, AVSZ, SQR, OP and MVMVA match the hardware; its RTPS keeps its own q19.12 scale and its colour ops are not hardware-exact - psx-retail-exec's gte is.Vendor src/gte/ without camera_relative.rs.

A worked example

crates/pcsx-redux/examples/bring_your_own_game.rs runs the generic crates on inputs built in code, none of them from a game: a 4-bit TIM with its CLUT through parse_strict, decode_rgba8 and write_png; a SEQ through Seq::parse; a two-directory ISO9660 image mastered as Mode 2/2352 sectors and read back by both the in-memory walker and RawDisc (it also lists and extracts under xorriso); and a made-up game's PCSX-Redux and mednafen states, opened structurally, diffed and bisected. A test runs the same code in CI, with no disc.

cargo run -p psx-pcsx-redux --example bring_your_own_game -- <out_dir>

The SEQ step is not evidence about other games

It builds the shape psx-seq parses: meta events without a length byte, which is what the libsnd reader in Legaia's executable consumes. No other title's SEQ is tested here. The step also shows the trap: an SMF-style tempo event (FF 51 03 tt tt tt) does not read as the right tempo.

Legaia-only crates

legaia-tmd reads Legaia's TMD variant, not Sony's. parse requires bit 31 of the id word (Legaia's 0x80000002) and rejects a stock 0x00000041 TMD, and the primitive walker reads Legaia's grouped primitives, not stock packets (TMD). The object table and vertex layout are shared, so it is a starting point for a stock reader, not one.

legaia-container-formats is Legaia's container layer (DATA_FIELD streams, the pack format, sound bundles); its tim_scan is generic but brings the crate's dependencies. prot, mes, anm, the format families and every engine-* crate describe Legaia's own data and runtime.

Emulator automation scripts

PCSX-Redux. scripts/pcsx-redux/lib/probe/ is a Lua library over the emulator's Lua API, and most of it knows nothing about Legaia: RAM reads, breakpoints, pad overrides, save-state loading, the boot / arm / capture / quit driver, instruction tracing and write attribution, watchpoints, CSV output, declarative .probe.toml specs. The Legaia parts are the USA-build fingerprint guard, the battle-state reader, the Ghidra-generated symbol table and the LEGAIA_* variable names. run_probe.sh stages the disc image in a private directory so a .ppf beside it is never applied by accident. See PCSX-Redux automation.

Mednafen. run-mednafen.sh, state-diff.py and watchpoint-bisect.py wrap the generic mednafen-state subcommands; the rest of scripts/mednafen/ reads Legaia structures. See Mednafen automation.

Fingerprint before you arm

A probe with hard-coded addresses, pointed at the wrong build or region, returns a CSV of plausible garbage with no error. Check a few words of always-resident code first, the way the version guard does.

The transferable method

The parts most worth taking to another game are practices, not crates. The retail-oracle method writes them up engine-neutral, in the order to adopt them. Each page below documents one for Legaia; nothing in them depends on Legaia beyond the examples.

Related pages: byte accounting, address reference scans and the soak harness.

Traps any PS1 project meets

  • A save state replays the RAM of the disc that booted it: a state loaded onto a patched image still runs the old code. Verify disc edits from a cold boot or a memory-card load.
  • PCSX-Redux silently applies a .ppf sitting beside the image (patched-disc taint).
  • Ghidra does not resolve MIPS lui / addiu pairs as references, so xref queries miss heavily used globals.
  • "No static caller in the executable" means "look in the overlays", not "dead code" (overlay capture).

See also