diff options
author | metamuffin <metamuffin@disroot.org> | 2025-03-25 03:09:35 +0100 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2025-03-25 03:09:35 +0100 |
commit | 466a8fce3c5693fa51cc34ec5d9e718459484e0b (patch) | |
tree | 8d53af8de01d50e8b858b29bfcb93bf2a8a66224 | |
parent | 407841b7d2516823e1d44344b3d2c1d52ffa2db9 (diff) | |
download | unity-tools-466a8fce3c5693fa51cc34ec5d9e718459484e0b.tar unity-tools-466a8fce3c5693fa51cc34ec5d9e718459484e0b.tar.bz2 unity-tools-466a8fce3c5693fa51cc34ec5d9e718459484e0b.tar.zst |
start on own fmod sound bank impl
-rw-r--r-- | Cargo.lock | 223 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | src/classes/audio_clip.rs | 19 | ||||
-rw-r--r-- | src/fmod.rs | 252 | ||||
-rw-r--r-- | src/helper.rs | 7 | ||||
-rw-r--r-- | src/lib.rs | 1 |
6 files changed, 272 insertions, 231 deletions
@@ -80,28 +80,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" [[package]] -name = "aotuv_lancer_vorbis_sys" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c22da1eae146e1ac2956da45bd1856b2f9ad788e6f7af535b861df497d726a1" -dependencies = [ - "cc", - "ogg_next_sys", -] - -[[package]] name = "arbitrary" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" [[package]] -name = "arbitrary-int" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "825297538d77367557b912770ca3083f778a196054b3ee63b22673c4a3cae0a5" - -[[package]] name = "arg_enum_proc_macro" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -154,29 +138,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] -name = "bilge" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc707ed8ebf81de5cd6c7f48f54b4c8621760926cdf35a57000747c512e67b57" -dependencies = [ - "arbitrary-int", - "bilge-impl", -] - -[[package]] -name = "bilge-impl" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feb11e002038ad243af39c2068c8a72bcf147acf05025dcdb916fcc000adb2d8" -dependencies = [ - "itertools 0.11.0", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - -[[package]] name = "bit_field" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -345,16 +306,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] -name = "errno" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" -dependencies = [ - "libc", - "windows-sys", -] - -[[package]] name = "exporter" version = "0.1.0" dependencies = [ @@ -406,19 +357,6 @@ dependencies = [ ] [[package]] -name = "fsbex" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "362e570e47b98ed9966c238a72cb0b64120f9dc6d44373c35b27ae2223ca7dcc" -dependencies = [ - "bilge", - "lewton", - "phf", - "tap", - "vorbis_rs", -] - -[[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -598,15 +536,6 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" @@ -672,16 +601,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] -name = "lewton" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "777b48df9aaab155475a83a7df3070395ea1ac6902f5cd062b8f2b028075c030" -dependencies = [ - "byteorder 1.5.0", - "tinyvec", -] - -[[package]] name = "libc" version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -851,15 +770,6 @@ dependencies = [ ] [[package]] -name = "ogg_next_sys" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c990730c2782b922815753a62af535e4205267df190dfbd8aa5d74e11d7dcc3" -dependencies = [ - "cc", -] - -[[package]] name = "once_cell" version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -872,48 +782,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] -name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_macros", - "phf_shared", -] - -[[package]] -name = "phf_generator" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared", - "rand", -] - -[[package]] -name = "phf_macros" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" -dependencies = [ - "phf_generator", - "phf_shared", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher", -] - -[[package]] name = "pkg-config" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -957,29 +825,6 @@ dependencies = [ ] [[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] name = "proc-macro2" version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1075,7 +920,7 @@ dependencies = [ "built", "cfg-if", "interpolate_name", - "itertools 0.12.1", + "itertools", "libc", "libfuzzer-sys", "log", @@ -1091,7 +936,7 @@ dependencies = [ "rand_chacha", "simd_helpers", "system-deps", - "thiserror 1.0.69", + "thiserror", "v_frame", "wasm-bindgen", ] @@ -1256,12 +1101,6 @@ dependencies = [ ] [[package]] -name = "siphasher" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" - -[[package]] name = "smallvec" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1298,12 +1137,6 @@ dependencies = [ ] [[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] name = "target-lexicon" version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1332,16 +1165,7 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" -dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl", ] [[package]] @@ -1356,17 +1180,6 @@ dependencies = [ ] [[package]] -name = "thiserror-impl" -version = "2.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] name = "tiff" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1378,21 +1191,6 @@ dependencies = [ ] [[package]] -name = "tinyvec" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] name = "toml" version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1448,7 +1246,6 @@ version = "0.1.0" dependencies = [ "anyhow", "env_logger", - "fsbex", "glam", "humansize", "image", @@ -1497,20 +1294,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] -name = "vorbis_rs" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874075ad757c0f6031d4706ed75703a9ba6c711164a0b1dab55af86800cd618f" -dependencies = [ - "aotuv_lancer_vorbis_sys", - "errno", - "getrandom", - "ogg_next_sys", - "thiserror 2.0.12", - "tinyvec", -] - -[[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -21,4 +21,3 @@ glam = { version = "0.30.0", features = ["serde"] } texpresso = "2.0.1" image = "0.25.5" texture2ddecoder = { git = "https://github.com/UniversalGameExtraction/texture2ddecoder", rev = "d2b4653fda298f1da39917da86bc509b17879808" } -fsbex = "0.3.0" diff --git a/src/classes/audio_clip.rs b/src/classes/audio_clip.rs index 53a265f..a3e8d22 100644 --- a/src/classes/audio_clip.rs +++ b/src/classes/audio_clip.rs @@ -1,10 +1,11 @@ use super::streamed_resource::StreamedResource; use crate::{ + fmod::FmodSoundBank, object::{Value, parser::FromValue}, unityfs::UnityFS, }; -use anyhow::{Result, anyhow, bail}; -use log::info; +use anyhow::{Ok, Result, anyhow, bail}; +use log::{debug, info}; use serde::Serialize; use std::io::{Cursor, Read, Seek}; @@ -48,15 +49,13 @@ impl AudioClip { match self.compression_format { AudioCompressionFormat::Vorbis => { info!("reading vorbis FMOD sound bank"); - let bank = fsbex::Bank::new(Cursor::new(data))?; - assert_eq!(bank.format(), fsbex::AudioFormat::Vorbis); - assert_eq!(u32::from(bank.num_streams()), 1); - let stream = bank.into_iter().next().unwrap(); + let mut bank = FmodSoundBank::open(Cursor::new(data))?; + // Ok(buf.into_inner()) + for i in 0..bank.streams.len() { + let data = bank.read_stream(i)?; + } - let mut buf = Cursor::new(Vec::new()); - stream.write(&mut buf)?; - - Ok(buf.into_inner()) + Ok(Vec::new()) } x => todo!("audio format {x:?}"), } diff --git a/src/fmod.rs b/src/fmod.rs new file mode 100644 index 0000000..f0e7b4c --- /dev/null +++ b/src/fmod.rs @@ -0,0 +1,252 @@ +use crate::helper::ReadExt; +use anyhow::{Ok, Result, bail}; +use log::debug; +use std::io::{Cursor, Read, Seek, SeekFrom}; + +pub struct FmodSoundBank<T> { + inner: T, + data_base_offset: u64, + header: FsbHeader, + pub streams: Vec<StreamInfo>, +} + +pub struct StreamInfo { + header: StreamHeader, +} + +struct FsbHeader { + version: u32, + num_streams: u32, + stream_headers_size: u32, + name_table_size: u32, + data_size: u32, + format: FsbFormat, +} + +#[repr(u32)] +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum FsbFormat { + None, + Pcm8, + Pcm16, + Pcm24, + Pcm32, + PcmFloat, + GcAdPcm, + ImaAdPcm, + Vag, + HeVag, + Xma, + Mpeg, + Celt, + At9, + Xwma, + Vorbis, + FAdPcm, + Opus, +} + +impl FsbHeader { + pub fn read(mut f: impl Read) -> Result<Self> { + let mut buf = [0; 4]; + f.read_exact(&mut buf)?; + if &buf != b"FSB5" { + bail!("invalid magic") + } + + let version = f.read_u32_le()?; + let num_streams = f.read_u32_le()?; + let stream_headers_size = f.read_u32_le()?; + let name_table_size = f.read_u32_le()?; + let data_size = f.read_u32_le()?; + let format = FsbFormat::from_num(f.read_u32_le()?)?; + + debug!("FSB version: {version}"); + debug!("Sample Count: {num_streams}"); + debug!("Format: {format:?}"); + + f.discard(32)?; + + Ok(Self { + data_size, + format, + name_table_size, + num_streams, + stream_headers_size, + version, + }) + } +} + +impl FsbFormat { + pub fn from_num(x: u32) -> Result<Self> { + if x < 18 { + Ok(unsafe { std::mem::transmute(x) }) + } else { + bail!("audio format out of range") + } + } +} + +impl<T: Read + Seek> FmodSoundBank<T> { + pub fn open(mut file: T) -> Result<Self> { + let header = FsbHeader::read(&mut file)?; + let mut streams = Vec::new(); + + for _ in 0..header.num_streams { + let header = StreamHeader::from_packed(file.read_u64_le()?)?; + eprintln!("{header:?}"); + let mut next = header.next_chunk; + while next { + let chunk = ChunkHeader::from_packed(file.read_u32_le()?)?; + + match chunk.kind { + k => todo!("chunk kind {k:?}"), + } + + next = chunk.next_chunk; + } + streams.push(StreamInfo { header }); + } + + if header.name_table_size != 0 { + let mut offsets = Vec::new(); + for _ in 0..header.num_streams { + offsets.push(file.read_u32_le()?); + } + let mut buf = vec![0u8; header.name_table_size as usize]; + file.read_exact(&mut buf)?; + let mut buf = Cursor::new(buf); + let mut names = Vec::new(); + for o in offsets { + buf.seek(SeekFrom::Start(o as u64))?; + names.push(buf.read_cstr()?); + } + } + let data_base_offset = file.stream_position()?; + + Ok(Self { + data_base_offset, + header, + streams, + inner: file, + }) + } + pub fn read_stream(&mut self, n: usize) -> Result<()> { + let info = &self.streams[n]; + + let start = info.header.data_offset; + let end = self + .streams + .get(n + 1) + .map_or(self.header.data_size, |s| s.header.data_offset); + + let mut data = vec![0; (end - start) as usize]; + debug!("{} {}", start, end); + self.inner + .seek(SeekFrom::Start(start as u64 + self.data_base_offset))?; + self.inner.read_exact(&mut data)?; + + Ok(()) + } +} + +#[derive(Debug)] +struct StreamHeader { + next_chunk: bool, + samplerate: u32, + channels: u8, + data_offset: u32, + samples: u32, +} +impl StreamHeader { + const MASKS: &[u64] = &[ + 0b1000000000000000000000000000000000000000000000000000000000000000, + 0b0111100000000000000000000000000000000000000000000000000000000000, + 0b0000011000000000000000000000000000000000000000000000000000000000, + 0b0000000111111111111111111111111111000000000000000000000000000000, + 0b0000000000000000000000000000000000111111111111111111111111111111, + ]; + pub fn from_packed(x: u64) -> Result<Self> { + let next_chunk = x & Self::MASKS[0] != 0; + let samplerate_raw = (x & Self::MASKS[1]) >> Self::MASKS[1].trailing_zeros(); + let channels_raw = x & Self::MASKS[2] >> Self::MASKS[2].trailing_zeros(); + let data_offset = (x & Self::MASKS[3]) >> Self::MASKS[3].trailing_zeros(); + let samples = x & Self::MASKS[4]; + Ok(Self { + next_chunk, + samplerate: match samplerate_raw { + 0 => 4000, + 1 => 8000, + 2 => 11000, + 3 => 11025, + 4 => 16000, + 5 => 22050, + 6 => 24000, + 7 => 32000, + 8 => 44100, + 9 => 48000, + 10 => 96000, + x => bail!("invalid sampling frequency: {x}"), + }, + channels: match channels_raw { + 0 => 1, + 1 => 2, + 2 => 4, + 3 => 8, + x => bail!("invalid channel count: {x}"), + }, + data_offset: data_offset as u32 * 16, + samples: samples as u32, + }) + } +} + +struct ChunkHeader { + next_chunk: bool, + size: u32, + kind: ChunkKind, +} +impl ChunkHeader { + const MASKS: &[u32] = &[ + 0b10000000000000000000000000000000, + 0b01111111111111111111111110000000, + 0b00000000000000000000000001111111, + ]; + pub fn from_packed(x: u32) -> Result<Self> { + let next_chunk = x & Self::MASKS[0] != 0; + let size = (x & Self::MASKS[1]) >> Self::MASKS[1].trailing_zeros(); + let kind = x & Self::MASKS[2]; + Ok(Self { + kind: ChunkKind::from_num(kind as u8)?, + size, + next_chunk, + }) + } +} + +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq)] +enum ChunkKind { + Channels, + Samplerate, + Loop, + Comment, + XmaSeekTable, + DspCoefficients, + Atrac9Config, + XwmaConfig, + VorbisSeekTable, + PeakVolume, + VorbisIntraLayers, + OpusDataSize, +} +impl ChunkKind { + pub fn from_num(n: u8) -> Result<Self> { + if n < 13 { + Ok(unsafe { std::mem::transmute(n) }) + } else { + bail!("unknown chunk type {n:02x}") + } + } +} diff --git a/src/helper.rs b/src/helper.rs index 0681b62..18efa4e 100644 --- a/src/helper.rs +++ b/src/helper.rs @@ -36,6 +36,7 @@ pub trait ReadExt { fn read_f64_be(&mut self) -> Result<f64>; fn read_f64_le(&mut self) -> Result<f64>; fn read_cstr(&mut self) -> Result<String>; + fn discard(&mut self, n: usize) -> Result<()>; } impl<T: Read> ReadExt for T { @@ -192,6 +193,12 @@ impl<T: Read> ReadExt for T { } Ok(String::from_utf8_lossy(&s).to_string()) } + fn discard(&mut self, n: usize) -> Result<()> { + for _ in 0..n { + self.read_u8()?; + } + Ok(()) + } } pub trait AlignExt { @@ -6,3 +6,4 @@ pub mod object; pub mod serialized_file; pub mod unityfs; pub mod assetbundle; +pub mod fmod; |