unity_asset_binary/typetree/
serializer.rs

1//! TypeTree serialization and deserialization
2//!
3//! This module provides functionality for serializing and deserializing
4//! Unity objects using TypeTree information.
5
6use super::types::{TypeTree, TypeTreeNode};
7use crate::asset::SerializedType;
8use crate::error::{BinaryError, Result};
9use crate::reader::{BinaryReader, ByteOrder};
10use indexmap::IndexMap;
11use unity_asset_core::UnityValue;
12
13/// TypeTree serializer
14///
15/// This struct provides methods for serializing and deserializing Unity objects
16/// using TypeTree structure information.
17pub struct TypeTreeSerializer<'a> {
18    tree: &'a TypeTree,
19}
20
21#[derive(Debug, Default, Clone, PartialEq, Eq)]
22pub struct PPtrScanResult {
23    pub internal: Vec<i64>,
24    pub external: Vec<(i32, i64)>,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
28pub enum TypeTreeParseMode {
29    Strict,
30    #[default]
31    Lenient,
32}
33
34#[derive(Debug, Clone, Copy, Default)]
35pub struct TypeTreeParseOptions {
36    pub mode: TypeTreeParseMode,
37}
38
39#[derive(Debug, Clone)]
40pub struct TypeTreeParseWarning {
41    pub field: String,
42    pub error: String,
43}
44
45#[derive(Debug, Default)]
46pub struct TypeTreeParseOutput {
47    pub properties: IndexMap<String, UnityValue>,
48    pub warnings: Vec<TypeTreeParseWarning>,
49}
50
51#[derive(Debug)]
52struct TypeTreeParseContext<'a> {
53    options: TypeTreeParseOptions,
54    ref_types: Option<&'a [SerializedType]>,
55    has_managed_registry: bool,
56}
57
58#[derive(Debug)]
59struct TypeTreeScanContext<'a> {
60    ref_types: Option<&'a [SerializedType]>,
61    has_managed_registry: bool,
62}
63
64impl<'a> TypeTreeSerializer<'a> {
65    const MAX_ARRAY_LEN: usize = 1_000_000;
66    const MAX_TYPELESSDATA_LEN: usize = Self::MAX_ARRAY_LEN;
67
68    /// Create a new serializer with a TypeTree
69    pub fn new(tree: &'a TypeTree) -> Self {
70        Self { tree }
71    }
72
73    /// Parse object data using the TypeTree structure
74    pub fn parse_object(&self, reader: &mut BinaryReader) -> Result<IndexMap<String, UnityValue>> {
75        Ok(self
76            .parse_object_detailed(reader, TypeTreeParseOptions::default())?
77            .properties)
78    }
79
80    pub fn parse_object_with_ref_types(
81        &self,
82        reader: &mut BinaryReader,
83        ref_types: &'a [SerializedType],
84    ) -> Result<IndexMap<String, UnityValue>> {
85        Ok(self
86            .parse_object_detailed_with_ref_types(
87                reader,
88                TypeTreeParseOptions::default(),
89                ref_types,
90            )?
91            .properties)
92    }
93
94    pub fn parse_object_detailed(
95        &self,
96        reader: &mut BinaryReader,
97        options: TypeTreeParseOptions,
98    ) -> Result<TypeTreeParseOutput> {
99        self.parse_object_prefix_detailed(reader, options, usize::MAX)
100    }
101
102    pub fn parse_object_detailed_with_ref_types(
103        &self,
104        reader: &mut BinaryReader,
105        options: TypeTreeParseOptions,
106        ref_types: &'a [SerializedType],
107    ) -> Result<TypeTreeParseOutput> {
108        self.parse_object_prefix_detailed_with_ref_types(reader, options, usize::MAX, ref_types)
109    }
110
111    /// Parse only the first `root_children` fields of the root node.
112    ///
113    /// This enables UnityPy-like fast paths such as `peek_name()` where we only need a small prefix
114    /// of the TypeTree to reach `m_Name`.
115    pub fn parse_object_prefix_detailed(
116        &self,
117        reader: &mut BinaryReader,
118        options: TypeTreeParseOptions,
119        root_children: usize,
120    ) -> Result<TypeTreeParseOutput> {
121        self.parse_object_prefix_ctx(
122            reader,
123            TypeTreeParseContext {
124                options,
125                ref_types: None,
126                has_managed_registry: false,
127            },
128            root_children,
129        )
130    }
131
132    pub fn parse_object_prefix_detailed_with_ref_types(
133        &self,
134        reader: &mut BinaryReader,
135        options: TypeTreeParseOptions,
136        root_children: usize,
137        ref_types: &'a [SerializedType],
138    ) -> Result<TypeTreeParseOutput> {
139        self.parse_object_prefix_ctx(
140            reader,
141            TypeTreeParseContext {
142                options,
143                ref_types: Some(ref_types),
144                has_managed_registry: false,
145            },
146            root_children,
147        )
148    }
149
150    fn parse_object_prefix_ctx(
151        &self,
152        reader: &mut BinaryReader,
153        mut ctx: TypeTreeParseContext<'a>,
154        root_children: usize,
155    ) -> Result<TypeTreeParseOutput> {
156        let mut out = TypeTreeParseOutput::default();
157
158        if let Some(root) = self.tree.nodes.first() {
159            for child in root.children.iter().take(root_children) {
160                if child.name.is_empty() {
161                    continue;
162                }
163                match self.parse_value_by_type_ctx(reader, child, &mut ctx) {
164                    Ok(value) => {
165                        out.properties.insert(child.name.clone(), value);
166                    }
167                    Err(e) => {
168                        if reader.remaining() == 0 {
169                            break;
170                        }
171                        match ctx.options.mode {
172                            TypeTreeParseMode::Strict => return Err(e),
173                            TypeTreeParseMode::Lenient => out.warnings.push(TypeTreeParseWarning {
174                                field: child.name.clone(),
175                                error: e.to_string(),
176                            }),
177                        }
178                    }
179                }
180            }
181        }
182
183        Ok(out)
184    }
185
186    /// Scan TypeTree-based object bytes and collect any encountered `PPtr` references without
187    /// allocating a full `UnityValue` tree.
188    pub fn scan_pptrs(&self, reader: &mut BinaryReader) -> Result<PPtrScanResult> {
189        self.scan_pptrs_with_ref_types(reader, None)
190    }
191
192    fn scan_value(
193        &self,
194        reader: &mut BinaryReader,
195        node: &TypeTreeNode,
196        out: &mut PPtrScanResult,
197    ) -> Result<()> {
198        let mut ctx = TypeTreeScanContext {
199            ref_types: None,
200            has_managed_registry: false,
201        };
202        self.scan_value_ctx(reader, node, out, &mut ctx)
203    }
204
205    /// Scan TypeTree-based object bytes and collect any encountered `PPtr` references, using
206    /// file-level `ref_types` to best-effort traverse managed reference payloads.
207    pub fn scan_pptrs_with_ref_types(
208        &self,
209        reader: &mut BinaryReader,
210        ref_types: Option<&[SerializedType]>,
211    ) -> Result<PPtrScanResult> {
212        let mut out = PPtrScanResult::default();
213        let mut ctx = TypeTreeScanContext {
214            ref_types,
215            has_managed_registry: false,
216        };
217        if let Some(root) = self.tree.nodes.first() {
218            for child in &root.children {
219                self.scan_value_ctx(reader, child, &mut out, &mut ctx)?;
220            }
221        }
222        Ok(out)
223    }
224
225    fn scan_value_ctx(
226        &self,
227        reader: &mut BinaryReader,
228        node: &TypeTreeNode,
229        out: &mut PPtrScanResult,
230        ctx: &mut TypeTreeScanContext<'_>,
231    ) -> Result<()> {
232        // Array types
233        if !node.children.is_empty() && node.children.iter().any(|c| c.type_name == "Array") {
234            self.scan_array(reader, node, out, ctx)?;
235            if node.is_aligned() {
236                reader.align_to(4)?;
237            }
238            return Ok(());
239        }
240
241        // Managed reference payload (`SerializeReference`): best-effort typed scanning via `ref_types`.
242        if node.type_name == "ReferencedObject" && !node.children.is_empty() {
243            let mut class: Option<String> = None;
244            let mut ns: Option<String> = None;
245            let mut asm: Option<String> = None;
246
247            for child in &node.children {
248                if child.type_name == "ManagedReferencesRegistry" {
249                    if ctx.has_managed_registry {
250                        self.scan_value_ctx(reader, child, out, ctx)?;
251                    } else {
252                        ctx.has_managed_registry = true;
253                        self.scan_value_ctx(reader, child, out, ctx)?;
254                    }
255                    continue;
256                }
257
258                if child.name == "type" && !child.children.is_empty() {
259                    for field in &child.children {
260                        if field.type_name == "string"
261                            && (field.name == "class" || field.name == "m_ClassName")
262                        {
263                            class = Some(reader.read_aligned_string()?);
264                            continue;
265                        }
266                        if field.type_name == "string"
267                            && (field.name == "ns" || field.name == "m_NameSpace")
268                        {
269                            ns = Some(reader.read_aligned_string()?);
270                            continue;
271                        }
272                        if field.type_name == "string"
273                            && (field.name == "asm" || field.name == "m_AssemblyName")
274                        {
275                            asm = Some(reader.read_aligned_string()?);
276                            continue;
277                        }
278                        self.scan_value_ctx(reader, field, out, ctx)?;
279                    }
280                    if child.is_aligned() {
281                        reader.align_to(4)?;
282                    }
283                    continue;
284                }
285
286                if child.type_name == "ReferencedObjectData" {
287                    if let (Some(class), Some(ns), Some(asm), Some(ref_types)) =
288                        (class.as_ref(), ns.as_ref(), asm.as_ref(), ctx.ref_types)
289                        && let Some(tree) = resolve_ref_type_tree_triplet(class, ns, asm, ref_types)
290                        && let Some(root) = tree.nodes.first()
291                    {
292                        for field in &root.children {
293                            self.scan_value_ctx(reader, field, out, ctx)?;
294                        }
295                        if child.is_aligned() {
296                            reader.align_to(4)?;
297                        }
298                        continue;
299                    }
300
301                    // Fallback: consume according to placeholder node (if any).
302                    self.scan_value_ctx(reader, child, out, ctx)?;
303                    continue;
304                }
305
306                self.scan_value_ctx(reader, child, out, ctx)?;
307            }
308
309            if node.is_aligned() {
310                reader.align_to(4)?;
311            }
312            return Ok(());
313        }
314
315        // `PPtr<T>` types (best-effort): parse `fileID` + `pathID` while still consuming all children.
316        let is_pptr = node.type_name == "PPtr" || node.type_name.starts_with("PPtr<");
317        if is_pptr && !node.children.is_empty() {
318            let mut file_id: Option<i32> = None;
319            let mut path_id: Option<i64> = None;
320
321            for child in &node.children {
322                if child.name.eq_ignore_ascii_case("fileID")
323                    || child.name.eq_ignore_ascii_case("m_FileID")
324                {
325                    // Unity encodes fileID as int.
326                    let v = self.scan_read_i32_like(reader, child)?;
327                    file_id = Some(v);
328                } else if child.name.eq_ignore_ascii_case("pathID")
329                    || child.name.eq_ignore_ascii_case("m_PathID")
330                {
331                    // Unity encodes pathID as long (may be 32-bit in older versions, TypeTree guides us).
332                    let v = self.scan_read_i64_like(reader, child)?;
333                    path_id = Some(v);
334                } else {
335                    self.scan_value_ctx(reader, child, out, ctx)?;
336                }
337            }
338
339            if let (Some(file_id), Some(path_id)) = (file_id, path_id)
340                && path_id != 0
341            {
342                if file_id == 0 {
343                    out.internal.push(path_id);
344                } else {
345                    out.external.push((file_id, path_id));
346                }
347            }
348
349            if node.is_aligned() {
350                reader.align_to(4)?;
351            }
352            return Ok(());
353        }
354
355        match node.type_name.as_str() {
356            "SInt8" | "char" | "UInt8" => {
357                let _ = reader.read_u8()?;
358            }
359            "bool" => {
360                let _ = reader.read_u8()?;
361            }
362            "SInt16" | "short" => {
363                let _ = reader.read_i16()?;
364            }
365            "UInt16" | "unsigned short" => {
366                let _ = reader.read_u16()?;
367            }
368            "SInt32" | "int" => {
369                let _ = reader.read_i32()?;
370            }
371            "UInt32" | "unsigned int" | "Type*" => {
372                let _ = reader.read_u32()?;
373            }
374            "SInt64" | "long long" => {
375                let _ = reader.read_i64()?;
376            }
377            "UInt64" | "unsigned long long" | "FileSize" => {
378                let _ = reader.read_u64()?;
379            }
380            "float" => {
381                let _ = reader.read_f32()?;
382            }
383            "double" => {
384                let _ = reader.read_f64()?;
385            }
386            "string" => {
387                let len = reader.read_i32()?;
388                if len < 0 {
389                    return Err(BinaryError::invalid_data(format!(
390                        "Negative string length: {}",
391                        len
392                    )));
393                }
394                let len: usize = len as usize;
395                if len > BinaryReader::DEFAULT_MAX_STRING_LEN {
396                    return Err(BinaryError::invalid_data(format!(
397                        "String length {} exceeds limit {}",
398                        len,
399                        BinaryReader::DEFAULT_MAX_STRING_LEN
400                    )));
401                }
402                reader.skip_bytes(len)?;
403                reader.align_to(4)?;
404            }
405            "TypelessData" => {
406                let length = reader.read_i32()?;
407                if length < 0 {
408                    return Err(BinaryError::invalid_data(format!(
409                        "Negative TypelessData length: {}",
410                        length
411                    )));
412                }
413                let length: usize = length as usize;
414                if length > Self::MAX_TYPELESSDATA_LEN {
415                    return Err(BinaryError::invalid_data(format!(
416                        "TypelessData length {} exceeds limit {}",
417                        length,
418                        Self::MAX_TYPELESSDATA_LEN
419                    )));
420                }
421                reader.skip_bytes(length)?;
422            }
423            _ => {
424                if !node.children.is_empty() {
425                    for child in &node.children {
426                        self.scan_value_ctx(reader, child, out, ctx)?;
427                    }
428                } else if node.byte_size > 0 {
429                    reader.skip_bytes(node.byte_size as usize)?;
430                }
431            }
432        }
433
434        if node.is_aligned() {
435            reader.align_to(4)?;
436        }
437        Ok(())
438    }
439
440    fn scan_read_i32_like(&self, reader: &mut BinaryReader, node: &TypeTreeNode) -> Result<i32> {
441        let v = match node.type_name.as_str() {
442            "SInt32" | "int" => reader.read_i32()?,
443            "UInt32" | "unsigned int" | "Type*" => reader.read_u32()? as i32,
444            "SInt16" | "short" => reader.read_i16()? as i32,
445            "UInt16" | "unsigned short" => reader.read_u16()? as i32,
446            "SInt8" => reader.read_i8()? as i32,
447            "char" => reader.read_u8()? as i32,
448            "UInt8" => reader.read_u8()? as i32,
449            other => {
450                return Err(BinaryError::invalid_data(format!(
451                    "Unsupported fileID type: {}",
452                    other
453                )));
454            }
455        };
456        if node.is_aligned() {
457            reader.align_to(4)?;
458        }
459        Ok(v)
460    }
461
462    fn scan_read_i64_like(&self, reader: &mut BinaryReader, node: &TypeTreeNode) -> Result<i64> {
463        let v = match node.type_name.as_str() {
464            "SInt64" | "long long" => reader.read_i64()?,
465            "UInt64" | "unsigned long long" | "FileSize" => reader.read_u64()? as i64,
466            "SInt32" | "int" => reader.read_i32()? as i64,
467            "UInt32" | "unsigned int" | "Type*" => reader.read_u32()? as i64,
468            other => {
469                return Err(BinaryError::invalid_data(format!(
470                    "Unsupported pathID type: {}",
471                    other
472                )));
473            }
474        };
475        if node.is_aligned() {
476            reader.align_to(4)?;
477        }
478        Ok(v)
479    }
480
481    fn scan_array(
482        &self,
483        reader: &mut BinaryReader,
484        node: &TypeTreeNode,
485        out: &mut PPtrScanResult,
486        ctx: &mut TypeTreeScanContext<'_>,
487    ) -> Result<()> {
488        let array_node = node
489            .children
490            .iter()
491            .find(|child| child.type_name == "Array")
492            .ok_or_else(|| BinaryError::invalid_data("Array node not found in array type"))?;
493
494        let size_i32 = reader.read_i32()?;
495        if size_i32 < 0 {
496            return Err(BinaryError::invalid_data(format!(
497                "Negative array size: {}",
498                size_i32
499            )));
500        }
501        if let Some(size_node) = array_node.children.first()
502            && size_node.is_aligned()
503        {
504            reader.align_to(4)?;
505        }
506        let size = size_i32 as usize;
507        if size > Self::MAX_ARRAY_LEN {
508            return Err(BinaryError::invalid_data(format!(
509                "Array size too large: {}",
510                size
511            )));
512        }
513
514        let element_node = array_node
515            .children
516            .get(1)
517            .ok_or_else(|| BinaryError::invalid_data("Array element type not found"))?;
518
519        // Mirror the deserializer fast paths, but skipping bytes instead of allocating.
520        if element_node.children.is_empty() {
521            match element_node.type_name.as_str() {
522                "UInt8" | "char" | "SInt8" | "bool" => {
523                    reader.skip_bytes(size)?;
524                    if array_node.is_aligned() {
525                        reader.align_to(4)?;
526                    }
527                    return Ok(());
528                }
529                "SInt16" | "short" | "UInt16" | "unsigned short" => {
530                    reader.skip_bytes(size.checked_mul(2).ok_or_else(|| {
531                        BinaryError::invalid_data("Array byte length overflow")
532                    })?)?;
533                    if array_node.is_aligned() {
534                        reader.align_to(4)?;
535                    }
536                    return Ok(());
537                }
538                "SInt32" | "int" | "UInt32" | "unsigned int" | "Type*" | "float" => {
539                    reader.skip_bytes(size.checked_mul(4).ok_or_else(|| {
540                        BinaryError::invalid_data("Array byte length overflow")
541                    })?)?;
542                    if array_node.is_aligned() {
543                        reader.align_to(4)?;
544                    }
545                    return Ok(());
546                }
547                "SInt64" | "long long" | "UInt64" | "unsigned long long" | "FileSize"
548                | "double" => {
549                    reader.skip_bytes(size.checked_mul(8).ok_or_else(|| {
550                        BinaryError::invalid_data("Array byte length overflow")
551                    })?)?;
552                    if array_node.is_aligned() {
553                        reader.align_to(4)?;
554                    }
555                    return Ok(());
556                }
557                _ => {}
558            }
559        }
560
561        for _ in 0..size {
562            self.scan_value_ctx(reader, element_node, out, ctx)?;
563        }
564        if array_node.is_aligned() {
565            reader.align_to(4)?;
566        }
567        Ok(())
568    }
569
570    /// Parse value based on TypeTree node type
571    fn parse_value_by_type_ctx(
572        &self,
573        reader: &mut BinaryReader,
574        node: &TypeTreeNode,
575        ctx: &mut TypeTreeParseContext<'a>,
576    ) -> Result<UnityValue> {
577        let value = match node.type_name.as_str() {
578            // Signed integers
579            "SInt8" => {
580                let val = reader.read_i8()?;
581                UnityValue::Integer(val as i64)
582            }
583            "char" => {
584                let val = reader.read_u8()?;
585                UnityValue::Integer(val as i64)
586            }
587            "SInt16" | "short" => {
588                let val = reader.read_i16()?;
589                UnityValue::Integer(val as i64)
590            }
591            "SInt32" | "int" => {
592                let val = reader.read_i32()?;
593                UnityValue::Integer(val as i64)
594            }
595            "SInt64" | "long long" => {
596                let val = reader.read_i64()?;
597                UnityValue::Integer(val)
598            }
599
600            // Unsigned integers
601            "UInt8" => {
602                let val = reader.read_u8()?;
603                UnityValue::Integer(val as i64)
604            }
605            "UInt16" | "unsigned short" => {
606                let val = reader.read_u16()?;
607                UnityValue::Integer(val as i64)
608            }
609            "UInt32" | "unsigned int" | "Type*" => {
610                let val = reader.read_u32()?;
611                UnityValue::Integer(val as i64)
612            }
613            "UInt64" | "unsigned long long" | "FileSize" => {
614                let val = reader.read_u64()?;
615                UnityValue::Integer(val as i64)
616            }
617
618            // Floating point
619            "float" => {
620                let val = reader.read_f32()?;
621                UnityValue::Float(val as f64)
622            }
623            "double" => {
624                let val = reader.read_f64()?;
625                UnityValue::Float(val)
626            }
627
628            // Boolean
629            "bool" => {
630                let val = reader.read_u8()? != 0;
631                UnityValue::Bool(val)
632            }
633
634            // String
635            "string" => UnityValue::String(reader.read_aligned_string()?),
636
637            // Typeless raw bytes (UnityPy: read_byte_array)
638            "TypelessData" => {
639                let length = reader.read_i32()?;
640                if length < 0 {
641                    return Err(BinaryError::invalid_data(format!(
642                        "Negative TypelessData length: {}",
643                        length
644                    )));
645                }
646                let length: usize = length as usize;
647                if length > Self::MAX_TYPELESSDATA_LEN {
648                    return Err(BinaryError::invalid_data(format!(
649                        "TypelessData length {} exceeds limit {}",
650                        length,
651                        Self::MAX_TYPELESSDATA_LEN
652                    )));
653                }
654                let bytes = reader.read_bytes(length)?;
655                UnityValue::Bytes(bytes)
656            }
657
658            // Array types
659            _ if !node.children.is_empty()
660                && node.children.iter().any(|c| c.type_name == "Array") =>
661            {
662                self.parse_array(reader, node, ctx)?
663            }
664
665            // Pair type
666            "pair" if node.children.len() == 2 => {
667                let first = self.parse_value_by_type_ctx(reader, &node.children[0], ctx)?;
668                let second = self.parse_value_by_type_ctx(reader, &node.children[1], ctx)?;
669                UnityValue::Array(vec![first, second])
670            }
671
672            // Managed reference payload (`SerializeReference`): best-effort typed parsing via `ref_types`.
673            "ReferencedObject" => {
674                let mut nested = IndexMap::new();
675                for child in &node.children {
676                    if child.type_name == "ManagedReferencesRegistry" {
677                        // Consume bytes without allocating: we only need the file-level `ref_types`
678                        // list for resolving `ReferencedObjectData`.
679                        ctx.has_managed_registry = true;
680                        let mut dummy = PPtrScanResult::default();
681                        self.scan_value(reader, child, &mut dummy)?;
682                        continue;
683                    }
684
685                    if child.type_name == "ReferencedObjectData" {
686                        let resolved = ctx
687                            .ref_types
688                            .and_then(|r| resolve_ref_type_tree(&nested, r));
689                        if let Some(tree) = resolved
690                            && let Some(root) = tree.nodes.first()
691                        {
692                            let mut props = IndexMap::new();
693                            for field in &root.children {
694                                if field.name.is_empty() {
695                                    continue;
696                                }
697                                let v = self.parse_value_by_type_ctx(reader, field, ctx)?;
698                                props.insert(field.name.clone(), v);
699                            }
700                            if !child.name.is_empty() {
701                                nested.insert(child.name.clone(), UnityValue::Object(props));
702                            }
703                            continue;
704                        }
705
706                        // Explainable fallback: mark unresolved referenced type so callers can
707                        // distinguish “parsed placeholder layout” vs “typed payload”.
708                        if let Some((class, ns, asm)) = referenced_type_triplet(&nested) {
709                            nested.insert(
710                                "_referenced_type_unresolved".to_string(),
711                                UnityValue::Bool(true),
712                            );
713                            nested.insert(
714                                "_referenced_type_key".to_string(),
715                                UnityValue::String(format!("{}|{}|{}", class, ns, asm)),
716                            );
717                        }
718
719                        // Fallback: parse according to the placeholder node.
720                        let v = self.parse_value_by_type_ctx(reader, child, ctx)?;
721                        if !child.name.is_empty() {
722                            nested.insert(child.name.clone(), v);
723                        }
724                        continue;
725                    }
726
727                    if !child.name.is_empty() {
728                        let v = self.parse_value_by_type_ctx(reader, child, ctx)?;
729                        nested.insert(child.name.clone(), v);
730                    } else {
731                        let _ = self.parse_value_by_type_ctx(reader, child, ctx)?;
732                    }
733                }
734                UnityValue::Object(nested)
735            }
736
737            // Complex object types
738            "ManagedReferencesRegistry" => {
739                // `ManagedReferencesRegistry` can be extremely large. We keep parsing byte-accurate
740                // by consuming the bytes according to the TypeTree, but we do not allocate/return a
741                // full parsed value.
742                ctx.has_managed_registry = true;
743                let mut dummy = PPtrScanResult::default();
744                self.scan_value(reader, node, &mut dummy)?;
745                UnityValue::Null
746            }
747            _ => {
748                if !node.children.is_empty() {
749                    let mut nested_props = IndexMap::new();
750                    for child in &node.children {
751                        if child.type_name == "ManagedReferencesRegistry" {
752                            // UnityPy uses a `has_registry` guard to avoid parsing multiple registries.
753                            // We skip registry nodes to keep parsing fast while remaining byte-accurate.
754                            if ctx.has_managed_registry {
755                                let mut dummy = PPtrScanResult::default();
756                                self.scan_value(reader, child, &mut dummy)?;
757                                continue;
758                            }
759                            ctx.has_managed_registry = true;
760                            let mut dummy = PPtrScanResult::default();
761                            self.scan_value(reader, child, &mut dummy)?;
762                            continue;
763                        }
764                        if !child.name.is_empty() {
765                            let child_value = self.parse_value_by_type_ctx(reader, child, ctx)?;
766                            nested_props.insert(child.name.clone(), child_value);
767                        } else {
768                            let _ = self.parse_value_by_type_ctx(reader, child, ctx)?;
769                        }
770                    }
771                    UnityValue::Object(nested_props)
772                } else {
773                    // Unknown type with no children, skip bytes if size is known
774                    if node.byte_size > 0 {
775                        let _data = reader.read_bytes(node.byte_size as usize)?;
776                        UnityValue::Null
777                    } else {
778                        UnityValue::Null
779                    }
780                }
781            }
782        };
783
784        // Unity aligns the stream after reading certain fields (meta flag 0x4000).
785        // This is essential for correctly parsing TypeTree-based objects with packed booleans and
786        // nested structs (e.g. StreamedResource / StreamingInfo).
787        if node.is_aligned() {
788            reader.align_to(4)?;
789        }
790
791        Ok(value)
792    }
793
794    /// Parse array from TypeTree node
795    fn parse_array(
796        &self,
797        reader: &mut BinaryReader,
798        node: &TypeTreeNode,
799        ctx: &mut TypeTreeParseContext<'a>,
800    ) -> Result<UnityValue> {
801        // Find the Array child node
802        let array_node = node
803            .children
804            .iter()
805            .find(|child| child.type_name == "Array")
806            .ok_or_else(|| BinaryError::invalid_data("Array node not found in array type"))?;
807
808        // Read array size (first child is size)
809        let size_i32 = reader.read_i32()?;
810        if size_i32 < 0 {
811            return Err(BinaryError::invalid_data(format!(
812                "Negative array size: {}",
813                size_i32
814            )));
815        }
816        if let Some(size_node) = array_node.children.first()
817            && size_node.is_aligned()
818        {
819            reader.align_to(4)?;
820        }
821        let size = size_i32 as usize;
822        if size > Self::MAX_ARRAY_LEN {
823            return Err(BinaryError::invalid_data(format!(
824                "Array size too large: {}",
825                size
826            )));
827        }
828
829        let mut elements = Vec::with_capacity(size);
830
831        // Find the element type (usually the second child of Array node)
832        let element_node = array_node
833            .children
834            .get(1)
835            .ok_or_else(|| BinaryError::invalid_data("Array element type not found"))?;
836
837        // Fast-path: byte/bool arrays are extremely common and are a hot path for large objects.
838        if element_node.children.is_empty() {
839            let byte_order = reader.byte_order();
840            match element_node.type_name.as_str() {
841                "UInt8" | "char" => {
842                    let bytes = reader.read_bytes(size)?;
843                    if array_node.is_aligned() {
844                        reader.align_to(4)?;
845                    }
846                    return Ok(UnityValue::Bytes(bytes));
847                }
848                "SInt8" => {
849                    let bytes = reader.read_bytes(size)?;
850                    // Preserve signedness as encoded bytes; callers that care can reinterpret.
851                    if array_node.is_aligned() {
852                        reader.align_to(4)?;
853                    }
854                    return Ok(UnityValue::Bytes(bytes));
855                }
856                "bool" => {
857                    let bytes = reader.read_bytes(size)?;
858                    let out = UnityValue::Array(
859                        bytes
860                            .into_iter()
861                            .map(|b| UnityValue::Bool(b != 0))
862                            .collect(),
863                    );
864                    if array_node.is_aligned() {
865                        reader.align_to(4)?;
866                    }
867                    return Ok(out);
868                }
869                "SInt16" | "short" => {
870                    let byte_len = size
871                        .checked_mul(2)
872                        .ok_or_else(|| BinaryError::invalid_data("Array byte length overflow"))?;
873                    let bytes = reader.read_bytes(byte_len)?;
874                    let mut out = Vec::with_capacity(size);
875                    for chunk in bytes.chunks_exact(2) {
876                        let raw: [u8; 2] = chunk.try_into().expect("chunks_exact size");
877                        let v = match byte_order {
878                            ByteOrder::Big => i16::from_be_bytes(raw),
879                            ByteOrder::Little => i16::from_le_bytes(raw),
880                        };
881                        out.push(UnityValue::Integer(v as i64));
882                    }
883                    if array_node.is_aligned() {
884                        reader.align_to(4)?;
885                    }
886                    return Ok(UnityValue::Array(out));
887                }
888                "UInt16" | "unsigned short" => {
889                    let byte_len = size
890                        .checked_mul(2)
891                        .ok_or_else(|| BinaryError::invalid_data("Array byte length overflow"))?;
892                    let bytes = reader.read_bytes(byte_len)?;
893                    let mut out = Vec::with_capacity(size);
894                    for chunk in bytes.chunks_exact(2) {
895                        let raw: [u8; 2] = chunk.try_into().expect("chunks_exact size");
896                        let v = match byte_order {
897                            ByteOrder::Big => u16::from_be_bytes(raw),
898                            ByteOrder::Little => u16::from_le_bytes(raw),
899                        };
900                        out.push(UnityValue::Integer(v as i64));
901                    }
902                    if array_node.is_aligned() {
903                        reader.align_to(4)?;
904                    }
905                    return Ok(UnityValue::Array(out));
906                }
907                "SInt32" | "int" => {
908                    let byte_len = size
909                        .checked_mul(4)
910                        .ok_or_else(|| BinaryError::invalid_data("Array byte length overflow"))?;
911                    let bytes = reader.read_bytes(byte_len)?;
912                    let mut out = Vec::with_capacity(size);
913                    for chunk in bytes.chunks_exact(4) {
914                        let raw: [u8; 4] = chunk.try_into().expect("chunks_exact size");
915                        let v = match byte_order {
916                            ByteOrder::Big => i32::from_be_bytes(raw),
917                            ByteOrder::Little => i32::from_le_bytes(raw),
918                        };
919                        out.push(UnityValue::Integer(v as i64));
920                    }
921                    if array_node.is_aligned() {
922                        reader.align_to(4)?;
923                    }
924                    return Ok(UnityValue::Array(out));
925                }
926                "UInt32" | "unsigned int" | "Type*" => {
927                    let byte_len = size
928                        .checked_mul(4)
929                        .ok_or_else(|| BinaryError::invalid_data("Array byte length overflow"))?;
930                    let bytes = reader.read_bytes(byte_len)?;
931                    let mut out = Vec::with_capacity(size);
932                    for chunk in bytes.chunks_exact(4) {
933                        let raw: [u8; 4] = chunk.try_into().expect("chunks_exact size");
934                        let v = match byte_order {
935                            ByteOrder::Big => u32::from_be_bytes(raw),
936                            ByteOrder::Little => u32::from_le_bytes(raw),
937                        };
938                        out.push(UnityValue::Integer(v as i64));
939                    }
940                    if array_node.is_aligned() {
941                        reader.align_to(4)?;
942                    }
943                    return Ok(UnityValue::Array(out));
944                }
945                "SInt64" | "long long" => {
946                    let byte_len = size
947                        .checked_mul(8)
948                        .ok_or_else(|| BinaryError::invalid_data("Array byte length overflow"))?;
949                    let bytes = reader.read_bytes(byte_len)?;
950                    let mut out = Vec::with_capacity(size);
951                    for chunk in bytes.chunks_exact(8) {
952                        let raw: [u8; 8] = chunk.try_into().expect("chunks_exact size");
953                        let v = match byte_order {
954                            ByteOrder::Big => i64::from_be_bytes(raw),
955                            ByteOrder::Little => i64::from_le_bytes(raw),
956                        };
957                        out.push(UnityValue::Integer(v));
958                    }
959                    if array_node.is_aligned() {
960                        reader.align_to(4)?;
961                    }
962                    return Ok(UnityValue::Array(out));
963                }
964                "UInt64" | "unsigned long long" | "FileSize" => {
965                    let byte_len = size
966                        .checked_mul(8)
967                        .ok_or_else(|| BinaryError::invalid_data("Array byte length overflow"))?;
968                    let bytes = reader.read_bytes(byte_len)?;
969                    let mut out = Vec::with_capacity(size);
970                    for chunk in bytes.chunks_exact(8) {
971                        let raw: [u8; 8] = chunk.try_into().expect("chunks_exact size");
972                        let v = match byte_order {
973                            ByteOrder::Big => u64::from_be_bytes(raw),
974                            ByteOrder::Little => u64::from_le_bytes(raw),
975                        };
976                        out.push(UnityValue::Integer(v as i64));
977                    }
978                    if array_node.is_aligned() {
979                        reader.align_to(4)?;
980                    }
981                    return Ok(UnityValue::Array(out));
982                }
983                "float" => {
984                    let byte_len = size
985                        .checked_mul(4)
986                        .ok_or_else(|| BinaryError::invalid_data("Array byte length overflow"))?;
987                    let bytes = reader.read_bytes(byte_len)?;
988                    let mut out = Vec::with_capacity(size);
989                    for chunk in bytes.chunks_exact(4) {
990                        let raw: [u8; 4] = chunk.try_into().expect("chunks_exact size");
991                        let bits = match byte_order {
992                            ByteOrder::Big => u32::from_be_bytes(raw),
993                            ByteOrder::Little => u32::from_le_bytes(raw),
994                        };
995                        out.push(UnityValue::Float(f32::from_bits(bits) as f64));
996                    }
997                    if array_node.is_aligned() {
998                        reader.align_to(4)?;
999                    }
1000                    return Ok(UnityValue::Array(out));
1001                }
1002                "double" => {
1003                    let byte_len = size
1004                        .checked_mul(8)
1005                        .ok_or_else(|| BinaryError::invalid_data("Array byte length overflow"))?;
1006                    let bytes = reader.read_bytes(byte_len)?;
1007                    let mut out = Vec::with_capacity(size);
1008                    for chunk in bytes.chunks_exact(8) {
1009                        let raw: [u8; 8] = chunk.try_into().expect("chunks_exact size");
1010                        let bits = match byte_order {
1011                            ByteOrder::Big => u64::from_be_bytes(raw),
1012                            ByteOrder::Little => u64::from_le_bytes(raw),
1013                        };
1014                        out.push(UnityValue::Float(f64::from_bits(bits)));
1015                    }
1016                    if array_node.is_aligned() {
1017                        reader.align_to(4)?;
1018                    }
1019                    return Ok(UnityValue::Array(out));
1020                }
1021                _ => {}
1022            }
1023        }
1024
1025        for _ in 0..size {
1026            let element = self.parse_value_by_type_ctx(reader, element_node, ctx)?;
1027            elements.push(element);
1028        }
1029        if array_node.is_aligned() {
1030            reader.align_to(4)?;
1031        }
1032
1033        Ok(UnityValue::Array(elements))
1034    }
1035
1036    /// Serialize object data using the TypeTree structure
1037    pub fn serialize_object(&self, data: &IndexMap<String, UnityValue>) -> Result<Vec<u8>> {
1038        let mut buffer = Vec::new();
1039
1040        if let Some(root) = self.tree.nodes.first() {
1041            for child in &root.children {
1042                if child.name.is_empty() {
1043                    continue;
1044                }
1045                if let Some(value) = data.get(&child.name) {
1046                    self.serialize_value(&mut buffer, value, child)?;
1047                }
1048            }
1049        }
1050
1051        Ok(buffer)
1052    }
1053
1054    /// Serialize a single value based on TypeTree node type
1055    fn serialize_value(
1056        &self,
1057        buffer: &mut Vec<u8>,
1058        value: &UnityValue,
1059        node: &TypeTreeNode,
1060    ) -> Result<()> {
1061        match node.type_name.as_str() {
1062            "SInt8" | "char" => {
1063                if let UnityValue::Integer(val) = value {
1064                    buffer.push(*val as u8);
1065                    self.align_buffer(buffer, 4);
1066                }
1067            }
1068            "SInt16" | "short" => {
1069                if let UnityValue::Integer(val) = value {
1070                    buffer.extend_from_slice(&(*val as i16).to_le_bytes());
1071                    self.align_buffer(buffer, 4);
1072                }
1073            }
1074            "SInt32" | "int" => {
1075                if let UnityValue::Integer(val) = value {
1076                    buffer.extend_from_slice(&(*val as i32).to_le_bytes());
1077                }
1078            }
1079            "SInt64" | "long long" => {
1080                if let UnityValue::Integer(val) = value {
1081                    buffer.extend_from_slice(&val.to_le_bytes());
1082                }
1083            }
1084            "UInt8" => {
1085                if let UnityValue::Integer(val) = value {
1086                    buffer.push(*val as u8);
1087                    self.align_buffer(buffer, 4);
1088                }
1089            }
1090            "UInt16" | "unsigned short" => {
1091                if let UnityValue::Integer(val) = value {
1092                    buffer.extend_from_slice(&(*val as u16).to_le_bytes());
1093                    self.align_buffer(buffer, 4);
1094                }
1095            }
1096            "UInt32" | "unsigned int" | "Type*" => {
1097                if let UnityValue::Integer(val) = value {
1098                    buffer.extend_from_slice(&(*val as u32).to_le_bytes());
1099                }
1100            }
1101            "UInt64" | "unsigned long long" | "FileSize" => {
1102                if let UnityValue::Integer(val) = value {
1103                    buffer.extend_from_slice(&(*val as u64).to_le_bytes());
1104                }
1105            }
1106            "float" => {
1107                if let UnityValue::Float(val) = value {
1108                    buffer.extend_from_slice(&(*val as f32).to_le_bytes());
1109                }
1110            }
1111            "double" => {
1112                if let UnityValue::Float(val) = value {
1113                    buffer.extend_from_slice(&val.to_le_bytes());
1114                }
1115            }
1116            "bool" => {
1117                if let UnityValue::Bool(val) = value {
1118                    buffer.push(if *val { 1 } else { 0 });
1119                    self.align_buffer(buffer, 4);
1120                }
1121            }
1122            "string" => {
1123                if let UnityValue::String(val) = value {
1124                    // Write string length
1125                    buffer.extend_from_slice(&(val.len() as u32).to_le_bytes());
1126                    // Write string data
1127                    buffer.extend_from_slice(val.as_bytes());
1128                    self.align_buffer(buffer, 4);
1129                }
1130            }
1131            _ if node.is_array() => {
1132                if let UnityValue::Array(elements) = value {
1133                    // Write array size
1134                    buffer.extend_from_slice(&(elements.len() as i32).to_le_bytes());
1135
1136                    // Find element type
1137                    if let Some(array_node) = node.children.iter().find(|c| c.type_name == "Array")
1138                        && let Some(element_node) = array_node.children.get(1)
1139                    {
1140                        for element in elements {
1141                            self.serialize_value(buffer, element, element_node)?;
1142                        }
1143                    }
1144                }
1145            }
1146            _ => {
1147                // Complex object
1148                if let UnityValue::Object(obj) = value {
1149                    for child in &node.children {
1150                        if child.name.is_empty() {
1151                            continue;
1152                        }
1153                        if let Some(child_value) = obj.get(&child.name) {
1154                            self.serialize_value(buffer, child_value, child)?;
1155                        }
1156                    }
1157                }
1158            }
1159        }
1160
1161        Ok(())
1162    }
1163
1164    /// Align buffer to specified boundary
1165    fn align_buffer(&self, buffer: &mut Vec<u8>, alignment: usize) {
1166        let remainder = buffer.len() % alignment;
1167        if remainder != 0 {
1168            let padding = alignment - remainder;
1169            buffer.resize(buffer.len() + padding, 0);
1170        }
1171    }
1172
1173    /// Get the TypeTree being used
1174    pub fn tree(&self) -> &TypeTree {
1175        self.tree
1176    }
1177
1178    /// Estimate serialized size
1179    pub fn estimate_size(&self, data: &IndexMap<String, UnityValue>) -> usize {
1180        let mut size = 0;
1181
1182        if let Some(root) = self.tree.nodes.first() {
1183            for child in &root.children {
1184                if child.name.is_empty() {
1185                    continue;
1186                }
1187                if let Some(value) = data.get(&child.name) {
1188                    size += Self::estimate_value_size(value, child);
1189                }
1190            }
1191        }
1192
1193        size
1194    }
1195
1196    /// Estimate size of a single value
1197    fn estimate_value_size(value: &UnityValue, node: &TypeTreeNode) -> usize {
1198        match node.type_name.as_str() {
1199            "SInt8" | "UInt8" | "char" | "bool" => 4, // Including alignment
1200            "SInt16" | "UInt16" | "short" | "unsigned short" => 4, // Including alignment
1201            "SInt32" | "UInt32" | "int" | "unsigned int" | "float" | "Type*" => 4,
1202            "SInt64" | "UInt64" | "long long" | "unsigned long long" | "double" | "FileSize" => 8,
1203            "string" => {
1204                if let UnityValue::String(s) = value {
1205                    4 + s.len() + (4 - (s.len() % 4)) % 4 // Length + data + alignment
1206                } else {
1207                    4
1208                }
1209            }
1210            _ if node.is_array() => {
1211                if let UnityValue::Array(elements) = value {
1212                    let mut size = 4; // Array size
1213                    if let Some(array_node) = node.children.iter().find(|c| c.type_name == "Array")
1214                        && let Some(element_node) = array_node.children.get(1)
1215                    {
1216                        for element in elements {
1217                            size += Self::estimate_value_size(element, element_node);
1218                        }
1219                    }
1220                    size
1221                } else {
1222                    4
1223                }
1224            }
1225            _ => {
1226                // Complex object
1227                if let UnityValue::Object(obj) = value {
1228                    let mut size = 0;
1229                    for child in &node.children {
1230                        if child.name.is_empty() {
1231                            continue;
1232                        }
1233                        if let Some(child_value) = obj.get(&child.name) {
1234                            size += Self::estimate_value_size(child_value, child);
1235                        }
1236                    }
1237                    size
1238                } else {
1239                    node.byte_size.max(0) as usize
1240                }
1241            }
1242        }
1243    }
1244}
1245
1246fn resolve_ref_type_tree<'a>(
1247    referenced_object: &IndexMap<String, UnityValue>,
1248    ref_types: &'a [SerializedType],
1249) -> Option<&'a TypeTree> {
1250    let type_v = referenced_object.get("type")?;
1251    let type_obj = type_v.as_object()?;
1252
1253    fn get_str_ci(map: &IndexMap<String, UnityValue>, keys: &[&str]) -> Option<String> {
1254        for key in keys {
1255            for (k, v) in map.iter() {
1256                if k.eq_ignore_ascii_case(key)
1257                    && let UnityValue::String(s) = v
1258                {
1259                    return Some(s.clone());
1260                }
1261            }
1262        }
1263        None
1264    }
1265
1266    let class = get_str_ci(type_obj, &["class", "m_ClassName"])?;
1267    if class.is_empty() {
1268        return None;
1269    }
1270    let ns = get_str_ci(type_obj, &["ns", "m_NameSpace"]).unwrap_or_default();
1271    let asm = get_str_ci(type_obj, &["asm", "m_AssemblyName"]).unwrap_or_default();
1272
1273    ref_types.iter().find_map(|t| {
1274        if !t.class_name.is_empty()
1275            && t.class_name == class
1276            && t.namespace == ns
1277            && t.assembly_name == asm
1278            && !t.type_tree.is_empty()
1279        {
1280            Some(&t.type_tree)
1281        } else {
1282            None
1283        }
1284    })
1285}
1286
1287fn resolve_ref_type_tree_triplet<'a>(
1288    class: &str,
1289    ns: &str,
1290    asm: &str,
1291    ref_types: &'a [SerializedType],
1292) -> Option<&'a TypeTree> {
1293    if class.is_empty() {
1294        return None;
1295    }
1296    ref_types.iter().find_map(|t| {
1297        if !t.class_name.is_empty()
1298            && t.class_name == class
1299            && t.namespace == ns
1300            && t.assembly_name == asm
1301            && !t.type_tree.is_empty()
1302        {
1303            Some(&t.type_tree)
1304        } else {
1305            None
1306        }
1307    })
1308}
1309
1310fn referenced_type_triplet(
1311    referenced_object: &IndexMap<String, UnityValue>,
1312) -> Option<(String, String, String)> {
1313    let type_v = referenced_object.get("type")?;
1314    let type_obj = type_v.as_object()?;
1315    let class = type_obj.get("class")?.as_str()?.to_string();
1316    let ns = type_obj
1317        .get("ns")
1318        .and_then(|v| v.as_str())
1319        .unwrap_or_default()
1320        .to_string();
1321    let asm = type_obj
1322        .get("asm")
1323        .and_then(|v| v.as_str())
1324        .unwrap_or_default()
1325        .to_string();
1326    Some((class, ns, asm))
1327}
1328
1329#[cfg(test)]
1330mod tests {
1331    use super::*;
1332
1333    fn node(type_name: &str, name: &str) -> TypeTreeNode {
1334        let mut n = TypeTreeNode::new();
1335        n.type_name = type_name.to_string();
1336        n.name = name.to_string();
1337        n
1338    }
1339
1340    #[test]
1341    fn test_serializer_creation() {
1342        let tree = TypeTree::new();
1343        let serializer = TypeTreeSerializer::new(&tree);
1344        assert!(serializer.tree().is_empty());
1345    }
1346
1347    #[test]
1348    fn test_buffer_alignment() {
1349        let tree = TypeTree::new();
1350        let serializer = TypeTreeSerializer::new(&tree);
1351
1352        let mut buffer = vec![1, 2, 3]; // 3 bytes
1353        serializer.align_buffer(&mut buffer, 4);
1354        assert_eq!(buffer.len(), 4); // Should be padded to 4 bytes
1355        assert_eq!(buffer[3], 0); // Padding should be zero
1356    }
1357
1358    #[test]
1359    fn typetree_array_alignment_honors_array_node_flag() {
1360        let mut tree = TypeTree::new();
1361
1362        let mut root = node("TestObject", "TestObject");
1363
1364        let mut vec_node = node("vector", "m_Data");
1365        let mut array_node = node("Array", "Array");
1366        array_node.meta_flags = 0x4000; // kAlignBytes
1367        array_node.children = vec![node("int", "size"), node("UInt8", "data")];
1368        vec_node.children = vec![array_node];
1369
1370        let next_node = node("int", "m_Next");
1371
1372        root.children = vec![vec_node, next_node];
1373        tree.nodes.push(root);
1374
1375        let serializer = TypeTreeSerializer::new(&tree);
1376
1377        let mut bytes = Vec::new();
1378        bytes.extend_from_slice(&1i32.to_le_bytes()); // array size
1379        bytes.push(0xAA); // one byte element
1380        bytes.extend_from_slice(&[0u8; 3]); // align to 4
1381        bytes.extend_from_slice(&0x11223344i32.to_le_bytes()); // next int
1382
1383        let mut reader = BinaryReader::new(&bytes, ByteOrder::Little);
1384        let props = serializer.parse_object(&mut reader).unwrap();
1385        assert_eq!(
1386            props.get("m_Next").and_then(|v| v.as_i64()),
1387            Some(0x11223344)
1388        );
1389    }
1390
1391    #[test]
1392    fn typetree_scan_pptrs_honors_array_node_alignment() {
1393        let mut tree = TypeTree::new();
1394
1395        let mut root = node("TestObject", "TestObject");
1396
1397        let mut vec_node = node("vector", "m_Data");
1398        let mut array_node = node("Array", "Array");
1399        array_node.meta_flags = 0x4000; // kAlignBytes
1400        array_node.children = vec![node("int", "size"), node("UInt8", "data")];
1401        vec_node.children = vec![array_node];
1402
1403        let next_node = node("int", "m_Next");
1404        root.children = vec![vec_node, next_node];
1405        tree.nodes.push(root);
1406
1407        let serializer = TypeTreeSerializer::new(&tree);
1408
1409        let mut bytes = Vec::new();
1410        bytes.extend_from_slice(&1i32.to_le_bytes());
1411        bytes.push(0xAA);
1412        bytes.extend_from_slice(&[0u8; 3]);
1413        bytes.extend_from_slice(&0x11223344i32.to_le_bytes());
1414
1415        let mut reader = BinaryReader::new(&bytes, ByteOrder::Little);
1416        let _ = serializer.scan_pptrs(&mut reader).unwrap();
1417        assert_eq!(reader.remaining(), 0);
1418    }
1419
1420    #[test]
1421    fn typetree_char_reads_as_unsigned_byte() {
1422        let mut tree = TypeTree::new();
1423        let mut root = node("TestObject", "TestObject");
1424
1425        let mut char_node = node("char", "m_Char");
1426        char_node.meta_flags = 0x4000;
1427        let next_node = node("int", "m_Next");
1428
1429        root.children = vec![char_node, next_node];
1430        tree.nodes.push(root);
1431
1432        let serializer = TypeTreeSerializer::new(&tree);
1433
1434        let mut bytes = Vec::new();
1435        bytes.push(0xFF);
1436        bytes.extend_from_slice(&[0u8; 3]);
1437        bytes.extend_from_slice(&0x11223344i32.to_le_bytes());
1438
1439        let mut reader = BinaryReader::new(&bytes, ByteOrder::Little);
1440        let props = serializer.parse_object(&mut reader).unwrap();
1441        assert_eq!(props.get("m_Char").and_then(|v| v.as_i64()), Some(255));
1442        assert_eq!(
1443            props.get("m_Next").and_then(|v| v.as_i64()),
1444            Some(0x11223344)
1445        );
1446    }
1447
1448    #[test]
1449    fn typetree_byte_arrays_are_emitted_as_bytes() {
1450        let mut tree = TypeTree::new();
1451        let mut root = node("TestObject", "TestObject");
1452
1453        let mut vec_u8 = node("vector", "m_UInt8");
1454        let mut arr_u8 = node("Array", "Array");
1455        arr_u8.children = vec![node("int", "size"), node("UInt8", "data")];
1456        vec_u8.children = vec![arr_u8];
1457
1458        let mut vec_char = node("vector", "m_Char");
1459        let mut arr_char = node("Array", "Array");
1460        arr_char.children = vec![node("int", "size"), node("char", "data")];
1461        vec_char.children = vec![arr_char];
1462
1463        root.children = vec![vec_u8, vec_char];
1464        tree.nodes.push(root);
1465
1466        let serializer = TypeTreeSerializer::new(&tree);
1467
1468        let mut bytes = Vec::new();
1469        bytes.extend_from_slice(&2i32.to_le_bytes());
1470        bytes.extend_from_slice(&[0xAB, 0xCD]);
1471        bytes.extend_from_slice(&3i32.to_le_bytes());
1472        bytes.extend_from_slice(&[0x41, 0x80, 0xFF]);
1473
1474        let mut reader = BinaryReader::new(&bytes, ByteOrder::Little);
1475        let props = serializer.parse_object(&mut reader).unwrap();
1476
1477        assert_eq!(
1478            props.get("m_UInt8").and_then(|v| v.as_bytes()),
1479            Some(&[0xAB, 0xCD][..])
1480        );
1481        assert_eq!(
1482            props.get("m_Char").and_then(|v| v.as_bytes()),
1483            Some(&[0x41, 0x80, 0xFF][..])
1484        );
1485    }
1486}