aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2025-02-15 13:21:25 +0100
committermetamuffin <metamuffin@disroot.org>2025-02-15 13:21:25 +0100
commit2f4a11ddda04604d5d756231d258ef60fa9f7bd8 (patch)
tree891c99da0038eb513ed6de4c8a892d3f421d9b92 /src
parentd116a1df8fe14edc8de157bf8088244261fca30f (diff)
downloadunity-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.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
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),
})