use crate::api::{self, cache, invoke_method};
use crate::structs::collections::Il2cppArray;
use crate::structs::core::{Class, Object, Type};
use std::ffi::c_void;
use std::ptr;
#[derive(Debug, Clone)]
pub struct Arg {
pub name: String,
pub type_info: Type,
}
#[derive(Debug, Clone)]
pub struct Method {
pub address: *mut c_void,
pub token: u32,
pub name: String,
pub class: Option<*const Class>,
pub return_type: Type,
pub flags: i32,
pub is_static: bool,
pub function: *mut c_void,
pub rva: u64,
pub va: u64,
pub args: Vec<Arg>,
pub is_generic: bool,
pub is_inflated: bool,
pub is_instance: bool,
pub param_count: u8,
pub declaring_type: *mut c_void,
pub instance: Option<*mut c_void>,
}
unsafe impl Send for Method {}
unsafe impl Sync for Method {}
impl std::fmt::Display for Method {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.fmt_method())
}
}
impl Method {
fn fmt_method(&self) -> String {
let access = self.get_attribute();
let flags = self.flags;
let is_abstract = (flags & api::METHOD_ATTRIBUTE_ABSTRACT) != 0;
let is_virtual = (flags & api::METHOD_ATTRIBUTE_VIRTUAL) != 0;
let is_final = (flags & api::METHOD_ATTRIBUTE_FINAL) != 0;
let qualifier = if self.is_static {
"static "
} else if is_abstract {
"abstract "
} else if is_virtual && !is_final {
"virtual "
} else {
""
};
let args_str = self
.args
.iter()
.map(|arg| format!("{} {}", arg.type_info.cpp_name(), arg.name))
.collect::<Vec<_>>()
.join(", ");
let rva_comment = if self.rva == 0 {
"// RVA: -1 Offset: -1 VA: -1".to_string()
} else {
format!(
"// RVA: 0x{:X} Offset: 0x{:X} VA: 0x{:X}",
self.rva, self.rva, self.va
)
};
format!(
"{}\n{} {}{} {}({}) {{ }}",
rva_comment,
access,
qualifier,
self.return_type.cpp_name(),
self.name,
args_str,
)
}
pub unsafe fn call<T: Copy>(&self, params: &[*mut c_void]) -> Result<T, String> {
let instance = if self.is_static {
ptr::null_mut()
} else {
match self.instance {
Some(inst) => inst,
None => {
return Err(format!(
"Method '{}' is not static but no instance was provided. Use Object::method or set the instance manually.",
self.name
));
}
}
};
if params.len() != self.args.len() {
return Err(format!(
"Argument count mismatch: expected {}, got {}",
self.args.len(),
params.len()
));
}
let params_ptr = if params.is_empty() {
ptr::null()
} else {
params.as_ptr()
};
let result = invoke_method(self.address, instance, params_ptr)?;
if std::mem::size_of::<T>() == 0 {
return Ok(std::mem::zeroed());
}
let return_class = api::class_from_type(self.return_type.address);
if return_class.is_null() {
if std::mem::size_of::<T>() != std::mem::size_of::<*mut c_void>() {
return Err(format!(
"Method '{}' returns an unmanaged pointer-sized value, but caller requested {} bytes",
self.name,
std::mem::size_of::<T>()
));
}
return Ok(std::mem::transmute_copy(&result));
}
if api::class_is_valuetype(return_class) {
if result.is_null() {
return Err(format!(
"Method '{}' returned null for value type '{}'",
self.name, self.return_type.name
));
}
let unboxed = api::object_unbox(result);
if unboxed.is_null() {
return Err(format!(
"Method '{}' returned a non-unboxable value for '{}'",
self.name, self.return_type.name
));
}
let expected_size = if self.return_type.size > 0 {
self.return_type.size as usize
} else {
api::class_value_size(return_class, ptr::null_mut()) as usize
};
if std::mem::size_of::<T>() != expected_size {
return Err(format!(
"Method '{}' returns '{}' ({} bytes), but caller requested {} bytes",
self.name,
self.return_type.name,
expected_size,
std::mem::size_of::<T>()
));
}
let mut value = std::mem::MaybeUninit::<T>::uninit();
ptr::copy_nonoverlapping(
unboxed as *const u8,
value.as_mut_ptr() as *mut u8,
expected_size,
);
Ok(value.assume_init())
} else {
if std::mem::size_of::<T>() != std::mem::size_of::<*mut c_void>() {
return Err(format!(
"Method '{}' returns a reference type, but caller requested {} bytes",
self.name,
std::mem::size_of::<T>()
));
}
Ok(std::mem::transmute_copy(&result))
}
}
pub fn get_attribute(&self) -> &'static str {
match self.flags & api::METHOD_ATTRIBUTE_MEMBER_ACCESS_MASK {
api::METHOD_ATTRIBUTE_PRIVATE => "private",
api::METHOD_ATTRIBUTE_PUBLIC => "public",
api::METHOD_ATTRIBUTE_FAMILY => "protected",
api::METHOD_ATTRIBUTE_ASSEM => "internal",
api::METHOD_ATTRIBUTE_FAM_AND_ASSEM => "private protected",
api::METHOD_ATTRIBUTE_FAM_OR_ASSEM => "protected internal",
_ => "private",
}
}
pub fn get_object(&self) -> *mut c_void {
unsafe { api::method_get_object(self.address, ptr::null_mut()) }
}
pub fn inflate(&self, classes: &[&Class]) -> Result<Method, String> {
unsafe {
if !self.is_generic {
return Err(format!(
"Method '{}' is not a generic method definition",
self.name
));
}
if classes.is_empty() {
return Err("No type arguments provided".to_string());
}
let mut type_args = Vec::with_capacity(classes.len());
for (i, cls) in classes.iter().enumerate() {
if cls.object.is_null() {
return Err(format!(
"Class '{}' (arg {}) has no Type object",
cls.name, i
));
}
type_args.push(cls.object);
}
let method_object = self.get_object();
if method_object.is_null() {
return Err(format!(
"Could not get MethodInfo object for method '{}'",
self.name
));
}
let method_obj = Object::from_ptr(method_object);
let corlib = cache::mscorlib();
let type_class = corlib
.class("System.Type")
.ok_or_else(|| "Could not find System.Type class".to_string())?;
let type_array = Il2cppArray::<*mut c_void>::new(&type_class, type_args.len());
if type_array.is_null() {
return Err("Could not create Type[] array".to_string());
}
let array_ref = &mut *type_array;
for (i, &type_arg) in type_args.iter().enumerate() {
array_ref.set(i, type_arg);
}
let make_generic_method = method_obj
.method(("MakeGenericMethod", ["System.Type[]"]))
.ok_or_else(|| "Could not find MakeGenericMethod(Type[]) method".to_string())?;
let inflated_method_obj =
make_generic_method.call::<*mut c_void>(&[type_array as *mut c_void])?;
if inflated_method_obj.is_null() {
return Err("MakeGenericMethod returned null".to_string());
}
let inflated_obj = Object::from_ptr(inflated_method_obj);
let mhandle_field = inflated_obj
.field("mhandle")
.unwrap()
.get_value::<*mut c_void>()
.map_err(|e| format!("Could not read mhandle field: {}", e))?;
if mhandle_field.is_null() {
return Err("mhandle field is null".to_string());
}
cache::method_from_ptr(mhandle_field).ok_or_else(|| {
"Could not convert inflated method pointer to Method struct".to_string()
})
}
}
}