// 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 [root, display="FMOD Sample Bank"] 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", "Magic"); if (magic == "FSB3") { i32 num_samples; ui("num_samples", "Sample Count"); i32 sample_header_size; ui("sample_header_size", "Sample Headers Size"); i32 data_size; ui("data_size", "Audio Data Size"); u32 version; ui("version", "Version"); u32 mode; ui("mode", "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 [display="Sample"] byteorder LE { u16 header_size; ui("header_size", "Header Size"); _name_bytes = read(30); name = clean(ascii(_name_bytes)); ui("name", "Name"); set_name(name); u32 length_samples; ui("length_samples", "Length (samples)"); u32 length_compressed; ui("length_compressed", "Compressed Size"); u32 loop_start; ui("loop_start", "Loop Start"); u32 loop_end; ui("loop_end", "Loop End"); u32 mode; ui("mode", "Mode Flags"); i32 frequency; ui("frequency", "Frequency (Hz)"); u16 volume; ui("volume", "Default Volume"); i16 pan; ui("pan", "Default Pan"); u16 priority; ui("priority", "Priority"); u16 channels; ui("channels", "Channels"); f32 min_distance; ui("min_distance", "Min Distance"); f32 max_distance; ui("max_distance", "Max Distance"); i32 var_freq; ui("var_freq", "Frequency Variance"); u16 var_vol; ui("var_vol", "Volume Variance"); i16 var_pan; ui("var_pan", "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", "Format"); if ((mode & 0x200) != 0) { format = "MPEG"; ui("format", "Format"); } if ((mode & 0x400000) != 0) { format = "IMA ADPCM"; ui("format", "Format"); } if ((mode & 0x1000000) != 0) { format = "XMA"; ui("format", "Format"); } if ((mode & 0x2000000) != 0) { format = "GC ADPCM"; ui("format", "Format"); } if ((mode & 0x800000) != 0) { format = "VAG"; ui("format", "Format"); } if ((mode & 0x10) != 0 && (mode & 0x200) == 0 && (mode & 0x400000) == 0 && (mode & 0x1000000) == 0) { format = "PCM16"; ui("format", "Format"); } if ((mode & 0x8) != 0) { format = "PCM8"; ui("format", "Format"); } } // FSB4 Content type fsb4_content [display="FSB4 Content"] byteorder LE { i32 num_samples; ui("num_samples", "Sample Count"); i32 sample_header_size; ui("sample_header_size", "Sample Headers Size"); i32 data_size; ui("data_size", "Audio Data Size"); u32 version; ui("version", "Version"); u32 mode; ui("mode", "Mode Flags"); skip(8); hash = read(16); ui("hash", "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 [display="FSB5 Content"] byteorder LE { i32 version; ui("version", "Version"); i32 num_samples; ui("num_samples", "Sample Count"); i32 sample_header_size; ui("sample_header_size", "Sample Headers Size"); i32 name_table_size; ui("name_table_size", "Name Table Size"); i32 data_size; ui("data_size", "Audio Data Size"); u32 mode; ui("mode", "Mode/Codec"); skip(8); hash = read(16); ui("hash", "Hash"); skip(8); _header_end = 60; _data_start = _header_end + sample_header_size + name_table_size; info = "FSB5 uses compact variable-length headers"; ui("info", "Note"); } // Embedded parser for PAK files type fmod_fsb_embedded [display="FMOD Sample Bank"] byteorder LE { magic = ascii(read(4)); ui("magic", "Magic"); if (magic == "FSB3") { i32 num_samples; ui("num_samples", "Sample Count"); i32 sample_header_size; ui("sample_header_size", "Sample Headers Size"); i32 data_size; ui("data_size", "Audio Data Size"); u32 version; ui("version", "Version"); u32 mode; ui("mode", "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"); } }