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/// Hydrates all classes with a progress callback reporting (class_name, current, total).
209pub(crate) fn hydrate_all_classes_with_progress(
210    on_progress: impl Fn(&str, usize, usize),
211) -> Result<usize, String> {
212    let assembly_names: Vec<String> = CACHE
213        .assemblies
214        .iter()
215        .map(|entry| entry.key().clone())
216        .collect();
217
218    // Pre-pass: count total classes across all assemblies
219    let mut total_classes = 0usize;
220    for name in &assembly_names {
221        if let Some(assembly) = CACHE.assemblies.get(name).map(|e| Arc::clone(e.value())) {
222            total_classes += unsafe { api::image_get_class_count(assembly.image.address) } as usize;
223        }
224    }
225
226    let throttle = if total_classes > 0 {
227        total_classes.div_ceil(50).max(1)
228    } else {
229        1
230    };
231    let mut hydrated_class_count = 0usize;
232
233    for name in assembly_names {
234        let assembly_opt = CACHE
235            .assemblies
236            .get(&name)
237            .map(|entry| Arc::clone(entry.value()));
238
239        if let Some(assembly) = assembly_opt {
240            let mut classes = Vec::new();
241
242            let class_count = unsafe { api::image_get_class_count(assembly.image.address) };
243            for i in 0..class_count {
244                let class_ptr = unsafe { api::image_get_class(assembly.image.address, i) };
245                if !class_ptr.is_null() {
246                    match unsafe { hydrate_class(class_ptr) } {
247                        Ok(class) => {
248                            hydrated_class_count += 1;
249                            if hydrated_class_count.is_multiple_of(throttle)
250                                || hydrated_class_count == total_classes
251                            {
252                                on_progress(&class.name, hydrated_class_count, total_classes);
253                            }
254                            classes.push(class);
255                        }
256                        Err(e) => {
257                            return Err(format!(
258                                "Failed to hydrate class {} in assembly '{}': {}",
259                                i, name, e
260                            ));
261                        }
262                    }
263                }
264            }
265
266            let mut new_assembly = (*assembly).clone();
267            new_assembly.classes = classes;
268
269            CACHE.assemblies.insert(name, Arc::new(new_assembly));
270        }
271    }
272    #[cfg(dev_release)]
273    logger::info(&format!("Hydrated {} classes", hydrated_class_count));
274    Ok(hydrated_class_count)
275}
276
277/// Loads all assemblies at initialization
278///
279/// # Returns
280/// * `Result<usize, String>` - The number of loaded assemblies or an error message
281pub(crate) unsafe fn load_all_assemblies() -> Result<usize, String> {
282    let domain = api::domain_get();
283    if domain.is_null() {
284        return Err("Failed to get domain".to_string());
285    }
286
287    let mut size = 0;
288    let assemblies_ptr = api::domain_get_assemblies(domain, &mut size);
289    if assemblies_ptr.is_null() {
290        return Err("Failed to get assemblies".to_string());
291    }
292
293    let assemblies_slice = std::slice::from_raw_parts(assemblies_ptr, size);
294    let mut count = 0;
295
296    for &assembly_ptr in assemblies_slice {
297        if assembly_ptr.is_null() {
298            continue;
299        }
300
301        let image = api::assembly_get_image(assembly_ptr);
302        if image.is_null() {
303            continue;
304        }
305
306        let name_ptr = api::image_get_name(image);
307        if name_ptr.is_null() {
308            continue;
309        }
310
311        let name = CStr::from_ptr(name_ptr).to_string_lossy();
312
313        if let Some(asm) = hydrate_assembly(assembly_ptr) {
314            CACHE.assemblies.insert(name.to_string(), asm);
315            count += 1;
316        }
317    }
318
319    Ok(count)
320}
321
322/// Hydrates an assembly from a raw pointer
323///
324/// # Arguments
325/// * `assembly_ptr` - Pointer to the Il2CppAssembly
326///
327/// # Returns
328/// * `Option<Arc<Assembly>>` - The hydrated Assembly struct
329unsafe fn hydrate_assembly(assembly_ptr: *mut c_void) -> Option<Arc<Assembly>> {
330    let image = api::assembly_get_image(assembly_ptr);
331    if image.is_null() {
332        return None;
333    }
334
335    let file_ptr = api::image_get_filename(image);
336    let name_ptr = api::image_get_name(image);
337
338    let file = if !file_ptr.is_null() {
339        CStr::from_ptr(file_ptr).to_string_lossy().into_owned()
340    } else {
341        String::new()
342    };
343
344    let name = if !name_ptr.is_null() {
345        CStr::from_ptr(name_ptr).to_string_lossy().into_owned()
346    } else {
347        String::new()
348    };
349
350    let image_wrapper = Image {
351        address: image,
352        name: name.clone(),
353        filename: file.clone(),
354        assembly: assembly_ptr,
355        entry_point: api::image_get_entry_point(image),
356    };
357
358    let assembly = Assembly {
359        image: image_wrapper,
360        address: assembly_ptr,
361        file: file.clone(),
362        name: name.clone(),
363        classes: Vec::new(),
364    };
365
366    Some(Arc::new(assembly))
367}
368
369/// Hydrates a class from a pointer, populating fields and methods
370///
371/// # Arguments
372/// * `class_ptr` - Pointer to the Il2CppClass
373///
374/// # Returns
375/// * `Result<Class, String>` - The hydrated Class struct or an error
376unsafe fn hydrate_class(class_ptr: *mut c_void) -> Result<Class, String> {
377    let name_ptr = api::class_get_name(class_ptr);
378    let namespace_ptr = api::class_get_namespace(class_ptr);
379
380    let name = CStr::from_ptr(name_ptr).to_string_lossy().into_owned();
381    let namespace = CStr::from_ptr(namespace_ptr).to_string_lossy().into_owned();
382
383    let key = if namespace.is_empty() {
384        name.clone()
385    } else {
386        format!("{}.{}", namespace, name)
387    };
388
389    if let Some(existing) = CACHE.classes.get(&key) {
390        if existing.address == class_ptr {
391            return Ok((**existing).clone());
392        }
393    }
394
395    let parent_ptr = api::class_get_parent(class_ptr);
396    let parent = if !parent_ptr.is_null() {
397        let p_name = api::class_get_name(parent_ptr);
398        let p_namespace = api::class_get_namespace(parent_ptr);
399        if !p_name.is_null() {
400            let name = CStr::from_ptr(p_name).to_string_lossy();
401            let namespace = if !p_namespace.is_null() {
402                CStr::from_ptr(p_namespace).to_string_lossy()
403            } else {
404                std::borrow::Cow::Borrowed("")
405            };
406            if namespace.is_empty() {
407                Some(name.into_owned())
408            } else {
409                Some(format!("{}.{}", namespace, name))
410            }
411        } else {
412            None
413        }
414    } else {
415        None
416    };
417
418    let mut interfaces = Vec::new();
419    let mut iter = ptr::null_mut();
420    loop {
421        let interface_ptr = api::class_get_interfaces(class_ptr, &mut iter);
422        if interface_ptr.is_null() {
423            break;
424        }
425        interfaces.push(interface_ptr);
426    }
427
428    let mut nested_types = Vec::new();
429    let mut iter = ptr::null_mut();
430    loop {
431        let nested_ptr = api::class_get_nested_types(class_ptr, &mut iter);
432        if nested_ptr.is_null() {
433            break;
434        }
435        nested_types.push(nested_ptr);
436    }
437
438    let assembly_name_ptr = api::class_get_assemblyname(class_ptr);
439    let assembly_name = if !assembly_name_ptr.is_null() {
440        CStr::from_ptr(assembly_name_ptr)
441            .to_string_lossy()
442            .into_owned()
443    } else {
444        String::new()
445    };
446
447    // Use Arc clone for cheap assembly reference
448    let assembly = CACHE.assemblies.get(&assembly_name).map(|a| Arc::clone(&a));
449
450    let mut class_box = Box::new(Class {
451        address: class_ptr,
452        image: api::class_get_image(class_ptr),
453        token: api::class_get_type_token(class_ptr),
454        name: name.clone(),
455        parent,
456        namespace: namespace.clone(),
457        is_enum: api::class_is_enum(class_ptr),
458        is_generic: api::class_is_generic(class_ptr),
459        is_inflated: api::class_is_inflated(class_ptr),
460        is_interface: api::class_is_interface(class_ptr),
461        is_abstract: api::class_is_abstract(class_ptr),
462        is_blittable: api::class_is_blittable(class_ptr),
463        is_valuetype: api::class_is_valuetype(class_ptr),
464        flags: api::class_get_flags(class_ptr) as i32,
465        rank: api::class_get_rank(class_ptr) as i32,
466        instance_size: api::class_instance_size(class_ptr),
467        array_element_size: api::class_array_element_size(class_ptr),
468        num_fields_count: api::class_num_fields(class_ptr),
469        enum_basetype: api::class_enum_basetype(class_ptr),
470        static_field_data: api::class_get_static_field_data(class_ptr),
471        assembly_name,
472        assembly,
473        fields: Vec::new(),
474        methods: Vec::new(),
475        properties: Vec::new(),
476        interfaces,
477        nested_types,
478        element_class: api::class_get_element_class(class_ptr),
479        declaring_type: api::class_get_declaring_type(class_ptr),
480        ty: api::class_get_type(class_ptr),
481        object: api::type_get_object(api::class_get_type(class_ptr)),
482    });
483
484    let full_class_name = if namespace.is_empty() {
485        name.clone()
486    } else {
487        format!("{}.{}", namespace, name)
488    };
489
490    archive_fields(&mut class_box, class_ptr)?;
491    archive_methods(&mut class_box, class_ptr, &full_class_name)?;
492    archive_properties(&mut class_box);
493
494    let class_clone = (*class_box).clone();
495
496    CACHE.classes.insert(key, class_box);
497
498    let image = api::class_get_image(class_ptr);
499    if !image.is_null() {
500        let image_name_ptr = api::image_get_name(image);
501        if !image_name_ptr.is_null() {
502            let image_name = CStr::from_ptr(image_name_ptr)
503                .to_string_lossy()
504                .into_owned();
505            if let Some(assembly) = CACHE.assemblies.get_mut(&image_name) {
506                drop(assembly);
507            }
508        }
509    }
510
511    Ok(class_clone)
512}
513
514/// Archives fields for a class
515///
516/// # Arguments
517/// * `class` - Mutuable reference to the Class struct
518/// * `class_ptr` - Pointer to the Il2CppClass
519///
520/// # Returns
521/// * `Result<(), String>` - Result indicating success
522unsafe fn archive_fields(class: &mut Class, class_ptr: *mut c_void) -> Result<(), String> {
523    let mut iter = ptr::null_mut();
524    loop {
525        let field_ptr = api::class_get_fields(class_ptr, &mut iter);
526        if field_ptr.is_null() {
527            break;
528        }
529
530        let name_ptr = api::field_get_name(field_ptr);
531        let name = CStr::from_ptr(name_ptr).to_string_lossy().into_owned();
532
533        let offset = api::field_get_offset(field_ptr);
534        let type_ptr = api::field_get_type(field_ptr);
535
536        let type_name_ptr = api::type_get_name(type_ptr);
537        let type_name = if !type_ptr.is_null() {
538            CStr::from_ptr(type_name_ptr).to_string_lossy().into_owned()
539        } else {
540            "System.Object".to_string()
541        };
542
543        let flags = api::field_get_flags(field_ptr);
544
545        let field = Field {
546            address: field_ptr,
547            name: name.to_string(),
548            type_info: Type {
549                address: type_ptr,
550                name: type_name,
551                size: get_type_size(type_ptr),
552            },
553            class: Some(class as *const Class),
554            offset,
555            flags,
556            is_static: (flags & api::FIELD_ATTRIBUTE_STATIC) != 0
557                || (flags & api::FIELD_ATTRIBUTE_LITERAL) != 0,
558            is_literal: (flags & api::FIELD_ATTRIBUTE_LITERAL) != 0,
559            is_readonly: (flags & api::FIELD_ATTRIBUTE_INIT_ONLY) != 0,
560            is_not_serialized: (flags & api::FIELD_ATTRIBUTE_NOT_SERIALIZED) != 0,
561            is_special_name: (flags & api::FIELD_ATTRIBUTE_SPECIAL_NAME) != 0,
562            is_pinvoke_impl: (flags & api::FIELD_ATTRIBUTE_PINVOKE_IMPL) != 0,
563            instance: None,
564        };
565
566        class.fields.push(field);
567    }
568    Ok(())
569}
570
571/// Archives methods for a class
572///
573/// # Arguments
574/// * `class` - Mutable reference to the Class struct
575/// * `class_ptr` - Pointer to the Il2CppClass
576/// * `full_class_name` - The fully qualified name of the class
577///
578/// # Returns
579/// * `Result<(), String>` - Result indicating success
580unsafe fn archive_methods(
581    class: &mut Class,
582    class_ptr: *mut c_void,
583    full_class_name: &str,
584) -> Result<(), String> {
585    let image_base = image::get_image_base(
586        crate::init::TARGET_IMAGE_NAME
587            .get()
588            .map(|s| s.as_str())
589            .unwrap_or(""),
590    )
591    .unwrap_or(0);
592
593    let mut iter = ptr::null_mut();
594    loop {
595        let method_ptr = api::class_get_methods(class_ptr, &mut iter);
596        if method_ptr.is_null() {
597            break;
598        }
599
600        let name_ptr = api::method_get_name(method_ptr);
601        let name = if !name_ptr.is_null() {
602            CStr::from_ptr(name_ptr).to_string_lossy().into_owned()
603        } else {
604            String::new()
605        };
606
607        let flags = api::method_get_flags(method_ptr, ptr::null_mut());
608        let return_type_ptr = api::method_get_return_type(method_ptr);
609        let return_type_name_ptr = api::type_get_name(return_type_ptr);
610        let return_type_name = if !return_type_name_ptr.is_null() {
611            CStr::from_ptr(return_type_name_ptr)
612                .to_string_lossy()
613                .into_owned()
614        } else {
615            String::new()
616        };
617
618        let function_ptr = *(method_ptr as *const *mut c_void);
619
620        let rva = if image_base > 0 && !function_ptr.is_null() {
621            (function_ptr as usize).wrapping_sub(image_base) as u64
622        } else {
623            0
624        };
625
626        let mut method = Method {
627            address: method_ptr,
628            token: api::method_get_token(method_ptr),
629            name: name.clone(),
630            class: Some(class as *const Class),
631            return_type: Type {
632                address: return_type_ptr,
633                name: return_type_name,
634                size: get_type_size(return_type_ptr),
635            },
636            flags: flags as i32,
637            is_static: (flags & api::METHOD_ATTRIBUTE_STATIC as u32) != 0,
638            function: function_ptr,
639            rva,
640            va: function_ptr as u64,
641            args: Vec::new(),
642            is_generic: api::method_is_generic(method_ptr),
643            is_inflated: api::method_is_inflated(method_ptr),
644            is_instance: api::method_is_instance(method_ptr),
645            param_count: api::method_get_param_count(method_ptr),
646            declaring_type: api::method_get_declaring_type(method_ptr),
647            instance: None,
648        };
649
650        let param_count = api::method_get_param_count(method_ptr);
651        for i in 0..param_count {
652            let param_name_ptr = api::method_get_param_name(method_ptr, i as u32);
653            let param_name = if !param_name_ptr.is_null() {
654                CStr::from_ptr(param_name_ptr)
655                    .to_string_lossy()
656                    .into_owned()
657            } else {
658                String::new()
659            };
660
661            let param_type_ptr = api::method_get_param(method_ptr, i as u32);
662            let param_type_name_ptr = api::type_get_name(param_type_ptr);
663            let param_type_name = if !param_type_name_ptr.is_null() {
664                CStr::from_ptr(param_type_name_ptr)
665                    .to_string_lossy()
666                    .into_owned()
667            } else {
668                String::new()
669            };
670
671            method.args.push(Arg {
672                name: param_name,
673                type_info: Type {
674                    address: param_type_ptr,
675                    name: param_type_name,
676                    size: get_type_size(param_type_ptr),
677                },
678            });
679        }
680
681        let method_key = format!("{}::{}", full_class_name, name);
682        CACHE.methods.insert(method_key, method.clone());
683
684        class.methods.push(method);
685    }
686
687    Ok(())
688}
689
690/// Archives properties for a class by combining get_/set_ method pairs
691///
692/// # Arguments
693/// * `class` - Mutable reference to the Class struct
694fn archive_properties(class: &mut Class) {
695    use std::collections::HashMap;
696
697    let mut property_map: HashMap<String, (Option<Method>, Option<Method>)> = HashMap::new();
698
699    for method in &class.methods {
700        let is_getter = method.name.starts_with("get_") && method.args.is_empty();
701        let is_setter = method.name.starts_with("set_") && method.args.len() == 1;
702
703        if is_getter || is_setter {
704            let prop_name = method.name[4..].to_string();
705            let entry = property_map.entry(prop_name).or_insert((None, None));
706
707            if is_getter {
708                entry.0 = Some(method.clone());
709            } else {
710                entry.1 = Some(method.clone());
711            }
712        }
713    }
714
715    let mut props: Vec<_> = property_map.into_iter().collect();
716    props.sort_by(|a, b| a.0.cmp(&b.0));
717
718    for (_name, (getter, setter)) in props {
719        if let Some(prop) = Property::from_methods(getter, setter) {
720            class.properties.push(prop);
721        }
722    }
723}
724
725/// Creates a Method struct from a raw pointer
726///
727/// # Arguments
728/// * `method_ptr` - Pointer to the MethodInfo
729///
730/// # Returns
731/// * `Option<Method>` - The hydrated Method struct
732pub fn method_from_ptr(method_ptr: *mut c_void) -> Option<Method> {
733    if method_ptr.is_null() {
734        return None;
735    }
736
737    unsafe {
738        let name_ptr = api::method_get_name(method_ptr);
739        let name = if !name_ptr.is_null() {
740            CStr::from_ptr(name_ptr).to_string_lossy().into_owned()
741        } else {
742            String::new()
743        };
744
745        let flags = api::method_get_flags(method_ptr, ptr::null_mut());
746        let return_type_ptr = api::method_get_return_type(method_ptr);
747        let return_type_name_ptr = api::type_get_name(return_type_ptr);
748        let return_type_name = if !return_type_name_ptr.is_null() {
749            CStr::from_ptr(return_type_name_ptr)
750                .to_string_lossy()
751                .into_owned()
752        } else {
753            String::new()
754        };
755
756        let function_ptr = *(method_ptr as *const *mut c_void);
757
758        let image_base = image::get_image_base(
759            crate::init::TARGET_IMAGE_NAME
760                .get()
761                .map(|s| s.as_str())
762                .unwrap_or(""),
763        )
764        .unwrap_or(0);
765        let mut rva = 0;
766
767        if image_base > 0 && !function_ptr.is_null() {
768            rva = (function_ptr as usize).wrapping_sub(image_base) as u64;
769        }
770
771        let class_ptr = api::method_get_class(method_ptr);
772        let mut class_ref: Option<*const Class> = None;
773
774        if !class_ptr.is_null() {
775            for entry in CACHE.classes.iter() {
776                if entry.value().address == class_ptr {
777                    class_ref = Some(&**entry.value() as *const Class);
778                    break;
779                }
780            }
781        }
782
783        let mut method = Method {
784            address: method_ptr,
785            token: api::method_get_token(method_ptr),
786            name: name.clone(),
787            class: class_ref,
788            return_type: Type {
789                address: return_type_ptr,
790                name: return_type_name,
791                size: get_type_size(return_type_ptr),
792            },
793            flags: flags as i32,
794            is_static: (flags & api::METHOD_ATTRIBUTE_STATIC as u32) != 0,
795            function: function_ptr,
796            rva,
797            va: function_ptr as u64,
798            args: Vec::new(),
799            is_generic: api::method_is_generic(method_ptr),
800            is_inflated: api::method_is_inflated(method_ptr),
801            is_instance: api::method_is_instance(method_ptr),
802            param_count: api::method_get_param_count(method_ptr),
803            declaring_type: api::method_get_declaring_type(method_ptr),
804            instance: None,
805        };
806
807        let param_count = api::method_get_param_count(method_ptr);
808        for i in 0..param_count {
809            let param_name_ptr = api::method_get_param_name(method_ptr, i as u32);
810            let param_name = if !param_name_ptr.is_null() {
811                CStr::from_ptr(param_name_ptr)
812                    .to_string_lossy()
813                    .into_owned()
814            } else {
815                String::new()
816            };
817
818            let param_type_ptr = api::method_get_param(method_ptr, i as u32);
819            let param_type_name_ptr = api::type_get_name(param_type_ptr);
820            let param_type_name = if !param_type_name_ptr.is_null() {
821                CStr::from_ptr(param_type_name_ptr)
822                    .to_string_lossy()
823                    .into_owned()
824            } else {
825                String::new()
826            };
827
828            method.args.push(Arg {
829                name: param_name,
830                type_info: Type {
831                    address: param_type_ptr,
832                    name: param_type_name,
833                    size: get_type_size(param_type_ptr),
834                },
835            });
836        }
837
838        Some(method)
839    }
840}
841
842/// Helper to get the size of a type
843///
844/// # Arguments
845/// * `type_ptr` - Pointer to the Il2CppType
846///
847/// # Returns
848/// * `i32` - The size of the type in bytes
849unsafe fn get_type_size(type_ptr: *mut c_void) -> i32 {
850    let class_ptr = api::class_from_type(type_ptr);
851    if class_ptr.is_null() {
852        return 0;
853    }
854
855    if api::class_is_valuetype(class_ptr) {
856        let mut align: usize = 0;
857        api::class_value_size(class_ptr, &mut align)
858    } else {
859        std::mem::size_of::<*mut c_void>() as i32
860    }
861}