Asset loader
How the runtime stitches per-scene assets together. Battle, field, sound, BGM — each scene type has its own loader, but they all share one idiom: a dev branch that loads by PROT index, and a retail branch that resolves dev-style paths through the CDNAME name map into the same indices.
How it works
At runtime, "load this scene" doesn't mean "load one file". A single battle scene needs character meshes, monster meshes, the active CLUTs (palettes), the per-monster sound bank, the visual-effect script archive, plus all the text the battle dialog box might print. They're scattered across dozens of PROT entries.
Each scene type has its own loader function whose job is to go fetch all that. They all share the same shape:
- The loader is a small state machine with one numbered case per asset slot.
- Each case calls the asset-type dispatcher
FUN_8001F05Cwith the bytes for one entry. - Some cases also kick off async CD reads to stage things into memory while the rest of the scene is initialising.
There's a dev/retail split that runs through nearly every loader. Dev builds load by PROT index directly — the index numbers are baked in. Retail builds resolve dev-style paths (h:\PROT\FIELD\<scene>\name) through FUN_8003E6BC, which uses the CDNAME.TXT name map to find the corresponding PROT index. Both branches end up at the same files; only the lookup mechanism differs. That's why we don't see a literal "scene N uses PROT entries [a, b, c]" table anywhere in the executable — the table is the CDNAME block layout itself.
The asset descriptor walker (FUN_80020224) is the loop that walks a per-scene asset descriptor and calls the dispatcher per descriptor. It has zero static callers in SCUS_942.54, which would suggest "dead code" — except its sole runtime caller is the town overlay's MAIN_INIT, at the start of every field session. So it is exercised by retail gameplay; the call just lives in RAM.
Battle bundle
- Function
FUN_800520F0- Shape
- 11-case state machine
Notable cases:
| Case | Loads |
|---|---|
| 6 | The befect_data bundle (PROT 0x369–0x36B) |
| 0xE | Initialises the runtime effect 2-pack wrapper via FUN_801DE914 |
| 0xFF | Dispatches 0x801F17F8, the side-band streaming-effect handler that streams summon.dat and readef.dat |
Two cases call FUN_8003E104(monster_idx, slot, dst_buf) to populate slots 7 and 8 with the active battle's monster sound banks — the per-monster body of h:\mpack\monster.snd. Each monster has a (start_lba, end_lba+1) entry pair in the TOC at 0x801C8980 - 0x10. See Audio → "Monster sound bank" for the full loader contract.
The asset-viewer's --bundle battle mode mirrors this loader's PROT 865–890 set so character meshes have the right CLUT bindings.
Field / town scene loader
- Functions
FUN_8001F7C0+FUN_800255B8- Path roots
DATA\FIELD\andh:\PROT\FIELD\<scene>\
Each scene reserves six file types in CDNAME's per-scene block, and the loader walks the scene asset table at the leading PROT entry to pull each file in turn. The on-disc form is the canonical 7-typed-asset bundle (07 00 00 00 lead).
The descriptor offsets past the first are not file-relative — they index into the loader's runtime decompression buffer. For example, 0031_izumi.BIN is 96 KB but desc[2].data_offset = 141 KB lands inside the decompressed working buffer, not the source file.
Per-scene reference resolution is partial — many scene-bundle entries cross-reference each other through indices in the descriptor table that the loader stitches at runtime. The asset chain shape is "load the scene asset table, decode each descriptor, then load each typed sub-asset using the dispatcher", but the precise indexing scheme is still under investigation for non-battle scenes.
WARP opcode → scene transition flow
- Field-VM opcode
0x3Ewithop0 ≥ 100- Scene handler
FUN_80025980
When the field VM executes opcode 0x3E with op0 ≥ 100, it stores map_id = op0 - 100 in DAT_8007ba34 and switches game mode to 0xe (SCENE_TRANSITION). The mode handler FUN_80025980 then loads a code overlay at PROT index map_id + 0x4d (or map_id + 0x4f when map_id ≥ 6).
Only 7 distinct WARP destinations exist (map_ids 0–6), each loading a scene-type overlay at PROT 0x4D–0x55 whose entry function resides at an overlay-resident address (0x801CF070, 0x801CE8A0, etc.).
The scene name (stored at DAT_80084548, max 8 chars) is pre-set before FUN_80025980 executes. The overlay entry function reads this buffer and passes it to FUN_8001F7C0 and FUN_80020118. The mechanism that writes the scene name before the WARP fires is in a pre-transition handler not yet fully traced.
| Global | Role |
|---|---|
DAT_80084548 | Scene name string (pre-set before WARP fires) |
DAT_80084540 | Current scene PROT base index (short) |
DAT_8007b768 | Pending destination PROT index; 0xffff = none |
DAT_8007ba34 | Pending warp map_id (0–6); read by FUN_80025980 |
The DefaultMapIdResolver in engine-core::scene uses CDNAME blocks in ascending PROT-index order as a positional approximation. The actual retail warp only supports 7 destinations and the scene name is determined by a pre-WARP state-machine path still to be captured.
Asset descriptor walker
- Function
FUN_80020224- Sole runtime caller
- Town overlay's
FUN_801D6704(MAIN_INIT) at0x801D6B0Cwitha0 = 0 - Result stored at
0x80087AF8
Walks the asset descriptor format and calls the asset-type dispatcher per descriptor. So the walker IS exercised by retail gameplay, just not from a static call site inside SCUS_942.54.
CLUT-data scattering
Many character meshes reference CLUT (palette) rows that live in different PROT entries from their TMD source. The runtime asset chain stitches them together — the loader puts the relevant TIMs into VRAM before the TMD is rendered.
The asset-viewer's --vram-extra-dir flag is the workaround until the chain is fully traced for every scene type. Battle is fully traced; field, town, and level-up still rely on the workaround.
Music / SFX selection (BGM lookup)
Documented in detail under the field VM → "BGM lookup table" section. The short version: the BGM ID is a PROT-relative offset, not a literal table lookup.
// FUN_800243F0
if (bgm_id < 2000) {
prot_idx = scene_local_base + 6 + bgm_id; // _DAT_80084540 + 6 + bgm_id
} else {
prot_idx = global_pool_base + (bgm_id - 2000); // _DAT_8007BC64 + bgm_id - 2000
}
The "BGM table" is the CDNAME.TXT per-scene block layout. There's no separate BGM index in SCUS_942.54.
Sound bank loader and streaming-asset loader
| Function | Role |
|---|---|
FUN_8001FA88 |
Sound subsystem init / .dpk loader. Loads bse.dat master bank once at boot, then per-scene .dpk files via the path-based opener with h:\main\bg\domepack\<name>.dpk. Dev builds bypass the path-builder and load PROT index 0x37A (sound_data2) plus param_1 + 5 directly. |
FUN_8001FC00 |
Streaming-asset loader. Builds paths under the sound\ prefix; the XA / .pac / STR consumer. Same dev/retail split as the field loader. |
Full breakdown in Audio.
Top-level extraction pipeline
legaia-extract (the binary in crates/extract) drives the offline preservation pipeline:
verify → disc → PROT → categorize → streaming-format extract → TIM → PNG
See the extraction tooling page for per-stage CLI invocations.