From 5555c8bbefb4f52f5002603eb91b6c95cbdd97e4 Mon Sep 17 00:00:00 2001 From: metamuffin Date: Wed, 12 Mar 2025 22:39:58 +0100 Subject: more parsing helpers --- src/object/helper.rs | 125 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/object/mod.rs | 28 ++++++++++++ src/object/parser.rs | 92 +++++++++++++++++++++++++++++++++++++ src/object/read.rs | 100 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 345 insertions(+) create mode 100644 src/object/helper.rs create mode 100644 src/object/mod.rs create mode 100644 src/object/parser.rs create mode 100644 src/object/read.rs (limited to 'src/object') 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 { + if let Value::String(s) = self { + Some(s) + } else { + None + } + } + pub fn as_i64(&self) -> Option { + if let Value::I64(s) = self { + Some(*s) + } else { + None + } + } + pub fn as_i32(&self) -> Option { + if let Value::I32(s) = self { + Some(*s) + } else { + None + } + } + pub fn as_u32(&self) -> Option { + if let Value::U32(s) = self { + Some(*s) + } else { + None + } + } + pub fn as_u8(&self) -> Option { + if let Value::U8(s) = self { + Some(*s) + } else { + None + } + } + pub fn as_u64(&self) -> Option { + match self { + Self::U64(x) => Some(*x), + Self::U32(x) => Some(*x as u64), + _ => None, + } + } + pub fn as_f32(&self) -> Option { + if let Value::F32(s) = self { + Some(*s) + } else { + None + } + } + pub fn as_u16(&self) -> Option { + if let Value::U16(s) = self { + Some(*s) + } else { + None + } + } + pub fn as_bool(&self) -> Option { + if let Value::Bool(s) = self { + Some(*s) + } else { + None + } + } + pub fn as_array(self) -> Option> { + if let Value::Array(s) = self { + Some(s) + } else { + None + } + } + pub fn as_typeless(self) -> Option> { + 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), + Object { + class: String, + fields: BTreeMap, + }, + Typeless(Vec), + 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; +} + +impl Value { + pub fn parse(self) -> Result { + T::from_value(self) + } + pub fn as_class(self, name: &'static str) -> Result { + 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, +} + +impl Fields { + pub fn field(&mut self, name: &str) -> Result { + 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 { + self.fields.remove(key) + } +} + +impl FromValue for u8 { + fn from_value(v: Value) -> anyhow::Result { + v.as_u8().ok_or(anyhow!("expected u8")) + } +} +impl FromValue for u16 { + fn from_value(v: Value) -> anyhow::Result { + v.as_u16().ok_or(anyhow!("expected u16")) + } +} +impl FromValue for f32 { + fn from_value(v: Value) -> anyhow::Result { + v.as_f32().ok_or(anyhow!("expected f32")) + } +} +impl FromValue for u32 { + fn from_value(v: Value) -> anyhow::Result { + v.as_u32().ok_or(anyhow!("expected u32")) + } +} +impl FromValue for i32 { + fn from_value(v: Value) -> anyhow::Result { + v.as_i32().ok_or(anyhow!("expected i32")) + } +} +impl FromValue for u64 { + fn from_value(v: Value) -> anyhow::Result { + v.as_u64().ok_or(anyhow!("expected u64")) + } +} +impl FromValue for bool { + fn from_value(v: Value) -> anyhow::Result { + v.as_bool().ok_or(anyhow!("expected bool")) + } +} +impl FromValue for String { + fn from_value(v: Value) -> anyhow::Result { + v.as_string().ok_or(anyhow!("expected string")) + } +} + +impl Value { + pub fn as_vector(self) -> Option> { + 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 { + 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::>(); + 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 +} -- cgit v1.2.3-70-g09d2