Skip to main content

il2cpp_bridge_rs/structs/core/hierarchy/
object.rs

1//! IL2CPP Object wrapper and operations
2use super::class::MethodSelector;
3use crate::api::{self, cache};
4use crate::structs::components::GameObject;
5use crate::structs::core::{Class, Field, Method, Property};
6use crate::structs::Il2cppString;
7use std::ffi::c_void;
8
9/// Low-level IL2CPP Object structure (matches C layout)
10#[repr(C)]
11#[derive(Debug, Clone, Copy)]
12pub struct Il2cppObject {
13    /// Pointer to the class definition
14    pub klass: *mut c_void,
15    /// Monitor for synchronization
16    pub monitor: *mut c_void,
17}
18
19/// Safe-ish wrapper around a managed IL2CPP object pointer.
20///
21/// Use this type when you already have a live managed object and want
22/// instance-bound access to methods, fields, or properties.
23#[repr(transparent)]
24#[derive(Debug, Clone, Copy)]
25pub struct Object {
26    /// Pointer to the internal IL2CPP object structure
27    pub ptr: *mut Il2cppObject,
28}
29
30impl Object {
31    /// Creates an Object from a raw pointer
32    ///
33    /// # Arguments
34    /// * `ptr` - The raw pointer to the IL2CPP object
35    pub unsafe fn from_ptr(ptr: *mut c_void) -> Self {
36        Self {
37            ptr: ptr as *mut Il2cppObject,
38        }
39    }
40
41    /// Returns the raw pointer to the object
42    ///
43    /// # Returns
44    /// * `*mut c_void` - The raw pointer
45    pub fn as_ptr(&self) -> *mut c_void {
46        self.ptr as *mut c_void
47    }
48
49    /// Returns an instance-bound field lookup.
50    ///
51    /// The returned [`Field`] carries this object's instance pointer so
52    /// [`Field::get_value`](crate::structs::Field::get_value) and
53    /// [`Field::set_value`](crate::structs::Field::set_value) can operate on it.
54    pub fn field(&self, name: &str) -> Option<Field> {
55        let class_ptr = unsafe { api::object_get_class(self.as_ptr()) };
56
57        if class_ptr.is_null() {
58            return None;
59        }
60
61        match cache::class_from_ptr(class_ptr) {
62            Some(class) => match class.field(name) {
63                Some(mut field) => {
64                    field.instance = Some(self.as_ptr());
65                    Some(field)
66                }
67                None => None,
68            },
69            None => None,
70        }
71    }
72
73    /// Returns an instance-bound method lookup.
74    ///
75    /// This is the preferred way to prepare instance method calls because the
76    /// returned [`Method`] already carries the correct `this` pointer.
77    pub fn method<S: MethodSelector>(&self, selector: S) -> Option<Method> {
78        unsafe {
79            let class_ptr = api::object_get_class(self.as_ptr());
80            if class_ptr.is_null() {
81                return None;
82            }
83            cache::class_from_ptr(class_ptr).and_then(|class| {
84                class.method(selector).map(|mut method| {
85                    method.instance = Some(self.as_ptr());
86                    method
87                })
88            })
89        }
90    }
91
92    /// Returns an instance-bound property lookup.
93    pub fn property(&self, name: &str) -> Option<Property> {
94        let class_ptr = unsafe { api::object_get_class(self.as_ptr()) };
95
96        if class_ptr.is_null() {
97            return None;
98        }
99
100        cache::class_from_ptr(class_ptr).and_then(|class| {
101            class
102                .property(name)
103                .map(|prop| prop.with_instance(self.as_ptr()))
104        })
105    }
106
107    /// Calls ToString on the object
108    ///
109    /// # Returns
110    /// * `String` - The string representation, or "null" if failed
111    pub fn il2cpp_to_string(&self) -> String {
112        unsafe {
113            if let Some(method) = self.method("ToString") {
114                if let Ok(result) = method.call::<*mut Il2cppString>(&[]) {
115                    if !result.is_null() {
116                        return (*result).to_string().unwrap_or_else(|| "null".to_string());
117                    }
118                }
119            }
120            "null".to_string()
121        }
122    }
123
124    /// Gets the GameObject associated with this object (if Is a Component)
125    ///
126    /// # Returns
127    /// * `Result<GameObject, String>` - The GameObject, or an error if null/not found
128    pub fn get_game_object(&self) -> Result<GameObject, String> {
129        unsafe {
130            let method = self
131                .method("get_gameObject")
132                .ok_or("Method 'get_gameObject' not found")?;
133            let result = method.call::<*mut c_void>(&[])?;
134
135            if result.is_null() {
136                return Err("GameObject is null".to_string());
137            }
138
139            Ok(GameObject::from_ptr(result))
140        }
141    }
142
143    /// Gets the size of the object header
144    ///
145    /// This is cached for performance.
146    ///
147    /// # Returns
148    /// * `usize` - The size of the header in bytes
149    pub fn get_header_size() -> usize {
150        use std::sync::OnceLock;
151        static HEADER_SIZE: OnceLock<usize> = OnceLock::new();
152
153        *HEADER_SIZE.get_or_init(|| unsafe {
154            let system_object_class = cache::mscorlib().class("System.Object");
155
156            if let Some(class) = system_object_class {
157                let size = api::class_instance_size(class.address);
158                if size > 0 {
159                    return size as usize;
160                }
161            }
162
163            std::mem::size_of::<Il2cppObject>()
164        })
165    }
166
167    /// Gets the class of this object
168    ///
169    /// # Returns
170    /// * `Option<Class>` - The class definition, or None if failed
171    pub fn get_class(&self) -> Option<Class> {
172        let class_ptr = unsafe { api::object_get_class(self.as_ptr()) };
173        cache::class_from_ptr(class_ptr)
174    }
175
176    /// Gets the virtual method implementation for this object
177    ///
178    /// # Arguments
179    /// * `method` - The method definition to resolve
180    ///
181    /// # Returns
182    /// * `*mut c_void` - Pointer to the implementation
183    pub fn get_virtual_method(&self, method: &Method) -> *mut c_void {
184        unsafe { api::object_get_virtual_method(self.as_ptr(), method.address) }
185    }
186
187    /// Initializes an exception object
188    ///
189    /// # Arguments
190    /// * `exc` - Pointer to the exception object
191    pub fn init_exception(&self, exc: &mut c_void) {
192        unsafe { api::runtime_object_init_exception(self.as_ptr(), exc) }
193    }
194
195    /// Gets the size of the object
196    ///
197    /// # Returns
198    /// * `u32` - Size in bytes
199    pub fn get_size(&self) -> u32 {
200        unsafe { api::object_get_size(self.as_ptr()) }
201    }
202
203    /// Unboxes a value type object
204    ///
205    /// # Returns
206    /// * `*mut c_void` - Pointer to the unboxed value
207    pub fn unbox(&self) -> *mut c_void {
208        unsafe { api::object_unbox(self.as_ptr()) }
209    }
210}