use std::borrow::Cow;
use std::collections::HashMap;
use std::fs;
use std::path::Path;
use dotnetdll::resolved::body;
use dotnetdll::resolved::il::Instruction;
use dotnetdll::resolved::members::{self, Field, Method, Property, Event, ParameterMetadata};
use dotnetdll::resolved::module::Module;
use dotnetdll::resolved::signature::{
MethodSignature, Parameter, ReturnType,
};
use dotnetdll::resolved::types::{
BaseType, ExternalTypeReference, MemberType, MethodType,
TypeDefinition as DotNetTypeDef, TypeFlags, TypeSource,
ResolutionScope, UserType, ValueKind, Accessibility as TypeAccessibility,
};
use dotnetdll::resolved::assembly::ExternalAssemblyReference;
use dotnetdll::resolved::generic;
use dotnetdll::resolved::attribute::{Attribute, CustomAttributeData, NamedArg, FixedArg};
use dotnetdll::resolution::{Resolution, MethodRefIndex};
use crate::il2cpp::metadata::Metadata;
use crate::il2cpp::base::Il2Cpp;
use crate::il2cpp::enums::Il2CppTypeEnum;
use crate::il2cpp::structures::*;
use crate::executor::Il2CppExecutor;
use crate::config::Config;
use crate::error::Result;
#[allow(dead_code)]
struct AttrCtors {
address_ctor: MethodRefIndex,
field_offset_ctor: MethodRefIndex,
token_ctor: MethodRefIndex,
attribute_ctor: MethodRefIndex,
metadata_offset_ctor: MethodRefIndex,
}
fn make_named_string_attr<'a>(ctor: MethodRefIndex, fields: Vec<(&str, String)>) -> Attribute<'a> {
let named_args = fields.into_iter().map(|(name, value)| {
NamedArg::Field(
Cow::Owned(name.to_string()),
FixedArg::String(Some(Cow::Owned(value))),
)
}).collect();
Attribute::new(
members::UserMethod::Reference(ctor),
CustomAttributeData {
constructor_args: vec![],
named_args,
},
)
}
fn push_attr_ctor(resolution: &mut Resolution<'_>, asm_ref: dotnetdll::resolution::AssemblyRefIndex, name: String) -> MethodRefIndex {
let type_ref = resolution.push_type_reference(
ExternalTypeReference::new(
None,
Cow::Owned(name),
ResolutionScope::Assembly(asm_ref),
),
);
let parent_type = MethodType::Base(Box::new(BaseType::Type {
value_kind: Some(ValueKind::Class),
source: TypeSource::User(UserType::Reference(type_ref)),
}));
resolution.push_method_reference(
members::ExternalMethodReference::new(
members::MethodReferenceParent::Type(parent_type),
".ctor",
MethodSignature::new(true, ReturnType::VOID, vec![]),
),
)
}
fn setup_il2cpp_dummy_dll_refs(resolution: &mut Resolution<'_>) -> AttrCtors {
let dummy_asm_ref = resolution.push_assembly_reference(
ExternalAssemblyReference::new("Il2CppDummyDll"),
);
AttrCtors {
address_ctor: push_attr_ctor(resolution, dummy_asm_ref, "AddressAttribute".to_string()),
field_offset_ctor: push_attr_ctor(resolution, dummy_asm_ref, "FieldOffsetAttribute".to_string()),
token_ctor: push_attr_ctor(resolution, dummy_asm_ref, "TokenAttribute".to_string()),
attribute_ctor: push_attr_ctor(resolution, dummy_asm_ref, "AttributeAttribute".to_string()),
metadata_offset_ctor: push_attr_ctor(resolution, dummy_asm_ref, "MetadataOffsetAttribute".to_string()),
}
}
#[allow(dead_code)]
struct DummyDllContext {
type_map: HashMap<usize, usize>,
mscorlib_ref: dotnetdll::resolution::AssemblyRefIndex,
void_type_ref: dotnetdll::resolution::TypeRefIndex,
}
fn type_accessibility_from_flags(flags: u32) -> TypeAccessibility {
use dotnetdll::resolved::Accessibility;
match flags & 0x7 {
0x0 => TypeAccessibility::NotPublic,
0x1 => TypeAccessibility::Public,
0x2 => TypeAccessibility::Nested(Accessibility::Public),
0x3 => TypeAccessibility::Nested(Accessibility::Private),
0x4 => TypeAccessibility::Nested(Accessibility::Family),
0x5 => TypeAccessibility::Nested(Accessibility::Assembly),
0x6 => TypeAccessibility::Nested(Accessibility::FamilyANDAssembly),
0x7 => TypeAccessibility::Nested(Accessibility::FamilyORAssembly),
_ => TypeAccessibility::NotPublic,
}
}
fn member_accessibility_from_flags(flags: u16) -> dotnetdll::resolved::Accessibility {
match flags & 0x7 {
0x1 => dotnetdll::resolved::Accessibility::Private,
0x2 => dotnetdll::resolved::Accessibility::FamilyANDAssembly,
0x3 => dotnetdll::resolved::Accessibility::Assembly,
0x4 => dotnetdll::resolved::Accessibility::Family,
0x5 => dotnetdll::resolved::Accessibility::FamilyORAssembly,
0x6 => dotnetdll::resolved::Accessibility::Public,
_ => dotnetdll::resolved::Accessibility::Private,
}
}
fn field_accessibility_from_attrs(attrs: u32) -> dotnetdll::resolved::Accessibility {
match attrs & 0x7 {
0x1 => dotnetdll::resolved::Accessibility::Private,
0x2 => dotnetdll::resolved::Accessibility::FamilyANDAssembly,
0x3 => dotnetdll::resolved::Accessibility::Assembly,
0x4 => dotnetdll::resolved::Accessibility::Family,
0x5 => dotnetdll::resolved::Accessibility::FamilyORAssembly,
0x6 => dotnetdll::resolved::Accessibility::Public,
_ => dotnetdll::resolved::Accessibility::Private,
}
}
pub fn generate_dummy_dlls(
executor: &mut Il2CppExecutor,
metadata: &mut Metadata,
il2cpp: &mut Il2Cpp,
_config: &Config,
output_dir: &str,
) -> Result<()> {
let dummy_dir = Path::new(output_dir).join("DummyDll");
fs::create_dir_all(&dummy_dir)
.map_err(|e| crate::error::Error::Other(format!("Failed to create DummyDll dir: {e}")))?;
let image_defs = metadata.image_defs.clone();
let assembly_defs = metadata.assembly_defs.clone();
let type_defs_all = metadata.type_defs.clone();
let field_defs_all = metadata.field_defs.clone();
let method_defs_all = metadata.method_defs.clone();
let property_defs_all = metadata.property_defs.clone();
let event_defs_all = metadata.event_defs.clone();
let parameter_defs_all = metadata.parameter_defs.clone();
let nested_type_indices = metadata.nested_type_indices.clone();
let interface_indices = metadata.interface_indices.clone();
let generic_containers = metadata.generic_containers.clone();
let generic_parameters = metadata.generic_parameters.clone();
let constraint_indices = metadata.constraint_indices.clone();
let types_all = il2cpp.types.clone();
let _version = metadata.version;
let mut dll_outputs: Vec<(String, Vec<u8>)> = Vec::new();
let mut global_type_map: HashMap<usize, (usize, usize)> = HashMap::new();
for (img_idx, image_def) in image_defs.iter().enumerate() {
let type_start = image_def.type_start as usize;
let type_end = type_start + image_def.type_count as usize;
for index in type_start..type_end {
global_type_map.insert(index, (img_idx, index));
}
}
for (img_idx, image_def) in image_defs.iter().enumerate() {
let image_name = metadata.get_string_from_index(image_def.name_index)
.unwrap_or_else(|_| format!("Assembly-{img_idx}.dll"));
let assembly_name = assembly_defs.get(image_def.assembly_index as usize)
.and_then(|ad| metadata.get_string_from_index(ad.aname.name_index).ok())
.unwrap_or_else(|| image_name.replace(".dll", ""));
let mut resolution = Resolution::new(Module::new(&image_name));
resolution.assembly = Some(dotnetdll::resolved::assembly::Assembly::new(&assembly_name));
if let Some(ad) = assembly_defs.get(image_def.assembly_index as usize) {
if let Some(ref mut asm) = resolution.assembly {
asm.version = dotnetdll::resolved::assembly::Version {
major: ad.aname.major as u16,
minor: ad.aname.minor as u16,
build: if ad.aname.build >= 0 { ad.aname.build as u16 } else { 0 },
revision: if ad.aname.revision >= 0 { ad.aname.revision as u16 } else { 0 },
};
}
}
resolution.type_definitions.clear();
let mscorlib_ref = resolution.push_assembly_reference(
ExternalAssemblyReference::new("mscorlib"),
);
let void_type_ref = resolution.push_type_reference(
ExternalTypeReference::new(
Some(Cow::Borrowed("System")),
"Void",
ResolutionScope::Assembly(mscorlib_ref),
),
);
let mut type_map: HashMap<usize, usize> = HashMap::new();
let type_start = image_def.type_start as usize;
let type_end = type_start + image_def.type_count as usize;
for index in type_start..type_end {
if let Some(type_def) = type_defs_all.get(index) {
let ns = metadata.get_string_from_index(type_def.namespace_index).unwrap_or_default();
let name = metadata.get_string_from_index(type_def.name_index)
.unwrap_or_else(|_| format!("Type_{index}"));
let ns_opt = if ns.is_empty() { None } else { Some(Cow::Owned(ns)) };
let mut td = DotNetTypeDef::new(ns_opt, Cow::Owned(name));
td.flags = TypeFlags::default();
td.flags.accessibility = type_accessibility_from_flags(type_def.flags);
td.flags.abstract_type = (type_def.flags & 0x80) != 0;
td.flags.sealed = (type_def.flags & 0x100) != 0;
if (type_def.flags & 0x20) != 0 {
td.flags.kind = dotnetdll::resolved::types::Kind::Interface;
}
td.flags.special_name = (type_def.flags & 0x400) != 0;
td.flags.serializable = (type_def.flags & 0x2000) != 0;
td.flags.before_field_init = (type_def.flags & 0x0010_0000) != 0;
td.flags.runtime_special_name = (type_def.flags & 0x800) != 0;
let dotnet_idx = resolution.type_definitions.len();
resolution.type_definitions.push(td);
type_map.insert(index, dotnet_idx);
}
}
let attr_ctors = setup_il2cpp_dummy_dll_refs(&mut resolution);
let ctx = DummyDllContext {
type_map: type_map.clone(),
mscorlib_ref,
void_type_ref,
};
for index in type_start..type_end {
if let Some(type_def) = type_defs_all.get(index) {
if let Some(&dotnet_idx) = type_map.get(&index) {
for i in 0..type_def.nested_type_count as usize {
if let Some(&nested_il2cpp) = nested_type_indices.get(type_def.nested_types_start as usize + i) {
if let Some(&nested_dotnet) = type_map.get(&(nested_il2cpp as usize)) {
if let Some(type_idx) = resolution.type_definition_index(dotnet_idx) {
resolution.type_definitions[nested_dotnet].encloser = Some(type_idx);
}
}
}
}
}
}
}
for index in type_start..type_end {
if let Some(type_def) = type_defs_all.get(index).cloned() {
if let Some(&dotnet_idx) = type_map.get(&index) {
if type_def.generic_container_index >= 0 {
if let Some(gc) = generic_containers.get(type_def.generic_container_index as usize) {
for i in 0..gc.type_argc as usize {
let gp_idx = gc.generic_parameter_start as usize + i;
if let Some(gp) = generic_parameters.get(gp_idx) {
let gp_name = metadata.get_string_from_index(gp.name_index as i32)
.unwrap_or_else(|_| format!("T{i}"));
let mut gen_param: generic::Type<'_> =
generic::Generic::new(Cow::Owned(gp_name));
gen_param.special_constraint.reference_type = (gp.flags & 0x04) != 0;
gen_param.special_constraint.value_type = (gp.flags & 0x08) != 0;
gen_param.special_constraint.has_default_constructor = (gp.flags & 0x10) != 0;
match gp.flags & 0x03 {
0x01 => gen_param.variance = generic::Variance::Covariant,
0x02 => gen_param.variance = generic::Variance::Contravariant,
_ => {}
}
for ci in 0..gp.constraints_count as usize {
let constraint_type_idx = constraint_indices
.get(gp.constraints_start as usize + ci)
.copied()
.unwrap_or(-1);
if constraint_type_idx >= 0 {
if let Some(ct) = types_all.get(constraint_type_idx as usize) {
let ctype = il2cpp_type_to_member(
ct, &types_all, &type_map, &mut resolution, &ctx,
);
gen_param.type_constraints.push(generic::Constraint {
attributes: vec![],
custom_modifiers: vec![],
constraint_type: ctype,
});
}
}
}
resolution.type_definitions[dotnet_idx].generic_parameters.push(gen_param);
}
}
}
}
if type_def.parent_index >= 0 {
if let Some(parent_type) = types_all.get(type_def.parent_index as usize) {
let parent_source = il2cpp_type_to_type_source(
parent_type, &types_all, &type_map, &mut resolution, &ctx,
);
if let Some(src) = parent_source {
resolution.type_definitions[dotnet_idx].extends = Some(src);
}
}
}
for i in 0..type_def.interfaces_count as usize {
if let Some(&iface_type_idx) = interface_indices.get(type_def.interfaces_start as usize + i) {
if let Some(iface_type) = types_all.get(iface_type_idx as usize) {
let iface_source = il2cpp_type_to_type_source(
iface_type, &types_all, &type_map, &mut resolution, &ctx,
);
if let Some(src) = iface_source {
resolution.type_definitions[dotnet_idx].implements.push((vec![], src));
}
}
}
}
}
}
}
let mut multicast_delegate_set: HashMap<usize, bool> = HashMap::new();
for index in type_start..type_end {
if let Some(type_def) = type_defs_all.get(index) {
let is_mcd = if type_def.parent_index >= 0 {
if let Some(parent_type) = types_all.get(type_def.parent_index as usize) {
if let Some(td) = get_type_def_from_il2cpp_type(parent_type, &type_defs_all) {
let ns = metadata.get_string_from_index(td.namespace_index).unwrap_or_default();
let name = metadata.get_string_from_index(td.name_index).unwrap_or_default();
ns == "System" && name == "MulticastDelegate"
} else {
false
}
} else {
false
}
} else {
false
};
multicast_delegate_set.insert(index, is_mcd);
}
}
for index in type_start..type_end {
if let Some(type_def) = type_defs_all.get(index).cloned() {
if let Some(&dotnet_idx) = type_map.get(&index) {
let skip_body = *multicast_delegate_set.get(&index).unwrap_or(&false);
let token_attr = make_named_string_attr(
attr_ctors.token_ctor,
vec![("Token", format!("0x{:X}", type_def.token))],
);
resolution.type_definitions[dotnet_idx].attributes.push(token_attr);
let field_end = type_def.field_start + type_def.field_count as i32;
for fi in type_def.field_start..field_end {
if let Some(field_def) = field_defs_all.get(fi as usize) {
let fname = metadata.get_string_from_index(field_def.name_index)
.unwrap_or_else(|_| format!("field_{fi}"));
let ft = types_all.get(field_def.type_index as usize);
let member_type = ft
.map(|t| il2cpp_type_to_member(t, &types_all, &type_map, &mut resolution, &ctx))
.unwrap_or_else(member_object);
let field_attrs = ft.map(|t| t.attrs).unwrap_or(0x6);
let access = field_accessibility_from_attrs(field_attrs);
let is_static = (field_attrs & 0x10) != 0;
let is_literal = (field_attrs & 0x40) != 0;
let is_init_only = (field_attrs & 0x20) != 0;
let mut field = Field::new(is_static, access, Cow::Owned(fname), member_type);
field.literal = is_literal;
field.init_only = is_init_only;
if let Some(fdv) = metadata.get_field_default_value(fi) {
if fdv.data_index != -1 {
match executor.try_get_default_value(
fdv.type_index, fdv.data_index, metadata, il2cpp,
) {
Ok(dv) => {
field.default = Some(default_value_to_constant(&dv));
}
Err(_) => {}
}
}
}
field.attributes.push(make_named_string_attr(
attr_ctors.token_ctor,
vec![("Token", format!("0x{:X}", field_def.token))],
));
if !is_literal {
let ft_enum = ft.map(|t| Il2CppTypeEnum::from_u8(t.type_enum));
let is_vt = matches!(ft_enum, Some(Some(Il2CppTypeEnum::ValueType)));
let field_offset = il2cpp.get_field_offset_from_index(
index, (fi - type_def.field_start) as usize, fi as usize,
is_vt, is_static,
);
if field_offset >= 0 {
field.attributes.push(make_named_string_attr(
attr_ctors.field_offset_ctor,
vec![("Offset", format!("0x{:X}", field_offset))],
));
}
}
resolution.type_definitions[dotnet_idx].fields.push(field);
}
}
let method_end = type_def.method_start + type_def.method_count as i32;
for mi in type_def.method_start..method_end {
if let Some(method_def) = method_defs_all.get(mi as usize).cloned() {
let mname = metadata.get_string_from_index(method_def.name_index as i32)
.unwrap_or_else(|_| format!("method_{mi}"));
let ret_il2cpp = types_all.get(method_def.return_type as usize);
let ret_type = ret_il2cpp
.map(|t| il2cpp_type_to_return(t, &types_all, &type_map, &mut resolution, &ctx))
.unwrap_or(ReturnType::VOID);
let mut params = Vec::new();
let mut param_metadata = Vec::new();
for j in 0..method_def.parameter_count as usize {
let pidx = method_def.parameter_start as usize + j;
if let Some(pdef) = parameter_defs_all.get(pidx) {
let pt = types_all.get(pdef.type_index as usize);
let ptype = pt
.map(|t| il2cpp_type_to_method_type(t, &types_all, &type_map, &mut resolution, &ctx))
.unwrap_or_else(method_object);
let is_byref = pt.map(|t| t.byref == 1).unwrap_or(false);
if is_byref {
params.push(Parameter::reference(ptype));
} else {
params.push(Parameter::value(ptype));
}
let pname = metadata.get_string_from_index(pdef.name_index)
.unwrap_or_else(|_| format!("param{j}"));
let mut pmeta = ParameterMetadata::name(Cow::Owned(pname));
pmeta.is_in = (pt.map(|t| t.attrs).unwrap_or(0) & 0x0001) != 0;
pmeta.is_out = (pt.map(|t| t.attrs).unwrap_or(0) & 0x0002) != 0;
pmeta.optional = (pt.map(|t| t.attrs).unwrap_or(0) & 0x0010) != 0;
if let Some(pdv) = metadata.get_parameter_default_value(pidx as i32) {
if pdv.data_index != -1 {
match executor.try_get_default_value(
pdv.type_index, pdv.data_index, metadata, il2cpp,
) {
Ok(dv) => {
pmeta.default = Some(default_value_to_constant(&dv));
}
Err(_) => {}
}
}
}
param_metadata.push(Some(pmeta));
}
}
let is_static = (method_def.flags & 0x10) != 0;
let is_abstract = (method_def.flags & 0x0400) != 0;
let is_virtual = (method_def.flags & 0x0040) != 0;
let is_final = (method_def.flags & 0x0020) != 0;
let is_hide_by_sig = (method_def.flags & 0x0080) != 0;
let is_special_name = (method_def.flags & 0x0800) != 0;
let is_rt_special_name = (method_def.flags & 0x1000) != 0;
let has_new_slot = (method_def.flags & 0x0100) != 0;
let sig = if is_static {
MethodSignature::static_member(ret_type.clone(), params)
} else {
MethodSignature::new(true, ret_type.clone(), params)
};
let has_body = !is_abstract && !skip_body;
let method_body = if has_body {
let is_void = ret_il2cpp
.map(|t| Il2CppTypeEnum::from_u8(t.type_enum) == Some(Il2CppTypeEnum::Void))
.unwrap_or(true);
if is_void {
Some(body::Method::new(vec![Instruction::Return]))
} else {
let is_value = ret_il2cpp
.map(|t| {
let te = Il2CppTypeEnum::from_u8(t.type_enum);
matches!(te,
Some(Il2CppTypeEnum::ValueType)
| Some(Il2CppTypeEnum::Boolean)
| Some(Il2CppTypeEnum::Char)
| Some(Il2CppTypeEnum::I1)
| Some(Il2CppTypeEnum::U1)
| Some(Il2CppTypeEnum::I2)
| Some(Il2CppTypeEnum::U2)
| Some(Il2CppTypeEnum::I4)
| Some(Il2CppTypeEnum::U4)
| Some(Il2CppTypeEnum::I8)
| Some(Il2CppTypeEnum::U8)
| Some(Il2CppTypeEnum::R4)
| Some(Il2CppTypeEnum::R8)
| Some(Il2CppTypeEnum::I)
| Some(Il2CppTypeEnum::U)
)
})
.unwrap_or(false);
if is_value {
Some(body::Method::new(vec![
Instruction::LoadConstantInt32(0),
Instruction::Return,
]))
} else {
Some(body::Method::new(vec![
Instruction::LoadNull,
Instruction::Return,
]))
}
}
} else {
None
};
let access = member_accessibility_from_flags(method_def.flags);
let mut method = Method::new(access, sig, Cow::Owned(mname), method_body);
method.abstract_member = is_abstract;
method.virtual_member = is_virtual;
method.sealed = is_final;
method.hide_by_sig = is_hide_by_sig;
method.special_name = is_special_name;
method.runtime_special_name = is_rt_special_name;
method.parameter_metadata = param_metadata;
if has_new_slot {
method.vtable_layout = members::VtableLayout::NewSlot;
}
method.attributes.push(make_named_string_attr(
attr_ctors.token_ctor,
vec![("Token", format!("0x{:X}", method_def.token))],
));
if !is_abstract {
let method_pointer = il2cpp.get_method_pointer(&image_name, &method_def);
if method_pointer > 0 {
let rva = il2cpp.get_rva(method_pointer);
let offset = il2cpp.map_vatr(method_pointer).unwrap_or(0);
let mut addr_fields = vec![
("RVA", format!("0x{:X}", rva)),
("Offset", format!("0x{:X}", offset)),
("VA", format!("0x{:X}", method_pointer)),
];
if method_def.slot != 0xFFFF {
addr_fields.push(("Slot", method_def.slot.to_string()));
}
method.attributes.push(make_named_string_attr(
attr_ctors.address_ctor,
addr_fields,
));
}
}
if method_def.generic_container_index >= 0 {
if let Some(gc) = generic_containers.get(method_def.generic_container_index as usize) {
for gi in 0..gc.type_argc as usize {
let gp_idx = gc.generic_parameter_start as usize + gi;
if let Some(gp) = generic_parameters.get(gp_idx) {
let gp_name = metadata.get_string_from_index(gp.name_index as i32)
.unwrap_or_else(|_| format!("M{gi}"));
let mut gen_param: generic::Method<'_> =
generic::Generic::new(Cow::Owned(gp_name));
gen_param.special_constraint.reference_type = (gp.flags & 0x04) != 0;
gen_param.special_constraint.value_type = (gp.flags & 0x08) != 0;
gen_param.special_constraint.has_default_constructor = (gp.flags & 0x10) != 0;
match gp.flags & 0x03 {
0x01 => gen_param.variance = generic::Variance::Covariant,
0x02 => gen_param.variance = generic::Variance::Contravariant,
_ => {}
}
method.generic_parameters.push(gen_param);
}
}
}
}
resolution.type_definitions[dotnet_idx].methods.push(method);
}
}
let prop_end = type_def.property_start + type_def.property_count as i32;
for pi in type_def.property_start..prop_end {
if let Some(prop_def) = property_defs_all.get(pi as usize) {
let pname = metadata.get_string_from_index(prop_def.name_index)
.unwrap_or_else(|_| format!("property_{pi}"));
let prop_type: MemberType = if prop_def.get >= 0 {
let gm_idx = (type_def.method_start + prop_def.get) as usize;
method_defs_all.get(gm_idx)
.and_then(|gm| types_all.get(gm.return_type as usize))
.map(|t| il2cpp_type_to_member(t, &types_all, &type_map, &mut resolution, &ctx))
.unwrap_or_else(member_object)
} else if prop_def.set >= 0 {
let sm_idx = (type_def.method_start + prop_def.set) as usize;
method_defs_all.get(sm_idx)
.and_then(|sm| parameter_defs_all.get(sm.parameter_start as usize))
.and_then(|p| types_all.get(p.type_index as usize))
.map(|t| il2cpp_type_to_member(t, &types_all, &type_map, &mut resolution, &ctx))
.unwrap_or_else(member_object)
} else {
member_object()
};
let is_static_prop = if prop_def.get >= 0 {
let gm_idx = (type_def.method_start + prop_def.get) as usize;
method_defs_all.get(gm_idx)
.map(|m| (m.flags & 0x10) != 0)
.unwrap_or(false)
} else if prop_def.set >= 0 {
let sm_idx = (type_def.method_start + prop_def.set) as usize;
method_defs_all.get(sm_idx)
.map(|m| (m.flags & 0x10) != 0)
.unwrap_or(false)
} else {
false
};
let mut property = Property::new(
is_static_prop,
Cow::Owned(pname),
Parameter::value(prop_type),
);
property.special_name = (prop_def.attrs & 0x0200) != 0;
property.runtime_special_name = (prop_def.attrs & 0x0400) != 0;
resolution.type_definitions[dotnet_idx].properties.push(property);
}
}
let event_end = type_def.event_start + type_def.event_count as i32;
for ei in type_def.event_start..event_end {
if let Some(event_def) = event_defs_all.get(ei as usize) {
let ename = metadata.get_string_from_index(event_def.name_index)
.unwrap_or_else(|_| format!("event_{ei}"));
let etype = types_all.get(event_def.type_index as usize)
.map(|t| il2cpp_type_to_member(t, &types_all, &type_map, &mut resolution, &ctx))
.unwrap_or_else(member_object);
let add_method = if event_def.add >= 0 {
let add_mi = (type_def.method_start + event_def.add) as usize;
if let Some(md) = method_defs_all.get(add_mi) {
let access = member_accessibility_from_flags(md.flags);
let is_static_event = (md.flags & 0x10) != 0;
let sig = if is_static_event {
MethodSignature::static_member(
ReturnType::VOID,
vec![Parameter::value(method_type_from_member(&etype))],
)
} else {
MethodSignature::new(
true,
ReturnType::VOID,
vec![Parameter::value(method_type_from_member(&etype))],
)
};
let add_name = metadata.get_string_from_index(md.name_index as i32)
.unwrap_or_else(|_| format!("add_{ename}"));
Method::new(
access,
sig,
Cow::Owned(add_name),
if !skip_body { Some(body::Method::new(vec![Instruction::Return])) } else { None },
)
} else {
make_stub_event_method(&format!("add_{ename}"), &etype, skip_body)
}
} else {
make_stub_event_method(&format!("add_{ename}"), &etype, skip_body)
};
let remove_method = if event_def.remove >= 0 {
let rm_mi = (type_def.method_start + event_def.remove) as usize;
if let Some(md) = method_defs_all.get(rm_mi) {
let access = member_accessibility_from_flags(md.flags);
let is_static_event = (md.flags & 0x10) != 0;
let sig = if is_static_event {
MethodSignature::static_member(
ReturnType::VOID,
vec![Parameter::value(method_type_from_member(&etype))],
)
} else {
MethodSignature::new(
true,
ReturnType::VOID,
vec![Parameter::value(method_type_from_member(&etype))],
)
};
let rm_name = metadata.get_string_from_index(md.name_index as i32)
.unwrap_or_else(|_| format!("remove_{ename}"));
Method::new(
access,
sig,
Cow::Owned(rm_name),
if !skip_body { Some(body::Method::new(vec![Instruction::Return])) } else { None },
)
} else {
make_stub_event_method(&format!("remove_{ename}"), &etype, skip_body)
}
} else {
make_stub_event_method(&format!("remove_{ename}"), &etype, skip_body)
};
let mut event = Event::new(
Cow::Owned(ename),
etype,
add_method,
remove_method,
);
event.special_name = (event_def.type_index as u32 & 0x0200) != 0;
resolution.type_definitions[dotnet_idx].events.push(event);
}
}
}
}
}
if metadata.version > 20.0 {
for index in type_start..type_end {
if let Some(type_def) = type_defs_all.get(index).cloned() {
if let Some(&dotnet_idx) = type_map.get(&index) {
add_attribute_attributes(
metadata, il2cpp, executor, img_idx,
type_def.custom_attribute_index, type_def.token,
&type_defs_all, &attr_ctors,
&mut resolution.type_definitions[dotnet_idx].attributes,
);
let field_end = type_def.field_start + type_def.field_count as i32;
for fi in type_def.field_start..field_end {
if let Some(field_def) = field_defs_all.get(fi as usize) {
let fi_in_type = (fi - type_def.field_start) as usize;
if fi_in_type < resolution.type_definitions[dotnet_idx].fields.len() {
add_attribute_attributes(
metadata, il2cpp, executor, img_idx,
field_def.custom_attribute_index, field_def.token,
&type_defs_all, &attr_ctors,
&mut resolution.type_definitions[dotnet_idx].fields[fi_in_type].attributes,
);
}
}
}
let method_end = type_def.method_start + type_def.method_count as i32;
for mi in type_def.method_start..method_end {
if let Some(method_def) = method_defs_all.get(mi as usize) {
let mi_in_type = (mi - type_def.method_start) as usize;
if mi_in_type < resolution.type_definitions[dotnet_idx].methods.len() {
add_attribute_attributes(
metadata, il2cpp, executor, img_idx,
method_def.custom_attribute_index, method_def.token,
&type_defs_all, &attr_ctors,
&mut resolution.type_definitions[dotnet_idx].methods[mi_in_type].attributes,
);
}
}
}
}
}
}
}
let dll_name = if image_name.ends_with(".dll") {
image_name.clone()
} else {
format!("{image_name}.dll")
};
match resolution.write(Default::default()) {
Ok(bytes) => {
dll_outputs.push((dll_name, bytes));
}
Err(e) => {
eprintln!("WARNING: Failed to serialize {dll_name}: {e:?}");
}
}
}
use rayon::prelude::*;
dll_outputs.par_iter().for_each(|(name, bytes)| {
let dll_path = dummy_dir.join(name);
if let Err(e) = fs::write(&dll_path, bytes) {
eprintln!("WARNING: Failed to write {name}: {e}");
}
});
generate_il2cpp_dummy_dll(&dummy_dir)?;
Ok(())
}
fn make_stub_event_method<'a>(name: &str, etype: &MemberType, skip_body: bool) -> Method<'a> {
let sig = MethodSignature::new(
true,
ReturnType::VOID,
vec![Parameter::value(method_type_from_member(etype))],
);
Method::new(
dotnetdll::resolved::Accessibility::Public,
sig,
Cow::Owned(name.to_string()),
if !skip_body { Some(body::Method::new(vec![Instruction::Return])) } else { None },
)
}
fn add_attribute_attributes<'a>(
metadata: &mut Metadata,
il2cpp: &Il2Cpp,
executor: &Il2CppExecutor,
image_index: usize,
custom_attribute_index: i32,
token: u32,
type_defs_all: &[Il2CppTypeDefinition],
attr_ctors: &AttrCtors,
target_attrs: &mut Vec<Attribute<'a>>,
) {
let attr_idx = match metadata.get_custom_attribute_index(image_index, custom_attribute_index, token) {
Some(idx) => idx,
None => return,
};
if metadata.version < 29.0 {
if attr_idx >= metadata.attribute_type_ranges.len() {
return;
}
let range = &metadata.attribute_type_ranges[attr_idx];
let range_start = range.start as usize;
let range_count = range.count as usize;
for i in 0..range_count {
let type_idx = match metadata.attribute_types.get(range_start + i) {
Some(&idx) => idx,
None => continue,
};
if type_idx < 0 || (type_idx as usize) >= il2cpp.types.len() {
continue;
}
let attr_type = &il2cpp.types[type_idx as usize];
let type_name = get_type_name_from_il2cpp_type(attr_type, metadata, type_defs_all);
let method_pointer = if attr_idx < executor.custom_attribute_generators.len() {
executor.custom_attribute_generators[attr_idx]
} else {
0
};
let mut fields = vec![("Name", type_name)];
if method_pointer > 0 {
let rva = il2cpp.get_rva(method_pointer);
let offset = il2cpp.map_vatr(method_pointer).unwrap_or(0);
fields.push(("RVA", format!("0x{:X}", rva)));
fields.push(("Offset", format!("0x{:X}", offset)));
}
target_attrs.push(make_named_string_attr(attr_ctors.attribute_ctor, fields));
}
} else {
let start_range = match metadata.attribute_data_ranges.get(attr_idx) {
Some(r) => r.clone(),
None => return,
};
let end_range = match metadata.attribute_data_ranges.get(attr_idx + 1) {
Some(r) => r.clone(),
None => return,
};
if end_range.start_offset <= start_range.start_offset {
return;
}
let data_offset = metadata.header.attribute_data_offset as u64 + start_range.start_offset as u64;
let data_size = (end_range.start_offset - start_range.start_offset) as usize;
if data_size == 0 || data_size > 1024 * 1024 {
return;
}
metadata.stream.set_position(data_offset);
let data = match metadata.stream.read_bytes(data_size) {
Ok(d) => d,
Err(_) => return,
};
let mut reader = match crate::executor::custom_attribute_reader::CustomAttributeDataReader::new(data) {
Ok(r) => r,
Err(_) => return,
};
if reader.count == 0 {
return;
}
for _ in 0..reader.count {
match reader.get_ctor_type_name(metadata) {
Ok(type_name) => {
let fields = vec![("Name", type_name)];
target_attrs.push(make_named_string_attr(attr_ctors.attribute_ctor, fields));
}
Err(_) => break,
}
}
}
}
fn get_type_name_from_il2cpp_type(
il2cpp_type: &Il2CppType,
metadata: &mut Metadata,
type_defs: &[Il2CppTypeDefinition],
) -> String {
let te = Il2CppTypeEnum::from_u8(il2cpp_type.type_enum);
match te {
Some(Il2CppTypeEnum::Class) | Some(Il2CppTypeEnum::ValueType) => {
let td_idx = il2cpp_type.datapoint as usize;
if let Some(td) = type_defs.get(td_idx) {
let ns = metadata.get_string_from_index(td.namespace_index).unwrap_or_default();
let name = metadata.get_string_from_index(td.name_index).unwrap_or_default();
if ns.is_empty() { name } else { format!("{ns}.{name}") }
} else {
format!("Type_{td_idx}")
}
}
_ => format!("il2cpp_type_{}", il2cpp_type.type_enum),
}
}
fn generate_il2cpp_dummy_dll(dummy_dir: &Path) -> Result<()> {
let mut resolution = Resolution::new(Module::new("Il2CppDummyDll.dll"));
resolution.assembly = Some(dotnetdll::resolved::assembly::Assembly::new("Il2CppDummyDll"));
resolution.type_definitions.clear();
let mscorlib_ref = resolution.push_assembly_reference(
ExternalAssemblyReference::new("mscorlib"),
);
let attribute_base_ref = resolution.push_type_reference(
ExternalTypeReference::new(
Some(Cow::Borrowed("System")),
"Attribute",
ResolutionScope::Assembly(mscorlib_ref),
),
);
let attribute_base = TypeSource::User(UserType::Reference(attribute_base_ref));
let string_type = MemberType::Base(Box::new(BaseType::String));
let attr_types = &[
("AddressAttribute", vec!["RVA", "Offset", "VA", "Slot"]),
("FieldOffsetAttribute", vec!["Offset"]),
("TokenAttribute", vec!["Token"]),
("AttributeAttribute", vec!["Name", "RVA", "Offset"]),
("MetadataOffsetAttribute", vec!["Offset"]),
];
for (name, fields) in attr_types {
let mut td = DotNetTypeDef::new(None, Cow::Owned(name.to_string()));
td.extends = Some(attribute_base.clone());
td.flags.accessibility = TypeAccessibility::Public;
td.flags.before_field_init = true;
for field_name in fields {
let field = Field::new(
false,
dotnetdll::resolved::Accessibility::Public,
Cow::Owned(field_name.to_string()),
string_type.clone(),
);
td.fields.push(field);
}
let ctor = Method::constructor(
dotnetdll::resolved::Accessibility::Public,
vec![],
Some(body::Method::new(vec![Instruction::Return])),
);
td.methods.push(ctor);
resolution.type_definitions.push(td);
}
let dll_path = dummy_dir.join("Il2CppDummyDll.dll");
match resolution.write(Default::default()) {
Ok(bytes) => {
if let Err(e) = fs::write(&dll_path, &bytes) {
eprintln!("WARNING: Failed to write Il2CppDummyDll.dll: {e}");
}
}
Err(e) => {
eprintln!("WARNING: Failed to serialize Il2CppDummyDll.dll: {e:?}");
}
}
Ok(())
}
fn default_value_to_constant(dv: &crate::executor::il2cpp_executor::DefaultValue) -> members::Constant {
use crate::executor::il2cpp_executor::DefaultValue;
match dv {
DefaultValue::Bool(v) => members::Constant::Boolean(*v),
DefaultValue::U8(v) => members::Constant::UInt8(*v),
DefaultValue::I8(v) => members::Constant::Int8(*v),
DefaultValue::Char(v) => members::Constant::Char(*v as u16),
DefaultValue::U16(v) => members::Constant::UInt16(*v),
DefaultValue::I16(v) => members::Constant::Int16(*v),
DefaultValue::U32(v) => members::Constant::UInt32(*v),
DefaultValue::I32(v) => members::Constant::Int32(*v),
DefaultValue::U64(v) => members::Constant::UInt64(*v),
DefaultValue::I64(v) => members::Constant::Int64(*v),
DefaultValue::F32(v) => members::Constant::Float32(*v),
DefaultValue::F64(v) => members::Constant::Float64(*v),
DefaultValue::String(v) => members::Constant::String(v.encode_utf16().collect()),
DefaultValue::Null => members::Constant::Null,
}
}
fn member_object() -> MemberType {
MemberType::Base(Box::new(BaseType::Object))
}
fn method_object() -> MethodType {
MethodType::Base(Box::new(BaseType::Object))
}
fn method_type_from_member(m: &MemberType) -> MethodType {
match m {
MemberType::Base(b) => MethodType::Base(Box::new(base_member_to_method(b))),
MemberType::TypeGeneric(i) => MethodType::TypeGeneric(*i),
}
}
fn base_member_to_method(b: &BaseType<MemberType>) -> BaseType<MethodType> {
match b {
BaseType::Type { value_kind, source } => BaseType::Type {
value_kind: *value_kind,
source: match source {
TypeSource::User(u) => TypeSource::User(*u),
TypeSource::Generic { base, parameters } => TypeSource::Generic {
base: *base,
parameters: parameters.iter().map(|p| method_type_from_member(p)).collect(),
},
},
},
BaseType::Boolean => BaseType::Boolean,
BaseType::Char => BaseType::Char,
BaseType::Int8 => BaseType::Int8,
BaseType::UInt8 => BaseType::UInt8,
BaseType::Int16 => BaseType::Int16,
BaseType::UInt16 => BaseType::UInt16,
BaseType::Int32 => BaseType::Int32,
BaseType::UInt32 => BaseType::UInt32,
BaseType::Int64 => BaseType::Int64,
BaseType::UInt64 => BaseType::UInt64,
BaseType::Float32 => BaseType::Float32,
BaseType::Float64 => BaseType::Float64,
BaseType::IntPtr => BaseType::IntPtr,
BaseType::UIntPtr => BaseType::UIntPtr,
BaseType::Object => BaseType::Object,
BaseType::String => BaseType::String,
BaseType::Vector(mods, inner) => {
BaseType::Vector(mods.clone(), method_type_from_member(inner))
}
BaseType::ValuePointer(mods, opt) => {
BaseType::ValuePointer(mods.clone(), opt.as_ref().map(method_type_from_member))
}
_ => BaseType::Object,
}
}
fn get_type_def_from_il2cpp_type<'a>(
il2cpp_type: &Il2CppType,
type_defs: &'a [Il2CppTypeDefinition],
) -> Option<&'a Il2CppTypeDefinition> {
let type_enum = Il2CppTypeEnum::from_u8(il2cpp_type.type_enum);
match type_enum {
Some(Il2CppTypeEnum::Class) | Some(Il2CppTypeEnum::ValueType) => {
type_defs.get(il2cpp_type.datapoint as usize)
}
_ => None,
}
}
fn resolve_type_source_for_typedef(
td_index: usize,
type_map: &HashMap<usize, usize>,
resolution: &mut Resolution<'_>,
ctx: &DummyDllContext,
_is_valuetype: bool,
) -> TypeSource<MemberType> {
if let Some(&dotnet_idx) = type_map.get(&td_index) {
if let Some(type_idx) = resolution.type_definition_index(dotnet_idx) {
return TypeSource::User(UserType::Definition(type_idx));
}
}
let type_ref = resolution.push_type_reference(
ExternalTypeReference::new(
Some(Cow::Owned(String::new())),
format!("__External_{td_index}"),
ResolutionScope::Assembly(ctx.mscorlib_ref),
),
);
TypeSource::User(UserType::Reference(type_ref))
}
fn il2cpp_type_to_type_source(
il2cpp_type: &Il2CppType,
types: &[Il2CppType],
type_map: &HashMap<usize, usize>,
resolution: &mut Resolution<'_>,
ctx: &DummyDllContext,
) -> Option<TypeSource<MemberType>> {
let type_enum = Il2CppTypeEnum::from_u8(il2cpp_type.type_enum)?;
match type_enum {
Il2CppTypeEnum::Class | Il2CppTypeEnum::ValueType => {
let td_index = il2cpp_type.datapoint as usize;
Some(resolve_type_source_for_typedef(
td_index, type_map, resolution, ctx,
type_enum == Il2CppTypeEnum::ValueType,
))
}
_ => {
let mt = il2cpp_type_to_member(il2cpp_type, types, type_map, resolution, ctx);
match mt {
MemberType::Base(b) => match *b {
BaseType::Type { value_kind: _, source } => Some(source),
_ => None,
},
_ => None,
}
}
}
}
fn il2cpp_type_to_base_member(
il2cpp_type: &Il2CppType,
_types: &[Il2CppType],
type_map: &HashMap<usize, usize>,
resolution: &mut Resolution<'_>,
ctx: &DummyDllContext,
) -> BaseType<MemberType> {
let type_enum = Il2CppTypeEnum::from_u8(il2cpp_type.type_enum);
match type_enum {
Some(Il2CppTypeEnum::Boolean) => BaseType::Boolean,
Some(Il2CppTypeEnum::Char) => BaseType::Char,
Some(Il2CppTypeEnum::I1) => BaseType::Int8,
Some(Il2CppTypeEnum::U1) => BaseType::UInt8,
Some(Il2CppTypeEnum::I2) => BaseType::Int16,
Some(Il2CppTypeEnum::U2) => BaseType::UInt16,
Some(Il2CppTypeEnum::I4) => BaseType::Int32,
Some(Il2CppTypeEnum::U4) => BaseType::UInt32,
Some(Il2CppTypeEnum::I8) => BaseType::Int64,
Some(Il2CppTypeEnum::U8) => BaseType::UInt64,
Some(Il2CppTypeEnum::R4) => BaseType::Float32,
Some(Il2CppTypeEnum::R8) => BaseType::Float64,
Some(Il2CppTypeEnum::String) => BaseType::String,
Some(Il2CppTypeEnum::Object) => BaseType::Object,
Some(Il2CppTypeEnum::I) => BaseType::IntPtr,
Some(Il2CppTypeEnum::U) => BaseType::UIntPtr,
Some(Il2CppTypeEnum::Void) => BaseType::Object,
Some(Il2CppTypeEnum::Class) | Some(Il2CppTypeEnum::ValueType) => {
let td_index = il2cpp_type.datapoint as usize;
let vk = if type_enum == Some(Il2CppTypeEnum::ValueType) {
Some(ValueKind::ValueType)
} else {
Some(ValueKind::Class)
};
let source = resolve_type_source_for_typedef(td_index, type_map, resolution, ctx, vk == Some(ValueKind::ValueType));
BaseType::Type { value_kind: vk, source }
}
Some(Il2CppTypeEnum::SzArray) => {
BaseType::vector(member_object())
}
Some(Il2CppTypeEnum::Array) => {
BaseType::vector(member_object())
}
Some(Il2CppTypeEnum::Ptr) => {
BaseType::ValuePointer(vec![], None)
}
Some(Il2CppTypeEnum::GenericInst) => {
BaseType::Object
}
_ => BaseType::Object,
}
}
fn il2cpp_type_to_member(
il2cpp_type: &Il2CppType,
types: &[Il2CppType],
type_map: &HashMap<usize, usize>,
resolution: &mut Resolution<'_>,
ctx: &DummyDllContext,
) -> MemberType {
let type_enum = Il2CppTypeEnum::from_u8(il2cpp_type.type_enum);
if let Some(Il2CppTypeEnum::Var) = type_enum {
return MemberType::TypeGeneric(il2cpp_type.datapoint as usize);
}
MemberType::Base(Box::new(il2cpp_type_to_base_member(il2cpp_type, types, type_map, resolution, ctx)))
}
fn il2cpp_type_to_base_method(
il2cpp_type: &Il2CppType,
_types: &[Il2CppType],
type_map: &HashMap<usize, usize>,
resolution: &mut Resolution<'_>,
ctx: &DummyDllContext,
) -> BaseType<MethodType> {
let type_enum = Il2CppTypeEnum::from_u8(il2cpp_type.type_enum);
match type_enum {
Some(Il2CppTypeEnum::Boolean) => BaseType::Boolean,
Some(Il2CppTypeEnum::Char) => BaseType::Char,
Some(Il2CppTypeEnum::I1) => BaseType::Int8,
Some(Il2CppTypeEnum::U1) => BaseType::UInt8,
Some(Il2CppTypeEnum::I2) => BaseType::Int16,
Some(Il2CppTypeEnum::U2) => BaseType::UInt16,
Some(Il2CppTypeEnum::I4) => BaseType::Int32,
Some(Il2CppTypeEnum::U4) => BaseType::UInt32,
Some(Il2CppTypeEnum::I8) => BaseType::Int64,
Some(Il2CppTypeEnum::U8) => BaseType::UInt64,
Some(Il2CppTypeEnum::R4) => BaseType::Float32,
Some(Il2CppTypeEnum::R8) => BaseType::Float64,
Some(Il2CppTypeEnum::String) => BaseType::String,
Some(Il2CppTypeEnum::Object) => BaseType::Object,
Some(Il2CppTypeEnum::I) => BaseType::IntPtr,
Some(Il2CppTypeEnum::U) => BaseType::UIntPtr,
Some(Il2CppTypeEnum::Void) => BaseType::Object,
Some(Il2CppTypeEnum::Class) | Some(Il2CppTypeEnum::ValueType) => {
let td_index = il2cpp_type.datapoint as usize;
let vk = if type_enum == Some(Il2CppTypeEnum::ValueType) {
Some(ValueKind::ValueType)
} else {
Some(ValueKind::Class)
};
let source = resolve_type_source_for_typedef(td_index, type_map, resolution, ctx, vk == Some(ValueKind::ValueType));
let method_source = match source {
TypeSource::User(u) => TypeSource::User(u),
TypeSource::Generic { base, parameters } => TypeSource::Generic {
base,
parameters: parameters.into_iter().map(|p| method_type_from_member(&p)).collect(),
},
};
BaseType::Type { value_kind: vk, source: method_source }
}
Some(Il2CppTypeEnum::SzArray) => {
BaseType::vector(method_object())
}
Some(Il2CppTypeEnum::Array) => {
BaseType::vector(method_object())
}
Some(Il2CppTypeEnum::Ptr) => {
BaseType::ValuePointer(vec![], None)
}
_ => BaseType::Object,
}
}
fn il2cpp_type_to_method_type(
il2cpp_type: &Il2CppType,
types: &[Il2CppType],
type_map: &HashMap<usize, usize>,
resolution: &mut Resolution<'_>,
ctx: &DummyDllContext,
) -> MethodType {
let type_enum = Il2CppTypeEnum::from_u8(il2cpp_type.type_enum);
if let Some(Il2CppTypeEnum::Var) = type_enum {
return MethodType::TypeGeneric(il2cpp_type.datapoint as usize);
}
if let Some(Il2CppTypeEnum::MVar) = type_enum {
return MethodType::MethodGeneric(il2cpp_type.datapoint as usize);
}
MethodType::Base(Box::new(il2cpp_type_to_base_method(il2cpp_type, types, type_map, resolution, ctx)))
}
fn il2cpp_type_to_return(
il2cpp_type: &Il2CppType,
types: &[Il2CppType],
type_map: &HashMap<usize, usize>,
resolution: &mut Resolution<'_>,
ctx: &DummyDllContext,
) -> ReturnType<MethodType> {
let type_enum = Il2CppTypeEnum::from_u8(il2cpp_type.type_enum);
if let Some(Il2CppTypeEnum::Void) = type_enum {
return ReturnType::VOID;
}
let mt = il2cpp_type_to_method_type(il2cpp_type, types, type_map, resolution, ctx);
if il2cpp_type.byref == 1 {
ReturnType::reference(mt)
} else {
ReturnType::value(mt)
}
}