diff options
author | metamuffin <metamuffin@disroot.org> | 2025-02-15 13:21:25 +0100 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2025-02-15 13:21:25 +0100 |
commit | 2f4a11ddda04604d5d756231d258ef60fa9f7bd8 (patch) | |
tree | 891c99da0038eb513ed6de4c8a892d3f421d9b92 /src | |
parent | d116a1df8fe14edc8de157bf8088244261fca30f (diff) | |
download | unity-tools-2f4a11ddda04604d5d756231d258ef60fa9f7bd8.tar unity-tools-2f4a11ddda04604d5d756231d258ef60fa9f7bd8.tar.bz2 unity-tools-2f4a11ddda04604d5d756231d258ef60fa9f7bd8.tar.zst |
can read objects
Diffstat (limited to 'src')
-rw-r--r-- | src/bin/parse.rs (renamed from src/main.rs) | 10 | ||||
-rw-r--r-- | src/bin/probe.rs | 25 | ||||
-rw-r--r-- | src/helper.rs | 38 | ||||
-rw-r--r-- | src/object.rs | 87 | ||||
-rw-r--r-- | src/serialized_file.rs | 45 | ||||
-rw-r--r-- | src/unityfs.rs | 23 |
6 files changed, 212 insertions, 16 deletions
diff --git a/src/main.rs b/src/bin/parse.rs index 450f4a7..d436aad 100644 --- a/src/main.rs +++ b/src/bin/parse.rs @@ -3,7 +3,7 @@ use std::{ fs::File, io::{BufReader, Read, Seek, SeekFrom}, }; -use unity_tools::{serialized_file::read_serialized_file, unityfs::UnityFS}; +use unity_tools::{object::read_value, serialized_file::read_serialized_file, unityfs::UnityFS}; fn main() -> anyhow::Result<()> { env_logger::init_from_env("LOG"); @@ -20,10 +20,11 @@ fn main() -> anyhow::Result<()> { // 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 ob_data = cab.by_ref().take(ob.data_size as u64); + let mut ob_data = cab.by_ref(); //.take(ob.data_size as u64); eprintln!("{:#?}", ob); let typetree = if ob.type_id < 0 { @@ -34,6 +35,11 @@ fn main() -> anyhow::Result<()> { .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); } 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<i64>; fn read_u128_be(&mut self) -> Result<u128>; fn read_cstr(&mut self) -> Result<String>; + fn read_f32(&mut self, e: Endianness) -> Result<f32>; + fn read_f32_be(&mut self) -> Result<f32>; + fn read_f32_le(&mut self) -> Result<f32>; + fn read_f64(&mut self, e: Endianness) -> Result<f64>; + fn read_f64_be(&mut self) -> Result<f64>; + fn read_f64_le(&mut self) -> Result<f64>; } impl<T: Read> ReadExt for T { @@ -132,6 +138,38 @@ impl<T: Read> ReadExt for T { self.read_exact(&mut buf)?; Ok(i64::from_le_bytes(buf)) } + fn read_f32(&mut self, e: Endianness) -> Result<f32> { + match e { + Endianness::Big => self.read_f32_be(), + Endianness::Little => self.read_f32_le(), + } + } + fn read_f32_be(&mut self) -> Result<f32> { + let mut buf = [0; 4]; + self.read_exact(&mut buf)?; + Ok(f32::from_be_bytes(buf)) + } + fn read_f32_le(&mut self) -> Result<f32> { + let mut buf = [0; 4]; + self.read_exact(&mut buf)?; + Ok(f32::from_le_bytes(buf)) + } + fn read_f64(&mut self, e: Endianness) -> Result<f64> { + match e { + Endianness::Big => self.read_f64_be(), + Endianness::Little => self.read_f64_le(), + } + } + fn read_f64_be(&mut self) -> Result<f64> { + let mut buf = [0; 8]; + self.read_exact(&mut buf)?; + Ok(f64::from_be_bytes(buf)) + } + fn read_f64_le(&mut self) -> Result<f64> { + let mut buf = [0; 8]; + self.read_exact(&mut buf)?; + Ok(f64::from_le_bytes(buf)) + } fn read_u128_be(&mut self) -> Result<u128> { let mut buf = [0; 16]; self.read_exact(&mut buf)?; 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<Value>), + Object { + class: String, + fields: BTreeMap<String, Value>, + }, + String(String), +} + +pub fn read_value( + ty: &TypeTreeNode, + e: Endianness, + data: &mut (impl Read + Seek), +) -> Result<Value> { + 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::<Vec<_>>(); + 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<SeralizedType>, pub externals: Vec<External>, pub scripts: Vec<Script>, pub objects: Vec<ObjectInfo>, pub user_string: String, + pub endianness: Endianness, } -pub fn read_serialized_file(mut file: impl Read + Seek) -> Result<SerializedFile> { +#[derive(Debug)] +pub struct SerializedFileHeader { + _metadata_size: u32, + _file_size: u64, + pub format: u32, + data_offset: u64, + endianness: Endianness, + pub generator_version: String, + pub target_platform: u32, +} + +pub fn read_serialized_file_header(mut file: impl Read + Seek) -> Result<SerializedFileHeader> { let mut metadata_size = file.read_u32_be()?; let mut file_size = file.read_u32_be()? as u64; let format = file.read_u32_be()?; @@ -92,6 +105,20 @@ pub fn read_serialized_file(mut file: impl Read + Seek) -> Result<SerializedFile let target_platform = file.read_u32_le()?; info!("Generator version: {generator_version:?}"); debug!("target_platform={target_platform}"); + Ok(SerializedFileHeader { + data_offset, + _file_size: file_size, + endianness: e, + format, + _metadata_size: metadata_size, + target_platform, + generator_version, + }) +} + +pub fn read_serialized_file(mut file: impl Read + Seek) -> Result<SerializedFile> { + let h = read_serialized_file_header(&mut file)?; + let e = h.endianness; let has_type_trees = file.read_u8()? != 0; let num_types = file.read_u32(e)?; @@ -171,7 +198,7 @@ pub fn read_serialized_file(mut file: impl Read + Seek) -> Result<SerializedFile } type_tree = parents.pop(); - if format >= 21 { + if h.format >= 21 { let num_deps = file.read_u32(e)?; trace!("num_deps={num_deps}"); for _ in 0..num_deps { @@ -196,12 +223,12 @@ pub fn read_serialized_file(mut file: impl Read + Seek) -> Result<SerializedFile for _ in 0..num_objects { file.align(4)?; let path_id = file.read_i64(e)?; - let data_offset = if format >= 22 { + let data_offset = if h.format >= 22 { file.align(4)?; file.read_u64(e)? } else { file.read_u32(e)? as u64 - } + data_offset; + } + h.data_offset; let data_size = file.read_u32(e)?; let type_id = file.read_i32(e)?; objects.push(ObjectInfo { @@ -241,7 +268,7 @@ pub fn read_serialized_file(mut file: impl Read + Seek) -> Result<SerializedFile }) } - if format >= 20 { + if h.format >= 20 { let num_ref_types = file.read_i32(e)?; debug!("num_ref_types={num_ref_types}"); // let mut ref_types = Vec::new(); @@ -253,10 +280,18 @@ pub fn read_serialized_file(mut file: impl Read + Seek) -> Result<SerializedFile let user_string = file.read_cstr()?; Ok(SerializedFile { + header: h, types, externals, + endianness: e, objects, scripts, user_string, }) } + +impl TypeTreeNode { + pub fn post_align(&self) -> bool { + self.flags & 0x4000 != 0 + } +} diff --git a/src/unityfs.rs b/src/unityfs.rs index bd0c7bb..677a135 100644 --- a/src/unityfs.rs +++ b/src/unityfs.rs @@ -6,6 +6,9 @@ use std::io::{Cursor, Error, ErrorKind, Read, Seek, SeekFrom}; pub struct UnityFS<T> { nodes: Vec<NodeInfo>, reader: BlocksReader<T>, + pub file_version: u32, + pub player_version: String, + pub unity_version: String, } #[derive(Debug, Clone)] @@ -32,6 +35,10 @@ pub struct NodeReader<'a, T> { impl<T: Read + Seek> UnityFS<T> { pub fn open(mut file: T) -> Result<Self> { let signature = file.read_cstr()?; + if signature.as_str() != "UnityFS" { + bail!("unknown signature {signature:?}") + } + let file_version = file.read_u32_be()?; let player_version = file.read_cstr()?; let unity_version = file.read_cstr()?; @@ -40,15 +47,14 @@ impl<T: Read + Seek> UnityFS<T> { let blockindex_decomp_size = file.read_u32_be()?; let flags = file.read_u32_be()?; - let meta_comp_scheme = CompressionScheme::from_flag_num((flags & 0x3f) as u8) - .ok_or(anyhow!("unknown block compression"))?; + let meta_comp_scheme = CompressionScheme::from_flag_num(flags as u8).ok_or(anyhow!( + "unknown block compression 0x{:02x}", + (flags & 0x3f) as u8 + ))?; let blockindex_eof = flags & 0x80 != 0; let blockindex_has_directory = flags & 0x40 != 0; let blockindex_need_padding = flags & 0x200 != 0; - if signature.as_str() != "UnityFS" { - bail!("unknown signature {signature:?}") - } info!("File Version: {file_version:?}"); info!("Player Version: {player_version:?}"); info!("Unity Version: {unity_version:?}"); @@ -92,8 +98,8 @@ impl<T: Read + Seek> UnityFS<T> { let decomp_size = blockindex.read_u32_be()?; let comp_size = blockindex.read_u32_be()?; let flags = blockindex.read_u16_be()?; - let comp_scheme = CompressionScheme::from_flag_num((flags & 0x3f) as u8) - .ok_or(anyhow!("unknown block compression"))?; + let comp_scheme = CompressionScheme::from_flag_num(flags as u8) + .ok_or(anyhow!("unknown block compression 0x{:02x}", flags & 0x3f))?; blocks.push(BlockInfo { comp_size, decomp_size, @@ -121,6 +127,9 @@ impl<T: Read + Seek> UnityFS<T> { let position = file.stream_position()?; Ok(Self { + file_version, + player_version, + unity_version, nodes, reader: BlocksReader::new(blocks, file, position), }) |