TO:R Data Map

sabf: sead sound banks

Source
docs/formats/sabf.md
SHA-256
7d901804…9e89ec00

A sound bank of Square Enix's sead sound engine (SQEX::Sd), which the game links: a 16-byte header with the bank's name and number, a table of four chunks (snd , seq , trk , mtrl), then the materials, which hold the audio itself. Every material in the game is CRI HCA audio. Banks are loose .sab files under sound/bin/ (music, voices, movie audio), entries in the pakd of sound/bin/SECommon.dat (sound effects), and entries in title/title.dat and event/scene/*.dat. The tables that say which bank and file a sound comes from are in sound/sound.dat, sound_ja.dat, sound_en.dat and SECommon.dat#0 (Sound tables). The engine's own settings are in sound/sead.config.

Based on Every sabf in the game: the 22,105 loose .sab files under sound/bin/, and 620 pakd entries (4 in sound/bin/SECommon.dat, 5 in title/title.dat, 611 in 244 event/scene/*.dat); sound/sound.dat, sound/sound_ja.dat, sound/sound_en.dat; FileTable.bin; the unpacked executable, SHA-256 7b0230a11498f40396c9048345e7cfe0ef442cb7ca8faffc6f197920d2669599. Outside source: vgmstream's src/meta/sqex_sead.c (checked 2026-10-11).
Checked 2026-10-11
Method Single-file C# programs (session scratchpad, with the toolchain's Containers library): sabscan.cs walks every bank's header, chunk table, chunk headers, chunk offsets and materials and checks how they tile the file; ftsound.cs lists every sound/ file in FileTable.bin with its group and file ID; voicejoin.cs joins the voice tables with the file table; sounddat.cs dumps the tables. Static analysis with Ghidra 12.1.4 through the decompiled code in local/exe/decomp/ of the game's sound manager (0x140287200 and around), its stream reader (0x140290830), and sead's bank code (0x1405d95b0, 0x1405d9920, 0x1405f3570, 0x1405fe4e0, 0x1405faf80); hex inspection of the executable's data. Not yet decoded or rebuilt by the toolchain, and no in-game tests.

Where banks are and how the game finds them

Every loose bank and .dat here has a record in FileTable.bin, and the game opens them by group and file ID, never by name (confirmed: ftsound.cs; the opens below are static analysis, likely). Group 400 is sound/; 401 holds the Japanese voices and 402 the English ones, picked by the voice-language setting (byte 0x2B of the game's settings at DAT_140ce4e30: 0 Japanese, 1 English).

Banks Where Group, file ID Loaded Bank number (0x0A) Found by
Sound effects: menu, battle, weather, effect sound/bin/SECommon.dat #1–#4 400, 0 Whole, into memory, at startup 2, 3, 1, 6 Sound ID, through SECommon.dat#0
Music, taco_BGM01–84 sound/bin/bgm/taco_BGMnn.sab 400, nn (1–84) Streamed 0 File ID; in English, 4 IDs swap through sound.dat#2
Opening movie audio sound/bin/OPN_movie/chapter1.sab 400, 100 Streamed 7 File ID
Movie audio sound/bin/{ja,en}/Movie/*.sab (7 each) 401 or 402, 1000–1012, even IDs Streamed 7 File ID
Voice lines sound/bin/{ja,en}/voice_*/*.sab (10,310 ja, 11,693 en) 401 or 402, 2000–25446, even IDs Streamed 2000–2012, one per folder Name, through sound_ja.dat or sound_en.dat; title calls through sound.dat#0
Damage cries sound/bin/{ja,en}/voice_DAMAGE/voice_DAMAGE.sab 401, 0x1FF; 402, 0x200 Whole, into memory 2013 Sound index in the bank
Scene sounds event/scene/*.dat, 1–8 in each of 244 files (the scene's file) Whole, into memory, up to 16 per scene 5 The scene's sound list
Title sounds title/title.dat #6–#10 2500, 0 Whole, into memory 5 Entry index, fixed in the code (0x1404abe70)
3013_wheel_of_fortune sound/bin/event/3013_wheel_of_fortune.sab 700, 40000 Not traced 5 —

Sound tables

Tables that map sound IDs, file IDs and voice names to banks. All are xlce tables in a pakd. None has a table schema yet; the row shapes here are from the code that reads them and the C# programs above.

SECommon.dat#0: sound-effect banks

4 rows of 16 bytes, one per bank in entries #1–#4, in order. Read by 0x140285a50 (which checks the entry's xlc magic with strncmp) and 0x140288850.

Offset Size Type Name Meaning Confidence Source
0x0 4 u32 bank_slot Equals the bank's number at 0x0A in all 4 (2, 3, 1, 6). The game doesn't read it: it loads the banks into the slots in its own array at 0x14067bc1c ({2, 3, 1, 6}) and overwrites this field in memory with the slot it got. likely 0x140285a50 → 0x1401a9840; sounddat.cs, sabscan.cs.
0x4 4 u32 index_offset Added to ID − first_sound_id to give the sound index. 0 in all 4. likely 0x140288850.
0x8 4 u32 first_sound_id First sound ID of the bank. confirmed 0x140288850; ranges match the snd counts.
0xC 4 u32 end_sound_id One past the last sound ID: first_sound_id + the bank's snd count. confirmed Same.

sound_ja.dat and sound_en.dat: voice names

11 tables each (#0–#10), all with 16-byte rows. #0–#9 cover the story by chapter (SCENARIO_C1, C2A, C2B, C3A, C3B, C3C, C4 from 001 and from 331, DLC, EP); #10 lists every voice file of the language once (11,724 en, 10,310 ja rows). Read by 0x140285c90, which checks the file's pak magic and builds one name → row map per language; the first row with a name wins.

Offset Size Type Name Meaning Confidence Source
0x0 8 str_ofs64 name The voice's name, e.g. SCENARIO_C1_001_001_00, after a U+F8FC header that the game skips (txte.md); in every row the header is EF A3 BC 02 02 01 and two more bytes. likely 0x140285c90; voicejoin.cs.
0x8 2 u16 file_id The voice file's ID in group 401 (ja) or 402 (en). confirmed voicejoin.cs: every row with has_voice 1 names the file <name>.sab.
0xA 2 u16 has_voice 1: the line is voiced. 0: it isn't, and file_id points at some other line's file or at no file. likely voicejoin.cs: none of the 145 en and 1,605 ja rows with 0 names its own file; meaning not traced in code.
0xC 4 u32 unk_0xC 0 in every row. confirmed voicejoin.cs.

sound.dat

Entry Rows × size What it is Row Read by
#0 114 × 10 Title-call voices, in groups: a row with a non-zero group starts a group, the rows with 0 after it continue it. The game picks one row of a group at random, weighted. u8 group; u8 unk_0x01; u16 ja_file_id (group 401); u16 en_file_id (group 402, 0 in 19 rows); u16 ja_weight; u16 en_weight 0x1402889c0 (likely)
#1 48 × 8 Which title-call groups can play, by sound flag (a bit of the 128-bit array at DAT_140ce4e30 +0x254, 0x1402d6960). u8 index (= row); u8 unk_0x01; u8 unk_0x02; u8 group (in #0); u8 unk_0x04; u8 0; u16 sound_flag_id 0x14032b5c0 (guess)
#2 4 × 4 Music swapped when the voice language is English: 66 → 77, 70 → 78, 80 → 81, 83 → 84. u16 from_file_id; u16 to_file_id (group 400) 0x140288700, 0x1402889c0 (likely)

confirmed: every #0 file ID names a file in voice_TITLECALL/, except the 19 English IDs that are 0. Source: voicejoin.cs.

0x10 bytes, then the name. Offsets are from the start of the bank.

Offset Size Type Name Meaning Confidence Source
0x00 4 raw[4] magic sabf. The only field the game checks. confirmed C# scan, all 22,725; checked by 0x1405d90e0.
0x04 1 u8 version 2 in every bank. sead finds the chunk table by it (below). confirmed C# scan; 0x1405d8920.
0x05 1 u8 unk_0x05 0 in the 6 on-memory banks of sound/ (the 4 in SECommon.dat, both voice_DAMAGE.sab), 1 in all others, the title banks (on-memory too) among them. sead reads bit 0 (0x1405b8070, 0x1405db4f0). — C# scan (hdr05.cs); 0x1405b8070, 0x1405db4f0. vgmstream: flags.
0x06 2 u16 header_size 0x10 in every bank. confirmed C# scan; 0x1405d8920.
0x08 1 u8 chunk_count 4 in every bank. confirmed C# scan; 0x1405d8920 (loop bound).
0x09 1 u8 name_length Length of name, without its NUL. confirmed C# scan (= strlen in all); 0x1405d8920.
0x0A 2 u16 bank_number The bank's number in sead: see Where banks are. likely 0x1405db750, 0x1405db610 (% 257, a hash bucket); C# scan (values).
0x0C 4 u32 file_size The bank's size. The game doesn't read it. confirmed C# scan: the file's size in all 22,105 loose banks; the entry's size − 16 in all 620 pakd entries. Not read by 0x1405d95b0, 0x1405fe4e0, 0x1405f3570.
0x10 name_length + 1 raw name ASCII, NUL-terminated: the folder or kind (bgm, menu, voice_SCENARIO_C1, event, …). confirmed C# scan.
after name to chunk_table raw padding Zeros up to 0x10 + align16(name_length + 1). confirmed C# scan, all zero.

Chunk table

At header_size + align16(name_length + 1) (sead computes it as ((name_length + 0x10) & 0xF0) + header_size when version is 2 or more, else header_size); chunk_count records of 16 bytes. In every bank the tags are snd , seq , trk , mtrl, in that order, and the first chunk starts right after the table (confirmed, C# scan).

Offset Size Type Name Meaning Confidence Source
0x0 4 raw[4] tag The chunk's tag. sead finds a chunk by searching the table for its tag (tags at 0x1406a60d0). likely 0x1405d8920 (mtrl), the readers around it; C# scan.
0x4 1 u8 unk_0x4 0 in every record. confirmed C# scan.
0x5 1 u8 unk_0x5 0 in every record. confirmed C# scan.
0x6 2 u16 unk_0x6 16 in every record. vgmstream: the record's size. confirmed C# scan; vgmstream.
0x8 4 u32 offset The chunk's start, from the start of the bank; a multiple of 16, ascending. confirmed C# scan; 0x1405d8920.
0xC 4 u32 unk_0xC 0 in every record. confirmed C# scan.

Chunks

Each chunk starts with a 16-byte header and a table of offsets to what it holds: sounds (snd ), sequences (seq ), tracks (trk ) or materials (mtrl), the kinds sead's own classes name.

Offset Size Type Name Meaning Confidence Source
0x0 1 u8 version 1 in snd , 0 in the others. When it isn't 0, an offset of 0xFFFFFFFF means none there. confirmed C# scan; 0x1405d9100.
0x1 1 u8 unk_0x1 0 in every chunk. confirmed C# scan.
0x2 2 u16 header_size 0x10 in every chunk: where the offset table starts. confirmed C# scan; 0x1405d89b0.
0x4 2 u16 count Number of sounds, sequences, tracks or materials. confirmed C# scan; 0x1405d9100.
0x6 2 u16 unk_0x6 0 in every chunk. confirmed C# scan.
0x8 8 raw[8] unk_0x8 0 in every chunk. confirmed C# scan.
0x10 4 × count u32[count] offsets Where each one starts, from the start of the chunk. confirmed C# scan (all inside their chunk); 0x1405d89b0.

Materials

The mtrl chunk's materials, one per piece of audio. Each is a 32-byte header, the codec's extra data, then the audio.

Offset Size Type Name Meaning Confidence Source
0x00 1 u8 unk_0x00 1 in every material. No code read of it found. — C# scan.
0x01 1 u8 unk_0x01 0 in every material. confirmed C# scan.
0x02 2 u16 header_size 0x20 in every material: where the extra data starts. confirmed C# scan; 0x1405faf80.
0x04 1 u8 channels 1 (23,062) or 2 (392); 0 in empty materials. confirmed C# scan; read by 0x1405faf80.
0x05 1 u8 codec See Codecs. 7 (HCA) in 23,454 materials, 0 (none) in 20. confirmed C# scan; 0x1405faf80.
0x06 2 u16 number The material's index in the chunk (0 in empty ones). confirmed C# scan.
0x08 4 u32 sample_rate 48000 (22,534), 44100 (862), or 18000–32000 in 58; 0 in empty ones. confirmed C# scan; read by 0x1405faf80.
0x0C 4 u32 loop_start Loop start in samples; 0 when the material doesn't loop. likely 0x1405faf80; C# scan (looping music has values). Agrees with vgmstream.
0x10 4 u32 loop_end Loop end in samples; 0 when the material doesn't loop. likely Same.
0x14 4 u32 extra_data_size Size of the extra data. For HCA, 0x10 + the HCA header's size. confirmed C# scan (all 23,454 HCA).
0x18 4 u32 stream_size Size of the audio after the extra data. confirmed C# scan (tiling, below).
0x1C 2 u16 extra_data_id Varies (0 to over 1000). Not read by the code traced. vgmstream treats its low byte as an HCA key; no HCA here is encrypted. guess C# scan. vgmstream extradata_id.
0x1E 2 u16 unk_0x1E 0 in every material. confirmed C# scan.
0x20 extra_data_size raw extra_data Codec data; for HCA, below. confirmed C# scan.
0x20 + extra_data_size stream_size raw stream The encoded audio. confirmed C# scan.

Codecs

The codec names come from sead's table at 0x140ca57e0: 0 None, 1 Pcm, 2 ADPCM, 3 VORBIS, 4 ATRAC9, 5 XMA2, 6 MP3, 7 HCA, 8 SWITCHOPUS (likely, hex of the executable's data; vgmstream has the same list). On PC, a sead voice plays only 1 (16-bit PCM), 2 (Microsoft ADPCM) and 7 (CRI HCA); any other code fails with Voice::Initialize failed no support format!!! (likely, 0x1405faf80). The game's banks use only 7.

HCA extra data

The extra_data of an HCA material: a 16-byte sead header, then the HCA file's own header (HCA, fmt, comp, ciph, … chunks, big-endian), whose frames are the stream. sead hands the HCA header and frames to CRI's HCA decoder (HCADecoder_*, overview); the HCA format isn't laid out here.

Offset Size Type Name Meaning Confidence Source
0x00 1 u8 version 1 in every material. confirmed C# scan.
0x01 1 u8 header_size 0x10: where the HCA header starts. likely C# scan.
0x02 2 u16 hca_header_size The HCA header's size; equals the size in the HCA header itself. confirmed C# scan, all 23,454.
0x04 2 u16 frame_size HCA frame size. likely 0x1405faf80. Agrees with vgmstream.
0x06 2 u16 loop_start_frame The loop's first frame: equals the HCA loop chunk's value. likely 0x1405faf80; C# scan. Agrees with vgmstream.
0x08 2 u16 loop_end_frame The loop's last frame, as loop_start_frame. likely Same.
0x0A 2 u16 encoder_delay A copy of the HCA fmt chunk's value at +0x0C, in all 22,538 where it's set. guess C# scan. vgmstream "inserted samples (encoder delay)".
0x0C 1 u8 use_mixer 0 in every material. When it isn't 0, the sead voice skips its own decode buffers and registers itself elsewhere (0x140627a60). guess 0x1405faf80; C# scan. vgmstream "use mixer".
0x0D 1 u8 encrypted 1 in the 84 music banks and the 14 Movie banks, 0 in all others; with 1 the sead voice allocates a one-frame buffer. Every HCA header here has cipher type 0 (none), so the name is only vgmstream's reading. guess 0x1405faf80; C# scan. vgmstream (encryption flag).
0x0E 2 u16 unk_0x0E likely vgmstream (reserved).
0x10 hca_header_size raw hca_header The HCA header, starting HCA\0. confirmed C# scan, all 23,454.

How the game reads it

Static analysis, so likely throughout; the functions are in the names list.

On-memory banks

The sound effects, voice_DAMAGE.sab, the scene banks and the title banks.

Streaming banks

Music, voices and movie audio.

Playing a material

Rebuilding and replacing audio

None of this is proven by a round trip or an in-game test yet.

To replace a sound's audio, a modder keeps:

Leads