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) 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
253unsafe 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
300unsafe 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 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
445unsafe 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
502unsafe 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
621fn 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
656pub 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
773unsafe 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}