Skip to main content

il2cpp_bridge_rs/structs/core/members/
method.rs

1//! IL2CPP Method definition and functionality
2use crate::api::{self, cache, invoke_method};
3use crate::structs::collections::Il2cppArray;
4use crate::structs::core::{Class, Object, Type};
5use std::ffi::c_void;
6use std::ptr;
7
8/// Argument information for a method
9#[derive(Debug, Clone)]
10pub struct Arg {
11    /// Name of the argument
12    pub name: String,
13    /// Type information of the argument
14    pub type_info: Type,
15}
16
17/// Hydrated IL2CPP method metadata plus optional bound instance state.
18#[derive(Debug, Clone)]
19pub struct Method {
20    /// Pointer to the internal IL2CPP method structure
21    pub address: *mut c_void,
22    /// Metadata token for this method
23    pub token: u32,
24    /// Name of the method
25    pub name: String,
26    /// Class defining this method
27    pub class: Option<*const Class>,
28    /// Return type of the method
29    pub return_type: Type,
30    /// Flags associated with the method
31    pub flags: i32,
32    /// Whether the method is static
33    pub is_static: bool,
34    /// Pointer to the potentially compiled native function
35    pub function: *mut c_void,
36    /// Relative Virtual Address of the method (static offset from image base)
37    pub rva: u64,
38    /// Virtual Address of the method at runtime (includes ASLR slide)
39    pub va: u64,
40    /// List of arguments for this method
41    pub args: Vec<Arg>,
42    /// Whether the method is generic
43    pub is_generic: bool,
44    /// Whether the method is an inflated generic method
45    pub is_inflated: bool,
46    /// Whether the method is an instance method
47    pub is_instance: bool,
48    /// Number of parameters this method accepts
49    pub param_count: u8,
50    /// Pointer to the declaring type
51    pub declaring_type: *mut c_void,
52    /// Instance pointer for instance methods
53    pub instance: Option<*mut c_void>,
54}
55
56unsafe impl Send for Method {}
57unsafe impl Sync for Method {}
58
59impl std::fmt::Display for Method {
60    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61        write!(f, "{}", self.fmt_method())
62    }
63}
64
65/// Implementation of Method operations
66impl Method {
67    /// Generates a string representation of the method signature
68    ///
69    /// # Returns
70    /// * `String` - The formatted method signature
71    fn fmt_method(&self) -> String {
72        let access = self.get_attribute();
73        let flags = self.flags;
74
75        let is_abstract = (flags & api::METHOD_ATTRIBUTE_ABSTRACT) != 0;
76        let is_virtual = (flags & api::METHOD_ATTRIBUTE_VIRTUAL) != 0;
77        let is_final = (flags & api::METHOD_ATTRIBUTE_FINAL) != 0;
78
79        let qualifier = if self.is_static {
80            "static "
81        } else if is_abstract {
82            "abstract "
83        } else if is_virtual && !is_final {
84            "virtual "
85        } else {
86            ""
87        };
88
89        let args_str = self
90            .args
91            .iter()
92            .map(|arg| format!("{} {}", arg.type_info.cpp_name(), arg.name))
93            .collect::<Vec<_>>()
94            .join(", ");
95
96        let rva_comment = if self.rva == 0 {
97            "// RVA: -1 Offset: -1 VA: -1".to_string()
98        } else {
99            format!(
100                "// RVA: 0x{:X} Offset: 0x{:X} VA: 0x{:X}",
101                self.rva, self.rva, self.va
102            )
103        };
104
105        format!(
106            "{}\n{} {}{} {}({}) {{ }}",
107            rva_comment,
108            access,
109            qualifier,
110            self.return_type.cpp_name(),
111            self.name,
112            args_str,
113        )
114    }
115
116    /// Invokes the method with the provided parameter pointers.
117    ///
118    /// For static methods, `self.instance` is ignored. For instance methods,
119    /// the method must either have been returned from `Object::method(...)` or
120    /// have its `instance` pointer set manually.
121    ///
122    /// `T` must match the actual managed return shape:
123    ///
124    /// - use pointer-sized types for reference returns
125    /// - use the concrete value type for value-type returns
126    /// - use `()` for `void`
127    ///
128    /// Managed exceptions are converted into `Err(String)`.
129    pub unsafe fn call<T: Copy>(&self, params: &[*mut c_void]) -> Result<T, String> {
130        let instance = if self.is_static {
131            ptr::null_mut()
132        } else {
133            match self.instance {
134                Some(inst) => inst,
135                None => {
136                    return Err(format!(
137                        "Method '{}' is not static but no instance was provided. Use Object::method or set the instance manually.",
138                        self.name
139                    ));
140                }
141            }
142        };
143
144        if params.len() != self.args.len() {
145            return Err(format!(
146                "Argument count mismatch: expected {}, got {}",
147                self.args.len(),
148                params.len()
149            ));
150        }
151
152        let params_ptr = if params.is_empty() {
153            ptr::null()
154        } else {
155            params.as_ptr()
156        };
157
158        let result = invoke_method(self.address, instance, params_ptr)?;
159
160        if std::mem::size_of::<T>() == 0 {
161            return Ok(std::mem::zeroed());
162        }
163
164        let return_class = api::class_from_type(self.return_type.address);
165        if return_class.is_null() {
166            if std::mem::size_of::<T>() != std::mem::size_of::<*mut c_void>() {
167                return Err(format!(
168                    "Method '{}' returns an unmanaged pointer-sized value, but caller requested {} bytes",
169                    self.name,
170                    std::mem::size_of::<T>()
171                ));
172            }
173            return Ok(std::mem::transmute_copy(&result));
174        }
175
176        if api::class_is_valuetype(return_class) {
177            if result.is_null() {
178                return Err(format!(
179                    "Method '{}' returned null for value type '{}'",
180                    self.name, self.return_type.name
181                ));
182            }
183
184            let unboxed = api::object_unbox(result);
185            if unboxed.is_null() {
186                return Err(format!(
187                    "Method '{}' returned a non-unboxable value for '{}'",
188                    self.name, self.return_type.name
189                ));
190            }
191
192            let expected_size = if self.return_type.size > 0 {
193                self.return_type.size as usize
194            } else {
195                api::class_value_size(return_class, ptr::null_mut()) as usize
196            };
197
198            if std::mem::size_of::<T>() != expected_size {
199                return Err(format!(
200                    "Method '{}' returns '{}' ({} bytes), but caller requested {} bytes",
201                    self.name,
202                    self.return_type.name,
203                    expected_size,
204                    std::mem::size_of::<T>()
205                ));
206            }
207
208            let mut value = std::mem::MaybeUninit::<T>::uninit();
209            ptr::copy_nonoverlapping(
210                unboxed as *const u8,
211                value.as_mut_ptr() as *mut u8,
212                expected_size,
213            );
214            Ok(value.assume_init())
215        } else {
216            if std::mem::size_of::<T>() != std::mem::size_of::<*mut c_void>() {
217                return Err(format!(
218                    "Method '{}' returns a reference type, but caller requested {} bytes",
219                    self.name,
220                    std::mem::size_of::<T>()
221                ));
222            }
223            Ok(std::mem::transmute_copy(&result))
224        }
225    }
226
227    /// Gets the access modifier attribute string
228    pub fn get_attribute(&self) -> &'static str {
229        match self.flags & api::METHOD_ATTRIBUTE_MEMBER_ACCESS_MASK {
230            api::METHOD_ATTRIBUTE_PRIVATE => "private",
231            api::METHOD_ATTRIBUTE_PUBLIC => "public",
232            api::METHOD_ATTRIBUTE_FAMILY => "protected",
233            api::METHOD_ATTRIBUTE_ASSEM => "internal",
234            api::METHOD_ATTRIBUTE_FAM_AND_ASSEM => "private protected",
235            api::METHOD_ATTRIBUTE_FAM_OR_ASSEM => "protected internal",
236            _ => "private",
237        }
238    }
239
240    /// Gets the method object
241    ///
242    /// # Returns
243    /// * `*mut c_void` - The pointer to the MethodInfo object
244    pub fn get_object(&self) -> *mut c_void {
245        unsafe { api::method_get_object(self.address, ptr::null_mut()) }
246    }
247
248    /// Inflates a generic method with the specified type arguments to create a concrete generic method.
249    ///
250    /// # Arguments
251    /// * `classes` - The type arguments
252    ///
253    /// # Returns
254    /// * `Result<Method, String>` - The inflated method
255    pub fn inflate(&self, classes: &[&Class]) -> Result<Method, String> {
256        unsafe {
257            if !self.is_generic {
258                return Err(format!(
259                    "Method '{}' is not a generic method definition",
260                    self.name
261                ));
262            }
263
264            if classes.is_empty() {
265                return Err("No type arguments provided".to_string());
266            }
267
268            let mut type_args = Vec::with_capacity(classes.len());
269            for (i, cls) in classes.iter().enumerate() {
270                if cls.object.is_null() {
271                    return Err(format!(
272                        "Class '{}' (arg {}) has no Type object",
273                        cls.name, i
274                    ));
275                }
276                type_args.push(cls.object);
277            }
278
279            let method_object = self.get_object();
280            if method_object.is_null() {
281                return Err(format!(
282                    "Could not get MethodInfo object for method '{}'",
283                    self.name
284                ));
285            }
286
287            let method_obj = Object::from_ptr(method_object);
288
289            let corlib = cache::mscorlib();
290            let type_class = corlib
291                .class("System.Type")
292                .ok_or_else(|| "Could not find System.Type class".to_string())?;
293
294            let type_array = Il2cppArray::<*mut c_void>::new(&type_class, type_args.len());
295            if type_array.is_null() {
296                return Err("Could not create Type[] array".to_string());
297            }
298
299            let array_ref = &mut *type_array;
300            for (i, &type_arg) in type_args.iter().enumerate() {
301                array_ref.set(i, type_arg);
302            }
303
304            let make_generic_method = method_obj
305                .method(("MakeGenericMethod", ["System.Type[]"]))
306                .ok_or_else(|| "Could not find MakeGenericMethod(Type[]) method".to_string())?;
307
308            let inflated_method_obj =
309                make_generic_method.call::<*mut c_void>(&[type_array as *mut c_void])?;
310
311            if inflated_method_obj.is_null() {
312                return Err("MakeGenericMethod returned null".to_string());
313            }
314
315            let inflated_obj = Object::from_ptr(inflated_method_obj);
316
317            let mhandle_field = inflated_obj
318                .field("mhandle")
319                .unwrap()
320                .get_value::<*mut c_void>()
321                .map_err(|e| format!("Could not read mhandle field: {}", e))?;
322
323            if mhandle_field.is_null() {
324                return Err("mhandle field is null".to_string());
325            }
326
327            cache::method_from_ptr(mhandle_field).ok_or_else(|| {
328                "Could not convert inflated method pointer to Method struct".to_string()
329            })
330        }
331    }
332}