From 2f4a11ddda04604d5d756231d258ef60fa9f7bd8 Mon Sep 17 00:00:00 2001 From: metamuffin Date: Sat, 15 Feb 2025 13:21:25 +0100 Subject: can read objects --- Cargo.lock | 41 ------------------------ Cargo.toml | 2 +- readme.md | 3 ++ src/bin/parse.rs | 48 ++++++++++++++++++++++++++++ src/bin/probe.rs | 25 +++++++++++++++ src/helper.rs | 38 ++++++++++++++++++++++ src/main.rs | 42 ------------------------ src/object.rs | 87 ++++++++++++++++++++++++++++++++++++++++++++++++-- src/serialized_file.rs | 45 +++++++++++++++++++++++--- src/unityfs.rs | 23 +++++++++---- 10 files changed, 256 insertions(+), 98 deletions(-) create mode 100644 src/bin/parse.rs create mode 100644 src/bin/probe.rs delete mode 100644 src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 9c94035..64f091c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,15 +73,6 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855" -[[package]] -name = "cc" -version = "1.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7777341816418c02e033934a09f20dc0ccaf65a5201ef8a450ae0105a573fda" -dependencies = [ - "shlex", -] - [[package]] name = "cfg-if" version = "1.0.0" @@ -129,37 +120,12 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" -[[package]] -name = "libc" -version = "0.2.169" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" - [[package]] name = "log" version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" -[[package]] -name = "lz4" -version = "1.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a20b523e860d03443e98350ceaac5e71c6ba89aea7d960769ec3ce37f4de5af4" -dependencies = [ - "lz4-sys", -] - -[[package]] -name = "lz4-sys" -version = "1.11.1+lz4-1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bd8c0d6c6ed0cd30b3652886bb8711dc4bb01d637a68105a3d5158039b418e6" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "lz4_flex" version = "0.11.3" @@ -219,12 +185,6 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - [[package]] name = "static_assertions" version = "1.1.0" @@ -248,7 +208,6 @@ dependencies = [ "anyhow", "env_logger", "log", - "lz4", "lz4_flex", "lzma", ] diff --git a/Cargo.toml b/Cargo.toml index 1ed74f4..bcfc594 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,4 +9,4 @@ env_logger = "0.11.6" anyhow = "1.0.95" lz4_flex = "0.11.3" lzma = "0.2.2" -lz4 = "1.28.1" +# lz4 = "1.28.1" diff --git a/readme.md b/readme.md index e0de8bf..7e76025 100644 --- a/readme.md +++ b/readme.md @@ -2,6 +2,9 @@ Rust library for various file formats used in the Unity game engine. +- UnityFS +- AssetBundle + ## License AGPL-3.0-only; See [COPYING](./COPYING) diff --git a/src/bin/parse.rs b/src/bin/parse.rs new file mode 100644 index 0000000..d436aad --- /dev/null +++ b/src/bin/parse.rs @@ -0,0 +1,48 @@ +use std::{ + env::args, + fs::File, + io::{BufReader, Read, Seek, SeekFrom}, +}; +use unity_tools::{object::read_value, serialized_file::read_serialized_file, unityfs::UnityFS}; + +fn main() -> anyhow::Result<()> { + env_logger::init_from_env("LOG"); + let file = BufReader::new(File::open(args().nth(1).unwrap())?); + let mut fs = UnityFS::open(file)?; + + for node in fs.nodes().to_vec() { + if node.name.ends_with(".resource") || node.name.ends_with(".resS") { + continue; + } + let mut cab = fs.read(&node)?; + // let mut writer = File::create(format!("/tmp/{}", node.name))?; + // std::io::copy(&mut cab, &mut writer)?; + // continue; + + let file = read_serialized_file(&mut cab)?; + let e = file.endianness; + + for ob in file.objects { + cab.seek(SeekFrom::Start(ob.data_offset))?; + let mut ob_data = cab.by_ref(); //.take(ob.data_size as u64); + + eprintln!("{:#?}", ob); + let typetree = if ob.type_id < 0 { + unimplemented!() + } else { + file.types + .iter() + .find(|t| t.class_id == ob.type_id) + .expect("unknown type") + }; + eprintln!("{typetree:#?}"); + + let value = read_value(typetree.type_tree.as_ref().unwrap(), e, &mut ob_data)?; + + eprintln!("{value:#?}") + } + // eprintln!("{:#?}", file.types); + } + + Ok(()) +} diff --git a/src/bin/probe.rs b/src/bin/probe.rs new file mode 100644 index 0000000..85b2a7a --- /dev/null +++ b/src/bin/probe.rs @@ -0,0 +1,25 @@ +use anyhow::Result; +use std::{env::args, fs::File, io::BufReader}; +use unity_tools::{serialized_file::read_serialized_file_header, unityfs::UnityFS}; + +fn main() -> Result<()> { + let file = BufReader::new(File::open(args().nth(1).unwrap())?); + let mut fs = UnityFS::open(file)?; + + for node in fs.nodes().to_vec() { + if node.name.ends_with(".resource") || node.name.ends_with(".resS") { + continue; + } + let mut cab = fs.read(&node)?; + let ch = read_serialized_file_header(&mut cab)?; + + if fs.unity_version.is_ascii() && ch.generator_version.is_ascii() && ch.format < 100 { + println!( + "{}\t{}\t{}\t{}", + fs.file_version, fs.unity_version, ch.format, ch.generator_version + ); + } + } + + Ok(()) +} diff --git a/src/helper.rs b/src/helper.rs index cb52b8c..a65d3e8 100644 --- a/src/helper.rs +++ b/src/helper.rs @@ -28,6 +28,12 @@ pub trait ReadExt { fn read_i64_le(&mut self) -> Result; fn read_u128_be(&mut self) -> Result; fn read_cstr(&mut self) -> Result; + fn read_f32(&mut self, e: Endianness) -> Result; + fn read_f32_be(&mut self) -> Result; + fn read_f32_le(&mut self) -> Result; + fn read_f64(&mut self, e: Endianness) -> Result; + fn read_f64_be(&mut self) -> Result; + fn read_f64_le(&mut self) -> Result; } impl ReadExt for T { @@ -132,6 +138,38 @@ impl ReadExt for T { self.read_exact(&mut buf)?; Ok(i64::from_le_bytes(buf)) } + fn read_f32(&mut self, e: Endianness) -> Result { + match e { + Endianness::Big => self.read_f32_be(), + Endianness::Little => self.read_f32_le(), + } + } + fn read_f32_be(&mut self) -> Result { + let mut buf = [0; 4]; + self.read_exact(&mut buf)?; + Ok(f32::from_be_bytes(buf)) + } + fn read_f32_le(&mut self) -> Result { + let mut buf = [0; 4]; + self.read_exact(&mut buf)?; + Ok(f32::from_le_bytes(buf)) + } + fn read_f64(&mut self, e: Endianness) -> Result { + match e { + Endianness::Big => self.read_f64_be(), + Endianness::Little => self.read_f64_le(), + } + } + fn read_f64_be(&mut self) -> Result { + let mut buf = [0; 8]; + self.read_exact(&mut buf)?; + Ok(f64::from_be_bytes(buf)) + } + fn read_f64_le(&mut self) -> Result { + let mut buf = [0; 8]; + self.read_exact(&mut buf)?; + Ok(f64::from_le_bytes(buf)) + } fn read_u128_be(&mut self) -> Result { let mut buf = [0; 16]; self.read_exact(&mut buf)?; diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 450f4a7..0000000 --- a/src/main.rs +++ /dev/null @@ -1,42 +0,0 @@ -use std::{ - env::args, - fs::File, - io::{BufReader, Read, Seek, SeekFrom}, -}; -use unity_tools::{serialized_file::read_serialized_file, unityfs::UnityFS}; - -fn main() -> anyhow::Result<()> { - env_logger::init_from_env("LOG"); - let file = BufReader::new(File::open(args().nth(1).unwrap())?); - let mut fs = UnityFS::open(file)?; - - for node in fs.nodes().to_vec() { - if node.name.ends_with(".resource") || node.name.ends_with(".resS") { - continue; - } - let mut cab = fs.read(&node)?; - // let mut writer = File::create(format!("/tmp/{}", node.name))?; - // std::io::copy(&mut cab, &mut writer)?; - // continue; - - let file = read_serialized_file(&mut cab)?; - - for ob in file.objects { - cab.seek(SeekFrom::Start(ob.data_offset))?; - let ob_data = cab.by_ref().take(ob.data_size as u64); - - eprintln!("{:#?}", ob); - let typetree = if ob.type_id < 0 { - unimplemented!() - } else { - file.types - .iter() - .find(|t| t.class_id == ob.type_id) - .expect("unknown type") - }; - } - // eprintln!("{:#?}", file.types); - } - - Ok(()) -} diff --git a/src/object.rs b/src/object.rs index 9875517..5ad1441 100644 --- a/src/object.rs +++ b/src/object.rs @@ -1,4 +1,87 @@ +use crate::helper::{AlignExt, Endianness, ReadExt}; use crate::serialized_file::TypeTreeNode; -use std::io::Read; +use anyhow::Result; +use log::debug; +use std::io::Seek; +use std::{collections::BTreeMap, io::Read}; -pub fn read_value(ty: TypeTreeNode, data: &mut impl Read) {} +#[derive(Debug)] +pub enum Value { + Bool(bool), + U8(u8), + U16(u16), + U32(u32), + I32(i32), + F32(f32), + I64(i64), + F64(f64), + Array(Vec), + Object { + class: String, + fields: BTreeMap, + }, + String(String), +} + +pub fn read_value( + ty: &TypeTreeNode, + e: Endianness, + data: &mut (impl Read + Seek), +) -> Result { + match ty.type_string.as_str() { + "char" => Ok(Value::U8(data.read_u8()?)), + "int" => Ok(Value::I32(data.read_i32(e)?)), + "unsigned int" => Ok(Value::U32(data.read_u32(e)?)), + "UInt16" => Ok(Value::U16(data.read_u16(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| { + if let Value::U8(x) = e { + x + } else { + unreachable!() + } + }) + .collect::>(); + Ok(Value::String(String::from_utf8(bytes)?)) + } + "Array" => { + let Value::I32(size) = read_value(&ty.children[0], e, data)? else { + unreachable!() + }; + debug!("array of size {size}"); + let mut elems = Vec::new(); + for _ in 0..size { + elems.push(read_value(&ty.children[1], e, data)?); + } + Ok(Value::Array(elems)) + } + _ => { + if ty.children.is_empty() && ty.byte_size != -1 { + todo!("need type {:?}", ty.type_string); + } + let mut fields = BTreeMap::new(); + for c in &ty.children { + fields.insert(c.name_string.clone(), read_value(&c, e, data)?); + } + Ok(Value::Object { + fields, + class: ty.type_string.clone(), + }) + } + } +} diff --git a/src/serialized_file.rs b/src/serialized_file.rs index 4b348e0..cdf6125 100644 --- a/src/serialized_file.rs +++ b/src/serialized_file.rs @@ -56,14 +56,27 @@ pub struct External { #[derive(Debug)] pub struct SerializedFile { + pub header: SerializedFileHeader, pub types: Vec, pub externals: Vec, pub scripts: Vec