Skip to main content

il2cpp_bridge_rs/api/core/
cache.rs

1//! Global cache for hydrated IL2CPP metadata.
2//!
3//! The cache is populated during [`crate::init`] and is the normal starting
4//! point for metadata-driven workflows. Most users should use the helper
5//! functions in this module rather than traversing IL2CPP metadata manually.
6use super::api;
7#[cfg(dev_release)]
8use crate::logger;
9use dashmap::DashMap;
10use once_cell::sync::Lazy;
11use std::ffi::{c_void, CStr};
12use std::ptr;
13use std::sync::atomic::{AtomicBool, Ordering};
14use std::sync::Arc;
15
16static CLASSES_HYDRATED: AtomicBool = AtomicBool::new(false);
17
18use crate::memory::image;
19use crate::structs::{Arg, Assembly, Class, Field, Image, Method, Property, Type};
20
21/// Caches IL2CPP assemblies, classes, and methods
22pub struct Il2CppCache {
23    /// Helper map of assembly names to Assembly structs (Arc for cheap cloning)
24    pub assemblies: DashMap<String, Arc<Assembly>>,
25    /// Helper map of class names to Class structs (boxed)
26    pub classes: DashMap<String, Box<Class>>,
27    /// Helper map of method keys to Method structs
28    pub methods: DashMap<String, Method>,
29}
30
31unsafe impl Send for Il2CppCache {}
32unsafe impl Sync for Il2CppCache {}
33
34/// Global cache instance
35pub static CACHE: Lazy<Il2CppCache> = Lazy::new(|| Il2CppCache {
36    assemblies: DashMap::new(),
37    classes: DashMap::new(),
38    methods: DashMap::new(),
39});
40
41/// Retrieves an assembly by name.
42///
43/// The name may be provided with or without the `.dll` suffix.
44///
45/// Returns a cheap [`Arc`] clone if the assembly exists in the hydrated cache.
46pub fn assembly(name: &str) -> Option<Arc<Assembly>> {
47    if let Some(asm) = CACHE.assemblies.get(name) {
48        return Some(Arc::clone(&asm));
49    }
50
51    if !name.ends_with(".dll") {
52        let name_with_ext = format!("{}.dll", name);
53        if let Some(asm) = CACHE.assemblies.get(&name_with_ext) {
54            return Some(Arc::clone(&asm));
55        }
56    }
57
58    None
59}
60
61/// Returns the cached `mscorlib.dll` assembly.
62///
63/// # Panics
64///
65/// Panics if the cache is not ready or if `mscorlib.dll` is unavailable in the
66/// current runtime.
67pub fn mscorlib() -> Arc<Assembly> {
68    const KEY: &str = "mscorlib.dll";
69
70    if let Some(asm) = CACHE.assemblies.get(KEY) {
71        return Arc::clone(&asm);
72    }
73
74    assembly(KEY).expect("mscorlib not found")
75}
76
77/// Returns the cached `Assembly-CSharp.dll` assembly.
78///
79/// # Panics
80///
81/// Panics if the cache is not ready or if `Assembly-CSharp.dll` is unavailable
82/// in the current runtime.
83pub fn csharp() -> Arc<Assembly> {
84    const KEY: &str = "Assembly-CSharp.dll";
85
86    if let Some(asm) = CACHE.assemblies.get(KEY) {
87        return Arc::clone(&asm);
88    }
89
90    assembly(KEY).expect("Assembly-CSharp not found")
91}
92
93/// Returns the cached `UnityEngine.CoreModule.dll` assembly.
94///
95/// # Panics
96///
97/// Panics if the cache is not ready or if `UnityEngine.CoreModule.dll` is
98/// unavailable in the current runtime.
99pub fn coremodule() -> Arc<Assembly> {
100    const KEY: &str = "UnityEngine.CoreModule.dll";
101
102    if let Some(asm) = CACHE.assemblies.get(KEY) {
103        return Arc::clone(&asm);
104    }
105
106    assembly(KEY).expect("UnityEngine.CoreModule not found")
107}
108
109/// Hydrates a [`Class`] from a raw `Il2CppClass` pointer.
110///
111/// This is mainly useful when lower-level APIs hand you raw class pointers and
112/// you want to re-enter the safe-ish metadata layer.
113pub fn class_from_ptr(ptr: *mut c_void) -> Option<Class> {
114    if ptr.is_null() {
115        return None;
116    }
117    unsafe { hydrate_class(ptr).ok() }
118}
119
120/// Initializes the cache by loading all assemblies and resetting prior state.
121///
122/// Class hydration is deferred and performed via [`ensure_hydrated`].
123///
124/// This is primarily used internally by [`crate::init`].
125pub fn init() -> bool {
126    CACHE.assemblies.clear();
127    CACHE.classes.clear();
128    CACHE.methods.clear();
129    CLASSES_HYDRATED.store(false, Ordering::SeqCst);
130
131    unsafe {
132        match load_all_assemblies() {
133            Ok(_count) => {
134                #[cfg(dev_release)]
135                logger::info(&format!("Cache initialized: {} assemblies loaded", _count));
136                true
137            }
138            Err(_e) => {
139                #[cfg(dev_release)]
140                logger::error(&format!("Cache init failed: {}", _e));
141                false
142            }
143        }
144    }
145}
146
147/// Ensures full class hydration has happened for the current initialization pass.
148///
149/// Safe to call multiple times. Hydration runs at most once after each
150/// successful [`init`] call.
151pub fn ensure_hydrated() {
152    if CLASSES_HYDRATED
153        .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
154        .is_ok()
155    {
156        hydrate_all_classes();
157    }
158}
159
160/// Hydrates all classes in all cached assemblies.
161///
162/// Prefer [`ensure_hydrated`] unless you are working on cache internals.
163pub fn hydrate_all_classes() {
164    let assembly_names: Vec<String> = CACHE
165        .assemblies
166        .iter()
167        .map(|entry| entry.key().clone())
168        .collect();
169
170    for name in assembly_names {
171        let assembly_opt = CACHE
172            .assemblies
173            .get(&name)
174            .map(|entry| Arc::clone(entry.value()));
175
176        if let Some(assembly) = assembly_opt {
177            let mut classes = Vec::new();
178
179            let class_count = unsafe { api::image_get_class_count(assembly.image.address) };
180            for i in 0..class_count {
181                let class_ptr = unsafe { api::image_get_class(assembly.image.address, i) };
182                if !class_ptr.is_null() {
183                    unsafe {
184                        if let Ok(class) = hydrate_class(class_ptr) {
185                            classes.push(class);
186                        }
187                    }
188                }
189            }
190
191            let mut new_assembly = (*assembly).clone();
192            new_assembly.classes = classes;
193
194            CACHE.assemblies.insert(name, Arc::new(new_assembly));
195        }
196    }
197    #[cfg(dev_release)]
198    logger::info(&format!("Hydrated {} classes", CACHE.classes.len()));
199}
200
201/// Loads all assemblies at initialization
202///
203/// # Returns
204/// * `Result<usize, String>` - The number of loaded assemblies or an error message
205unsafe fn load_all_assemblies() -> Result<usize, String> {
206    let domain = api::domain_get();
207    if domain.is_null() {
208        return Err("Failed to get domain".to_string());
209    }
210
211    let mut size = 0;
212    let assemblies_ptr = api::domain_get_assemblies(domain, &mut size);
213    if assemblies_ptr.is_null() {
214        return Err("Failed to get assemblies".to_string());
215    }
216
217    let assemblies_slice = std::slice::from_raw_parts(assemblies_ptr, size);
218    let mut count = 0;
219
220    for &assembly_ptr in assemblies_slice {
221        if assembly_ptr.is_null() {
222            continue;
223        }
224
225        let image = api::assembly_get_image(assembly_ptr);
226        if image.is_null() {
227            continue;
228        }
229
230        let name_ptr = api::image_get_name(image);
231        if name_ptr.is_null() {
232            continue;
233        }
234
235        let name = CStr::from_ptr(name_ptr).to_string_lossy();
236
237        if let Some(asm) = hydrate_assembly(assembly_ptr) {
238            CACHE.assemblies.insert(name.to_string(), asm);
239            count += 1;
240        }
241    }
242
243    Ok(count)
244}
245
246/// Hydrates an assembly from a raw pointer
247///
248/// # Arguments
249/// * `assembly_ptr` - Pointer to the Il2CppAssembly
250///
251/// # Returns
252/// * `Option<Arc<Assembly>>` - The hydrated Assembly struct
253unsafe fn hydrate_assembly(assembly_ptr: *mut c_void) -> Option<Arc<Assembly>> {
254    let image = api::assembly_get_image(assembly_ptr);
255    if image.is_null() {
256        return None;
257    }
258
259    let file_ptr = api::image_get_filename(image);
260    let name_ptr = api::image_get_name(image);
261
262    let file = if !file_ptr.is_null() {
263        CStr::from_ptr(file_ptr).to_string_lossy().into_owned()
264    } else {
265        String::new()
266    };
267
268    let name = if !name_ptr.is_null() {
269        CStr::from_ptr(name_ptr).to_string_lossy().into_owned()
270    } else {
271        String::new()
272    };
273
274    let image_wrapper = Image {
275        address: image,
276        name: name.clone(),
277        filename: file.clone(),
278        assembly: assembly_ptr,
279        entry_point: api::image_get_entry_point(image),
280    };
281
282    let assembly = Assembly {
283        image: image_wrapper,
284        address: assembly_ptr,
285        file: file.clone(),
286        name: name.clone(),
287        classes: Vec::new(),
288    };
289
290    Some(Arc::new(assembly))
291}
292
293/// Hydrates a class from a pointer, populating fields and methods
294///
295/// # Arguments
296/// * `class_ptr` - Pointer to the Il2CppClass
297///
298/// # Returns
299/// * `Result<Class, String>` - The hydrated Class struct or an error
300unsafe fn hydrate_class(class_ptr: *mut c_void) -> Result<Class, String> {
301    let name_ptr = api::class_get_name(class_ptr);
302    let namespace_ptr = api::class_get_namespace(class_ptr);
303
304    let name = CStr::from_ptr(name_ptr).to_string_lossy().into_owned();
305    let namespace = CStr::from_ptr(namespace_ptr).to_string_lossy().into_owned();
306
307    let key = if namespace.is_empty() {
308        name.clone()
309    } else {
310        format!("{}.{}", namespace, name)
311    };
312
313    if let Some(existing) = CACHE.classes.get(&key) {
314        if existing.address == class_ptr {
315            return Ok((**existing).clone());
316        }
317    }
318
319    let parent_ptr = api::class_get_parent(class_ptr);
320    let parent = if !parent_ptr.is_null() {
321        let p_name = api::class_get_name(parent_ptr);
322        let p_namespace = api::class_get_namespace(parent_ptr);
323        if !p_name.is_null() {
324            let name = CStr::from_ptr(p_name).to_string_lossy();
325            let namespace = if !p_namespace.is_null() {
326                CStr::from_ptr(p_namespace).to_string_lossy()
327            } else {
328                std::borrow::Cow::Borrowed("")
329            };
330            if namespace.is_empty() {
331                Some(name.into_owned())
332            } else {
333                Some(format!("{}.{}", namespace, name))
334            }
335        } else {
336            None
337        }
338    } else {
339        None
340    };
341
342    let mut interfaces = Vec::new();
343    let mut iter = ptr::null_mut();
344    loop {
345        let interface_ptr = api::class_get_interfaces(class_ptr, &mut iter);
346        if interface_ptr.is_null() {
347            break;
348        }
349        interfaces.push(interface_ptr);
350    }
351
352    let mut nested_types = Vec::new();
353    let mut iter = ptr::null_mut();
354    loop {
355        let nested_ptr = api::class_get_nested_types(class_ptr, &mut iter);
356        if nested_ptr.is_null() {
357            break;
358        }
359        nested_types.push(nested_ptr);
360    }
361
362    let assembly_name_ptr = api::class_get_assemblyname(class_ptr);
363    let assembly_name = if !assembly_name_ptr.is_null() {
364        CStr::from_ptr(assembly_name_ptr)
365            .to_string_lossy()
366            .into_owned()
367    } else {
368        String::new()
369    };
370
371    // Use Arc clone for cheap assembly reference
372    let assembly = CACHE.assemblies.get(&assembly_name).map(|a| Arc::clone(&a));
373
374    let mut class_box = Box::new(Class {
375        address: class_ptr,
376        image: api::class_get_image(class_ptr),
377        token: api::class_get_type_token(class_ptr),
378        name: name.clone(),
379        parent,
380        namespace: namespace.clone(),
381        is_enum: api::class_is_enum(class_ptr),
382        is_generic: api::class_is_generic(class_ptr),
383        is_inflated: api::class_is_inflated(class_ptr),
384        is_interface: api::class_is_interface(class_ptr),
385        is_abstract: api::class_is_abstract(class_ptr),
386        is_blittable: api::class_is_blittable(class_ptr),
387        is_valuetype: api::class_is_valuetype(class_ptr),
388        flags: api::class_get_flags(class_ptr) as i32,
389        rank: api::class_get_rank(class_ptr) as i32,
390        instance_size: api::class_instance_size(class_ptr),
391        array_element_size: api::class_array_element_size(class_ptr),
392        num_fields_count: api::class_num_fields(class_ptr),
393        enum_basetype: api::class_enum_basetype(class_ptr),
394        static_field_data: api::class_get_static_field_data(class_ptr),
395        assembly_name,
396        assembly,
397        fields: Vec::new(),
398        methods: Vec::new(),
399        properties: Vec::new(),
400        interfaces,
401        nested_types,
402        element_class: api::class_get_element_class(class_ptr),
403        declaring_type: api::class_get_declaring_type(class_ptr),
404        ty: api::class_get_type(class_ptr),
405        object: api::type_get_object(api::class_get_type(class_ptr)),
406    });
407
408    let full_class_name = if namespace.is_empty() {
409        name.clone()
410    } else {
411        format!("{}.{}", namespace, name)
412    };
413
414    archive_fields(&mut class_box, class_ptr)?;
415    archive_methods(&mut class_box, class_ptr, &full_class_name)?;
416    archive_properties(&mut class_box);
417
418    let class_clone = (*class_box).clone();
419
420    CACHE.classes.insert(key, class_box);
421
422    let image = api::class_get_image(class_ptr);
423    if !image.is_null() {
424        let image_name_ptr = api::image_get_name(image);
425        if !image_name_ptr.is_null() {
426            let image_name = CStr::from_ptr(image_name_ptr)
427                .to_string_lossy()
428                .into_owned();
429            if let Some(assembly) = CACHE.assemblies.get_mut(&image_name) {
430                drop(assembly);
431            }
432        }
433    }
434
435    Ok(class_clone)
436}
437
438/// Archives fields for a class
439///
440/// # Arguments
441/// * `class` - Mutuable reference to the Class struct
442/// * `class_ptr` - Pointer to the Il2CppClass
443///
444/// # Returns
445/// * `Result<(), String>` - Result indicating success
446unsafe fn archive_fields(class: &mut Class, class_ptr: *mut c_void) -> Result<(), String> {
447    let mut iter = ptr::null_mut();
448    loop {
449        let field_ptr = api::class_get_fields(class_ptr, &mut iter);
450        if field_ptr.is_null() {
451            break;
452        }
453
454        let name_ptr = api::field_get_name(field_ptr);
455        let name = CStr::from_ptr(name_ptr).to_string_lossy().into_owned();
456
457        let offset = api::field_get_offset(field_ptr);
458        let type_ptr = api::field_get_type(field_ptr);
459
460        let type_name_ptr = api::type_get_name(type_ptr);
461        let type_name = if !type_ptr.is_null() {
462            CStr::from_ptr(type_name_ptr).to_string_lossy().into_owned()
463        } else {
464            "System.Object".to_string()
465        };
466
467        let flags = api::field_get_flags(field_ptr);
468
469        let field = Field {
470            address: field_ptr,
471            name: name.to_string(),
472            type_info: Type {
473                address: type_ptr,
474                name: type_name,
475                size: get_type_size(type_ptr),
476            },
477            class: Some(class as *const Class),
478            offset,
479            flags,
480            is_static: (flags & api::FIELD_ATTRIBUTE_STATIC) != 0
481                || (flags & api::FIELD_ATTRIBUTE_LITERAL) != 0,
482            is_literal: (flags & api::FIELD_ATTRIBUTE_LITERAL) != 0,
483            is_readonly: (flags & api::FIELD_ATTRIBUTE_INIT_ONLY) != 0,
484            is_not_serialized: (flags & api::FIELD_ATTRIBUTE_NOT_SERIALIZED) != 0,
485            is_special_name: (flags & api::FIELD_ATTRIBUTE_SPECIAL_NAME) != 0,
486            is_pinvoke_impl: (flags & api::FIELD_ATTRIBUTE_PINVOKE_IMPL) != 0,
487            instance: None,
488        };
489
490        class.fields.push(field);
491    }
492    Ok(())
493}
494
495/// Archives methods for a class
496///
497/// # Arguments
498/// * `class` - Mutable reference to the Class struct
499/// * `class_ptr` - Pointer to the Il2CppClass
500/// * `full_class_name` - The fully qualified name of the class
501///
502/// # Returns
503/// * `Result<(), String>` - Result indicating success
504unsafe fn archive_methods(
505    class: &mut Class,
506    class_ptr: *mut c_void,
507    full_class_name: &str,
508) -> Result<(), String> {
509    let image_base = image::get_image_base(
510        crate::init::TARGET_IMAGE_NAME
511            .get()
512            .map(|s| s.as_str())
513            .unwrap_or(""),
514    )
515    .unwrap_or(0);
516
517    let mut iter = ptr::null_mut();
518    loop {
519        let method_ptr = api::class_get_methods(class_ptr, &mut iter);
520        if method_ptr.is_null() {
521            break;
522        }
523
524        let name_ptr = api::method_get_name(method_ptr);
525        let name = if !name_ptr.is_null() {
526            CStr::from_ptr(name_ptr).to_string_lossy().into_owned()
527        } else {
528            String::new()
529        };
530
531        let flags = api::method_get_flags(method_ptr, ptr::null_mut());
532        let return_type_ptr = api::method_get_return_type(method_ptr);
533        let return_type_name_ptr = api::type_get_name(return_type_ptr);
534        let return_type_name = if !return_type_name_ptr.is_null() {
535            CStr::from_ptr(return_type_name_ptr)
536                .to_string_lossy()
537                .into_owned()
538        } else {
539            String::new()
540        };
541
542        let function_ptr = *(method_ptr as *const *mut c_void);
543
544        let rva = if image_base > 0 && !function_ptr.is_null() {
545            (function_ptr as usize).wrapping_sub(image_base) as u64
546        } else {
547            0
548        };
549
550        let mut method = Method {
551            address: method_ptr,
552            token: api::method_get_token(method_ptr),
553            name: name.clone(),
554            class: Some(class as *const Class),
555            return_type: Type {
556                address: return_type_ptr,
557                name: return_type_name,
558                size: get_type_size(return_type_ptr),
559            },
560            flags: flags as i32,
561            is_static: (flags & api::METHOD_ATTRIBUTE_STATIC as u32) != 0,
562            function: function_ptr,
563            rva,
564            va: function_ptr as u64,
565            args: Vec::new(),
566            is_generic: api::method_is_generic(method_ptr),
567            is_inflated: api::method_is_inflated(method_ptr),
568            is_instance: api::method_is_instance(method_ptr),
569            param_count: api::method_get_param_count(method_ptr),
570            declaring_type: api::method_get_declaring_type(method_ptr),
571            instance: None,
572        };
573
574        let param_count = api::method_get_param_count(method_ptr);
575        for i in 0..param_count {
576            let param_name_ptr = api::method_get_param_name(method_ptr, i as u32);
577            let param_name = if !param_name_ptr.is_null() {
578                CStr::from_ptr(param_name_ptr)
579                    .to_string_lossy()
580                    .into_owned()
581            } else {
582                String::new()
583            };
584
585            let param_type_ptr = api::method_get_param(method_ptr, i as u32);
586            let param_type_name_ptr = api::type_get_name(param_type_ptr);
587            let param_type_name = if !param_type_name_ptr.is_null() {
588                CStr::from_ptr(param_type_name_ptr)
589                    .to_string_lossy()
590                    .into_owned()
591            } else {
592                String::new()
593            };
594
595            method.args.push(Arg {
596                name: param_name,
597                type_info: Type {
598                    address: param_type_ptr,
599                    name: param_type_name,
600                    size: get_type_size(param_type_ptr),
601                },
602            });
603        }
604
605        let method_key = format!("{}::{}", full_class_name, name);
606        CACHE.methods.insert(method_key, method.clone());
607
608        class.methods.push(method);
609    }
610
611    Ok(())
612}
613
614/// Archives properties for a class by combining get_/set_ method pairs
615///
616/// # Arguments
617/// * `class` - Mutable reference to the Class struct
618fn archive_properties(class: &mut Class) {
619    use std::collections::HashMap;
620
621    let mut property_map: HashMap<String, (Option<Method>, Option<Method>)> = HashMap::new();
622
623    for method in &class.methods {
624        let is_getter = method.name.starts_with("get_") && method.args.is_empty();
625        let is_setter = method.name.starts_with("set_") && method.args.len() == 1;
626
627        if is_getter || is_setter {
628            let prop_name = method.name[4..].to_string();
629            let entry = property_map.entry(prop_name).or_insert((None, None));
630
631            if is_getter {
632                entry.0 = Some(method.clone());
633            } else {
634                entry.1 = Some(method.clone());
635            }
636        }
637    }
638
639    let mut props: Vec<_> = property_map.into_iter().collect();
640    props.sort_by(|a, b| a.0.cmp(&b.0));
641
642    for (_name, (getter, setter)) in props {
643        if let Some(prop) = Property::from_methods(getter, setter) {
644            class.properties.push(prop);
645        }
646    }
647}
648
649/// Creates a Method struct from a raw pointer
650///
651/// # Arguments
652/// * `method_ptr` - Pointer to the MethodInfo
653///
654/// # Returns
655/// * `Option<Method>` - The hydrated Method struct
656pub fn method_from_ptr(method_ptr: *mut c_void) -> Option<Method> {
657    if method_ptr.is_null() {
658        return None;
659    }
660
661    unsafe {
662        let name_ptr = api::method_get_name(method_ptr);
663        let name = if !name_ptr.is_null() {
664            CStr::from_ptr(name_ptr).to_string_lossy().into_owned()
665        } else {
666            String::new()
667        };
668
669        let flags = api::method_get_flags(method_ptr, ptr::null_mut());
670        let return_type_ptr = api::method_get_return_type(method_ptr);
671        let return_type_name_ptr = api::type_get_name(return_type_ptr);
672        let return_type_name = if !return_type_name_ptr.is_null() {
673            CStr::from_ptr(return_type_name_ptr)
674                .to_string_lossy()
675                .into_owned()
676        } else {
677            String::new()
678        };
679
680        let function_ptr = *(method_ptr as *const *mut c_void);
681
682        let image_base = image::get_image_base(
683            crate::init::TARGET_IMAGE_NAME
684                .get()
685                .map(|s| s.as_str())
686                .unwrap_or(""),
687        )
688        .unwrap_or(0);
689        let mut rva = 0;
690
691        if image_base > 0 && !function_ptr.is_null() {
692            rva = (function_ptr as usize).wrapping_sub(image_base) as u64;
693        }
694
695        let class_ptr = api::method_get_class(method_ptr);
696        let mut class_ref: Option<*const Class> = None;
697
698        if !class_ptr.is_null() {
699            for entry in CACHE.classes.iter() {
700                if entry.value().address == class_ptr {
701                    class_ref = Some(&**entry.value() as *const Class);
702                    break;
703                }
704            }
705        }
706
707        let mut method = Method {
708            address: method_ptr,
709            token: api::method_get_token(method_ptr),
710            name: name.clone(),
711            class: class_ref,
712            return_type: Type {
713                address: return_type_ptr,
714                name: return_type_name,
715                size: get_type_size(return_type_ptr),
716            },
717            flags: flags as i32,
718            is_static: (flags & api::METHOD_ATTRIBUTE_STATIC as u32) != 0,
719            function: function_ptr,
720            rva,
721            va: function_ptr as u64,
722            args: Vec::new(),
723            is_generic: api::method_is_generic(method_ptr),
724            is_inflated: api::method_is_inflated(method_ptr),
725            is_instance: api::method_is_instance(method_ptr),
726            param_count: api::method_get_param_count(method_ptr),
727            declaring_type: api::method_get_declaring_type(method_ptr),
728            instance: None,
729        };
730
731        let param_count = api::method_get_param_count(method_ptr);
732        for i in 0..param_count {
733            let param_name_ptr = api::method_get_param_name(method_ptr, i as u32);
734            let param_name = if !param_name_ptr.is_null() {
735                CStr::from_ptr(param_name_ptr)
736                    .to_string_lossy()
737                    .into_owned()
738            } else {
739                String::new()
740            };
741
742            let param_type_ptr = api::method_get_param(method_ptr, i as u32);
743            let param_type_name_ptr = api::type_get_name(param_type_ptr);
744            let param_type_name = if !param_type_name_ptr.is_null() {
745                CStr::from_ptr(param_type_name_ptr)
746                    .to_string_lossy()
747                    .into_owned()
748            } else {
749                String::new()
750            };
751
752            method.args.push(Arg {
753                name: param_name,
754                type_info: Type {
755                    address: param_type_ptr,
756                    name: param_type_name,
757                    size: get_type_size(param_type_ptr),
758                },
759            });
760        }
761
762        Some(method)
763    }
764}
765
766/// Helper to get the size of a type
767///
768/// # Arguments
769/// * `type_ptr` - Pointer to the Il2CppType
770///
771/// # Returns
772/// * `i32` - The size of the type in bytes
773unsafe fn get_type_size(type_ptr: *mut c_void) -> i32 {
774    let class_ptr = api::class_from_type(type_ptr);
775    if class_ptr.is_null() {
776        return 0;
777    }
778
779    if api::class_is_valuetype(class_ptr) {
780        let mut align: usize = 0;
781        api::class_value_size(class_ptr, &mut align)
782    } else {
783        std::mem::size_of::<*mut c_void>() as i32
784    }
785}