1use 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
18pub struct Il2CppCache {
20 pub assemblies: DashMap<String, Arc<Assembly>>,
22 pub classes: DashMap<String, Box<Class>>,
24 pub methods: DashMap<String, Method>,
26}
27
28unsafe impl Send for Il2CppCache {}
29unsafe impl Sync for Il2CppCache {}
30
31pub static CACHE: Lazy<Il2CppCache> = Lazy::new(|| Il2CppCache {
33 assemblies: DashMap::new(),
34 classes: DashMap::new(),
35 methods: DashMap::new(),
36});
37
38pub 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
58pub 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
74pub 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
90pub 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
106pub 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
117pub 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
153pub(crate) fn clear() {
155 CACHE.assemblies.clear();
156 CACHE.classes.clear();
157 CACHE.methods.clear();
158}
159
160pub(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
208pub(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 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
277pub(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
322unsafe 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
369unsafe 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 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
514unsafe 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
571unsafe 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
690fn 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
725pub 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
842unsafe 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}