Skip to main content

unity_formats/
serialized.rs

1//! The SerializedFile container: what `globalgamemanagers`, `*.assets` and `levelN` are.
2//!
3//! Layout (container versions 21 and 22): a big-endian header followed by metadata in the file's
4//! endianness: Unity version, target platform, whether type trees are embedded, the types (class id, stripped,
5//! script index, hashes), the object table (path id, data offset, size, type index), script references, external
6//! files, reference types and a user string. Objects' bytes follow at the data offset.
7//! Version 21 stores metadata size, file size and data offset as 32-bit header fields, followed by an
8//! endianness byte and three reserved bytes; metadata starts at byte 20. Version 22 repeats those sizes
9//! after the legacy fields as 32/64/64-bit numbers plus eight reserved bytes; metadata starts at byte 48.
10//! Object offsets are unsigned 32-bit in version 21 and signed 64-bit in version 22.
11//!
12//! Only versions verified against real data are read; others are refused. A file's types may carry their type trees
13//! (an AssetBundle's files do; a player build strips them and objects are decoded with a [`TypeTreePackage`] instead):
14//! they are read into [`SerializedType::tree`], their common-string offsets resolved from the package for the file's
15//! Unity version.
16
17use std::borrow::Cow;
18use std::collections::BTreeMap;
19use std::sync::Arc;
20
21use crate::bundle::Bundle;
22use crate::reader::Reader;
23use crate::tpk::{CommonStrings, TypeTreePackage};
24use crate::typetree::{Node, TypeTree};
25use crate::value::Value;
26use crate::version::UnityVersion;
27use crate::{Error, Result, invalid};
28
29/// Container versions read (verified on real files).
30pub const VERSIONS: std::ops::RangeInclusive<u32> = 21..=22;
31
32/// The MonoBehaviour class: its type tree covers the base fields; the script's own fields follow.
33pub const MONO_BEHAVIOUR: i32 = 114;
34
35/// One entry of a file's type table: the class the objects of this type are of, as the file declares it.
36#[derive(Clone, Debug, PartialEq)]
37pub struct SerializedType {
38    /// Unity's class id (1 GameObject, 4 Transform, 114 MonoBehaviour, ...).
39    pub class_id: i32,
40    /// Whether the build stripped the class's type tree.
41    pub stripped: bool,
42    /// Index into the script references (-1: none).
43    pub script_type_index: i16,
44    /// The script's 16-byte hash: present for a MonoBehaviour type, and for a reference type whose script index is
45    /// not -1.
46    pub script_id: Option<[u8; 16]>,
47    /// The 16-byte hash the file stores for the class's layout.
48    pub old_type_hash: [u8; 16],
49    /// The type tree the file stores for the type, when it stores trees (an AssetBundle's files do). A MonoBehaviour
50    /// type's tree holds its script's fields too.
51    pub tree: Option<Arc<TypeTree>>,
52    /// The types a type's tree refers to (the file's type indices), for a type of a file that stores trees.
53    pub dependencies: Vec<i32>,
54    /// The managed class a reference type stands for, for a reference type of a file that stores trees.
55    pub reference: Option<ReferenceClass>,
56}
57
58/// The managed class a reference type (`[SerializeReference]`) names.
59#[derive(Clone, Debug, PartialEq, Eq)]
60pub struct ReferenceClass {
61    /// The class name.
62    pub class: String,
63    /// Its namespace.
64    pub namespace: String,
65    /// The assembly it is in.
66    pub assembly: String,
67}
68
69/// One entry of the object table.
70#[derive(Clone, Copy, Debug, PartialEq, Eq)]
71pub struct ObjectInfo {
72    /// The object's id within its file; unique per file.
73    pub path_id: i64,
74    /// The object's bytes: offset in the file and size.
75    pub offset: u64,
76    /// The object's size in bytes.
77    pub size: u32,
78    /// Index into [`SerializedFile::types`].
79    pub type_index: u32,
80    /// The class id of the object's type.
81    pub class_id: i32,
82}
83
84/// Another file this one's pointers can name (file id = index + 1).
85#[derive(Clone, Debug, PartialEq)]
86pub struct External {
87    /// The external file's 16-byte GUID as stored.
88    pub guid: [u8; 16],
89    /// The file's kind as stored (`type`).
90    pub kind: i32,
91    /// The external file's path as stored; [`crate::player::Player::resolve`] names the file by its last path segment,
92    /// lower-cased.
93    pub path: String,
94}
95
96/// A decoded object: its value, and the bytes after what its type tree covers.
97#[derive(Clone, Debug, PartialEq)]
98pub struct Decoded {
99    /// The decoded object.
100    pub value: Value,
101    /// How many of the object's bytes lie after what the type tree covers.
102    pub rest: usize,
103}
104
105impl Decoded {
106    /// Whether the tree covered the object: every byte, or a MonoBehaviour's base fields (its script's fields follow,
107    /// and a stripped build has no type trees for them).
108    pub fn covered(&self, class_id: i32) -> bool {
109        self.rest == 0 || class_id == MONO_BEHAVIOUR
110    }
111}
112
113/// A parsed SerializedFile: its header fields, type table, object table, script references and external files, with
114/// the file's bytes kept so objects can be read from it.
115#[derive(Clone, Debug)]
116pub struct SerializedFile {
117    /// The container version (21 or 22: see [`VERSIONS`]).
118    pub version: u32,
119    /// The Unity version string the file was built with (`2019.4.20f1`).
120    pub unity_version: String,
121    /// [`SerializedFile::unity_version`], parsed.
122    pub unity: UnityVersion,
123    /// Unity's `BuildTarget` number the file was built for.
124    pub target_platform: i32,
125    /// Whether the metadata and the objects' bytes are big-endian.
126    pub big_endian: bool,
127    /// The type table.
128    pub types: Vec<SerializedType>,
129    /// The object table, in file order.
130    pub objects: Vec<ObjectInfo>,
131    /// Script references: (file index, path id).
132    pub script_types: Vec<(i32, i64)>,
133    /// The files this file's pointers can name (file id = index + 1).
134    pub externals: Vec<External>,
135    /// The table of types of managed references (`[SerializeReference]`).
136    pub ref_types: Vec<SerializedType>,
137    /// The user string at the end of the metadata.
138    pub user_information: String,
139    data: Backing,
140    by_path_id: BTreeMap<i64, usize>,
141}
142
143/// Where a file's bytes are: in memory, or in a node of a bundle that is read by range as objects are asked for.
144#[derive(Clone, Debug)]
145enum Backing {
146    Memory(Vec<u8>),
147    Node { bundle: Arc<Bundle>, node: usize },
148}
149
150fn hash(r: &mut Reader) -> Result<[u8; 16]> {
151    Ok(r.take(16)?.try_into().expect("16 bytes"))
152}
153
154fn count(r: &mut Reader, what: &str) -> Result<usize> {
155    let n = r.i32()?;
156    let n = usize::try_from(n).map_err(|_| invalid(format!("negative {what} count {n}")))?;
157    if n > r.remaining() {
158        return Err(invalid(format!("{what} count {n} larger than the metadata")));
159    }
160    Ok(n)
161}
162
163/// What reading a file's types needs besides its bytes: the common string table of its Unity version, and the
164/// strings its trees repeat, shared.
165struct TreeContext<'a> {
166    common: Option<CommonStrings<'a>>,
167    version: UnityVersion,
168    strings: BTreeMap<String, Arc<str>>,
169}
170
171impl TreeContext<'_> {
172    fn intern(&mut self, s: &str) -> Arc<str> {
173        if let Some(a) = self.strings.get(s) {
174            return a.clone();
175        }
176        let a: Arc<str> = Arc::from(s);
177        self.strings.insert(s.to_owned(), a.clone());
178        a
179    }
180
181    /// The string a node's offset names: in the tree's own buffer, or (top bit set) in the common string table.
182    fn string(&mut self, buffer: &[u8], offset: u32) -> Result<Arc<str>> {
183        if offset & COMMON_STRING != 0 {
184            let at = offset & !COMMON_STRING;
185            let common = self.common.ok_or_else(|| {
186                Error::Unsupported(format!(
187                    "a type tree names common string {at}: read the file with the type tree package of Unity {}",
188                    self.version
189                ))
190            })?;
191            let s = common.get(at).ok_or_else(|| {
192                invalid(format!(
193                    "a type tree names common string {at}, which the table of Unity {} lacks",
194                    self.version
195                ))
196            })?;
197            return Ok(self.intern(s));
198        }
199        let rest = usize::try_from(offset).ok().and_then(|o| buffer.get(o..));
200        let bytes = rest
201            .and_then(|r| r.iter().position(|&b| b == 0).and_then(|n| r.get(..n)))
202            .ok_or_else(|| {
203                invalid(format!(
204                    "a type tree names string {offset}, outside its {} byte buffer",
205                    buffer.len()
206                ))
207            })?;
208        let s = std::str::from_utf8(bytes)
209            .map_err(|_| invalid(format!("type tree string {offset} is not UTF-8")))?;
210        Ok(self.intern(s))
211    }
212}
213
214/// An offset with this bit set names a string of the engine's common string table instead of the tree's buffer.
215const COMMON_STRING: u32 = 0x8000_0000;
216
217/// The bytes of one embedded type tree node in the metadata (version 19 and later): version `i16`, level `u8`, type
218/// flags `u8`, type string offset `u32`, name string offset `u32`, byte size `i32`, index `i32`, meta flags `u32`,
219/// reference type hash `u64`.
220const TREE_NODE_BYTES: usize = 32;
221
222/// One type's embedded tree: a node count, the string buffer's size, the nodes (each [`TREE_NODE_BYTES`], with the
223/// nesting level), the buffer. The nodes are in depth-first order; a node with a level is the child of the nearest
224/// node before it with a lower one.
225fn read_tree(r: &mut Reader, context: &mut TreeContext) -> Result<Arc<TypeTree>> {
226    let nodes = r.i32()?;
227    let buffer_size = r.i32()?;
228    let nodes = usize::try_from(nodes)
229        .ok()
230        .filter(|&n| n > 0 && n <= r.remaining() / TREE_NODE_BYTES)
231        .ok_or_else(|| invalid(format!("a type tree of {nodes} nodes in {} bytes", r.remaining())))?;
232    let buffer_size = usize::try_from(buffer_size)
233        .map_err(|_| invalid(format!("a type tree string buffer of {buffer_size} bytes")))?;
234    let raw = r.take(nodes.saturating_mul(TREE_NODE_BYTES))?;
235    let buffer = r.take(buffer_size)?;
236    let mut nodes: Vec<Node> = Vec::with_capacity(nodes);
237    // the open ancestors: node indices by level
238    let mut open: Vec<usize> = Vec::new();
239    let mut n = Reader::new(raw, r.big_endian);
240    while n.remaining() > 0 {
241        let version = n.i16()?;
242        let level = usize::from(n.u8()?);
243        let _type_flags = n.u8()?;
244        let (type_offset, name_offset) = (n.u32()?, n.u32()?);
245        let byte_size = n.i32()?;
246        let _index = n.i32()?;
247        let meta_flag = n.u32()?;
248        let _reference_hash = n.u64()?;
249        if level > open.len() || (level == 0 && !nodes.is_empty()) {
250            return Err(invalid(format!(
251                "type tree node {} is at level {level}, below {} open levels",
252                nodes.len(),
253                open.len()
254            )));
255        }
256        open.truncate(level);
257        let at = nodes.len();
258        if let Some(&parent) = open.last() {
259            nodes
260                .get_mut(parent)
261                .ok_or_else(|| invalid("a type tree node went missing"))?
262                .children
263                .push(at);
264        }
265        open.push(at);
266        nodes.push(Node {
267            ty: context.string(buffer, type_offset)?,
268            name: context.string(buffer, name_offset)?,
269            byte_size,
270            version: i32::from(version),
271            meta_flag,
272            children: Vec::new(),
273        });
274    }
275    Ok(Arc::new(TypeTree::new(nodes)?))
276}
277
278/// A type entry. `trees` reads the tree and what follows it when the file stores trees.
279fn serialized_type(
280    r: &mut Reader,
281    reference: bool,
282    trees: Option<&mut TreeContext>,
283) -> Result<SerializedType> {
284    let class_id = r.i32()?;
285    let stripped = r.bool()?;
286    let script_type_index = r.i16()?;
287    let script_id = if (reference && script_type_index >= 0) || (!reference && class_id == MONO_BEHAVIOUR) {
288        Some(hash(r)?)
289    } else {
290        None
291    };
292    let old_type_hash = hash(r)?;
293    let (mut tree, mut dependencies, mut reference_class) = (None, Vec::new(), None);
294    if let Some(context) = trees {
295        tree = Some(read_tree(r, context)?);
296        if reference {
297            reference_class = Some(ReferenceClass {
298                class: r.cstr()?,
299                namespace: r.cstr()?,
300                assembly: r.cstr()?,
301            });
302        } else {
303            let n = count(r, "type dependency")?;
304            dependencies = (0..n).map(|_| r.i32()).collect::<Result<Vec<_>>>()?;
305        }
306    }
307    Ok(SerializedType {
308        class_id,
309        stripped,
310        script_type_index,
311        script_id,
312        old_type_hash,
313        tree,
314        dependencies,
315        reference: reference_class,
316    })
317}
318
319/// A SerializedFile's header, version 21 or 22.
320struct Header {
321    version: u32,
322    big_endian: bool,
323    /// Where the metadata starts (the header's length).
324    metadata_start: usize,
325    metadata_size: usize,
326    file_size: u64,
327    data_offset: u64,
328    /// The bytes from the start of the file to the end of the metadata.
329    head_len: u64,
330}
331
332impl Header {
333    /// The header at the start of `head`.
334    fn of(head: &[u8]) -> Result<Header> {
335        let mut h = Reader::new(head, true);
336        let legacy_metadata = h.u32()?;
337        let legacy_size = h.u32()?;
338        let version = h.u32()?;
339        let legacy_offset = h.u32()?;
340        if !VERSIONS.contains(&version) {
341            return Err(Error::Unsupported(format!(
342                "SerializedFile version {version} (read: {}..={})",
343                VERSIONS.start(),
344                VERSIONS.end()
345            )));
346        }
347        let big_endian = h.bool()?;
348        h.take(3)?;
349        let (metadata_size, file_size, data_offset) = if version == 22 {
350            let metadata_size = h.u32()?;
351            let file_size = h.u64()?;
352            let data_offset = h.u64()?;
353            let _reserved = h.u64()?;
354            (metadata_size, file_size, data_offset)
355        } else {
356            (legacy_metadata, u64::from(legacy_size), u64::from(legacy_offset))
357        };
358        let metadata_size =
359            usize::try_from(metadata_size).map_err(|_| invalid("metadata size overflows its byte range"))?;
360        let head_len =
361            u64::try_from(h.pos.saturating_add(metadata_size)).map_err(|_| invalid("metadata size"))?;
362        Ok(Header {
363            version,
364            big_endian,
365            metadata_start: h.pos,
366            metadata_size,
367            file_size,
368            data_offset,
369            head_len,
370        })
371    }
372}
373
374impl SerializedFile {
375    /// Whether `bytes` start like a SerializedFile of a supported version (to tell them from other files).
376    pub fn looks_like(bytes: &[u8]) -> bool {
377        let be = |at: usize| -> Option<u32> {
378            Some(u32::from_be_bytes(
379                bytes.get(at..at.checked_add(4)?)?.try_into().ok()?,
380            ))
381        };
382        let Ok(len) = u64::try_from(bytes.len()) else {
383            return false;
384        };
385        let check = || -> Option<bool> {
386            Some(match be(8)? {
387                21 => {
388                    let end = 20u64.checked_add(u64::from(be(0)?))?;
389                    let (size, offset) = (u64::from(be(4)?), u64::from(be(12)?));
390                    be(0)? > 0 && size == len && end <= offset && offset <= size
391                }
392                22 => len >= 48 && be(0)? == 0,
393                _ => false,
394            })
395        };
396        check().unwrap_or(false)
397    }
398
399    /// The container version of `bytes` when they are a SerializedFile of any version, supported or not: a supported
400    /// version by its field alone (a damaged file of it is for [`SerializedFile::parse`] to refuse), another version
401    /// only when the header's own size fields agree with `bytes.len()`, so that other files (streamed resources,
402    /// configs) are not taken for one. `None` for bytes that are no SerializedFile.
403    pub fn container_version(bytes: &[u8]) -> Option<u32> {
404        let be = |at: usize| -> Option<u32> {
405            Some(u32::from_be_bytes(
406                bytes.get(at..at.checked_add(4)?)?.try_into().ok()?,
407            ))
408        };
409        let len = u64::try_from(bytes.len()).ok()?;
410        let version = be(8)?;
411        if VERSIONS.contains(&version) {
412            return Some(version);
413        }
414        let consistent = if version < *VERSIONS.start() {
415            // the legacy header: metadata size, file size, version, data offset
416            version > 0 && u64::from(be(4)?) == len && u64::from(be(12)?) <= len
417        } else {
418            // the header of 22 and later: the legacy fields are zero, the file size follows the metadata size
419            be(0)? == 0
420                && be(4)? == 0
421                && be(12)? == 0
422                && u64::from_be_bytes(bytes.get(24..32)?.try_into().ok()?) == len
423        };
424        consistent.then_some(version)
425    }
426
427    /// Parse the SerializedFile `data`. The header's sizes must match `data`, the object table must lie inside it and
428    /// the metadata must end exactly where the header says; a version outside [`VERSIONS`], a duplicate path id and
429    /// every length that runs past the bytes are refused with an [`Error`] that names what was found. A file that
430    /// stores its type trees is read whole, but a string of the engine's common string table that a tree names is
431    /// refused as [`Error::Unsupported`]: [`SerializedFile::parse_with`] reads such files.
432    pub fn parse(data: Vec<u8>) -> Result<Self> {
433        Self::parse_with(data, None)
434    }
435
436    /// As [`SerializedFile::parse`], reading the common strings of embedded type trees from `package` (the table of the
437    /// file's own Unity version).
438    pub fn parse_with(data: Vec<u8>, package: Option<&TypeTreePackage>) -> Result<Self> {
439        let size = u64::try_from(data.len()).map_err(|_| invalid("file past 2^64 bytes"))?;
440        let mut file = Self::parse_head(&data, size, package)?;
441        file.data = Backing::Memory(data);
442        Ok(file)
443    }
444
445    /// Read the file `path` of `bundle` without reading its objects: the header and metadata are read, each object's
446    /// bytes later, by range, when [`SerializedFile::bytes`] asks for them. `path` must name a serialized node.
447    pub fn from_bundle(bundle: &Arc<Bundle>, path: &str, package: Option<&TypeTreePackage>) -> Result<Self> {
448        let (index, node) = bundle
449            .nodes()
450            .iter()
451            .enumerate()
452            .find(|(_, n)| n.path == path)
453            .ok_or_else(|| invalid(format!("{}: no node {path:?}", bundle.path().display())))?;
454        let named = |e: Error| match e {
455            Error::Invalid(m) => invalid(format!("{}: {path}: {m}", bundle.path().display())),
456            Error::Unsupported(m) => Error::Unsupported(format!("{}: {path}: {m}", bundle.path().display())),
457            other => other,
458        };
459        let first = bundle
460            .read(
461                node,
462                0,
463                usize::try_from(node.size.min(Self::HEADER_BYTES)).unwrap_or(0),
464            )
465            .map_err(named)?;
466        let head_len = Header::of(&first).map_err(named)?.head_len;
467        let head = bundle
468            .read(
469                node,
470                0,
471                usize::try_from(head_len.min(node.size)).map_err(|_| invalid("metadata size"))?,
472            )
473            .map_err(named)?;
474        let mut file = Self::parse_head(&head, node.size, package).map_err(named)?;
475        file.data = Backing::Node {
476            bundle: bundle.clone(),
477            node: index,
478        };
479        Ok(file)
480    }
481
482    /// The bytes that hold every header version read.
483    const HEADER_BYTES: u64 = 48;
484
485    /// Parse the header and metadata at the start of a file of `file_size` bytes, `head` being at least those; the
486    /// returned file has no bytes of its own yet.
487    fn parse_head(head: &[u8], file_size: u64, package: Option<&TypeTreePackage>) -> Result<Self> {
488        let Header {
489            version,
490            big_endian,
491            metadata_start,
492            metadata_size,
493            file_size: stated_size,
494            data_offset,
495            ..
496        } = Header::of(head)?;
497        if stated_size != file_size {
498            return Err(invalid(format!(
499                "header file size {stated_size}, file has {file_size} bytes"
500            )));
501        }
502        let meta_end = metadata_start
503            .checked_add(metadata_size)
504            .ok_or_else(|| invalid("metadata size overflows its byte range"))?;
505        if meta_end as u64 > data_offset || data_offset > file_size {
506            return Err(invalid(format!(
507                "metadata ends at {meta_end}, data at {data_offset}, file size {file_size}"
508            )));
509        }
510        let metadata = head.get(..meta_end).ok_or_else(|| {
511            invalid(format!(
512                "metadata ends at {meta_end}, past the {} bytes read",
513                head.len()
514            ))
515        })?;
516        let mut r = Reader::new(metadata, big_endian);
517        r.pos = metadata_start;
518        let unity_version = r.cstr()?;
519        let unity = UnityVersion::parse(&unity_version)?;
520        let target_platform = r.i32()?;
521        let has_trees = r.bool()?;
522        let mut context = TreeContext {
523            common: package.and_then(|p| p.common_strings(unity)),
524            version: unity,
525            strings: BTreeMap::new(),
526        };
527        let types = (0..count(&mut r, "type")?)
528            .map(|_| serialized_type(&mut r, false, has_trees.then_some(&mut context)))
529            .collect::<Result<Vec<_>>>()?;
530        let mut objects = Vec::new();
531        for _ in 0..count(&mut r, "object")? {
532            r.align(4)?;
533            let path_id = r.i64()?;
534            let start = if version == 21 {
535                i64::from(r.u32()?)
536            } else {
537                r.i64()?
538            };
539            let size = r.u32()?;
540            let type_index = r.u32()?;
541            let ty = types.get(type_index as usize).ok_or_else(|| {
542                invalid(format!(
543                    "object {path_id}: type index {type_index} of {}",
544                    types.len()
545                ))
546            })?;
547            let offset = u64::try_from(start)
548                .ok()
549                .and_then(|s| s.checked_add(data_offset))
550                .filter(|&o| o.checked_add(u64::from(size)).is_some_and(|end| end <= file_size))
551                .ok_or_else(|| invalid(format!("object {path_id}: bytes {start}+{size} outside the file")))?;
552            objects.push(ObjectInfo {
553                path_id,
554                offset,
555                size,
556                type_index,
557                class_id: ty.class_id,
558            });
559        }
560        let mut script_types = Vec::new();
561        for _ in 0..count(&mut r, "script")? {
562            let file = r.i32()?;
563            r.align(4)?;
564            script_types.push((file, r.i64()?));
565        }
566        let mut externals = Vec::new();
567        for _ in 0..count(&mut r, "external")? {
568            let _empty = r.cstr()?;
569            let guid = hash(&mut r)?;
570            let kind = r.i32()?;
571            let path = r.cstr()?;
572            externals.push(External { guid, kind, path });
573        }
574        let ref_types = (0..count(&mut r, "reference type")?)
575            .map(|_| serialized_type(&mut r, true, has_trees.then_some(&mut context)))
576            .collect::<Result<Vec<_>>>()?;
577        let user_information = r.cstr()?;
578        if r.pos != meta_end {
579            return Err(invalid(format!(
580                "metadata read to {}, header says it ends at {meta_end}",
581                r.pos
582            )));
583        }
584        let mut by_path_id = BTreeMap::new();
585        for (i, o) in objects.iter().enumerate() {
586            if by_path_id.insert(o.path_id, i).is_some() {
587                return Err(invalid(format!("path id {} twice", o.path_id)));
588            }
589        }
590        Ok(SerializedFile {
591            version,
592            unity_version,
593            unity,
594            target_platform,
595            big_endian,
596            types,
597            objects,
598            script_types,
599            externals,
600            ref_types,
601            user_information,
602            data: Backing::Memory(Vec::new()),
603            by_path_id,
604        })
605    }
606
607    /// The object with this path id, if the file has one.
608    pub fn object(&self, path_id: i64) -> Option<&ObjectInfo> {
609        self.by_path_id.get(&path_id).and_then(|&i| self.objects.get(i))
610    }
611
612    /// The type tree the file stores for the type of `o`; `None` for a file that stores none (a player's).
613    pub fn tree_of(&self, o: &ObjectInfo) -> Option<&Arc<TypeTree>> {
614        self.types.get(usize::try_from(o.type_index).ok()?)?.tree.as_ref()
615    }
616
617    /// The bytes of an object of this file. `o` should come from this file's own [`SerializedFile::objects`]: an
618    /// [`ObjectInfo`] of another file that names bytes outside this one is an error naming the object and its range.
619    /// A file read from memory lends its bytes; one read from a bundle ([`SerializedFile::from_bundle`]) reads them
620    /// from the bundle, decompressing the blocks they lie in.
621    pub fn bytes(&self, o: &ObjectInfo) -> Result<Cow<'_, [u8]>> {
622        let outside = |len: u64| {
623            invalid(format!(
624                "object {}: bytes {}+{} are outside the file's {len} bytes",
625                o.path_id, o.offset, o.size,
626            ))
627        };
628        match &self.data {
629            Backing::Memory(data) => {
630                let len = u64::try_from(data.len()).unwrap_or(u64::MAX);
631                let start = usize::try_from(o.offset).map_err(|_| outside(len))?;
632                let end = start
633                    .checked_add(usize::try_from(o.size).map_err(|_| outside(len))?)
634                    .ok_or_else(|| outside(len))?;
635                data.get(start..end)
636                    .map(Cow::Borrowed)
637                    .ok_or_else(|| outside(len))
638            }
639            Backing::Node { bundle, node } => {
640                let node = bundle
641                    .nodes()
642                    .get(*node)
643                    .ok_or_else(|| invalid("the file's bundle node is gone"))?;
644                if o.offset
645                    .checked_add(u64::from(o.size))
646                    .is_none_or(|end| end > node.size)
647                {
648                    return Err(outside(node.size));
649                }
650                let len = usize::try_from(o.size).map_err(|_| outside(node.size))?;
651                bundle.read(node, o.offset, len).map(Cow::Owned)
652            }
653        }
654    }
655
656    /// The bundle and node this file was read from ([`SerializedFile::from_bundle`]); `None` for a file read from
657    /// memory.
658    pub fn bundle(&self) -> Option<(&Arc<Bundle>, &crate::bundle::BundleNode)> {
659        match &self.data {
660            Backing::Memory(_) => None,
661            Backing::Node { bundle, node } => bundle.nodes().get(*node).map(|n| (bundle, n)),
662        }
663    }
664
665    /// Decode an object's bytes from its start with `tree` (its class's type tree), reporting the bytes after what the
666    /// tree covers in [`Decoded::rest`] (see [`Decoded::covered`]).
667    pub fn decode_with(&self, o: &ObjectInfo, tree: &TypeTree) -> Result<Decoded> {
668        let bytes = self.bytes(o)?;
669        let (value, used) = tree
670            .decode(&bytes, self.big_endian)
671            .map_err(|e| invalid(format!("object {} (class {}): {e}", o.path_id, o.class_id)))?;
672        let rest = bytes.len().checked_sub(used).ok_or_else(|| {
673            invalid(format!(
674                "object {} (class {}): decoded {used} of {} bytes",
675                o.path_id,
676                o.class_id,
677                bytes.len()
678            ))
679        })?;
680        Ok(Decoded { value, rest })
681    }
682
683    /// Decode an object with its class's type tree from `package`. The tree must cover the object's bytes, but for a
684    /// MonoBehaviour, whose script fields follow its base fields.
685    pub fn decode(&self, o: &ObjectInfo, package: &TypeTreePackage) -> Result<Decoded> {
686        let d = match self.tree_of(o) {
687            Some(tree) => self.decode_with(o, tree)?,
688            None => self.decode_with(o, &package.type_tree(o.class_id, self.unity)?)?,
689        };
690        if !d.covered(o.class_id) {
691            return Err(invalid(format!(
692                "object {} (class {}): {} bytes after its type tree",
693                o.path_id, o.class_id, d.rest
694            )));
695        }
696        Ok(d)
697    }
698}