aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock41
-rw-r--r--Cargo.toml2
-rw-r--r--readme.md3
-rw-r--r--src/bin/parse.rs (renamed from src/main.rs)10
-rw-r--r--src/bin/probe.rs25
-rw-r--r--src/helper.rs38
-rw-r--r--src/object.rs87
-rw-r--r--src/serialized_file.rs45
-rw-r--r--src/unityfs.rs23
9 files changed, 216 insertions, 58 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 9c94035..64f091c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -74,15 +74,6 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -130,37 +121,12 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -220,12 +186,6 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -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/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),
})