diff options
author | metamuffin <metamuffin@disroot.org> | 2025-03-23 20:18:10 +0100 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2025-03-23 20:18:10 +0100 |
commit | d5aa53fafed0dbf6d43943e3b88b99693821e5cd (patch) | |
tree | 1facf5a2e9d83fa018b03636f03c641ec1a1c46a /src/classes | |
parent | 756664281e6f8c37653e8769890962e8bab933e9 (diff) | |
download | unity-tools-d5aa53fafed0dbf6d43943e3b88b99693821e5cd.tar unity-tools-d5aa53fafed0dbf6d43943e3b88b99693821e5cd.tar.bz2 unity-tools-d5aa53fafed0dbf6d43943e3b88b99693821e5cd.tar.zst |
audio clips
Diffstat (limited to 'src/classes')
-rw-r--r-- | src/classes/audio_clip.rs | 93 | ||||
-rw-r--r-- | src/classes/cubemap.rs | 2 | ||||
-rw-r--r-- | src/classes/mesh.rs | 2 | ||||
-rw-r--r-- | src/classes/mod.rs | 6 | ||||
-rw-r--r-- | src/classes/streamed_resource.rs | 50 | ||||
-rw-r--r-- | src/classes/streaming_info.rs (renamed from src/classes/streaminginfo.rs) | 3 | ||||
-rw-r--r-- | src/classes/texture2d.rs | 2 |
7 files changed, 151 insertions, 7 deletions
diff --git a/src/classes/audio_clip.rs b/src/classes/audio_clip.rs new file mode 100644 index 0000000..18bd297 --- /dev/null +++ b/src/classes/audio_clip.rs @@ -0,0 +1,93 @@ +use super::streamed_resource::StreamedResource; +use crate::{ + object::{Value, parser::FromValue}, + unityfs::UnityFS, +}; +use anyhow::{Result, anyhow, bail}; +use log::info; +use serde::Serialize; +use std::io::{Cursor, Read, Seek}; + +#[derive(Debug, Serialize)] +pub struct AudioClip { + pub resource: StreamedResource, + pub channels: i32, + pub ambisonic: bool, + pub bits_per_sample: i32, + pub compression_format: AudioCompressionFormat, + pub frequency: i32, + pub is_tracker_format: bool, + pub legacy_3d: bool, + pub length: f32, + pub name: String, + pub subsound_index: i32, +} + +impl FromValue for AudioClip { + fn from_value(v: Value) -> Result<Self> { + let mut fields = v.as_class("AudioClip")?; + Ok(AudioClip { + resource: fields.field("m_Resource")?, + ambisonic: fields.field("m_Ambisonic")?, + bits_per_sample: fields.field("m_BitsPerSample")?, + compression_format: fields.field("m_CompressionFormat")?, + channels: fields.field("m_Channels")?, + frequency: fields.field("m_Frequency")?, + is_tracker_format: fields.field("m_IsTrackerFormat")?, + legacy_3d: fields.field("m_Legacy3D")?, + length: fields.field("m_Length")?, + name: fields.field("m_Name")?, + subsound_index: fields.field("m_SubsoundIndex")?, + }) + } +} + +impl AudioClip { + pub fn read_ogg(&self, fs: &UnityFS<impl Read + Seek>) -> Result<Vec<u8>> { + let data = self.resource.read(&fs)?; + 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 buf = Cursor::new(Vec::new()); + stream.write(&mut buf)?; + Ok(buf.into_inner()) + } + x => todo!("audio format {x:?}"), + } + } +} + +impl FromValue for AudioCompressionFormat { + fn from_value(v: Value) -> Result<Self> { + let x = v + .as_i32() + .ok_or(anyhow!("expected i32 AudioCompressionFormat"))?; + if x < 10 { + Ok(unsafe { std::mem::transmute(x) }) + } else { + bail!("AudioCompressionFormat out of range") + } + } +} + +#[allow(non_camel_case_types)] +#[repr(i32)] +#[derive(Debug, Serialize, PartialEq, Clone, Copy)] + +pub enum AudioCompressionFormat { + PCM, + Vorbis, + ADPCM, + MP3, + PSMVAG, + HEVAG, + XMA, + AAC, + GCADPCM, + ATRAC9, +} diff --git a/src/classes/cubemap.rs b/src/classes/cubemap.rs index bd80408..653ec38 100644 --- a/src/classes/cubemap.rs +++ b/src/classes/cubemap.rs @@ -1,4 +1,4 @@ -use super::{streaminginfo::StreamingInfo, texture2d::TextureFormat}; +use super::{streaming_info::StreamingInfo, texture2d::TextureFormat}; use crate::object::{Value, parser::FromValue}; use anyhow::Result; use glam::{UVec2, Vec3Swizzles, uvec2, vec2, vec3}; diff --git a/src/classes/mesh.rs b/src/classes/mesh.rs index 96e42c2..09efe66 100644 --- a/src/classes/mesh.rs +++ b/src/classes/mesh.rs @@ -1,4 +1,4 @@ -use super::streaminginfo::StreamingInfo; +use super::streaming_info::StreamingInfo; use crate::object::{Value, parser::FromValue}; use anyhow::{Result, anyhow, bail}; use glam::{Mat4, Vec2, Vec3, Vec3A, Vec4}; diff --git a/src/classes/mod.rs b/src/classes/mod.rs index c312c6a..2bba6c1 100644 --- a/src/classes/mod.rs +++ b/src/classes/mod.rs @@ -3,7 +3,7 @@ pub mod gameobject; pub mod material; pub mod mesh; pub mod pptr; -pub mod streaminginfo; +pub mod streaming_info; pub mod texture2d; pub mod transform; pub mod vectors; @@ -11,6 +11,8 @@ pub mod renderer; pub mod shader; pub mod cubemap; pub mod render_settings; +pub mod audio_clip; +pub mod streamed_resource; use crate::object::{Value, parser::FromValue}; use anyhow::Result; @@ -21,7 +23,7 @@ use mesh::Mesh; use pptr::PPtr; use serde::Serialize; use std::collections::BTreeMap; -use streaminginfo::StreamingInfo; +use streaming_info::StreamingInfo; use texture2d::Texture2D; use transform::Transform; diff --git a/src/classes/streamed_resource.rs b/src/classes/streamed_resource.rs new file mode 100644 index 0000000..945c8e1 --- /dev/null +++ b/src/classes/streamed_resource.rs @@ -0,0 +1,50 @@ +use crate::{ + object::{Value, parser::FromValue}, + unityfs::UnityFS, +}; +use anyhow::{Result, anyhow, bail}; +use serde::Serialize; +use std::io::{Read, Seek, SeekFrom}; + +#[derive(Debug, Serialize)] +pub struct StreamedResource { + pub offset: u64, + pub size: u64, + pub path: String, +} + +impl FromValue for StreamedResource { + fn from_value(v: Value) -> Result<Self> { + let mut fields = v.as_class("StreamedResource")?; + Ok(StreamedResource { + offset: fields.field("m_Offset")?, + size: fields.field("m_Size")?, + path: fields.field("m_Source")?, + }) + } +} + +impl StreamedResource { + pub fn read(&self, fs: &UnityFS<impl Read + Seek>) -> Result<Vec<u8>> { + if !self.path.starts_with("archive:") { + bail!( + "StreamedResource path does not start on 'archive:' ({:?})", + self.path + ) + } + let nodeinfo = fs + .header + .nodes() + .iter() + .find(|n| self.path.ends_with(&n.name)) + .ok_or(anyhow!("node with path {:?} not found", self.path))? + .to_owned(); + + let mut buf = Vec::new(); + + let mut node = fs.read(&nodeinfo)?; + node.seek(SeekFrom::Start(self.offset))?; + node.take(self.size).read_to_end(&mut buf)?; + Ok(buf) + } +} diff --git a/src/classes/streaminginfo.rs b/src/classes/streaming_info.rs index d593a5b..9f7ce23 100644 --- a/src/classes/streaminginfo.rs +++ b/src/classes/streaming_info.rs @@ -1,11 +1,10 @@ -use std::io::{Read, Seek, SeekFrom}; - use crate::{ object::{Value, parser::FromValue}, unityfs::UnityFS, }; use anyhow::{Result, anyhow, bail}; use serde::Serialize; +use std::io::{Read, Seek, SeekFrom}; #[derive(Debug, Serialize)] pub struct StreamingInfo { diff --git a/src/classes/texture2d.rs b/src/classes/texture2d.rs index 8f87625..41820b0 100644 --- a/src/classes/texture2d.rs +++ b/src/classes/texture2d.rs @@ -1,4 +1,4 @@ -use super::streaminginfo::StreamingInfo; +use super::streaming_info::StreamingInfo; use crate::object::{Value, parser::FromValue}; use anyhow::{Result, anyhow, bail}; use image::{DynamicImage, ImageBuffer, Luma, Rgb, Rgba}; |