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}