Tier C overview
Tier C of the A–F ladder - like tier B it edits inside compression, but the payload is executable bytecode, which raises the bar on finding the byte. Start at the series index for the editing model.

Mechanism at a glance

Target
an opcode's inline operand in a per-scene MAN's field-VM script (dispatcher FUN_801DE840)
Find
an opcode-aware, dialogue-skipping walk from each record's true entry PC - never a raw byte scan
Write
overwrite the operand same-size, recompress the MAN, keep the stream within its footprint budget
Budget
distance from the MAN's data_offset to the next descriptor's - read from the boundary, not the rewritten stream, so multi-pass edits stay stable
Opcodes
chest 0x39 + display 0xC2 · shop 0x49 · encounter formation ids · house-door 0xA3 0xF8
Oracles
chest_patch_real.rs · shop_patch_real.rs · encounter_patch_real.rs · house_door_patch_real.rs

The reversing: don't mistake data for an instruction

Field-VM bytecode is variable-width and interleaved with inline dialogue - runs of 0x1F-led glyph segments that are not opcodes. A chest's GIVE_ITEM (0x39 item_id) almost always sits after the dialogue that announces it, so a naive scan for the byte 0x39 would trip over a glyph or an operand byte and rewrite the wrong thing. The site finder instead walks each partition-1 record with the field-VM disassembler from its true entry PC, decoding opcode by opcode: a decode error at a 0x1F is treated as a dialogue segment to skip (advance past it, consume glyphs to the terminating 0x00), and decoding resumes - the inter-segment control bytes stay in sync, so the walk reaches the real post-dialogue give. Any other decode error stops the walk, and each record's walk is bounded to the next record's start, so it can never run off into unrelated data. An earlier walk that stopped at the first 0x1F silently missed the give in ~85% of sites - the kind of bug a byte scan hides.

Two pins make the rest exact: each opcode's operand encoding comes from the field-VM dispatcher (FUN_801DE840, see script VM), and a chest's announcement text renders its item name from a separate 0xC2 id dialogue token - patch only the 0x39 give and the flavor text still names the old item, so both bytes move together.

The footprint budget

After the operand is rewritten the MAN is recompressed, and the new stream must still fit where the old one lived - the gap to the next asset's descriptor. The budget is read from the descriptor boundary, not the just-written stream, which matters because several passes (encounter, chest, shop) each decode and re-pack the same MAN: our packer is often a touch tighter, so reading the budget back from a shorter stream would shrink it each pass and make a later pass overflow a scene it should have edited.

MAN recompression must fit within the descriptor-boundary footprint original MAN LZS stream headroom re-packed edited MAN (re-packed) headroom data_offset budget = descriptor boundary − data_offset (constant across passes)
Both streams must end left of the next descriptor. Reading the budget from that fixed boundary - not the rewritten stream - keeps every pass on the same full budget; a scene whose re-pack would overflow is skipped rather than corrupted.

Worked examples

Treasure chests & town shops

Chests rewrite the 0x39 give operand and the matching 0xC2 display token together. Shops reassign the inline op-0x49 stock list - the gold-merchant's wares are bytecode operands in the scene script, not a separate table (mind the unsellable template tail that over-counts stock).

Random encounters & house doors

Encounters rewrite a formation's monster-id bytes while preserving the leading count/reserved bytes, so the formation shape is intact. House doors shuffle the player-warp operands of the intra-town 0xA3 0xF8 move-to ops, class-preserving across the named IN/OUT records so interiors still connect sensibly.

Composition

Tier C edits are same-size at the operand level, but several can target the same scene MAN, so they share one rule: every pass reads the footprint budget from the descriptor boundary, never from a previously-rewritten stream. With that, encounter, chest, and shop passes over one scene compose cleanly. The variable-length cousin - when an edit must change a byte count - is tier D.

Mods using this technique

  • Treasure chests (--chests) · Town shops (--shops) · Random encounters (--encounters) · House doors (--house-doors)

Adding a tier-C mod: find sites with an opcode-aware walk bounded to each record (never a raw byte scan), patch any paired display token alongside the operand, recompress reading the budget from the descriptor boundary, skip scenes that overflow, and add a disc-gated oracle.

See also