aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bin/json.rs4
-rw-r--r--src/bin/textures.rs4
-rw-r--r--src/bin/yaml.rs22
-rw-r--r--src/classes/assetinfo.rs15
-rw-r--r--src/classes/gameobject.rs12
-rw-r--r--src/classes/mesh.rs128
-rw-r--r--src/classes/mod.rs22
-rw-r--r--src/classes/pptr.rs4
-rw-r--r--src/classes/streaminginfo.rs12
-rw-r--r--src/classes/texture2d.rs4
-rw-r--r--src/classes/transform.rs4
-rw-r--r--src/classes/vectors.rs55
-rw-r--r--src/object.rs246
-rw-r--r--src/object/helper.rs125
-rw-r--r--src/object/mod.rs28
-rw-r--r--src/object/parser.rs92
-rw-r--r--src/object/read.rs100
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
+}