unity_asset_binary/
object.rs

1//! Unity object parsing and representation
2
3use crate::error::{BinaryError, Result};
4use crate::reader::{BinaryReader, ByteOrder};
5use crate::typetree::{TypeTree, TypeTreeNode};
6use crate::unity_objects::{GameObject, Transform};
7// Removed unused serde imports
8use std::collections::HashMap;
9use unity_asset_core::{UnityClass, UnityValue};
10
11/// Information about a Unity object in a serialized file
12#[derive(Debug, Clone)]
13pub struct ObjectInfo {
14    /// Path ID (unique identifier within the file)
15    pub path_id: i64,
16    /// Byte offset in the data section
17    pub byte_start: u64,
18    /// Size of the object data in bytes
19    pub byte_size: u32,
20    /// Class ID of the object
21    pub class_id: i32,
22    /// Type ID (used for type lookup)
23    pub type_id: i32,
24    /// Byte order for reading this object
25    pub byte_order: ByteOrder,
26    /// Raw object data
27    pub data: Vec<u8>,
28    /// Type information for this object
29    pub type_tree: Option<TypeTree>,
30}
31
32impl ObjectInfo {
33    /// Create a new ObjectInfo
34    pub fn new(path_id: i64, byte_start: u64, byte_size: u32, class_id: i32) -> Self {
35        Self {
36            path_id,
37            byte_start,
38            byte_size,
39            class_id,
40            type_id: class_id, // Default to same as class_id
41            byte_order: ByteOrder::Little,
42            data: Vec::new(),
43            type_tree: None,
44        }
45    }
46
47    /// Get a binary reader for this object's data
48    pub fn reader(&self) -> BinaryReader<'_> {
49        BinaryReader::new(&self.data, self.byte_order)
50    }
51
52    /// Get the Unity class name for this object
53    pub fn class_name(&self) -> String {
54        unity_asset_core::get_class_name(self.class_id)
55            .unwrap_or_else(|| format!("Class_{}", self.class_id))
56    }
57
58    /// Parse this object into a UnityClass using TypeTree information
59    pub fn parse_object(&self) -> Result<UnityClass> {
60        let mut unity_class =
61            UnityClass::new(self.class_id, self.class_name(), self.path_id.to_string());
62
63        if let Some(ref type_tree) = self.type_tree {
64            let mut reader = self.reader();
65            let properties = self.parse_with_typetree(&mut reader, type_tree)?;
66
67            for (key, value) in properties {
68                unity_class.set(key, value);
69            }
70        } else {
71            // Fallback: try to parse as raw data
72            unity_class.set(
73                "_raw_data".to_string(),
74                UnityValue::Array(
75                    self.data
76                        .iter()
77                        .map(|&b| UnityValue::Integer(b as i64))
78                        .collect(),
79                ),
80            );
81        }
82
83        Ok(unity_class)
84    }
85
86    /// Parse object data using TypeTree information
87    fn parse_with_typetree(
88        &self,
89        reader: &mut BinaryReader,
90        type_tree: &TypeTree,
91    ) -> Result<HashMap<String, UnityValue>> {
92        let mut properties = HashMap::new();
93
94        if let Some(root) = type_tree.nodes.first() {
95            self.parse_node(reader, root, &mut properties)?;
96        }
97
98        Ok(properties)
99    }
100
101    /// Parse a single TypeTree node
102    fn parse_node(
103        &self,
104        reader: &mut BinaryReader,
105        node: &TypeTreeNode,
106        properties: &mut HashMap<String, UnityValue>,
107    ) -> Result<()> {
108        if node.name.is_empty() || !node.name.starts_with("m_") {
109            // Skip nodes without proper names or that aren't member variables
110            return Ok(());
111        }
112
113        let value = match node.type_name.as_str() {
114            "bool" => UnityValue::Bool(reader.read_bool()?),
115            "SInt8" => UnityValue::Integer(reader.read_i8()? as i64),
116            "UInt8" => UnityValue::Integer(reader.read_u8()? as i64),
117            "SInt16" => UnityValue::Integer(reader.read_i16()? as i64),
118            "UInt16" => UnityValue::Integer(reader.read_u16()? as i64),
119            "SInt32" | "int" => UnityValue::Integer(reader.read_i32()? as i64),
120            "UInt32" => UnityValue::Integer(reader.read_u32()? as i64),
121            "SInt64" => UnityValue::Integer(reader.read_i64()?),
122            "UInt64" => UnityValue::Integer(reader.read_u64()? as i64),
123            "float" => UnityValue::Float(reader.read_f32()? as f64),
124            "double" => UnityValue::Float(reader.read_f64()?),
125            "string" => {
126                let length = reader.read_u32()? as usize;
127                let bytes = reader.read_bytes(length)?;
128                let string = String::from_utf8(bytes).map_err(|e| {
129                    BinaryError::invalid_data(format!("Invalid UTF-8 string: {}", e))
130                })?;
131                reader.align()?; // Strings are aligned
132                UnityValue::String(string)
133            }
134            "Array" => {
135                // Parse array
136                let size = reader.read_u32()? as usize;
137                let mut array = Vec::new();
138
139                // For now, treat array elements as raw bytes
140                // A full implementation would recursively parse based on element type
141                for _ in 0..size {
142                    if node.children.len() > 1 {
143                        // Array has element type information
144                        if let Some(element_node) = node.children.get(1) {
145                            let element_value = self.parse_single_value(reader, element_node)?;
146                            array.push(element_value);
147                        }
148                    } else {
149                        // Fallback: read as bytes
150                        array.push(UnityValue::Integer(reader.read_u8()? as i64));
151                    }
152                }
153
154                UnityValue::Array(array)
155            }
156            _ => {
157                // Complex type or unknown type
158                if node.byte_size > 0 && node.byte_size <= 1024 {
159                    // Read as raw bytes for small objects
160                    let bytes = reader.read_bytes(node.byte_size as usize)?;
161                    UnityValue::Array(
162                        bytes
163                            .into_iter()
164                            .map(|b| UnityValue::Integer(b as i64))
165                            .collect(),
166                    )
167                } else {
168                    // Skip large or variable-size objects
169                    UnityValue::Null
170                }
171            }
172        };
173
174        properties.insert(node.name.clone(), value);
175        Ok(())
176    }
177
178    /// Parse a single value based on TypeTree node
179    fn parse_single_value(
180        &self,
181        reader: &mut BinaryReader,
182        node: &TypeTreeNode,
183    ) -> Result<UnityValue> {
184        match node.type_name.as_str() {
185            "bool" => Ok(UnityValue::Bool(reader.read_bool()?)),
186            "SInt8" => Ok(UnityValue::Integer(reader.read_i8()? as i64)),
187            "UInt8" => Ok(UnityValue::Integer(reader.read_u8()? as i64)),
188            "SInt16" => Ok(UnityValue::Integer(reader.read_i16()? as i64)),
189            "UInt16" => Ok(UnityValue::Integer(reader.read_u16()? as i64)),
190            "SInt32" | "int" => Ok(UnityValue::Integer(reader.read_i32()? as i64)),
191            "UInt32" => Ok(UnityValue::Integer(reader.read_u32()? as i64)),
192            "SInt64" => Ok(UnityValue::Integer(reader.read_i64()?)),
193            "UInt64" => Ok(UnityValue::Integer(reader.read_u64()? as i64)),
194            "float" => Ok(UnityValue::Float(reader.read_f32()? as f64)),
195            "double" => Ok(UnityValue::Float(reader.read_f64()?)),
196            _ => {
197                // For complex types, read as raw bytes
198                if node.byte_size > 0 && node.byte_size <= 64 {
199                    let bytes = reader.read_bytes(node.byte_size as usize)?;
200                    Ok(UnityValue::Array(
201                        bytes
202                            .into_iter()
203                            .map(|b| UnityValue::Integer(b as i64))
204                            .collect(),
205                    ))
206                } else {
207                    Ok(UnityValue::Null)
208                }
209            }
210        }
211    }
212}
213
214/// A Unity object with parsed data
215#[derive(Debug, Clone)]
216pub struct UnityObject {
217    /// Object information
218    pub info: ObjectInfo,
219    /// Parsed Unity class data
220    pub class: UnityClass,
221}
222
223impl UnityObject {
224    /// Create a new Unity object
225    pub fn new(info: ObjectInfo) -> Result<Self> {
226        let class = info.parse_object()?;
227        Ok(Self { info, class })
228    }
229
230    /// Get the object's path ID
231    pub fn path_id(&self) -> i64 {
232        self.info.path_id
233    }
234
235    /// Get the object's class ID
236    pub fn class_id(&self) -> i32 {
237        self.info.class_id
238    }
239
240    /// Get the object's class name
241    pub fn class_name(&self) -> &str {
242        &self.class.class_name
243    }
244
245    /// Get the object's name (if it has one)
246    pub fn name(&self) -> Option<String> {
247        self.class.get("m_Name").and_then(|v| match v {
248            UnityValue::String(s) => Some(s.clone()),
249            _ => None,
250        })
251    }
252
253    /// Get a property value
254    pub fn get(&self, key: &str) -> Option<&UnityValue> {
255        self.class.get(key)
256    }
257
258    /// Set a property value
259    pub fn set(&mut self, key: String, value: UnityValue) {
260        self.class.set(key, value);
261    }
262
263    /// Check if the object has a property
264    pub fn has_property(&self, key: &str) -> bool {
265        self.class.has_property(key)
266    }
267
268    /// Get all property names
269    pub fn property_names(&self) -> Vec<&String> {
270        self.class.properties().keys().collect()
271    }
272
273    /// Get the underlying UnityClass
274    pub fn as_unity_class(&self) -> &UnityClass {
275        &self.class
276    }
277
278    /// Get the underlying UnityClass (mutable)
279    pub fn as_unity_class_mut(&mut self) -> &mut UnityClass {
280        &mut self.class
281    }
282
283    /// Try to parse this object as a GameObject
284    pub fn as_gameobject(&self) -> Result<GameObject> {
285        if self.class_id() != 1 {
286            return Err(BinaryError::invalid_data(format!(
287                "Object is not a GameObject (class_id: {})",
288                self.class_id()
289            )));
290        }
291        GameObject::from_typetree(self.class.properties())
292    }
293
294    /// Try to parse this object as a Transform
295    pub fn as_transform(&self) -> Result<Transform> {
296        if self.class_id() != 4 {
297            return Err(BinaryError::invalid_data(format!(
298                "Object is not a Transform (class_id: {})",
299                self.class_id()
300            )));
301        }
302        Transform::from_typetree(self.class.properties())
303    }
304
305    /// Check if this object is a GameObject
306    pub fn is_gameobject(&self) -> bool {
307        self.class_id() == 1
308    }
309
310    /// Check if this object is a Transform
311    pub fn is_transform(&self) -> bool {
312        self.class_id() == 4
313    }
314
315    /// Get a human-readable description of this object
316    pub fn describe(&self) -> String {
317        let name = self.name().unwrap_or_else(|| "<unnamed>".to_string());
318        format!(
319            "{} '{}' (ID:{}, PathID:{})",
320            self.class_name(),
321            name,
322            self.class_id(),
323            self.path_id()
324        )
325    }
326
327    /// Parse object data using TypeTree (dictionary mode)
328    pub fn parse_with_typetree(
329        &self,
330        typetree: &crate::typetree::TypeTree,
331    ) -> Result<indexmap::IndexMap<String, unity_asset_core::UnityValue>> {
332        let mut reader =
333            crate::reader::BinaryReader::new(&self.info.data, crate::reader::ByteOrder::Little);
334        let serializer = crate::typetree::TypeTreeSerializer::new(typetree);
335        serializer.parse_object(&mut reader)
336    }
337
338    /// Get raw object data
339    pub fn raw_data(&self) -> &[u8] {
340        &self.info.data
341    }
342
343    /// Get object byte size
344    pub fn byte_size(&self) -> u32 {
345        self.info.byte_size
346    }
347
348    /// Get object byte start position
349    pub fn byte_start(&self) -> u64 {
350        self.info.byte_start
351    }
352}
353
354#[cfg(test)]
355mod tests {
356    use super::*;
357
358    #[test]
359    fn test_object_info_creation() {
360        let info = ObjectInfo::new(12345, 1000, 256, 1);
361        assert_eq!(info.path_id, 12345);
362        assert_eq!(info.byte_start, 1000);
363        assert_eq!(info.byte_size, 256);
364        assert_eq!(info.class_id, 1);
365    }
366
367    #[test]
368    fn test_object_info_class_name() {
369        let info = ObjectInfo::new(1, 0, 0, 1);
370        assert_eq!(info.class_name(), "GameObject");
371
372        let info = ObjectInfo::new(1, 0, 0, 999999);
373        assert_eq!(info.class_name(), "Class_999999");
374    }
375
376    #[test]
377    fn test_object_creation() {
378        let mut info = ObjectInfo::new(1, 0, 4, 1);
379        info.data = vec![1, 0, 0, 0]; // Simple test data
380
381        let result = UnityObject::new(info);
382        assert!(result.is_ok());
383
384        let object = result.unwrap();
385        assert_eq!(object.path_id(), 1);
386        assert_eq!(object.class_id(), 1);
387        assert_eq!(object.class_name(), "GameObject");
388    }
389}