diff options
author | metamuffin <metamuffin@disroot.org> | 2025-03-13 20:10:32 +0100 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2025-03-13 20:10:32 +0100 |
commit | 8f80baff4d18b67859eded67b8c5097324693862 (patch) | |
tree | 380c5a9bfbd427c8363032a231b1da1fb30db960 /src | |
parent | d3006b6d05f7995c6a49d67401293f1266b3ea1f (diff) | |
download | unity-tools-8f80baff4d18b67859eded67b8c5097324693862.tar unity-tools-8f80baff4d18b67859eded67b8c5097324693862.tar.bz2 unity-tools-8f80baff4d18b67859eded67b8c5097324693862.tar.zst |
read object abstraction
Diffstat (limited to 'src')
-rw-r--r-- | src/bin/gltf.rs | 57 | ||||
-rw-r--r-- | src/bin/json.rs | 36 | ||||
-rw-r--r-- | src/bin/meshes.rs | 56 | ||||
-rw-r--r-- | src/bin/textures.rs | 56 | ||||
-rw-r--r-- | src/bin/yaml.rs | 46 | ||||
-rw-r--r-- | src/object/read.rs | 170 | ||||
-rw-r--r-- | src/serialized_file.rs | 42 |
7 files changed, 202 insertions, 261 deletions
diff --git a/src/bin/gltf.rs b/src/bin/gltf.rs index 07adec6..323b6d2 100644 --- a/src/bin/gltf.rs +++ b/src/bin/gltf.rs @@ -1,16 +1,7 @@ #![feature(array_chunks)] use anyhow::anyhow; -use std::{ - env::args, - fs::{File, create_dir_all}, - io::{BufReader, BufWriter, Seek, SeekFrom, Write}, -}; -use unity_tools::{ - classes::mesh::{Mesh, VertexDataChannel}, - object::{parser::FromValue, read::read_value}, - serialized_file::SerializedFile, - unityfs::UnityFS, -}; +use std::{env::args, fs::File, io::BufReader}; +use unity_tools::{serialized_file::SerializedFile, unityfs::UnityFS}; fn main() -> anyhow::Result<()> { env_logger::init_from_env("LOG"); @@ -18,9 +9,6 @@ fn main() -> anyhow::Result<()> { let mut fs = UnityFS::open(file())?; // let mut fs2 = UnityFS::open(file())?; - let mut i = 0; - create_dir_all("/tmp/a").unwrap(); - let cabfile = fs .find_main_file() .ok_or(anyhow!("no CAB file found"))? @@ -28,46 +16,7 @@ fn main() -> anyhow::Result<()> { let mut cab = fs.read(&cabfile)?; let file = SerializedFile::read(&mut cab)?; - for ob in file.objects { - cab.seek(SeekFrom::Start(ob.data_offset))?; - let typetree = if ob.type_id < 0 { - unimplemented!() - } else { - &file.types[ob.type_id as usize] - }; - if let Some(typetree) = &typetree.type_tree { - if typetree.type_string != "Mesh" { - continue; - } - let value = read_value(typetree, file.endianness, &mut cab)?; - let mesh = Mesh::from_value(value).unwrap(); - // if mesh.stream_data.len() == 0 { - // let ress = ress.as_mut().unwrap(); - // ress.seek(SeekFrom::Start(mesh.stream_data.offset))?; - // ress.by_ref() - // .take(mesh.stream_data.size as u64) - // .read_to_end(&mut mesh.)?; - // } - let mut obj = BufWriter::new(File::create(format!( - "/tmp/a/{}_{i}.obj", - mesh.name.replace("/", "-").replace(".", "-") - ))?); - - let (pos_dims, positions) = mesh - .vertex_data - .read_channel(VertexDataChannel::Position) - .unwrap(); - assert_eq!(pos_dims, 3); - - for [x, y, z] in positions.array_chunks() { - writeln!(obj, "v {x} {y} {z}")?; - } - for [a, b, c] in mesh.read_indecies() { - writeln!(obj, "f {} {} {}", a + 1, b + 1, c + 1)?; - } - i += 1; - } - } + for _ob in file.objects {} Ok(()) } diff --git a/src/bin/json.rs b/src/bin/json.rs index 2d09306..dd83de6 100644 --- a/src/bin/json.rs +++ b/src/bin/json.rs @@ -1,9 +1,9 @@ use std::{ env::{args, var}, fs::File, - io::{BufReader, Seek, SeekFrom, stdout}, + io::{BufReader, stdout}, }; -use unity_tools::{object::read::read_value, serialized_file::SerializedFile, unityfs::UnityFS}; +use unity_tools::{serialized_file::SerializedFile, unityfs::UnityFS}; fn main() -> anyhow::Result<()> { env_logger::init_from_env("LOG"); @@ -14,28 +14,20 @@ fn main() -> anyhow::Result<()> { let node = fs.find_main_file().unwrap().to_owned(); let mut cab = fs.read(&node)?; - let file = SerializedFile::read(&mut cab)?; - for ob in file.objects { - cab.seek(SeekFrom::Start(ob.data_offset))?; - let typetree = if ob.type_id < 0 { - unimplemented!() - } else { - &file.types[ob.type_id as usize] - }; - if let Some(typetree) = &typetree.type_tree { - if let Some(f) = &filter { - if typetree.type_string != *f && ob.path_id.to_string() != *f { - continue; - } - } - let value = read_value(typetree, file.endianness, &mut cab)?; - if pretty { - serde_json::to_writer_pretty(stdout(), &value.to_json()).unwrap(); - } else { - serde_json::to_writer(stdout(), &value.to_json()).unwrap(); + let mut file = SerializedFile::read(&mut cab)?; + for ob in file.objects.clone() { + if let Some(f) = &filter { + if file.get_object_type_tree(&ob)?.type_string != *f && ob.path_id.to_string() != *f { + continue; } - println!() } + let value = file.read_object(ob)?; + if pretty { + serde_json::to_writer_pretty(stdout(), &value.to_json()).unwrap(); + } else { + serde_json::to_writer(stdout(), &value.to_json()).unwrap(); + } + println!() } Ok(()) diff --git a/src/bin/meshes.rs b/src/bin/meshes.rs index f2b4edf..d59b496 100644 --- a/src/bin/meshes.rs +++ b/src/bin/meshes.rs @@ -3,11 +3,11 @@ use anyhow::anyhow; use std::{ env::args, fs::{File, create_dir_all}, - io::{BufReader, BufWriter, Seek, SeekFrom, Write}, + io::{BufReader, BufWriter, Write}, }; use unity_tools::{ classes::mesh::{Mesh, VertexDataChannel}, - object::{parser::FromValue, read::read_value}, + object::parser::FromValue, serialized_file::SerializedFile, unityfs::UnityFS, }; @@ -29,39 +29,31 @@ fn main() -> anyhow::Result<()> { .to_owned(); let mut cab = fs.read(&cabfile)?; - let file = SerializedFile::read(&mut cab)?; - for ob in file.objects { - cab.seek(SeekFrom::Start(ob.data_offset))?; - let typetree = if ob.type_id < 0 { - unimplemented!() - } else { - &file.types[ob.type_id as usize] - }; - if let Some(typetree) = &typetree.type_tree { - if typetree.type_string != "Mesh" { - continue; - } - let value = read_value(typetree, file.endianness, &mut cab)?; - let mesh = Mesh::from_value(value).unwrap(); - let mut obj = BufWriter::new(File::create(format!( - "/tmp/a/{}_{i}.obj", - mesh.name.replace("/", "-").replace(".", "-") - ))?); + let mut file = SerializedFile::read(&mut cab)?; + for ob in file.objects.clone() { + if file.get_object_type_tree(&ob)?.type_string != "Mesh" { + continue; + } + let value = file.read_object(ob)?; + let mesh = Mesh::from_value(value).unwrap(); + let mut obj = BufWriter::new(File::create(format!( + "/tmp/a/{}_{i}.obj", + mesh.name.replace("/", "-").replace(".", "-") + ))?); - let (pos_dims, positions) = mesh - .vertex_data - .read_channel(VertexDataChannel::Position) - .unwrap(); - assert_eq!(pos_dims, 3); + let (pos_dims, positions) = mesh + .vertex_data + .read_channel(VertexDataChannel::Position) + .unwrap(); + assert_eq!(pos_dims, 3); - for [x, y, z] in positions.array_chunks() { - writeln!(obj, "v {x} {y} {z}")?; - } - for [a, b, c] in mesh.read_indecies() { - writeln!(obj, "f {} {} {}", a + 1, b + 1, c + 1)?; - } - i += 1; + for [x, y, z] in positions.array_chunks() { + writeln!(obj, "v {x} {y} {z}")?; + } + for [a, b, c] in mesh.read_indecies() { + writeln!(obj, "f {} {} {}", a + 1, b + 1, c + 1)?; } + i += 1; } Ok(()) diff --git a/src/bin/textures.rs b/src/bin/textures.rs index 6ceabac..2e077fe 100644 --- a/src/bin/textures.rs +++ b/src/bin/textures.rs @@ -3,12 +3,10 @@ use log::warn; use std::{ env::args, fs::{File, create_dir_all}, - io::{BufReader, Seek, SeekFrom}, + io::BufReader, }; use unity_tools::{ - classes::texture2d::Texture2D, - object::{parser::FromValue, read::read_value}, - serialized_file::SerializedFile, + classes::texture2d::Texture2D, object::parser::FromValue, serialized_file::SerializedFile, unityfs::UnityFS, }; @@ -27,38 +25,30 @@ fn main() -> anyhow::Result<()> { .to_owned(); let mut cab = fs.read(&cabfile)?; - let file = SerializedFile::read(&mut cab)?; - for ob in file.objects { - cab.seek(SeekFrom::Start(ob.data_offset))?; - let typetree = if ob.type_id < 0 { - unimplemented!() - } else { - &file.types[ob.type_id as usize] - }; - if let Some(typetree) = &typetree.type_tree { - if typetree.type_string != "Texture2D" { - continue; - } - let value = read_value(typetree, file.endianness, &mut cab)?; - let mut texture = Texture2D::from_value(value)?; - if texture.image_data.is_empty() { - texture.image_data = texture.stream_data.read(&mut fs2)?; - } - let path = format!( - "/tmp/a/{}_{i}.png", - texture.name.replace("/", "-").replace(".", "-") - ); - match texture.to_image() { - Ok(im) => { - if !im.as_rgba32f().is_some() { - im.save(&path).unwrap(); - println!("{path}"); - } + let mut file = SerializedFile::read(&mut cab)?; + for ob in file.objects.clone() { + if file.get_object_type_tree(&ob)?.type_string != "Texture2D" { + continue; + } + let value = file.read_object(ob)?; + let mut texture = Texture2D::from_value(value)?; + if texture.image_data.is_empty() { + texture.image_data = texture.stream_data.read(&mut fs2)?; + } + let path = format!( + "/tmp/a/{}_{i}.png", + texture.name.replace("/", "-").replace(".", "-") + ); + match texture.to_image() { + Ok(im) => { + if !im.as_rgba32f().is_some() { + im.save(&path).unwrap(); + println!("{path}"); } - Err(e) => warn!("{e}"), } - i += 1; + Err(e) => warn!("{e}"), } + i += 1; } Ok(()) diff --git a/src/bin/yaml.rs b/src/bin/yaml.rs index fd3eec6..4ef8933 100644 --- a/src/bin/yaml.rs +++ b/src/bin/yaml.rs @@ -2,11 +2,9 @@ use serde_yml::Value; use std::{ env::args, fs::File, - io::{BufReader, Seek, SeekFrom, stdout}, -}; -use unity_tools::{ - classes::HValue, object::read::read_value, serialized_file::SerializedFile, unityfs::UnityFS, + io::{BufReader, stdout}, }; +use unity_tools::{classes::HValue, serialized_file::SerializedFile, unityfs::UnityFS}; fn main() -> anyhow::Result<()> { env_logger::init_from_env("LOG"); @@ -17,44 +15,36 @@ fn main() -> anyhow::Result<()> { let node = fs.find_main_file().unwrap().to_owned(); let mut cab = fs.read(&node)?; - let file = SerializedFile::read(&mut cab)?; - for ob in file.objects { - cab.seek(SeekFrom::Start(ob.data_offset))?; - let typetree = if ob.type_id < 0 { - unimplemented!() - } else { - &file.types[ob.type_id as usize] - }; - if let Some(typetree) = &typetree.type_tree { - if let Some(f) = &filter { - if typetree.type_string != *f && ob.path_id.to_string() != *f { - continue; - } + let mut file = SerializedFile::read(&mut cab)?; + for ob in file.objects.clone() { + if let Some(f) = &filter { + if file.get_object_type_tree(&ob)?.type_string != *f && ob.path_id.to_string() != *f { + continue; } - let value = read_value(typetree, file.endianness, &mut cab)?; - let hvalue = HValue::from_value(value)?; + } + let value = file.read_object(ob)?; + let hvalue = HValue::from_value(value)?; - let mut hvalue = serde_yml::to_value(hvalue)?; - reduce_large_arrays(&mut hvalue); + let mut hvalue = serde_yml::to_value(hvalue)?; + trim_large_arrays(&mut hvalue); - serde_yml::to_writer(stdout(), &hvalue).unwrap(); - println!() - } + serde_yml::to_writer(stdout(), &hvalue).unwrap(); + println!() } Ok(()) } -fn reduce_large_arrays(v: &mut Value) { +fn trim_large_arrays(v: &mut Value) { match v { Value::Sequence(values) => { - values.iter_mut().for_each(reduce_large_arrays); + values.iter_mut().for_each(trim_large_arrays); while values.len() > 32 { values.pop(); } } - Value::Mapping(mapping) => mapping.map.values_mut().for_each(reduce_large_arrays), - Value::Tagged(tagged_value) => reduce_large_arrays(&mut tagged_value.value), + Value::Mapping(mapping) => mapping.map.values_mut().for_each(trim_large_arrays), + Value::Tagged(tagged_value) => trim_large_arrays(&mut tagged_value.value), _ => (), } } diff --git a/src/object/read.rs b/src/object/read.rs index 5adbe3f..641bdcc 100644 --- a/src/object/read.rs +++ b/src/object/read.rs @@ -6,95 +6,93 @@ use log::trace; use std::io::Seek; use std::{collections::BTreeMap, io::Read}; -pub fn read_value( - ty: &TypeTreeNode, - e: Endianness, - data: &mut (impl Read + Seek), -) -> Result<Value> { - let mut align = false; - let pos_before = data.stream_position()?; - let r = match ty.type_string.as_str() { - "char" => { - assert_eq!(ty.byte_size, 1); - Ok(Value::U8(data.read_u8()?)) - } - "Type*" => Ok(Value::U32(data.read_u32(e)?)), - "int" => Ok(Value::I32(data.read_i32(e)?)), - "unsigned int" => Ok(Value::U32(data.read_u32(e)?)), - "UInt8" => Ok(Value::U8(data.read_u8()?)), - "UInt16" => Ok(Value::U16(data.read_u16(e)?)), - "UInt32" => Ok(Value::U32(data.read_u32(e)?)), - "UInt64" => Ok(Value::U64(data.read_u64(e)?)), - "SInt8" => Ok(Value::I8(data.read_i8()?)), - "SInt16" => Ok(Value::I16(data.read_i16(e)?)), - "SInt32" => Ok(Value::I32(data.read_i32(e)?)), - "SInt64" => Ok(Value::I64(data.read_i64(e)?)), - "bool" => Ok(Value::Bool(data.read_u8()? != 0)), - "float" => { - data.align(4)?; - Ok(Value::F32(data.read_f32(e)?)) - } - "double" => { - data.align(4)?; - Ok(Value::F64(data.read_f64(e)?)) - } - "string" => { - let Value::Array(arr) = read_value(&ty.children[0], e, data)? else { - unreachable!() - }; - let bytes = arr - .into_iter() - .map(|e| match e { - Value::U8(x) => x, - _ => unreachable!(), - }) - .collect::<Vec<_>>(); - Ok(Value::String(String::from_utf8(bytes)?)) - } - "Array" => { - align |= ty.children[0].post_align(); - assert_eq!(ty.byte_size, -1); - let Value::I32(size) = read_value(&ty.children[0], e, data)? else { - unreachable!() - }; - trace!("array of size {size}"); - let mut elems = Vec::new(); - for _ in 0..size { - elems.push(read_value(&ty.children[1], e, data)?); +impl Value { + pub fn read(ty: &TypeTreeNode, e: Endianness, data: &mut (impl Read + Seek)) -> Result<Value> { + let mut align = false; + let pos_before = data.stream_position()?; + let r = match ty.type_string.as_str() { + "char" => { + assert_eq!(ty.byte_size, 1); + Ok(Value::U8(data.read_u8()?)) } - Ok(Value::Array(elems)) - } - "TypelessData" => { - let len = data.read_u32(e)?; - let mut buf = vec![0u8; len as usize]; - data.read_exact(&mut buf)?; - Ok(Value::Typeless(buf)) - } - _ => { - if ty.children.is_empty() && ty.byte_size != 0 { - todo!("need type {:?}", ty.type_string); + "Type*" => Ok(Value::U32(data.read_u32(e)?)), + "int" => Ok(Value::I32(data.read_i32(e)?)), + "unsigned int" => Ok(Value::U32(data.read_u32(e)?)), + "UInt8" => Ok(Value::U8(data.read_u8()?)), + "UInt16" => Ok(Value::U16(data.read_u16(e)?)), + "UInt32" => Ok(Value::U32(data.read_u32(e)?)), + "UInt64" => Ok(Value::U64(data.read_u64(e)?)), + "SInt8" => Ok(Value::I8(data.read_i8()?)), + "SInt16" => Ok(Value::I16(data.read_i16(e)?)), + "SInt32" => Ok(Value::I32(data.read_i32(e)?)), + "SInt64" => Ok(Value::I64(data.read_i64(e)?)), + "bool" => Ok(Value::Bool(data.read_u8()? != 0)), + "float" => { + data.align(4)?; + Ok(Value::F32(data.read_f32(e)?)) } - let mut fields = BTreeMap::new(); - for c in &ty.children { - fields.insert(c.name_string.clone(), read_value(&c, e, data)?); + "double" => { + data.align(4)?; + Ok(Value::F64(data.read_f64(e)?)) } - Ok(Value::Object { - fields, - class: ty.type_string.clone(), - }) + "string" => { + let Value::Array(arr) = Value::read(&ty.children[0], e, data)? else { + unreachable!() + }; + let bytes = arr + .into_iter() + .map(|e| match e { + Value::U8(x) => x, + _ => unreachable!(), + }) + .collect::<Vec<_>>(); + Ok(Value::String(String::from_utf8(bytes)?)) + } + "Array" => { + align |= ty.children[0].post_align(); + assert_eq!(ty.byte_size, -1); + let Value::I32(size) = Value::read(&ty.children[0], e, data)? else { + unreachable!() + }; + trace!("array of size {size}"); + let mut elems = Vec::new(); + for _ in 0..size { + elems.push(Value::read(&ty.children[1], e, data)?); + } + Ok(Value::Array(elems)) + } + "TypelessData" => { + let len = data.read_u32(e)?; + let mut buf = vec![0u8; len as usize]; + data.read_exact(&mut buf)?; + Ok(Value::Typeless(buf)) + } + _ => { + if ty.children.is_empty() && ty.byte_size != 0 { + todo!("need type {:?}", ty.type_string); + } + let mut fields = BTreeMap::new(); + for c in &ty.children { + fields.insert(c.name_string.clone(), Value::read(&c, e, data)?); + } + Ok(Value::Object { + fields, + class: ty.type_string.clone(), + }) + } + }; + let pos_after = data.stream_position()?; + if ty.byte_size != -1 && pos_after - pos_before < ty.byte_size as u64 { + bail!( + "did not read enough data ({} expected, {} actual)", + ty.byte_size, + pos_after - pos_before + ); } - }; - let pos_after = data.stream_position()?; - if ty.byte_size != -1 && pos_after - pos_before < ty.byte_size as u64 { - bail!( - "did not read enough data ({} expected, {} actual)", - ty.byte_size, - pos_after - pos_before - ); - } - if align || ty.post_align() { - trace!("post align"); - data.align(4)?; + if align || ty.post_align() { + trace!("post align"); + data.align(4)?; + } + r } - r } diff --git a/src/serialized_file.rs b/src/serialized_file.rs index b56fa3a..b3c1e3c 100644 --- a/src/serialized_file.rs +++ b/src/serialized_file.rs @@ -1,10 +1,11 @@ use crate::{ common_strings::COMMON_STRINGS, helper::{AlignExt, Endianness, ReadExt}, + object::Value, }; -use anyhow::{Result, bail}; +use anyhow::{Result, anyhow, bail}; use log::{debug, info, trace, warn}; -use std::io::{Cursor, Read, Seek}; +use std::io::{Cursor, Read, Seek, SeekFrom}; #[derive(Debug, Clone)] pub struct TypeTreeNode { @@ -32,7 +33,7 @@ pub struct SeralizedType { pub type_deps: Vec<u32>, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ObjectInfo { pub path_id: i64, pub data_offset: u64, @@ -55,7 +56,8 @@ pub struct External { } #[derive(Debug)] -pub struct SerializedFile { +pub struct SerializedFile<T> { + pub file: T, pub header: SerializedFileHeader, pub types: Vec<SeralizedType>, pub externals: Vec<External>, @@ -120,8 +122,9 @@ impl SerializedFileHeader { }) } } -impl SerializedFile { - pub fn read(mut file: impl Read + Seek) -> Result<SerializedFile> { + +impl<T: Read + Seek> SerializedFile<T> { + pub fn read(mut file: T) -> Result<SerializedFile<T>> { let h = SerializedFileHeader::read(&mut file)?; let e = h.endianness; @@ -293,6 +296,7 @@ impl SerializedFile { let user_string = file.read_cstr()?; Ok(SerializedFile { + file, header: h, types, externals, @@ -302,6 +306,32 @@ impl SerializedFile { user_string, }) } + + pub fn get_object_type_tree(&self, ob: &ObjectInfo) -> Result<&'_ TypeTreeNode> { + let r#type = if ob.type_id < 0 { + unimplemented!() + } else { + &self.types[ob.type_id as usize] + }; + r#type + .type_tree + .as_ref() + .ok_or(anyhow!("type tree missing")) + } + pub fn read_object(&mut self, ob: ObjectInfo) -> Result<Value> { + self.file.seek(SeekFrom::Start(ob.data_offset))?; + //? Duplicated impl from get_object_type_tree because borrow problem + let r#type = if ob.type_id < 0 { + unimplemented!() + } else { + &self.types[ob.type_id as usize] + }; + let typetree = r#type + .type_tree + .as_ref() + .ok_or(anyhow!("type tree missing"))?; + Value::read(typetree, self.endianness, &mut self.file) + } } impl TypeTreeNode { |