Skip to main content

il2cpp_bridge_rs/structs/core/metadata/
assembly.rs

1//! Assembly metadata wrapper.
2
3use crate::api::{self, cache, dump_assembly};
4use crate::logger;
5use crate::structs::core::Class;
6use std::ffi::c_void;
7
8use super::image::Image;
9
10/// Represents a hydrated IL2CPP assembly.
11///
12/// In normal usage, values of this type come from [`crate::api::cache`] helper
13/// functions such as [`crate::api::cache::csharp`].
14#[derive(Debug, Clone)]
15pub struct Assembly {
16    /// Wrapper for the image associated with this assembly
17    pub image: Image,
18    /// Pointer to the internal IL2CPP assembly structure
19    pub address: *mut c_void,
20    /// Filename of the assembly (e.g. "Assembly-CSharp.dll")
21    pub file: String,
22    /// Name of the assembly (e.g. "Assembly-CSharp")
23    pub name: String,
24    /// List of classes defined in this assembly
25    pub classes: Vec<Class>,
26}
27
28unsafe impl Send for Assembly {}
29unsafe impl Sync for Assembly {}
30
31impl std::fmt::Display for Assembly {
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        write!(f, "{}", self.fmt_assembly())
34    }
35}
36
37impl Assembly {
38    /// Generates a string representation of the assembly
39    ///
40    /// This includes the assembly name, file path, address, and a dump of all classes if any are loaded.
41    ///
42    /// # Returns
43    /// * `String` - The formatted string representation
44    fn fmt_assembly(&self) -> String {
45        let mut s = format!(
46            "// Assembly: {} ({}) @ {:?}\n",
47            self.name, self.file, self.address
48        );
49
50        if !self.classes.is_empty() {
51            for class in &self.classes {
52                s.push('\n');
53                s.push_str(&class.to_string());
54            }
55        }
56        s
57    }
58
59    /// Finds a class by name within the assembly.
60    ///
61    /// Accepts either a fully qualified type name such as
62    /// `UnityEngine.GameObject` or an unqualified name for the global
63    /// namespace.
64    pub fn class(&self, name: &str) -> Option<Class> {
65        let (namespace, class_name) = if let Some(last_dot) = name.rfind('.') {
66            (&name[..last_dot], &name[last_dot + 1..])
67        } else {
68            ("", name)
69        };
70
71        let namespace_cstr = std::ffi::CString::new(namespace).ok()?;
72        let name_cstr = std::ffi::CString::new(class_name).ok()?;
73
74        unsafe {
75            let class_ptr = api::class_from_name(
76                self.image.address,
77                namespace_cstr.as_ptr(),
78                name_cstr.as_ptr(),
79            );
80
81            if !class_ptr.is_null() {
82                return cache::class_from_ptr(class_ptr);
83            }
84        }
85
86        if let Some(class) = cache::CACHE.classes.get(name) {
87            return Some((**class).clone());
88        }
89
90        None
91    }
92
93    /// Dumps this assembly into a C#-like pseudo-code file and returns `self`.
94    ///
95    /// This is mainly intended for debugging and offline inspection.
96    pub fn dump(&self) -> &Self {
97        if dump_assembly(Some(&self.name)).is_none() {
98            logger::error("Failed to dump assembly");
99        }
100        self
101    }
102}