Three capabilities the preservation track never needed

The asset-preservation half of the project only ever reads the disc. Writing one back required three new pieces, and every mod in this series stands on them:

  1. An LZS encoder (legaia_lzs::compress). Retail ships only the decoder FUN_8001A55C; there was no way to produce a stream it accepts. Greedy LZSS, weaker than Sony's packer but always accepted - see Legaia LZS.
  2. Mode 2/2352 sector write-back (legaia_iso::write). Overwriting a sector's 2048-byte payload also means recomputing its 4-byte EDC and 276-byte P/Q ECC, or the sector reads as corrupt - see PSX disc geometry.
  3. A disc bridge (disc::DiscPatcher) tying the editing primitives to the sector write-back through the PROT.DAT TOC.

The editing model: same-size, except when it can't be

Almost every edit overwrites bytes in place and never changes a byte count, so no LBA, PROT TOC, or ISO 9660 directory record ever moves - the patch is a pure byte-overwrite plus EDC/ECC recompute. That works because each target sits in a fixed slot with slack (the monster archive's 0x14000-byte records) or is a fixed-width table field. The exception is when an edit's meaning forces a length change - a scene-transition door carries its destination's name inline, so re-pointing it resizes the record. Those go through the MAN relocation engine, which rebuilds the decompressed script, fixes every disturbed offset, and keeps the recompressed stream inside the asset's on-disc footprint (or skips the scene). The disc's total size never changes either way.

The technique ladder

Six techniques, in ascending order of what they let you change - and of how much reversing they demand before the first byte can move. Each rung has its own page.

The disc-patching technique ladder, tier A (simplest) up to tier F (most advanced) More advanced - more reversing F Overlay dead-region injection Seru-trading vendor · (roadmap: battle assist, seru racing, Muscle Dome betting, Blood Moon) new systems E Code injection via the rodata gap Bonus equipment drops · run-away EXP new behavior D Variable-length relocation Doors (scene transitions) · high-capacity starting bag resize + fix-ups C Rewriting field-VM bytecode Treasure chests · town shops · random encounters · house doors operand rewrite B Editing inside LZS Monster drops · monster combat stats · weapon specialty decompress / repack A Static-table overwrites Steal items · spell costs · element affinity · prices · casino same-size data Foundational - same-size data edits
Each rung is a page in this series. Variants (a GIVE_ITEM splice, a new-game seed stamp, a string injection, an engine-facing config blob) are specializations of a parent tier and are tagged in the catalog below rather than given their own rung.

Mod catalog

Every current mod, grouped by the technique it uses, plus the roadmap. This table is generated from site/_content/writeups/disc-patching/mods.toml at build time, so it never drifts: adding a mod is one row there. The Gap column flags the mods that share the executable's reclaimed rodata region (a finite resource - the contention that tier F exists to escape). Edit is in-place (same-size) or relocate (footprint-bounded). Each shipped mod is backed by a disc-gated oracle test.

ModFlagTargetEditGapStatusTest
Tier A - Static-table overwrites - Same-size byte edits to a fixed table in SCUS or a raw overlay. No compression, no relocation - but reverse-engineered to the exact field.
Steal items--stealsSCUS steal table DAT_80077828, item byte at +1 (chance byte untouched)in-place-shippedsteal_patch_real.rs
Spell MP costs--spell-costSCUS spell table DAT_800754C8, cost byte at +3in-place-shippedspell_cost_real.rs
Equipment stat bonuses--equip-bonusSCUS equip-bonus table DAT_80074F68, stat tuple +0..+4 (mask/slot bytes kept)in-place-shippedequip_bonuses_real.rs
Special-attack power--move-powerBattle overlay (PROT 0898) move-power table, power halfword at +0x00in-place-shippedmove_power_real.rs
Element-affinity matrix--element-affinityBattle overlay (PROT 0898) 8x8 affinity matrixin-place-shippedelement_affinity_real.rs
Item prices--shops (pricing)SCUS item table 0x80074368, u16 price at record +2in-place-shippeditem_price_real.rs
Casino prize exchange--casinoMenu overlay (PROT 0899) raw prize table at file offset 0x15D00in-place-shippeddisc_patch_real.rs
Starting level H--starting-levelSCUS new-game roster seed: level literal +0x130, growth-curve stats, lead XP +0x0/+0x4in-place-shippedstarting_level_real.rs
Unused-item naming (Seru Bell) I--unused-itemsSeru Bell name string at reserved gap 0x8007AB40 + name-pointer repoint for id 0xFDin-placeshared gapshippedunused_content_real.rs
Tier B - Editing inside LZS - The value lives in a compressed slot: decompress, mutate, re-pack (one-step lazy matching, about as tight as Sony's - not tighter), zero-pad back to the fixed slot so nothing downstream moves.
Monster drops--dropsMonster archive (PROT 867) slot, drop id/chance at +0x48/+0x49in-place-shippedmonster_drop_real.rs
Monster combat stats--monster-statsMonster archive (PROT 867) slot, HP/MP/ATK/DEF/INT/SPD halfwords at +0x0C..in-place-shippedmonster_stats_real.rs
Weapon specialty--weapon-specialtyPlayer battle files (PROT 0863..0865) swing record, arm-cost byte at +0x74in-place-shippedweapon_specialty_real.rs
Tier C - Rewriting field-VM bytecode - The data is bytecode - rewrite an opcode's inline operand and recompress the per-scene MAN within its original footprint budget. Needs an opcode-aware walk to find sites safely.
Treasure chests--chestsPer-scene MAN: GIVE_ITEM op 0x39 operand + 0xC2 display tokenin-place-shippedchest_patch_real.rs
Town shops--shopsPer-scene MAN: inline op 0x49 shop-stock item idsin-place-shippedshop_patch_real.rs
Random encounters--encountersPer-scene MAN: formation monster-id bytes (count/reserved preserved)incl. encounter-scope + on-by-default solo-strong passin-place-shippedencounter_patch_real.rs
House doors (intra-town)--house-doorsPer-scene MAN partition-0: player-warp op 0xA3 0xF8 tile operandsin-place-shippedhouse_door_patch_real.rs
Tier D - Variable-length relocation - When an edit must change a byte count: rebuild the decompressed MAN, fix every internal offset / table / delta, keep it inside the asset's footprint.
Doors (scene transitions)--doorsPer-scene MAN: 0x3F transition destination name (resizes the record)relocate-shippeddoor_patch_real.rs
Starting bag (high-capacity) G--starting-itemsOpening-scene MAN: guarded GIVE_ITEM block (0x70 test / 0x39 give / 0x50 set) spliced inrelocate-shippedstarting_bag_real.rs
Starting items (seed code) H--starting-itemsSCUS new-game seed routine FUN_80034A6C: reclaimed region re-stamped with grant codelow-count path; high-count uses the MAN splice (G) abovein-place-shippedstarting_items_patch_real.rs
Tier E - Code injection via the rodata gap - Behavior the game can't express as data: detour an existing routine into hand-assembled MIPS parked in a reclaimed all-zero region of the executable.
Bonus equipment drops--equipment-dropsDetour at reward routine 0x8004F610 -> hand-assembled routine + id table at gap 0x8007AB80in-placeshared gapshippedequipment_drops_real.rs
Run-away EXP--flee-expDetour at battle-action escape teardown 0x801E5A10 -> routine at gap 0x8007AD00in-placeshared gapshippedflee_exp_real.rs
Enemy ally (charm)--enemy-allyDetour at battle setup 0x80051990 -> charm routine at gap 0x8007ACA0; + one-word victory-mask widen 0x801E6638 in overlay 0898A per-battle chance to flip an enemy onto your side, riding the game's own Confuse/Charm retarget; gated to multi-enemy fights so scripted solo and tutorial battles can't softlock. Full story on the Tier E page.in-placeshared gapshippedenemy_ally_real.rs
Shiny Seru--shiny-seru9 detours across the battle / menu / capture paths; routines + data in four read-watch-verified-dead SCUS regions outside every live table (gap1 0x80077728, arena1 0x8007AE00, arena2 0x8007AFF8, slot6 0x80078A88)A per-battle chance that a capturable enemy spawns shiny (+35% stats, translucent); the Seru you capture from it keeps a permanent +35% damage bonus, stored in a spare per-Seru byte so it survives saving. The Tier E page tells the placement story (the 'zero is not dead' trap that bit this three times).in-placeshared gapshippedshiny_seru_real.rs
Tier F - Overlay dead-region injection - Host hand-assembled code inside a resident overlay's reference-free dead space - so a whole new screen or system costs nothing and never competes for the shared rodata gap.
Seru trading vendor J--seru-tradeMenu overlay (PROT 0899) dead region 0x801E74E0..0x801E83E0: full Trade screen; SCUS config blob 0x8007AF00 drives the engine mirrorconfig blob is the only gap-resident byte; the UI escapes the gap entirelyin-place-shippedseru_trade_real.rs
Enemy battle assist(planned)Battle overlay: a defeated/random enemy joins as a non-controllable ally actornew battle-actor behavior; likely overlay-hosted code + an extra actor slotin-place-planned-
Seru racing(planned)New minigame (horse-racing shape): race state machine, odds, betting payoutnew system; hosted in a minigame overlay's dead spacein-place-planned-
Muscle Dome spectate + betting(planned)Muscle Dome overlay: watch two NPCs fight, place a pre-match wager, settle payoutextends the existing match SM with a spectator + betting pathin-place-planned-
Blood Moon event(planned)Periodic overworld event: red tint + stronger enemies + better loot for a window every ~30 minperiodic global timer + encounter/loot modulation + overworld CLUT tintin-place-planned-

Tier links activate as each tier's page lands; planned rows are on the roadmap and concentrate at the top of the ladder, where system-level changes live.

See also