Why resize a record at all?

Nobody edits a shipped game file for fun - two real consumers force the problem:

  • The door randomizer. The randomizer's door shuffle re-points a scene exit at a different destination. Because the destination is an inline name, swapping town01 for a longer scene name grows the record; without the fixups below, every record after it - and the tables that index them - would decode as garbage.
  • Translation. The same machinery generalizes from a door's destination name to any interior byte run: a community language pack's dialog line that runs longer than the English original grows its record the same way. Growth is budgeted by the MAN's on-disc footprint - the recompressed file must still fit its original slot on the disc - so a wordier localization only fits when the rewritten MAN recompresses no larger than the original.

The reason a resize is safe at all is structural: the doors are reached at runtime through an offset table the format already exposes (next section), not through absolute addresses scattered in code - fix the table and every door stays addressable.

The destination table is the partition-2 record-offset table

(Terms: the field VM is the interpreter that runs a scene's event bytecode - FUN_801DE840 in Ghidra's naming, where FUN_80xxxxxx labels a traced function by address. A MAN's records are grouped into three partitions; partition 2 holds the named event records, doors included.)

A 0x3F op carries its destination inline:

0x3F  [i16 index]  [u8 name_len]  [name bytes]  [entry_x]  [entry_z]  [dir]

The ops are partition-2 MAN records (the named records - see the MAN layout). They are reached at runtime through the partition-2 record-offset table: on a transition the field controller sets the VM bytecode base to man_base + data_region + partition2[slot] and runs that record's small script by fall-through (SysFlag.Set; CFlag.Set; EFFECT; yield; 0x3F; entry-trailer; JmpRel-1 self-loop), whose 0x3F op warps by the inline name. Selection is by stable slot index, so the op's index field is only the destination-scene id passed to the warp packet (FUN_8001FD44), not a record selector.

Pinned by a live PCSX-Redux dispatch trace (autorun_door_dispatch_trace.lua on the drake_castle_to_worldmap capture): the executing op's bytecode base minus the MAN base equalled data_region + partition2[0] exactly. Corpus census (clean partition walk, disc-wide): 160 destination ops across 48 scenes, 153 in partition 2; zero absolute-reference ops at/after any destination op. The consequence: the destination "index" is a structural offset table the MAN parser already exposes, so resizing a record is safe - fix the table and every door stays addressable.

Relocation surface

A mid-buffer insert/delete of delta bytes at a destination op needs these fixups:

  1. Partition record-offset tables (MAN[0x2B..], u24LE entries relative to data_region_offset): bump every entry whose record starts after the edit by delta. This table is the door dispatch index.
  2. u24_at_28 (header section-0 offset, relative to data_region): the section chain sits after the records, so it shifts by the total delta.
  3. Intra-record relative jumps in the edited record whose source/target straddle the edit (0x26 / 0x42 / 0x4D / 0x4E / 0x70): the stored u16 delta is recomputed; a jump wholly on one side is unaffected. The delta field sits at the jump's relative base (target - delta), so the rewrite is op-agnostic. (37 of 160 corpus ops have such a jump.)
  4. External descriptor (scene_asset_table): the MAN's decompressed size is stored only in the scene-bundle descriptor word (type<<24)|size - rewrite it after recompressing.

data_region_offset is derived (0x2B + 3*total_records) and does not move.

Safety

Absolute-reference ops (0x45 0xC0 camera-apply, 0x4E abs-jump) are record-local and can't be relocated blindly; apply_dest_edits errors out if it finds one in an edited record (none exist in the retail corpus), and the caller leaves that scene unchanged. man_edit::validate re-parses + re-walks the rebuilt MAN and confirms each edited op still decodes as a 0x3F carrying the intended name. The recompressed MAN must fit the original asset's on-disc footprint (the gap to the next descriptor); a scene that can't grow in place - the big overworld hubs, whose next asset is flush after the MAN - is skipped and reported rather than relocating the whole bundle.

See also