aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock63
-rw-r--r--Cargo.toml2
-rw-r--r--src/assets.rs35
-rw-r--r--src/bin/debug.rs21
-rw-r--r--src/bin/json.rs27
-rw-r--r--src/bin/yaml.rs44
-rw-r--r--src/classes.rs101
-rw-r--r--src/lib.rs2
-rw-r--r--src/object.rs31
-rw-r--r--src/serialized_file.rs13
10 files changed, 269 insertions, 70 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 726677f..c7d676c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 075e17f..7a7bd59 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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,
+}
diff --git a/src/lib.rs b/src/lib.rs
index 14bdc34..c2416c3 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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)