At a glance

Magic
none - a string cluster in SCUS_942.54 at RAM 0x8007B380 (file offset 0x6BB80)
Where on disc
The .dpk payloads are the sound_data2 PROT family (0877..0885); bse.dat is the master bank the sound subsystem loads once at init
Parser
legaia_asset::sound_pack (extract returns the reconstituted VAB + SEQ); walker parse_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 0 VAB header chunk type 1 VAB sample pool chunk type 2 = end SEQ pBAV pQES one contiguous VAB total_size = size0 + size1 the scene's SEQ plays against the VAB
Types 0 + 1 concatenate into one VAB; the terminator chunk is the SEQ itself.
Chunk typePayloadMagic
0VAB header sectionpBAV (0x5641_4270 LE)
1VAB sample section (SPU-ADPCM / VAG waveform pool)- (raw samples)
2SEQ (the stream terminator)pQES
  • Decisive invariant: type-0 + type-1 reconstitute one contiguous VAB whose header total_size equals chunk[0].size + chunk[1].size. Byte-verified across 08770885; the reconstituted VAB parses with legaia_vab, the SEQ with legaia_seq.
  • Same walker, different terminator. The chunk walker is the one the DATA_FIELD dispatcher models, but it ends on a type-0x02 chunk (non-zero size) where DATA_FIELD ends on a zero-size header - so the DATA_FIELD parser mis-reads a .dpk. Use StreamTerminator::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

OffsetBytesMeaning
0x8007B38012 bytesPer-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).
0x8007B3A48 bytesNot 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) plus param_1 + 5 for per-scene variations - via the index-based loader.
  • Dev branch (flag zero) opens an h:\main\bg\domepack\<name>.dpk path through a helper that is strcpy, 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

FunctionAddressWhat it provesDump
Sound init / .dpk loaderFUN_8001FA88Allocates 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 0x37Afuncs/8001fa88.txt
Streaming-asset path builderFUN_8001FC00Builds paths under the sound\ prefix; the XA / .pac / STR consumerfuncs/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 walkfuncs/8001fe70.txt, funcs/800513f0.txt
Not-a-sound-consumer trapFUN_8001EBECReads 0x8007B3A4/0x8007B3A8 as equipment-swap selectors against DAT_8007C018 TMD pointers - a graphics routinefuncs/8001ebec.txt
Locator sweepghidra/scripts/find_sound_path_builders.pyThese are the only SCUS functions touching the cluster-
Disc pinssound_pack_stream_real, sound_pack_vab_seq_realPROT 0877 walks as [0: 0x2820][1: 0x1BA90][2: SEQ]; 0x2820 + 0x1BA90 == 0x1E2B0 = the VAB's declared total_size; verified across 0877..0885disc-gated tests

See also