Tier B overview
Tier B of the A–F ladder - same idea as tier A (overwrite a located field), but the field lives inside compression. Start at the series index for the editing model.

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 decoder FUN_8001A55C accepts (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 to 0x14000 so no downstream offset moves
Edit class
in-place / same-size (slot stride preserved); repack_slot rejects 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.)

Re-packing a monster slot, zero-padded back to its fixed 0x14000 stride original size Sony LZS stream pad re-packed size our LZS stream (looser - a touch longer) zero pad slot base +0x14000 fixed slot stride - identical in both rows, so nothing downstream moves
The decoded record is unchanged; our slightly looser stream eats a little into the zero pad, but the fixed slot absorbs it. 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.

See also