bse.dat: the battle sound-effect bank Confirmed
The game's sound effects are described by a table of small records: which sample, which tone, how many voices. Cue ids below 0x200 come from a table built into the executable. Ids from 0x200 up come from a bank loaded at runtime - and while a battle is on screen, that bank is bse.dat. Same record format, different carrier.
At a glance
- Where
- Extraction PROT entry 888 (raw TOC index
0x37A); 297 records - Loader
FUN_8001FA88, whose only caller on the disc is battle initFUN_800513F0- Record
- 8 bytes, the same columns as the static SFX descriptor
- Index
- Row = cue id −
0x200 - Session handle
gp+0x678(0x8007B990): pointer to this bank's record table- Parser
legaia_container_formats::bse_bank; detection classbse_bank- Used by
- battle cue router
FUN_8004FE5C, the sound drainerFUN_80016B6C
A battle load, not a boot load
The loader looks like a sound-init routine, and it has exactly one caller anywhere on the disc: battle init, run once at battle-scene setup. So the bank is loaded for each battle rather than once at start-up.
The loader names the file two ways on a dev / retail flag - by path string in the dev branch, by archive index in the retail branch - and both write the same buffer. That is what ties the file name bse.dat to the archive entry.
Two pointers, two jobs
| Pointer | What it is | Who uses it |
|---|---|---|
gp+0x5B8 (0x8007B8D0) | the sound subsystem's shared current-bundle slot; whatever loaded last owns it | the drainer reads a descriptor for a cue >= 0x200 out of whatever bundle is current |
gp+0x678 (0x8007B990) | the session handle to this bank's record table | the battle cue router writes one descriptor column before enqueueing the cue |
The current-bundle slot holds this bank only until the next field scene loads and repoints it. Because every caller of the router is battle-side, the write and the read land on the same row.
The second pointer looks consumer-less to an ordinary address sweep: a gp-relative access carries no address at all. Finding its readers takes a scan for that form - see the address-reference scan.
Layout
The second header word is consumed as a byte offset, not a count. The walk ends at a record whose first four bytes are zero.
Record columns
These are the static table's columns not by analogy but because one block of code decodes both tables: the drainer picks the static row or this bank's row on a single compare against 0x200, then falls into the same field reads. In n the low five bits are the voice count and bit 0x20 marks a sustained cue. The category selects a mixer record whose own fields are the enable gate and the VAB slot the cue keys.
An earlier reading took +4 for a 32-bit value. It is a one-byte category followed by three bytes nothing reads.
The category is rewritten per cue
The authored category column is a default. Before it enqueues a cue, the battle cue router writes that row's category from a byte on the actor that fired it, so a live cue's sound-bank slot is chosen by the actor - which is how one cue plays from a party member's bank or a monster's. Both of the router's legs reduce to the same expression: byte +4 of row cue_id - 0x200, exactly the row the drainer will resolve.
One format, two occupants
The >= 0x200 bank is a role, and this format is what fills it; which file fills it depends on the mode. In battle it is bse.dat. In the field it is the scene's own prescript record 0, authored per scene. A second archive entry the detector matches (extraction 1195, seven rows) is one such per-scene bank, not a rival copy of bse.dat.
The bytes past the table are not this bank's
The table ends well before the entry does, and the remaining bytes are neither padding nor a second record family. They are sound-bank tone-attribute rows left in the sector by whatever occupied this disc slot before bse.dat was written over its head - byte-identical to a neighbouring entry's tone table at the same file offsets. Nothing in them indexes the bank above.
One consequence: the entry has no authored terminator. The walk stops on four zero bytes that are really a field of the first leftover row - a field that is structurally zero on every retail tone, so the stop is reliable, but it is a foreign row's field.
See also
Source of record: docs/formats/bse-dat.md.