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