DATA_FIELD streaming format Confirmed
When the game loads a scene or a battle effect it usually needs several different things at once - a texture, a mesh, an animation - and it wants to read them off the disc in one go. DATA_FIELD is the container that does that: a stream of typed chunks, each a 4-byte header (what it is, how long it is) followed by raw data, read one after another until a terminator. The game hands each chunk to the asset-type dispatcher, which routes it to the right format handler.
At a glance
- Magic
- none - the first
u32is already a chunk header (type byte in the top 8 bits) - Where on disc
- 34
PROT.DATentries strict-validate as classdata_field_streaming: theother5character/effect run (1204..1219) and a family of scene bundles - Parser
crates/asset/src/lib.rs::parse_streaming;asset scan-stream/asset extract- Confidence
- Confirmed - consumer
FUN_8002541C, the0x14(DATA_FIELD) branch, feedingFUN_8001F05Cwithcopy_only=1 - Used by
- asset loader, battle scene loader, characters page (battle-form meshes + atlases)
Overview
The walker passes every chunk through the dispatcher with copy_only=1, so chunks are always uncompressed - the container trades disc space for a load path with no decompression step. Implementation: crates/asset/src/lib.rs::parse_streaming.
Layout
| Offset | Size | Field | Meaning |
|---|---|---|---|
+0x00 | u32 | type_size | (type_byte << 24) | (size_bytes & 0x00FFFFFF); type_byte per the asset type table |
+0x04 | size_bytes | data | the raw asset, uncompressed |
+0x04 + (size & ~3) | u32 | next type_size | header + size truncated to a 4-byte boundary; sizes are 4-aligned in practice |
| last | u32 | terminator | any header whose low 24 bits are zero |
What's in the wild
asset scan-stream strict-validates 34 PROT entries (class data_field_streaming), in two families.
Character / effect streams
The other5 run, entries 1204..1219. The common shape is three single-asset chunks:
chunk[0]: TIM (single, magic 0x10) - sprite atlas / texture
chunk[1]: TMD2 (single, magic 0x80000002) - single Legaia TMD
chunk[2]: MOVE2 (single, magic 0x08) - animation data
terminator
The two heads of the run are homogeneous instead: 1204 is five TMD2 chunks (the battle-form party meshes used by the Baka Fighter minigame and as the default-equipment sibling pack - the in-battle player meshes themselves are assembled from the PLAYER1..4 files) and 1205 is eight TIM chunks of 0x8220 bytes each (their atlases) - which is what makes the atlas stride 0x8224, chunk header included.
Scene bundles
MAN / MES / MOVE / VDF, four chunks (dolk2, rikuroa, rikuroa2, rayman, station, taiku, taiku2, doman, nilboa2, edbalden, eddoman). Shorter variants drop the trailing VDF (balden2, ropeway2) or lead with a bare TMD where a MAN would sit (balden, bubu1, edbubu). Two entries are neither family: init_data is TIM_LIST + TMD, and 0890_sound_data2 is two TIMs.
The chunk layouts are single assets in the other5 family (one TIM, one TMD2, one MOVE2), not packs. Other clusters do use pack-shaped TIM_LIST / TMD chunks; the pack format handles that case.
Trailer data
Some entries contain bytes past the streaming terminator. asset extract preserves these as _trailer.bin next to the extracted chunks. The function that consumes the trailer has not been located; tracing the caller of FUN_8002541C in the field/town overlay is the next move if a specific entry's trailer looks structured.
Per-scene field bundles - what's still open
The CDNAME block for a typical field/town scene (e.g. town01, bubu1) carries 8-12 PROT entries. Categorize (the sweep that classifies every archive entry by format) identifies several known shapes per block. Slots below are in the retail block frame (see CDNAME § numbering space): slot 0 is the scene's .MAP and slot 1 its v12 trigger sidecar, which is why the classes below start one or two slots later than a naive count.
| Common slot | Class | Typical content |
|---|---|---|
| 0 / 1 | SceneTmdStream or TmdSizePrefix | scene mesh (room geometry) |
| 1 / 2 | Pack (TIM-pack) | scene textures / sprite atlas |
| 2 / 3 | SceneEventScripts | per-event field-VM bytecode (the event-script prescript) |
| 3 / 4 | MesContainer | dialog text |
| 4 / 5 | Pack (ANM-pack) | per-actor animation sets |
| 5..7 | PochiFiller | reserved-but-unused dev fillers (one sector each) |
| 6..8 | SceneVabStream (rare; only on scenes with custom audio) | per-scene VAB + SEQ |
What is not modelled yet:
- Cross-entry pointers (NPC references that point into other PROT entries - the asset chain in asset loader is best-effort).
- The runtime-reconstructed slot-to-asset mapping inside field-pack containers (magic
0x01059B84, 124 entries) - known shape, unknown slot semantics. - The retail engine's per-scene "asset table" indirection (the
SceneAssetTabledetector fires on a small fraction of entries; a full reverse needs an overlay capture ofFUN_8001f7c0at scene-load time).
The categorize sweep classifies every PROT entry to something; the items above are the residual edge. See the engine page for how scene loading consumes these shapes.
History: the "scripted asset table" class
A SceneScriptedAssetTable class - a descriptor table found at a sector-aligned offset inside an entry that led with an event prescript - was an artefact of the over-read entry size: every such offset was the next entry's start LBA, i.e. the neighbour's ordinary offset-0 table. Read against their own sectors, the prescript entries classify as event scripts and the 88 bare asset tables stay where they are. See PROT TOC § history of the over-read.
How we know
| Evidence | Address / test | What it proves |
|---|---|---|
| Retail consumer | FUN_8002541C, the 0x14 branch | The chunk walk: header split, (size & ~3) + 4 advance, zero-size terminator. |
| Dispatcher hand-off | FUN_8001F05C called with copy_only=1 | Chunks are consumed uncompressed, one dispatcher call per chunk. |
| Corpus sweep | asset scan-stream strict validation | The 34-entry census and the two family shapes above. |