Skip to main content

unity_formats/
listing.rs

1//! A player's objects listed by class and name from its object tables: the class of each object is in the table, its
2//! name is read from the object's first bytes ([`Player::field`]: the fields before `m_Name` are walked, nothing
3//! after it), so listing every mesh, texture and material of a player decodes none of them whole.
4
5use std::collections::BTreeMap;
6
7use crate::Result;
8use crate::player::{ObjRef, Player};
9
10/// One object of a listing.
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct ObjectEntry {
13    /// The object's file and path id.
14    pub object: ObjRef,
15    /// Unity's class id from the object table.
16    pub class_id: i32,
17    /// The class name from the type tree package; `None` for a class id the package lacks for this Unity version.
18    pub class: Option<String>,
19    /// Its `m_Name`; `None` for a class without one, or one whose fields before it do not decode (counted in
20    /// [`Listing::unnamed`]). An object stored with an empty name has `Some("")`.
21    pub name: Option<String>,
22    /// The object's size in the file, in bytes.
23    pub size: u32,
24}
25
26/// What [`list`] found.
27#[derive(Clone, Debug, Default, PartialEq, Eq)]
28pub struct Listing {
29    /// The objects of the classes asked for, by file name then path id.
30    pub entries: Vec<ObjectEntry>,
31    /// Objects left without a name, by why: `class has no m_Name`, or the decoding error.
32    pub unnamed: BTreeMap<String, u64>,
33}
34
35/// The objects of `player` whose class is one of `classes` (class names such as `Mesh`; every object when `None`),
36/// each with its name where its class has one. A class name the package does not know for a file's Unity version
37/// matches nothing there. Objects of a class id the package lacks are listed only when every object is asked for.
38pub fn list(player: &Player, classes: Option<&[&str]>) -> Result<Listing> {
39    let mut listing = Listing::default();
40    for (file, f) in &player.files {
41        for o in &f.objects {
42            let object = ObjRef {
43                file: file.clone(),
44                path_id: o.path_id,
45            };
46            let class = player.class_name(&object).map(str::to_owned);
47            if let Some(wanted) = classes
48                && !class.as_deref().is_some_and(|c| wanted.contains(&c))
49            {
50                continue;
51            }
52            let name = match class.as_deref() {
53                // no layout for the class: nothing to walk
54                None => {
55                    crate::bump(
56                        &mut listing.unnamed,
57                        "class unknown to the type tree package".into(),
58                    );
59                    None
60                }
61                Some(_) => match player.field(&object, "m_Name") {
62                    Ok(Some(v)) => v.as_str().map(str::to_owned),
63                    Ok(None) => {
64                        crate::bump(&mut listing.unnamed, "class has no m_Name".into());
65                        None
66                    }
67                    Err(e) => {
68                        crate::bump(&mut listing.unnamed, e.to_string());
69                        None
70                    }
71                },
72            };
73            listing.entries.push(ObjectEntry {
74                object,
75                class_id: o.class_id,
76                class,
77                name,
78                size: o.size,
79            });
80        }
81    }
82    Ok(listing)
83}