XPlor/definitions/fmod/fsb.xscript
njohnson 7b1f5d34a1 Consolidate XScript definitions with byte order inheritance
- Volition VPP: Unified BE/LE types using inheritance pattern
- THQA PAK: Child types now inherit byte order from parent
- Various XScript definition updates and fixes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 16:08:46 -05:00

234 lines
6.6 KiB
Plaintext

// FMOD Sample Bank (FSB) - Audio Container Format
// Firelight Technologies Pty, Ltd.
// Supports FSB3, FSB4, FSB5
//
// Reference: https://github.com/gdawg/fsbext
// Root FSB type - auto-detects version
type fmod_fsb ui("FMOD Sample Bank", root) byteorder LE
{
criteria {
require ascii(bytesat(0, 4)) == "FSB3" ||
ascii(bytesat(0, 4)) == "FSB4" ||
ascii(bytesat(0, 4)) == "FSB5" ||
_ext == "fsb";
}
magic = ascii(read(4)) ui("Magic");
if (magic == "FSB3") {
i32 num_samples ui("Sample Count");
i32 sample_header_size ui("Sample Headers Size");
i32 data_size ui("Audio Data Size");
u32 version ui("Version");
u32 mode ui("Mode Flags");
_header_end = 24;
_audio_data_start = _header_end + sample_header_size;
_current_audio_offset = _audio_data_start;
if (num_samples > 0 && num_samples < 10000) {
repeat(num_samples) {
_sample = parse_here("fsb3_sample_header");
_compressed_size = get(_sample, "length_compressed");
_sample_name = get(_sample, "name");
_sample_mode = get(_sample, "mode");
// Export and preview audio file
if (_compressed_size > 0 && _compressed_size < 100000000) {
_audio_bytes = bytesat(_current_audio_offset, _compressed_size);
_export_name = _sample_name + ".raw";
if ((_sample_mode & 0x200) != 0) {
_export_name = _sample_name + ".mp3";
}
write_file(_export_name, _audio_bytes);
set_preview(_export_name, _audio_bytes);
set_viewer("audio");
}
_current_audio_offset = _current_audio_offset + _compressed_size;
content = push("content", _sample);
}
}
}
if (magic == "FSB4") {
content = parse_here("fsb4_content");
}
if (magic == "FSB5") {
content = parse_here("fsb5_content");
}
}
// FSB3 Sample Header
type fsb3_sample_header ui("Sample") byteorder LE
{
u16 header_size ui("Header Size");
_name_bytes = read(30);
name = clean(ascii(_name_bytes)) ui("Name");
set_name(name);
u32 length_samples ui("Length (samples)");
u32 length_compressed ui("Compressed Size");
u32 loop_start ui("Loop Start");
u32 loop_end ui("Loop End");
u32 mode ui("Mode Flags");
i32 frequency ui("Frequency (Hz)");
u16 volume ui("Default Volume");
i16 pan ui("Default Pan");
u16 priority ui("Priority");
u16 channels ui("Channels");
f32 min_distance ui("Min Distance");
f32 max_distance ui("Max Distance");
i32 var_freq ui("Frequency Variance");
u16 var_vol ui("Volume Variance");
i16 var_pan ui("Pan Variance");
_bytes_read = 80;
_extra_bytes = header_size - _bytes_read;
if (_extra_bytes > 0 && _extra_bytes < 10000) {
skip(_extra_bytes);
}
// Decode format
format = "Unknown" ui("Format");
if ((mode & 0x200) != 0) {
format = "MPEG" ui("Format");
}
if ((mode & 0x400000) != 0) {
format = "IMA ADPCM" ui("Format");
}
if ((mode & 0x1000000) != 0) {
format = "XMA" ui("Format");
}
if ((mode & 0x2000000) != 0) {
format = "GC ADPCM" ui("Format");
}
if ((mode & 0x800000) != 0) {
format = "VAG" ui("Format");
}
if ((mode & 0x10) != 0 && (mode & 0x200) == 0 &&
(mode & 0x400000) == 0 && (mode & 0x1000000) == 0) {
format = "PCM16" ui("Format");
}
if ((mode & 0x8) != 0) {
format = "PCM8" ui("Format");
}
}
// FSB4 Content
type fsb4_content ui("FSB4 Content") byteorder LE
{
i32 num_samples ui("Sample Count");
i32 sample_header_size ui("Sample Headers Size");
i32 data_size ui("Audio Data Size");
u32 version ui("Version");
u32 mode ui("Mode Flags");
skip(8);
hash = read(16) ui("Hash");
_header_end = 48;
_audio_data_start = _header_end + sample_header_size;
if (num_samples > 0 && num_samples < 10000) {
_current_audio_offset = _audio_data_start;
repeat(num_samples) {
_sample = parse_here("fsb3_sample_header");
_compressed_size = get(_sample, "length_compressed");
_sample_name = get(_sample, "name");
_sample_mode = get(_sample, "mode");
// Export and preview audio file
if (_compressed_size > 0 && _compressed_size < 100000000) {
_audio_bytes = bytesat(_current_audio_offset, _compressed_size);
_export_name = _sample_name + ".raw";
if ((_sample_mode & 0x200) != 0) {
_export_name = _sample_name + ".mp3";
}
write_file(_export_name, _audio_bytes);
set_preview(_export_name, _audio_bytes);
set_viewer("audio");
}
_current_audio_offset = _current_audio_offset + _compressed_size;
content = push("content", _sample);
}
}
}
// FSB5 Content
type fsb5_content ui("FSB5 Content") byteorder LE
{
i32 version ui("Version");
i32 num_samples ui("Sample Count");
i32 sample_header_size ui("Sample Headers Size");
i32 name_table_size ui("Name Table Size");
i32 data_size ui("Audio Data Size");
u32 mode ui("Mode/Codec");
skip(8);
hash = read(16) ui("Hash");
skip(8);
_header_end = 60;
_data_start = _header_end + sample_header_size + name_table_size;
info = "FSB5 uses compact variable-length headers" ui("Note");
}
// Embedded parser for PAK files
type fmod_fsb_embedded ui("FMOD Sample Bank") byteorder LE
{
magic = ascii(read(4)) ui("Magic");
if (magic == "FSB3") {
i32 num_samples ui("Sample Count");
i32 sample_header_size ui("Sample Headers Size");
i32 data_size ui("Audio Data Size");
u32 version ui("Version");
u32 mode ui("Mode Flags");
_header_end = 24;
_audio_data_start = _header_end + sample_header_size;
_current_audio_offset = _audio_data_start;
if (num_samples > 0 && num_samples < 10000) {
repeat(num_samples) {
_sample = parse_here("fsb3_sample_header");
_compressed_size = get(_sample, "length_compressed");
_sample_name = get(_sample, "name");
_sample_mode = get(_sample, "mode");
// Export and preview audio file
if (_compressed_size > 0 && _compressed_size < 100000000) {
_audio_bytes = bytesat(_current_audio_offset, _compressed_size);
_export_name = _sample_name + ".raw";
if ((_sample_mode & 0x200) != 0) {
_export_name = _sample_name + ".mp3";
}
write_file(_export_name, _audio_bytes);
set_preview(_export_name, _audio_bytes);
set_viewer("audio");
}
_current_audio_offset = _current_audio_offset + _compressed_size;
content = push("content", _sample);
}
}
}
if (magic == "FSB4") {
content = parse_here("fsb4_content");
}
if (magic == "FSB5") {
content = parse_here("fsb5_content");
}
}