Sound-driver path-string cluster Confirmed
When a scene starts and its music and sound effects load, the game builds a file path out of a small set of extensions its developers' sound tools used - .dpk, .spk, .MAP, .PCH - still sitting in the executable. Are these exotic sound formats to reverse? Mostly no. The per-scene .dpk sound pack is an ordinary VAB + SEQ pair in a chunked wrapper, and the .MAP / .PCH intermediates never made it onto the retail disc.
At a glance
- Magic
- none - a string cluster in
SCUS_942.54at RAM0x8007B380(file offset0x6BB80) - Where on disc
- The
.dpkpayloads are thesound_data2PROT family (0877..0885);bse.datis the master bank the sound subsystem loads once at init - Parser
legaia_asset::sound_pack(extractreturns the reconstituted VAB + SEQ); walkerparse_streaming_with(.., StreamTerminator::TypeTwo)- Confidence
- Confirmed - loader, path builders and chunk walker traced (how we know); VAB reconstitution byte-verified across
0877..0885 - Used by
- Audio stack
The .dpk sound pack is a VAB + SEQ bundle
A per-scene sound pack is a VAB followed by a SEQ in a streaming-chunk container - not a novel .MAP / .PCH / .spk layout. Each chunk is [u32 (type << 24) | size][payload, 4-padded]; the walk ends on the type-2 chunk, which is itself the SEQ.
| Chunk type | Payload | Magic |
|---|---|---|
0 | VAB header section | pBAV (0x5641_4270 LE) |
1 | VAB sample section (SPU-ADPCM / VAG waveform pool) | - (raw samples) |
2 | SEQ (the stream terminator) | pQES |
- Decisive invariant: type-0 + type-1 reconstitute one contiguous VAB whose header
total_sizeequalschunk[0].size + chunk[1].size. Byte-verified across0877–0885; the reconstituted VAB parses withlegaia_vab, the SEQ withlegaia_seq. - Same walker, different terminator. The chunk walker is the one the DATA_FIELD dispatcher models, but it ends on a type-
0x02chunk (non-zero size) where DATA_FIELD ends on a zero-size header - so the DATA_FIELD parser mis-reads a.dpk. UseStreamTerminator::TypeTwo. - Type-1 depends on the consumer. On the graphics battle-init walk, the type-1 handler is the TIM/CLUT upload; in a sound pack it is the VAB's sample pool.
- The content shape matches the scene_vab_stream BGM wrapper; only the container differs. The
.MAP(sample-address map) and.PCH(patch data) are the PsyQ SoundArtist dev-side inputs that produce this VAB - the retail disc does not carry them, so no dedicated parser is warranted.
History: the bare-VAG-container reading
An interim trace labelled the .dpk a bare VAG-record container. That is falsified: the VAG samples are the type-1 section of a VAB, indexed by the type-0 header, not a header-less stream. Falsified readings are catalogued in do-not-re-walk.
The string cluster
| Offset | Bytes | Meaning |
|---|---|---|
0x8007B380 | 12 bytes | Per-extension flag/mode metadata table: 00 01 FF FF FF FF 00 00 00 02 00 00. |
0x8007B38C | "sound\" | Path prefix for streaming-asset loads. |
0x8007B394 | ".spk" | SPU sample bank. |
0x8007B39C | ".LZS" | Compressed wrapper (per-file). |
0x8007B3A4 | 8 bytes | Not sound data - the per-character equipment-swap selector bytes read by a graphics-side routine; adjacent in BSS but unrelated (trap below). |
0x8007B3AC | "bse.dat" | Master sound-bank file name (loaded once at sound-init). |
0x8007B3B4 | ".dpk" | Per-scene sound pack - the VAB + SEQ bundle above. |
0x8007B3BC | ".MAP" | Sound bank map (PsyQ SoundArtist output; dev-only). |
0x8007B3C4 | ".PCH" | Patch / instrument data (PsyQ output; dev-only). |
0x8007B3CC | ".LZS" | Duplicate, fallback. |
0x8007B3D4 | ".pac" | Per-scene generic pack. |
0x8007B3DC | "STR" | Streaming audio (raw PSX .STR containers). |
Trap: a third routine reads the 0x8007B3A4/0x8007B3A8 bytes and looks like a sound consumer, but it is the character-TMD equipment-conditional group-transform swap - a graphics function. See character-mesh and how we know.
bse.dat master-bank header
The sound init loads bse.dat once into a 0x1800-byte buffer and keeps a pointer into it for the whole session. A single u16 at offset +2, rounded down to even, is the byte offset of a second section:
count = *(u16 *)(bse_base + 2)
gp[0x678] = bse_base + (count & ~1) ; even byte offset, not a divisor
So bse.dat leads with [u16 @0][u16 @2 = offset to section B][section A ...][section B]. The offset indexes the master bank itself, not the per-scene .dpk.
Dev-build vs retail-build paths
Both path builders carry a debug-flag carve-out (_DAT_8007B8C2):
- Retail branch (flag non-zero, the value retail boots with) loads sound data by PROT index -
0x37A(=sound_data2) plusparam_1 + 5for per-scene variations - via the index-based loader. - Dev branch (flag zero) opens an
h:\main\bg\domepack\<name>.dpkpath through a helper that isstrcpy, a dev-station host trap (break 0x103), then fseek/fread/fclose. Retail hardware cannot service it.
Only the retail branch runs on a real disc; the dev branch is a build-time artefact whose paths are not in the filesystem. The same split appears in the asset loader, so it is a pattern that repeats across loading subsystems.
How we know
| Function | Address | What it proves | Dump |
|---|---|---|---|
Sound init / .dpk loader | FUN_8001FA88 | Allocates the 0x1800 buffer at _DAT_8007B8D0, loads bse.dat, derives the section-B offset from +2; assembles the dev .dpk path from the template at 0x800105C8; retail branch loads PROT index 0x37A | funcs/8001fa88.txt |
| Streaming-asset path builder | FUN_8001FC00 | Builds paths under the sound\ prefix; the XA / .pac / STR consumer | funcs/8001fc00.txt |
| Chunk walker (consumer) | FUN_8001FE70, called by FUN_800513F0 on the loaded buffer | [type|size] chunk walk, advance (size & ~3) + 4, type-2 terminator; type-1 → TIM upload on the graphics walk | funcs/8001fe70.txt, funcs/800513f0.txt |
| Not-a-sound-consumer trap | FUN_8001EBEC | Reads 0x8007B3A4/0x8007B3A8 as equipment-swap selectors against DAT_8007C018 TMD pointers - a graphics routine | funcs/8001ebec.txt |
| Locator sweep | ghidra/scripts/find_sound_path_builders.py | These are the only SCUS functions touching the cluster | - |
| Disc pins | sound_pack_stream_real, sound_pack_vab_seq_real | PROT 0877 walks as [0: 0x2820][1: 0x1BA90][2: SEQ]; 0x2820 + 0x1BA90 == 0x1E2B0 = the VAB's declared total_size; verified across 0877..0885 | disc-gated tests |