diff options
-rw-r--r-- | Cargo.lock | 63 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | src/assets.rs | 35 | ||||
-rw-r--r-- | src/bin/debug.rs | 21 | ||||
-rw-r--r-- | src/bin/json.rs | 27 | ||||
-rw-r--r-- | src/bin/yaml.rs | 44 | ||||
-rw-r--r-- | src/classes.rs | 101 | ||||
-rw-r--r-- | src/lib.rs | 2 | ||||
-rw-r--r-- | src/object.rs | 31 | ||||
-rw-r--r-- | src/serialized_file.rs | 13 |
10 files changed, 269 insertions, 70 deletions
@@ -109,6 +109,18 @@ dependencies = [ ] [[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] name = "humansize" version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -124,6 +136,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] +name = "indexmap" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -142,6 +164,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] +name = "libyml" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3302702afa434ffa30847a83305f0a69d6abd74293b6554c18ec85c7ef30c980" +dependencies = [ + "anyhow", + "version_check", +] + +[[package]] name = "log" version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -232,18 +264,18 @@ checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] name = "serde" -version = "1.0.218" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.218" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", @@ -263,6 +295,21 @@ dependencies = [ ] [[package]] +name = "serde_yml" +version = "0.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59e2dd588bf1597a252c3b920e0143eb99b0f76e4e082f4c92ce34fbc9e71ddd" +dependencies = [ + "indexmap", + "itoa", + "libyml", + "memchr", + "ryu", + "serde", + "version_check", +] + +[[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -305,7 +352,9 @@ dependencies = [ "log", "lz4_flex", "lzma", + "serde", "serde_json", + "serde_yml", ] [[package]] @@ -315,6 +364,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -11,3 +11,5 @@ lz4_flex = "0.11.3" lzma = "0.2.2" serde_json = "1.0.139" humansize = "2.1.3" +serde = { version = "1.0.219", features = ["derive"] } +serde_yml = "0.0.12" diff --git a/src/assets.rs b/src/assets.rs deleted file mode 100644 index a272920..0000000 --- a/src/assets.rs +++ /dev/null @@ -1,35 +0,0 @@ -use crate::object::Value; -use anyhow::Result; -use std::collections::BTreeMap; - -pub enum AssetValue { - Value(Value), - Array(Vec<AssetValue>), - Object { - class: String, - fields: BTreeMap<String, AssetValue>, - }, -} - -impl AssetValue { - pub fn from_value(value: Value) -> Result<Self> { - Ok(match value { - Value::Array(elems) => Self::Array( - elems - .into_iter() - .map(|e| AssetValue::from_value(e)) - .collect::<Result<Vec<_>>>()?, - ), - Value::Object { class, fields } => match class.as_str() { - _ => Self::Object { - class, - fields: fields - .into_iter() - .map(|(k, v)| Ok((k, AssetValue::from_value(v)?))) - .collect::<Result<BTreeMap<_, _>>>()?, - }, - }, - x => Self::Value(x), - }) - } -} diff --git a/src/bin/debug.rs b/src/bin/debug.rs index 9004055..5633387 100644 --- a/src/bin/debug.rs +++ b/src/bin/debug.rs @@ -3,10 +3,7 @@ use std::{ fs::File, io::{BufReader, Seek, SeekFrom}, }; -use unity_tools::{ - serialized_file::{TypeTreeNode, read_serialized_file}, - unityfs::UnityFS, -}; +use unity_tools::{serialized_file::read_serialized_file, unityfs::UnityFS}; fn main() -> anyhow::Result<()> { env_logger::init_from_env("LOG"); @@ -34,12 +31,12 @@ fn main() -> anyhow::Result<()> { // .expect("unknown type") &file.types[ob.type_id as usize] }; - fn print_types(tt: &TypeTreeNode) { - println!("{}", tt.type_string); - for c in &tt.children { - print_types(&c); - } - } + // fn print_types(tt: &TypeTreeNode) { + // println!("{}", tt.type_string); + // for c in &tt.children { + // print_types(&c); + // } + // } // fn print_crit_types(tt: &TypeTreeNode) { // let mut crit = tt.byte_size == -1 || tt.children.is_empty(); // for c in &tt.children { @@ -51,9 +48,9 @@ fn main() -> anyhow::Result<()> { // } // } if let Some(tree) = &typetree.type_tree { - // println!("{}", tree.type_string); + println!("{}", tree.type_string); // print_crit_types(tree); - print_types(tree); + // print_types(tree); } // eprintln!("{typetree:#?}"); diff --git a/src/bin/json.rs b/src/bin/json.rs index 3155b02..4611530 100644 --- a/src/bin/json.rs +++ b/src/bin/json.rs @@ -1,19 +1,16 @@ use std::{ - env::args, + env::{args, var}, fs::File, io::{BufReader, Seek, SeekFrom, stdout}, }; -use unity_tools::{ - object::{Value, read_value}, - 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"); let file = BufReader::new(File::open(args().nth(1).unwrap())?); let mut fs = UnityFS::open(file)?; let filter = args().nth(2); + let pretty = var("PRETTY").is_ok(); for node in fs.nodes().to_vec() { if node.name.ends_with(".resource") || node.name.ends_with(".resS") { @@ -21,8 +18,6 @@ fn main() -> anyhow::Result<()> { } let mut cab = fs.read(&node)?; let file = read_serialized_file(&mut cab)?; - let e = file.endianness; - for ob in file.objects { cab.seek(SeekFrom::Start(ob.data_offset))?; let typetree = if ob.type_id < 0 { @@ -30,18 +25,20 @@ fn main() -> anyhow::Result<()> { } else { &file.types[ob.type_id as usize] }; - let value = read_value(typetree.type_tree.as_ref().unwrap(), e, &mut cab)?; - if let Some(f) = &filter { - if let Value::Object { class, .. } = &value { - if class != f { + 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 { - continue; + serde_json::to_writer(stdout(), &value.to_json()).unwrap(); } + println!() } - serde_json::to_writer(stdout(), &value.to_json()).unwrap(); - println!() } } diff --git a/src/bin/yaml.rs b/src/bin/yaml.rs new file mode 100644 index 0000000..e541c62 --- /dev/null +++ b/src/bin/yaml.rs @@ -0,0 +1,44 @@ +use std::{ + env::args, + fs::File, + io::{BufReader, Seek, SeekFrom, stdout}, +}; +use unity_tools::{ + classes::HValue, 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)?; + let filter = args().nth(2); + + 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 file = read_serialized_file(&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)?; + let hvalue = HValue::from_value(value)?; + serde_yml::to_writer(stdout(), &hvalue).unwrap(); + println!() + } + } + } + + Ok(()) +} diff --git a/src/classes.rs b/src/classes.rs new file mode 100644 index 0000000..87bce8c --- /dev/null +++ b/src/classes.rs @@ -0,0 +1,101 @@ +use crate::object::Value; +use anyhow::Result; +use serde::Serialize; +use std::collections::BTreeMap; + +#[derive(Serialize)] +pub enum HValue { + PPtr(PPtr), + Pair(Box<HValue>, Box<HValue>), + Value([Value; 1]), + Map(BTreeMap<String, HValue>), + AssetInfo(AssetInfo), + + Array(Vec<HValue>), + Object { + class: String, + fields: BTreeMap<String, HValue>, + }, +} + +impl HValue { + pub fn from_value(v: Value) -> Result<HValue> { + Ok(match v { + Value::Array(a) => Self::Array( + a.into_iter() + .map(|e| HValue::from_value(e)) + .collect::<Result<Vec<_>>>()?, + ), + Value::Object { class, fields } => { + let mut fields = fields + .into_iter() + .map(|(k, v)| Ok((k, HValue::from_value(v)?))) + .collect::<Result<BTreeMap<_, _>>>()?; + + match class.as_str() { + x if x.starts_with("PPtr<") => { + let inner = x.strip_prefix("PPtr<").unwrap().strip_suffix(">").unwrap(); + Self::PPtr(PPtr { + class: inner.to_owned(), + file_id: fields["m_FileID"].as_value().unwrap().as_i32().unwrap(), + path_id: fields["m_PathID"].as_value().unwrap().as_i64().unwrap(), + }) + } + "AssetInfo" => Self::AssetInfo(AssetInfo { + preload_index: fields["preloadIndex"].as_value().unwrap().as_i32().unwrap(), + preload_size: fields["preloadSize"].as_value().unwrap().as_i32().unwrap(), + asset: fields.remove("asset").unwrap().as_pptr().unwrap(), + }), + "map" => { + let Self::Array(a) = fields.remove("Array").unwrap() else { + unreachable!() + }; + Self::Map( + a.into_iter() + .map(|e| { + let Self::Pair(k, v) = e else { unreachable!() }; + (k.as_value().unwrap().clone().as_string().unwrap(), *v) + }) + .collect(), + ) + } + "pair" => Self::Pair( + Box::new(fields.remove("first").unwrap()), + Box::new(fields.remove("second").unwrap()), + ), + "vector" => fields.remove("Array").unwrap(), + _ => Self::Object { class, fields }, + } + } + x => Self::Value([x]), + }) + } + pub fn as_value(&self) -> Option<&Value> { + if let HValue::Value(v) = self { + Some(&v[0]) + } else { + None + } + } + pub fn as_pptr(self) -> Option<PPtr> { + if let HValue::PPtr(v) = self { + Some(v) + } else { + None + } + } +} + +#[derive(Debug, Serialize)] +pub struct PPtr { + class: String, + file_id: i32, + path_id: i64, +} + +#[derive(Debug, Serialize)] +pub struct AssetInfo { + preload_index: i32, + preload_size: i32, + asset: PPtr, +} @@ -3,4 +3,4 @@ pub mod helper; pub mod common_strings; pub mod serialized_file; pub mod object; -pub mod assets; +pub mod classes; diff --git a/src/object.rs b/src/object.rs index be76c18..061f58e 100644 --- a/src/object.rs +++ b/src/object.rs @@ -2,10 +2,11 @@ use crate::helper::{AlignExt, Endianness, ReadExt}; use crate::serialized_file::TypeTreeNode; use anyhow::{Result, bail}; use log::trace; +use serde::Serialize; use std::io::Seek; use std::{collections::BTreeMap, io::Read}; -#[derive(Debug)] +#[derive(Debug, Clone, Serialize)] pub enum Value { Bool(bool), U8(u8), @@ -121,6 +122,34 @@ pub fn read_value( } impl Value { + pub fn as_class(self, name: &str) -> Option<BTreeMap<String, Value>> { + if let Value::Object { class, fields } = self { + if class == name { Some(fields) } else { None } + } else { + None + } + } + pub fn as_string(self) -> Option<String> { + if let Value::String(s) = self { + Some(s) + } else { + None + } + } + pub fn as_i64(&self) -> Option<i64> { + if let Value::I64(s) = self { + Some(*s) + } else { + None + } + } + pub fn as_i32(&self) -> Option<i32> { + if let Value::I32(s) = self { + Some(*s) + } else { + None + } + } pub fn to_json(self) -> serde_json::Value { match self { Value::Bool(x) => serde_json::Value::Bool(x), diff --git a/src/serialized_file.rs b/src/serialized_file.rs index 9397d53..26f9391 100644 --- a/src/serialized_file.rs +++ b/src/serialized_file.rs @@ -3,7 +3,7 @@ use crate::{ helper::{AlignExt, Endianness, ReadExt}, }; use anyhow::{Result, bail}; -use log::{debug, info, trace}; +use log::{debug, info, trace, warn}; use std::io::{Cursor, Read, Seek}; #[derive(Debug, Clone)] @@ -163,7 +163,12 @@ pub fn read_serialized_file(mut file: impl Read + Seek) -> Result<SerializedFile let get_string = |off: u32| { let data = if off & 0x80000000 != 0 { let off = off & 0x7fffffff; - &COMMON_STRINGS[off as usize..] + if off as usize > COMMON_STRINGS.len() { + warn!("common strings missing index {off:08x}"); + b"<common string missing>" + } else { + &COMMON_STRINGS[off as usize..] + } } else { &string_data[off as usize..] }; @@ -189,6 +194,10 @@ pub fn read_serialized_file(mut file: impl Read + Seek) -> Result<SerializedFile ref_type_hash: node_data.read_u64(e)?, children: vec![], }; + if node.level == 0 && !parents.is_empty() { + warn!("unexpected toplevel typetree node"); + parents.clear(); + } while parents.len() > node.level as usize { let n = parents.pop().unwrap(); parents.last_mut().unwrap().children.push(n) |