diff options
author | metamuffin <metamuffin@disroot.org> | 2025-03-12 22:39:58 +0100 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2025-03-12 22:39:58 +0100 |
commit | 5555c8bbefb4f52f5002603eb91b6c95cbdd97e4 (patch) | |
tree | 208e88359deb3cf5f7e2a4135693f12d76ad97e1 /src | |
parent | 4906844cbfd2717a29b434fb7d8f90c5117fddd5 (diff) | |
download | unity-tools-5555c8bbefb4f52f5002603eb91b6c95cbdd97e4.tar unity-tools-5555c8bbefb4f52f5002603eb91b6c95cbdd97e4.tar.bz2 unity-tools-5555c8bbefb4f52f5002603eb91b6c95cbdd97e4.tar.zst |
more parsing helpers
Diffstat (limited to 'src')
-rw-r--r-- | src/bin/json.rs | 4 | ||||
-rw-r--r-- | src/bin/textures.rs | 4 | ||||
-rw-r--r-- | src/bin/yaml.rs | 22 | ||||
-rw-r--r-- | src/classes/assetinfo.rs | 15 | ||||
-rw-r--r-- | src/classes/gameobject.rs | 12 | ||||
-rw-r--r-- | src/classes/mesh.rs | 128 | ||||
-rw-r--r-- | src/classes/mod.rs | 22 | ||||
-rw-r--r-- | src/classes/pptr.rs | 4 | ||||
-rw-r--r-- | src/classes/streaminginfo.rs | 12 | ||||
-rw-r--r-- | src/classes/texture2d.rs | 4 | ||||
-rw-r--r-- | src/classes/transform.rs | 4 | ||||
-rw-r--r-- | src/classes/vectors.rs | 55 | ||||
-rw-r--r-- | src/object.rs | 246 | ||||
-rw-r--r-- | src/object/helper.rs | 125 | ||||
-rw-r--r-- | src/object/mod.rs | 28 | ||||
-rw-r--r-- | src/object/parser.rs | 92 | ||||
-rw-r--r-- | src/object/read.rs | 100 |
17 files changed, 575 insertions, 302 deletions
diff --git a/src/bin/json.rs b/src/bin/json.rs index 4611530..b969beb 100644 --- a/src/bin/json.rs +++ b/src/bin/json.rs @@ -3,7 +3,9 @@ use std::{ fs::File, io::{BufReader, Seek, SeekFrom, stdout}, }; -use unity_tools::{object::read_value, serialized_file::read_serialized_file, unityfs::UnityFS}; +use unity_tools::{ + object::read::read_value, serialized_file::read_serialized_file, unityfs::UnityFS, +}; fn main() -> anyhow::Result<()> { env_logger::init_from_env("LOG"); diff --git a/src/bin/textures.rs b/src/bin/textures.rs index 5db888e..d85f494 100644 --- a/src/bin/textures.rs +++ b/src/bin/textures.rs @@ -6,8 +6,8 @@ use std::{ io::{BufReader, Read, Seek, SeekFrom}, }; use unity_tools::{ - classes::{FromValue, texture2d::Texture2D}, - object::read_value, + classes::texture2d::Texture2D, + object::{parser::FromValue, read::read_value}, serialized_file::read_serialized_file, unityfs::UnityFS, }; diff --git a/src/bin/yaml.rs b/src/bin/yaml.rs index e541c62..e3b1df8 100644 --- a/src/bin/yaml.rs +++ b/src/bin/yaml.rs @@ -1,10 +1,12 @@ +use serde_yml::Value; 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, + classes::HValue, object::read::read_value, serialized_file::read_serialized_file, + unityfs::UnityFS, }; fn main() -> anyhow::Result<()> { @@ -34,6 +36,10 @@ fn main() -> anyhow::Result<()> { } let value = read_value(typetree, file.endianness, &mut cab)?; let hvalue = HValue::from_value(value)?; + + let mut hvalue = serde_yml::to_value(hvalue)?; + reduce_large_arrays(&mut hvalue); + serde_yml::to_writer(stdout(), &hvalue).unwrap(); println!() } @@ -42,3 +48,17 @@ fn main() -> anyhow::Result<()> { Ok(()) } + +fn reduce_large_arrays(v: &mut Value) { + match v { + Value::Sequence(values) => { + values.iter_mut().for_each(reduce_large_arrays); + while values.len() > 32 { + values.pop(); + } + } + Value::Mapping(mapping) => mapping.map.values_mut().for_each(reduce_large_arrays), + Value::Tagged(tagged_value) => reduce_large_arrays(&mut tagged_value.value), + _ => (), + } +} diff --git a/src/classes/assetinfo.rs b/src/classes/assetinfo.rs index 9fad90a..01c4530 100644 --- a/src/classes/assetinfo.rs +++ b/src/classes/assetinfo.rs @@ -1,5 +1,5 @@ -use super::{FromValue, gameobject::GameObject, pptr::PPtr}; -use crate::object::Value; +use super::{gameobject::GameObject, pptr::PPtr}; +use crate::object::{Value, parser::FromValue}; use anyhow::Result; use serde::Serialize; @@ -13,14 +13,9 @@ impl FromValue for AssetInfo { fn from_value(v: Value) -> Result<Self> { let mut fields = v.as_class("AssetInfo").unwrap(); Ok(AssetInfo { - preload_index: fields["preloadIndex"].as_i32().unwrap(), - preload_size: fields["preloadSize"].as_i32().unwrap(), - asset: fields - .remove("asset") - .unwrap() - .parse::<PPtr>() - .unwrap() - .cast(), + preload_index: fields.field("preloadIndex")?, + preload_size: fields.field("preloadSize")?, + asset: fields.field("asset")?, }) } } diff --git a/src/classes/gameobject.rs b/src/classes/gameobject.rs index 94436fa..bb4f19b 100644 --- a/src/classes/gameobject.rs +++ b/src/classes/gameobject.rs @@ -1,5 +1,5 @@ -use super::{FromValue, pptr::PPtr}; -use crate::object::Value; +use super::pptr::PPtr; +use crate::object::{Value, parser::FromValue}; use anyhow::Result; use serde::Serialize; @@ -34,10 +34,10 @@ impl FromValue for GameObject { .unwrap() }) .collect(), - layer: fields["m_Layer"].as_u32().unwrap(), - tag: fields["m_Tag"].as_u16().unwrap(), - name: fields["m_Name"].clone().as_string().unwrap(), - is_active: fields["m_IsActive"].as_bool().unwrap(), + layer: fields.field("m_Layer")?, + tag: fields.field("m_Tag")?, + name: fields.field("m_Name")?, + is_active: fields.field("m_IsActive")?, }) } } diff --git a/src/classes/mesh.rs b/src/classes/mesh.rs new file mode 100644 index 0000000..1d6889d --- /dev/null +++ b/src/classes/mesh.rs @@ -0,0 +1,128 @@ +use super::streaminginfo::StreamingInfo; +use crate::object::{Value, parser::FromValue}; +use anyhow::Result; +use glam::Mat4; +use serde::Serialize; + +#[derive(Debug, Serialize)] +pub struct Mesh { + pub name: String, + pub bind_pose: Vec<Mat4>, + pub bone_name_hashes: Vec<u32>, + pub index_format: i32, + pub index_buffer: Vec<u8>, + pub sub_meshes: Vec<SubMesh>, + pub stream_data: StreamingInfo, +} + +#[derive(Debug, Serialize)] +pub struct SubMesh { + pub topology: i32, + pub vertex_count: u32, + pub base_vertex: u32, + pub first_byte: u32, + pub first_vertex: u32, + pub index_count: u32, +} + +#[derive(Debug, Serialize)] +pub struct VertexData { + pub channels: Vec<ChannelInfo>, + pub data_size: Vec<u8>, + pub vertex_count: u32, +} + +#[derive(Debug, Serialize)] +pub struct ChannelInfo { + pub dimension: u8, + pub format: u8, + pub offset: u8, + pub stream: u8, +} + +impl FromValue for Mesh { + fn from_value(v: Value) -> Result<Self> { + let mut fields = v.as_class("Mesh").unwrap(); + Ok(Mesh { + name: fields.remove("m_Name").unwrap().parse().unwrap(), + index_format: fields.remove("m_IndexFormat").unwrap().parse().unwrap(), + index_buffer: fields + .remove("m_IndexBuffer") + .unwrap() + .as_vector() + .unwrap() + .into_iter() + .map(|e| e.as_u8().unwrap()) + .collect(), + sub_meshes: fields + .remove("m_SubMeshes") + .unwrap() + .as_vector() + .unwrap() + .into_iter() + .map(|e| e.parse().unwrap()) + .collect(), + stream_data: fields.remove("m_StreamData").unwrap().parse().unwrap(), + bind_pose: fields + .remove("m_BindPose") + .unwrap() + .as_vector() + .unwrap() + .into_iter() + .map(|e| e.parse().unwrap()) + .collect(), + bone_name_hashes: fields + .remove("m_BoneNameHashes") + .unwrap() + .as_vector() + .unwrap() + .into_iter() + .map(|e| e.parse().unwrap()) + .collect(), + }) + } +} + +impl FromValue for VertexData { + fn from_value(v: Value) -> Result<Self> { + let mut fields = v.as_class("VertexData").unwrap(); + Ok(VertexData { + vertex_count: fields.remove("vertexCount").unwrap().parse().unwrap(), + channels: fields + .remove("m_Channels") + .unwrap() + .as_array() + .unwrap() + .into_iter() + .map(|e| e.parse().unwrap()) + .collect(), + data_size: fields.remove("m_DataSize").unwrap().as_typeless().unwrap(), + }) + } +} + +impl FromValue for ChannelInfo { + fn from_value(v: Value) -> Result<Self> { + let mut fields = v.as_class("ChannelInfo").unwrap(); + Ok(ChannelInfo { + dimension: fields.remove("dimension").unwrap().parse().unwrap(), + format: fields.remove("format").unwrap().parse().unwrap(), + offset: fields.remove("offset").unwrap().parse().unwrap(), + stream: fields.remove("stream").unwrap().parse().unwrap(), + }) + } +} + +impl FromValue for SubMesh { + fn from_value(v: Value) -> Result<Self> { + let mut fields = v.as_class("SubMesh").unwrap(); + Ok(SubMesh { + topology: fields.remove("topology").unwrap().parse().unwrap(), + vertex_count: fields.remove("vertexCount").unwrap().parse().unwrap(), + base_vertex: fields.remove("baseVertex").unwrap().parse().unwrap(), + first_byte: fields.remove("firstByte").unwrap().parse().unwrap(), + first_vertex: fields.remove("firstVertex").unwrap().parse().unwrap(), + index_count: fields.remove("indexCount").unwrap().parse().unwrap(), + }) + } +} diff --git a/src/classes/mod.rs b/src/classes/mod.rs index f7b89cb..501fdf9 100644 --- a/src/classes/mod.rs +++ b/src/classes/mod.rs @@ -1,15 +1,17 @@ pub mod assetinfo; pub mod gameobject; +pub mod mesh; pub mod pptr; pub mod streaminginfo; pub mod texture2d; pub mod transform; pub mod vectors; -use crate::object::Value; +use crate::object::{Value, parser::FromValue}; use anyhow::Result; use assetinfo::AssetInfo; use gameobject::GameObject; +use mesh::{ChannelInfo, Mesh, SubMesh, VertexData}; use pptr::PPtr; use serde::Serialize; use std::collections::BTreeMap; @@ -25,6 +27,10 @@ pub enum HValue { PPtr(PPtr), Texture2D(Texture2D), StreamingInfo(StreamingInfo), + SubMesh(SubMesh), + Mesh(Mesh), + VertexData(VertexData), + ChannelInfo(ChannelInfo), Pair(Box<HValue>, Box<HValue>), Value([Value; 1]), @@ -48,6 +54,10 @@ impl HValue { "Transform" => Self::Transform(Transform::from_value(value)?), "Texture2D" => Self::Texture2D(Texture2D::from_value(value)?), "StreamingInfo" => Self::StreamingInfo(StreamingInfo::from_value(value)?), + // "SubMesh" => Self::SubMesh(SubMesh::from_value(value)?), + "Mesh" => Self::Mesh(Mesh::from_value(value)?), + // "VertexData" => Self::VertexData(VertexData::from_value(value)?), + // "ChannelInfo" => Self::ChannelInfo(ChannelInfo::from_value(value)?), _ => { let Value::Object { class, fields } = value else { unreachable!() @@ -117,13 +127,3 @@ impl HValue { } } } - -pub trait FromValue: Sized { - fn from_value(v: Value) -> Result<Self>; -} - -impl Value { - pub(self) fn parse<T: FromValue>(self) -> Result<T> { - T::from_value(self) - } -} diff --git a/src/classes/pptr.rs b/src/classes/pptr.rs index b42527d..6c7efdb 100644 --- a/src/classes/pptr.rs +++ b/src/classes/pptr.rs @@ -1,5 +1,5 @@ -use super::{FromValue, HValue}; -use crate::object::Value; +use super::HValue; +use crate::object::{Value, parser::FromValue}; use anyhow::Result; use serde::Serialize; use std::marker::PhantomData; diff --git a/src/classes/streaminginfo.rs b/src/classes/streaminginfo.rs index 1f8cadb..bb0b5e1 100644 --- a/src/classes/streaminginfo.rs +++ b/src/classes/streaminginfo.rs @@ -1,9 +1,7 @@ -use crate::object::Value; +use crate::object::{Value, parser::FromValue}; use anyhow::Result; use serde::Serialize; -use super::FromValue; - #[derive(Debug, Serialize)] pub struct StreamingInfo { pub offset: u64, @@ -12,11 +10,11 @@ pub struct StreamingInfo { } impl FromValue for StreamingInfo { fn from_value(v: Value) -> Result<Self> { - let fields = v.as_class("StreamingInfo").unwrap(); + let mut fields = v.as_class("StreamingInfo").unwrap(); Ok(StreamingInfo { - offset: fields["offset"].as_u64().unwrap(), - size: fields["size"].as_u32().unwrap(), - path: fields["path"].to_owned().as_string().unwrap(), + offset: fields.field("offset")?, + size: fields.field("size")?, + path: fields.field("path")?, }) } } diff --git a/src/classes/texture2d.rs b/src/classes/texture2d.rs index e3f3225..147cb5d 100644 --- a/src/classes/texture2d.rs +++ b/src/classes/texture2d.rs @@ -1,5 +1,5 @@ -use super::{FromValue, streaminginfo::StreamingInfo}; -use crate::object::Value; +use super::streaminginfo::StreamingInfo; +use crate::object::{Value, parser::FromValue}; use anyhow::{Result, bail}; use image::{DynamicImage, ImageBuffer, Luma, Rgb, Rgba}; use log::info; diff --git a/src/classes/transform.rs b/src/classes/transform.rs index b05985f..c9948d3 100644 --- a/src/classes/transform.rs +++ b/src/classes/transform.rs @@ -1,5 +1,5 @@ -use super::{FromValue, gameobject::GameObject, pptr::PPtr}; -use crate::object::Value; +use super::{gameobject::GameObject, pptr::PPtr}; +use crate::object::{Value, parser::FromValue}; use anyhow::Result; use glam::{Quat, Vec3}; use serde::Serialize; diff --git a/src/classes/vectors.rs b/src/classes/vectors.rs index c6e3a0d..76760bc 100644 --- a/src/classes/vectors.rs +++ b/src/classes/vectors.rs @@ -1,26 +1,57 @@ -use super::FromValue; -use crate::object::Value; -use glam::{Quat, Vec3}; +use crate::object::{Value, parser::FromValue}; +use glam::{Mat4, Quat, Vec3}; impl FromValue for Vec3 { fn from_value(v: Value) -> anyhow::Result<Self> { - let fields = v.as_class("Vector3f").unwrap(); + let mut fields = v.as_class("Vector3f").unwrap(); Ok(Self { - x: fields["x"].as_f32().unwrap(), - y: fields["y"].as_f32().unwrap(), - z: fields["z"].as_f32().unwrap(), + x: fields.field("x")?, + y: fields.field("y")?, + z: fields.field("z")?, }) } } impl FromValue for Quat { fn from_value(v: Value) -> anyhow::Result<Self> { - let fields = v.as_class("Quaternionf").unwrap(); + let mut fields = v.as_class("Quaternionf").unwrap(); Ok(Self::from_array([ - fields["x"].as_f32().unwrap(), - fields["y"].as_f32().unwrap(), - fields["z"].as_f32().unwrap(), - fields["w"].as_f32().unwrap(), + fields.field("x")?, + fields.field("y")?, + fields.field("z")?, + fields.field("w")?, + ])) + } +} + +impl FromValue for Mat4 { + fn from_value(v: Value) -> anyhow::Result<Self> { + let mut fields = v.as_class("Matrix4x4f").unwrap(); + Ok(Self::from_cols_array_2d(&[ + [ + fields.field("e00")?, + fields.field("e01")?, + fields.field("e02")?, + fields.field("e03")?, + ], + [ + fields.field("e10")?, + fields.field("e11")?, + fields.field("e12")?, + fields.field("e13")?, + ], + [ + fields.field("e20")?, + fields.field("e21")?, + fields.field("e22")?, + fields.field("e23")?, + ], + [ + fields.field("e30")?, + fields.field("e31")?, + fields.field("e32")?, + fields.field("e33")?, + ], ])) } } diff --git a/src/object.rs b/src/object.rs deleted file mode 100644 index 92415e1..0000000 --- a/src/object.rs +++ /dev/null @@ -1,246 +0,0 @@ -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, Clone, Serialize)] -pub enum Value { - Bool(bool), - U8(u8), - I8(i8), - U16(u16), - I16(i16), - U32(u32), - I32(i32), - F32(f32), - U64(u64), - I64(i64), - F64(f64), - Array(Vec<Value>), - Object { - class: String, - fields: BTreeMap<String, Value>, - }, - Typeless(Vec<u8>), - String(String), -} - -pub fn read_value( - ty: &TypeTreeNode, - e: Endianness, - data: &mut (impl Read + Seek), -) -> Result<Value> { - let mut align = false; - let pos_before = data.stream_position()?; - let r = match ty.type_string.as_str() { - "char" => { - assert_eq!(ty.byte_size, 1); - Ok(Value::U8(data.read_u8()?)) - } - "Type*" => Ok(Value::U32(data.read_u32(e)?)), - "int" => Ok(Value::I32(data.read_i32(e)?)), - "unsigned int" => Ok(Value::U32(data.read_u32(e)?)), - "UInt8" => Ok(Value::U8(data.read_u8()?)), - "UInt16" => Ok(Value::U16(data.read_u16(e)?)), - "UInt32" => Ok(Value::U32(data.read_u32(e)?)), - "UInt64" => Ok(Value::U64(data.read_u64(e)?)), - "SInt8" => Ok(Value::I8(data.read_i8()?)), - "SInt16" => Ok(Value::I16(data.read_i16(e)?)), - "SInt32" => Ok(Value::I32(data.read_i32(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| match e { - Value::U8(x) => x, - _ => unreachable!(), - }) - .collect::<Vec<_>>(); - Ok(Value::String(String::from_utf8(bytes)?)) - } - "Array" => { - align |= ty.children[0].post_align(); - assert_eq!(ty.byte_size, -1); - let Value::I32(size) = read_value(&ty.children[0], e, data)? else { - unreachable!() - }; - trace!("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)) - } - "TypelessData" => { - let len = data.read_u32(e)?; - let mut buf = vec![0u8; len as usize]; - data.read_exact(&mut buf)?; - Ok(Value::Typeless(buf)) - } - _ => { - if ty.children.is_empty() && ty.byte_size != 0 { - 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(), - }) - } - }; - let pos_after = data.stream_position()?; - if ty.byte_size != -1 && pos_after - pos_before < ty.byte_size as u64 { - bail!( - "did not read enough data ({} expected, {} actual)", - ty.byte_size, - pos_after - pos_before - ); - } - if align || ty.post_align() { - trace!("post align"); - data.align(4)?; - } - r -} - -impl Value { - pub fn class_name(&self) -> Option<&String> { - if let Value::Object { class, .. } = self { - Some(class) - } else { - None - } - } - 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 as_u32(&self) -> Option<u32> { - if let Value::U32(s) = self { - Some(*s) - } else { - None - } - } - pub fn as_u64(&self) -> Option<u64> { - match self { - Self::U64(x) => Some(*x), - Self::U32(x) => Some(*x as u64), - _ => None, - } - } - pub fn as_f32(&self) -> Option<f32> { - if let Value::F32(s) = self { - Some(*s) - } else { - None - } - } - pub fn as_u16(&self) -> Option<u16> { - if let Value::U16(s) = self { - Some(*s) - } else { - None - } - } - pub fn as_bool(&self) -> Option<bool> { - if let Value::Bool(s) = self { - Some(*s) - } else { - None - } - } - pub fn as_array(self) -> Option<Vec<Value>> { - if let Value::Array(s) = self { - Some(s) - } else { - None - } - } - pub fn as_typeless(self) -> Option<Vec<u8>> { - if let Value::Typeless(s) = self { - Some(s) - } else { - None - } - } - pub fn to_json(self) -> serde_json::Value { - match self { - Value::Bool(x) => serde_json::Value::Bool(x), - Value::U8(x) => serde_json::Value::Number(x.into()), - Value::I8(x) => serde_json::Value::Number(x.into()), - Value::U16(x) => serde_json::Value::Number(x.into()), - Value::I16(x) => serde_json::Value::Number(x.into()), - Value::U32(x) => serde_json::Value::Number(x.into()), - Value::U64(x) => serde_json::Value::Number(x.into()), - Value::I32(x) => serde_json::Value::Number(x.into()), - Value::F32(x) => serde_json::Value::Number( - serde_json::Number::from_f64(x as f64).unwrap_or(0.into()), - ), - Value::I64(x) => serde_json::Value::Number(x.into()), - Value::F64(x) => serde_json::Value::Number(serde_json::Number::from_f64(x).unwrap()), - Value::String(x) => serde_json::Value::String(x), - Value::Typeless(values) => serde_json::Value::Array( - values - .into_iter() - .map(|e| serde_json::Value::Number(e.into())) - .collect(), - ), - Value::Array(values) => { - serde_json::Value::Array(values.into_iter().map(Value::to_json).collect()) - } - Value::Object { class, fields } => serde_json::Value::Object( - fields - .into_iter() - .map(|(k, v)| (k, v.to_json())) - .chain(Some(( - "@class".to_string(), - serde_json::Value::String(class), - ))) - .collect(), - ), - } - } -} diff --git a/src/object/helper.rs b/src/object/helper.rs new file mode 100644 index 0000000..3f41703 --- /dev/null +++ b/src/object/helper.rs @@ -0,0 +1,125 @@ +use super::Value; + +impl Value { + pub fn class_name(&self) -> Option<&String> { + if let Value::Object { class, .. } = self { + Some(class) + } 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 as_u32(&self) -> Option<u32> { + if let Value::U32(s) = self { + Some(*s) + } else { + None + } + } + pub fn as_u8(&self) -> Option<u8> { + if let Value::U8(s) = self { + Some(*s) + } else { + None + } + } + pub fn as_u64(&self) -> Option<u64> { + match self { + Self::U64(x) => Some(*x), + Self::U32(x) => Some(*x as u64), + _ => None, + } + } + pub fn as_f32(&self) -> Option<f32> { + if let Value::F32(s) = self { + Some(*s) + } else { + None + } + } + pub fn as_u16(&self) -> Option<u16> { + if let Value::U16(s) = self { + Some(*s) + } else { + None + } + } + pub fn as_bool(&self) -> Option<bool> { + if let Value::Bool(s) = self { + Some(*s) + } else { + None + } + } + pub fn as_array(self) -> Option<Vec<Value>> { + if let Value::Array(s) = self { + Some(s) + } else { + None + } + } + pub fn as_typeless(self) -> Option<Vec<u8>> { + if let Value::Typeless(s) = self { + Some(s) + } else { + None + } + } + pub fn to_json(self) -> serde_json::Value { + match self { + Value::Bool(x) => serde_json::Value::Bool(x), + Value::U8(x) => serde_json::Value::Number(x.into()), + Value::I8(x) => serde_json::Value::Number(x.into()), + Value::U16(x) => serde_json::Value::Number(x.into()), + Value::I16(x) => serde_json::Value::Number(x.into()), + Value::U32(x) => serde_json::Value::Number(x.into()), + Value::U64(x) => serde_json::Value::Number(x.into()), + Value::I32(x) => serde_json::Value::Number(x.into()), + Value::F32(x) => serde_json::Value::Number( + serde_json::Number::from_f64(x as f64).unwrap_or(0.into()), + ), + Value::I64(x) => serde_json::Value::Number(x.into()), + Value::F64(x) => serde_json::Value::Number(serde_json::Number::from_f64(x).unwrap()), + Value::String(x) => serde_json::Value::String(x), + Value::Typeless(values) => serde_json::Value::Array( + values + .into_iter() + .map(|e| serde_json::Value::Number(e.into())) + .collect(), + ), + Value::Array(values) => { + serde_json::Value::Array(values.into_iter().map(Value::to_json).collect()) + } + Value::Object { class, fields } => serde_json::Value::Object( + fields + .into_iter() + .map(|(k, v)| (k, v.to_json())) + .chain(Some(( + "@class".to_string(), + serde_json::Value::String(class), + ))) + .collect(), + ), + } + } +} diff --git a/src/object/mod.rs b/src/object/mod.rs new file mode 100644 index 0000000..6e643de --- /dev/null +++ b/src/object/mod.rs @@ -0,0 +1,28 @@ +use serde::Serialize; +use std::collections::BTreeMap; + +pub mod helper; +pub mod read; +pub mod parser; + +#[derive(Debug, Clone, Serialize)] +pub enum Value { + Bool(bool), + U8(u8), + I8(i8), + U16(u16), + I16(i16), + U32(u32), + I32(i32), + F32(f32), + U64(u64), + I64(i64), + F64(f64), + Array(Vec<Value>), + Object { + class: String, + fields: BTreeMap<String, Value>, + }, + Typeless(Vec<u8>), + String(String), +} diff --git a/src/object/parser.rs b/src/object/parser.rs new file mode 100644 index 0000000..5879e9a --- /dev/null +++ b/src/object/parser.rs @@ -0,0 +1,92 @@ +use super::Value; +use anyhow::{Context, Result, anyhow, bail}; +use std::collections::BTreeMap; + +pub trait FromValue: Sized { + fn from_value(v: Value) -> Result<Self>; +} + +impl Value { + pub fn parse<T: FromValue>(self) -> Result<T> { + T::from_value(self) + } + pub fn as_class(self, name: &'static str) -> Result<Fields> { + if let Value::Object { class, fields } = self { + if class == name { + Ok(Fields { + class: name, + fields, + }) + } else { + bail!("expected class {name} but found {class}") + } + } else { + bail!("expected class {name}") + } + } +} + +pub struct Fields { + class: &'static str, + fields: BTreeMap<String, Value>, +} + +impl Fields { + pub fn field<T: FromValue>(&mut self, name: &str) -> Result<T> { + self.fields + .remove(name) + .ok_or(anyhow!("expected {name} field in {}", self.class))? + .parse() + .context(anyhow!("in {}.{name} field", self.class)) + } + pub fn remove(&mut self, key: &str) -> Option<Value> { + self.fields.remove(key) + } +} + +impl FromValue for u8 { + fn from_value(v: Value) -> anyhow::Result<Self> { + v.as_u8().ok_or(anyhow!("expected u8")) + } +} +impl FromValue for u16 { + fn from_value(v: Value) -> anyhow::Result<Self> { + v.as_u16().ok_or(anyhow!("expected u16")) + } +} +impl FromValue for f32 { + fn from_value(v: Value) -> anyhow::Result<Self> { + v.as_f32().ok_or(anyhow!("expected f32")) + } +} +impl FromValue for u32 { + fn from_value(v: Value) -> anyhow::Result<Self> { + v.as_u32().ok_or(anyhow!("expected u32")) + } +} +impl FromValue for i32 { + fn from_value(v: Value) -> anyhow::Result<Self> { + v.as_i32().ok_or(anyhow!("expected i32")) + } +} +impl FromValue for u64 { + fn from_value(v: Value) -> anyhow::Result<Self> { + v.as_u64().ok_or(anyhow!("expected u64")) + } +} +impl FromValue for bool { + fn from_value(v: Value) -> anyhow::Result<Self> { + v.as_bool().ok_or(anyhow!("expected bool")) + } +} +impl FromValue for String { + fn from_value(v: Value) -> anyhow::Result<Self> { + v.as_string().ok_or(anyhow!("expected string")) + } +} + +impl Value { + pub fn as_vector(self) -> Option<Vec<Value>> { + self.as_class("vector").ok()?.remove("Array")?.as_array() + } +} diff --git a/src/object/read.rs b/src/object/read.rs new file mode 100644 index 0000000..5adbe3f --- /dev/null +++ b/src/object/read.rs @@ -0,0 +1,100 @@ +use super::Value; +use crate::helper::{AlignExt, Endianness, ReadExt}; +use crate::serialized_file::TypeTreeNode; +use anyhow::{Result, bail}; +use log::trace; +use std::io::Seek; +use std::{collections::BTreeMap, io::Read}; + +pub fn read_value( + ty: &TypeTreeNode, + e: Endianness, + data: &mut (impl Read + Seek), +) -> Result<Value> { + let mut align = false; + let pos_before = data.stream_position()?; + let r = match ty.type_string.as_str() { + "char" => { + assert_eq!(ty.byte_size, 1); + Ok(Value::U8(data.read_u8()?)) + } + "Type*" => Ok(Value::U32(data.read_u32(e)?)), + "int" => Ok(Value::I32(data.read_i32(e)?)), + "unsigned int" => Ok(Value::U32(data.read_u32(e)?)), + "UInt8" => Ok(Value::U8(data.read_u8()?)), + "UInt16" => Ok(Value::U16(data.read_u16(e)?)), + "UInt32" => Ok(Value::U32(data.read_u32(e)?)), + "UInt64" => Ok(Value::U64(data.read_u64(e)?)), + "SInt8" => Ok(Value::I8(data.read_i8()?)), + "SInt16" => Ok(Value::I16(data.read_i16(e)?)), + "SInt32" => Ok(Value::I32(data.read_i32(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| match e { + Value::U8(x) => x, + _ => unreachable!(), + }) + .collect::<Vec<_>>(); + Ok(Value::String(String::from_utf8(bytes)?)) + } + "Array" => { + align |= ty.children[0].post_align(); + assert_eq!(ty.byte_size, -1); + let Value::I32(size) = read_value(&ty.children[0], e, data)? else { + unreachable!() + }; + trace!("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)) + } + "TypelessData" => { + let len = data.read_u32(e)?; + let mut buf = vec![0u8; len as usize]; + data.read_exact(&mut buf)?; + Ok(Value::Typeless(buf)) + } + _ => { + if ty.children.is_empty() && ty.byte_size != 0 { + 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(), + }) + } + }; + let pos_after = data.stream_position()?; + if ty.byte_size != -1 && pos_after - pos_before < ty.byte_size as u64 { + bail!( + "did not read enough data ({} expected, {} actual)", + ty.byte_size, + pos_after - pos_before + ); + } + if align || ty.post_align() { + trace!("post align"); + data.align(4)?; + } + r +} |