Tier B - editing inside LZS
Most editable values don't sit in the open - they're inside a Legaia LZS stream the asset dispatcher decompresses at load. Changing one is decompress → mutate → recompress → write back, which is impossible until you can produce a stream the retail decoder accepts. The game ships only a decoder; this tier exists because the project wrote the missing encoder, and because the monster archive's fixed slots leave just enough slack to re-pack into.
Mechanism at a glance
- Target
- values inside an LZS-compressed slot the dispatcher decodes at load - chiefly the monster archive (PROT 867)
- The enabler
legaia_lzs::compress: a greedy LZSS encoder whose output the retail decoderFUN_8001A55Caccepts (decompress(compress(x)) == x)- The slack
- each monster gets a fixed
0x14000-byte slot[u32 decompressed_size][LZS stream]; the re-pack is zero-padded back to0x14000so no downstream offset moves - Edit class
- in-place / same-size (slot stride preserved);
repack_slotrejects the rare stream that wouldn't fit - Fields
- drops
+0x48/+0x49· combat stats+0x0C.. · weapon arm-cost+0x74(player files 0863..0865) - Oracles
monster_drop_real.rs·monster_stats_real.rs·weapon_specialty_real.rs
The reversing: an encoder, and a validity signal
Retail ships only the LZS decoder (FUN_8001A55C), reversed for the preservation track. To edit anything inside a stream you have to emit one the game will decode - so the project built an LZSS encoder (a greedy matcher with one-step lazy matching) whose output the retail decoder round-trips exactly. It is not better than Sony's packer - it leaves a small, near-constant gap versus retail (output equal-or-slightly-longer); the lazy matching only narrows that gap enough to fit. That's precisely why the slot slack below matters.
The decoder also hides a trap worth stating, because it shaped how every LZS edit is validated: “decompresses without error” is not a validity signal. The 4 KB ring buffer initialises to zeros, so most random input decodes to plausible-looking output. A decoded record is only trusted after a magic / structural check, never because decompression succeeded - see Legaia LZS. With a trustworthy round trip in hand, the rest is the same field-locating work as tier A, just performed against the decompressed record.
Why the slot stride is the whole trick
The monster archive (PROT 867) gives every monster a fixed 0x14000-byte slot, and that slot is the source of the slack - not our compressor. Because our re-pack is a little looser than Sony's, the new stream is usually a touch longer than the original; the fixed slot simply has enough built-in padding to absorb it, and the slot is re-emitted zero-padded back to its full 0x14000. The decoded record length never changed; the slot stride never changed; so nothing downstream of it moves and the edit stays a pure same-size overwrite. (There is no compression win here - for assets with no slack, like a scene MAN, the lazy matching has to fight to fit the original footprint exactly; see tier C.)
repack_slot rejects the rare case where even our stream would overflow the slot.Worked examples
Monster drops & combat stats
Inside each decompressed monster record, the drop is +0x48 (item id) / +0x49 (chance), and the combat stats are halfwords from +0x0C (HP, MP, ATK, two defence fields, AGL, SPD). The randomizer decodes the slot, rewrites those fields, recompresses, and pads back - drops and stats are independent passes over the same archive.
Weapon specialty (a per-character disc byte)
Which weapon class a character favours isn't a runtime comparison - it's a per-(character, weapon) byte sitting in the LZS-decoded equipment section of the player battle files (PROT 0863..0865), at the swing record's +0x74. It's copied verbatim into the arts command gauge at battle load (FUN_800557B8), which makes it a clean tier-B target: decode the section, rewrite the arm-cost byte, recompress. See arts command gauge.
Composition
Every tier-B edit is same-size at the slot level, so it composes with the rest of the ladder exactly like tier A. The one internal subtlety is multi-pass: drops and stats both decode and re-pack the same archive, but each works from the original decoded record and pads back to the same stride, so order doesn't matter and neither pass can shift the other's offsets.
Mods using this technique
- Monster drops (
--drops) · Monster combat stats (--monster-stats) · Weapon specialty (--weapon-specialty)
Adding a tier-B mod: confirm the field's offset in the decompressed record (magic-check the decode, don't trust a clean decompress), edit, recompress with legaia_lzs::compress, pad back to the slot stride, reject on overflow, and add a disc-gated oracle.