Modding and translation
A retail disc is a sealed box, but it can still be rewritten in place: shuffle what monsters drop, re-key doors, change a boss's stats, swap a texture, or put the whole script into another language. legaia-patcher does all of that against a disc image you supply and hands you a small, shareable patch file. Everything is built on same-size in-place sector edits with the EDC/ECC re-encoded, so patched images stay valid discs. Commands use the bare ./tool form (source builds live at target/release/).
At a glance
| I want to... | Subcommand | Section |
|---|---|---|
| Randomize a playthrough | legaia-patcher randomize | 1 |
| Apply a friend's patch | legaia-patcher verify | 3 |
| Rebalance one monster by hand | legaia-patcher monster-block | 4 |
| Replace a texture with my own PNG | legaia-patcher tim-list / tim-export / tim-replace | 4b |
| Translate the game | legaia-patcher translate ... | 5 |
| Read a memory-card save | save-tool | 6 |
| Look up an item or an art without a disc | gamedata-tool | 7 |
| Classify GameShark codes | cheat-tool | 8 |
- The tool never writes to
--input. The original image is opened read-only; output goes to the PPF patch and (optionally) a separate patched copy. Keep your original dump pristine anyway - it is your only source. - Nothing shareable contains Sony bytes. The PPF patch and the manifest are safe to share; a patched
.bin(--output) is for local play only, never redistribute it.
No terminal? The browser ROM patcher runs the randomizer, the language packs and the texture-replacement flow client-side - the same code compiled to WebAssembly; your disc never leaves the tab.
A patched USA image also boots in the port (legaia-engine accepts it like the retail disc). The port reads the disc's data, so data edits - drops, steals, encounters, chests, doors, shops, starting items - take effect there; mods that work by injecting MIPS code into the game's executable only run where that executable runs, on an emulator or a console.
1 · Randomize (onto a scratch copy)
./legaia-patcher randomize --input "/path/to/Legend of Legaia (USA).bin" --seed 12345 --patch legaia-12345.ppf --output legaia-12345.bin --manifest legaia-12345.tomlA few seconds on a modern machine. The run prints a disc: <label> line identifying the input build, the resolved numeric seed (so any run can be reproduced), and a spoiler-safe change summary. The same seed + options over the same disc is byte-deterministic: you and a friend get identical patches. --output also writes a .cue beside the .bin so emulators can open it directly. --dry-run plans without writing.
Nothing is randomized unless asked. --drops shuffle re-rolls monster item drops; dozens of flags cover encounters, chests, steals, arts combos, doors, shops, monster stats, spell costs, equipment, and code-hook extras (--equipment-drops, --flee-exp, --enemy-ally, --shiny-seru, ...). Read-only preview subcommands - drops, chests, steals, arts, doors, shops, monster-stats, spell-costs, and more - list what a feature would touch before you commit.
2 · The region guard
The randomizer's offsets and injected code target the USA build (SCUS-94254). Running randomize or verify against any other disc - the PAL SCES builds included - stops with a region error, because a USA-built patch "applies" to a PAL disc yet produces a corrupt hybrid. --allow-region-mismatch overrides the guard; only pass it when you know the patch was built for that exact disc. The PAL discs themselves are documented in tooling/pal-localizations.md.
4 · Hand-edit one monster (stats / element / name)
The randomizer shuffles monster stats wholesale; for a curated rebalance you edit records directly. Every monster lives in the battle_data archive (PROT entry 867) as an LZS-compressed block - monster-block does the slot math and the compression for you, so the edit loop is dump → hex-edit → write:
- Dump
monster-block --id 10 --dump m10.binwrites the decoded block (monster-statslists the 1-based ids). - Hex-editChange fields in the stat record at the top of
m10.bin(layout below). - Write back
monster-block --id 10 --write m10.bin --output edited.bin --patch m10.ppf
./legaia-patcher monster-block --input "/path/to/disc.bin" --id 10 --dump m10.bin./legaia-patcher monster-block --input "/path/to/disc.bin" --id 10 --write m10.bin --output edited.bin --patch m10.ppfThe stat record starts at +0x00 of the dumped block; multi-byte fields are little-endian. The name is a NUL-terminated string elsewhere in the same block, at the block-relative offset the first word holds.
The write is the same machinery as the randomizer - the block is recompressed (the encoder need not match Sony's bytes; the retail decoder accepts any valid stream), padded back into its fixed 0x14000-byte slot, and the touched sectors get their EDC/ECC re-encoded. With --output the tool re-parses the patched image and confirms the record still decodes before declaring success. The equivalent flags on the extracted tree are asset monster-archive --dump-block / --write-block (extracting assets).
4b · Replace a texture
Any texture on the disc can be swapped for your own PNG - same loop, three subcommands:
- Find it
tim-listprints coordinates, dimensions and curated labels (try grepping for "title"). - Export
tim-exportwrites it as an editable PNG. - Edit + replaceKeep the exact pixel size, then
tim-replacepatches it back.
./legaia-patcher tim-list --input "/path/to/disc.bin"./legaia-patcher tim-export --input "/path/to/disc.bin" --entry 890 --offset 0x14228 -o title.png./legaia-patcher tim-replace --input "/path/to/disc.bin" --entry 890 --offset 0x14228 --png title.png --output edited.bin --patch title.ppfThe replacement must keep the original's dimensions and fit its palette (the error names the offending pixels; --quantize folds extras to the nearest color), alpha becomes PSX transparency, and compressed (lzs-tier) textures apply only when the edit recompresses into the retail footprint. The same flow runs entirely in the browser on the ROM patcher page (scan / thumbnails / preview).
5 · Translate the game
The translate family exports the disc's text as an editable YAML language pack and imports a filled pack back onto a copy of the disc. It has its own step-by-step guide, written for translators: translating the game covers the browser and command-line paths, how much room a line has, and why a line can stay English.
A language pack composes with every randomizer option: on the ROM patcher, pick a language and any randomizer options and patch once. Owners of an official European or a fan-translated disc can lift its text onto the USA disc instead of translating from scratch (translate lift-official; translations from another disc).
6 · Inspect saves
save-tool reads PSX memory-card images (.mcr and friends, e.g. from an emulator's card directory):
./save-tool dir card.mcr./save-tool saves card.mcr./save-tool character card.mcr --block 1 --offset 0x5C8./save-tool party card.mcr --block 1 --offset 0x5C8 --count 3dir lists directory entries (magic-checked); saves lists active save blocks + product codes. In a retail Legaia save block the character-record array starts at block offset 0x5C8 with a 0x414-byte stride: 0x5C8 is the first character, 0x9DC the second, and so on. Each record's display name sits at record-relative +0x2A7. --offset / --block accept 0x-hex or decimal. sc-diff diffs the save block of two cards to pin what a gameplay change wrote.
7 · Look things up without a disc
gamedata-tool bakes in the curated game-data tables (arts, magic, items, weapons, armor, accessories, enemies, shops, casino, fishing) - handy as ground truth beside the randomizer's read-only listings:
./gamedata-tool find "Healing Leaf"./gamedata-tool list arts --character Vahn./gamedata-tool arts-by-command Vahn "Arms,Ra-Seru,High"./gamedata-tool shop "Rim Elm"./gamedata-tool dump-json enemies8 · Cheat databases
cheat-tool parses GameShark / Pro-Action-Replay cheat collections and classifies each code by the RAM region it targets. The community Legaia NTSC-U databases are embedded, so no input file is needed:
./cheat-tool list./cheat-tool classify./cheat-tool extract-offsets./cheat-tool parse my-cheats.chtlist names the built-in databases, classify rolls them up per category, extract-offsets pulls the codes that land in the character record, and parse points at your own file. A file that parses to zero entries warns on stderr rather than silently printing [].