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::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
21pub struct Il2CppCache {
23 pub assemblies: DashMap<String, Arc<Assembly>>,
25 pub classes: DashMap<String, Box<Class>>,
27 pub methods: DashMap<String, Method>,
29}
30
31unsafe impl Send for Il2CppCache {}
32unsafe impl Sync for Il2CppCache {}
33
34pub static CACHE: Lazy<Il2CppCache> = Lazy::new(|| Il2CppCache {
36 assemblies: DashMap::new(),
37 classes: DashMap::new(),
38 methods: DashMap::new(),
39});
40
41pub 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
61pub 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
77pub 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
93pub 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
109pub 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
120pub 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
147pub 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
160pub 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
201unsafe 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
246unsafe 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
293unsafe 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 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
438unsafe 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
495unsafe 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
614fn 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
649pub 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
766unsafe 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}