aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2025-03-25 03:09:35 +0100
committermetamuffin <metamuffin@disroot.org>2025-03-25 03:09:35 +0100
commit466a8fce3c5693fa51cc34ec5d9e718459484e0b (patch)
tree8d53af8de01d50e8b858b29bfcb93bf2a8a66224
parent407841b7d2516823e1d44344b3d2c1d52ffa2db9 (diff)
downloadunity-tools-466a8fce3c5693fa51cc34ec5d9e718459484e0b.tar
unity-tools-466a8fce3c5693fa51cc34ec5d9e718459484e0b.tar.bz2
unity-tools-466a8fce3c5693fa51cc34ec5d9e718459484e0b.tar.zst
start on own fmod sound bank impl
-rw-r--r--Cargo.lock223
-rw-r--r--Cargo.toml1
-rw-r--r--src/classes/audio_clip.rs19
-rw-r--r--src/fmod.rs252
-rw-r--r--src/helper.rs7
-rw-r--r--src/lib.rs1
6 files changed, 272 insertions, 231 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 48b8fb9..67645ec 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 462a69c..702632a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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 {
diff --git a/src/lib.rs b/src/lib.rs
index 68e80f4..60454ac 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -6,3 +6,4 @@ pub mod object;
pub mod serialized_file;
pub mod unityfs;
pub mod assetbundle;
+pub mod fmod;