use std::collections::{HashMap, HashSet};
use std::fmt::Write;
use std::fs;
use std::path::Path;
use crate::error::Result;
use crate::il2cpp::base::*;
use crate::il2cpp::metadata::Metadata;
use crate::il2cpp::enums::*;
use crate::il2cpp::structures::*;
use crate::executor::Il2CppExecutor;
use super::struct_generator::StructGenerator;
pub struct CppScaffolding;
struct FuncEntry {
rva: u64,
return_type: String,
func_name: String,
params: String,
method_info_rva: Option<u64>,
}
impl CppScaffolding {
pub fn build(
executor: &mut Il2CppExecutor,
metadata: &mut Metadata,
il2cpp: &mut Il2Cpp,
) -> Result<String> {
let struct_name_dic = StructGenerator::build_struct_name_dic_pub(executor, metadata, il2cpp);
let type_def_image_names = StructGenerator::build_type_def_image_names_pub(metadata);
let mut entries: Vec<FuncEntry> = Vec::new();
let mut seen_rvas: HashSet<u64> = HashSet::new();
let mut method_info_map: HashMap<(usize, usize), u64> = HashMap::new();
Self::collect_method_info_addresses(
&mut method_info_map, executor, metadata, il2cpp, &type_def_image_names,
);
let image_defs = metadata.image_defs.clone();
for image_def in &image_defs {
let image_name = metadata.get_string_from_index(image_def.name_index).unwrap_or_default();
let type_end = image_def.type_start as usize + image_def.type_count as usize;
for type_def_index in image_def.type_start as usize..type_end {
let type_def = metadata.type_defs[type_def_index].clone();
let type_name = executor.get_type_def_name(&type_def, type_def_index, metadata, il2cpp, true, true);
let method_end = type_def.method_start as usize + type_def.method_count as usize;
for method_index in type_def.method_start as usize..method_end {
let method_def = metadata.method_defs[method_index].clone();
let method_name_raw = metadata.get_string_from_index(method_def.name_index as i32).unwrap_or_default();
let method_pointer = il2cpp.get_method_pointer(&image_name, &method_def);
let mi_rva = method_info_map.get(&(type_def_index, method_index)).copied();
if method_pointer > 0 {
let rva = il2cpp.get_rva(method_pointer);
if !seen_rvas.insert(rva) { continue; }
let func_name = format!("{}$${}", fix_name_scaffold(&type_name), fix_name_scaffold(&method_name_raw));
let (return_type, params) = Self::build_func_signature(
executor, metadata, il2cpp, &method_def, &type_def,
type_def_index, &struct_name_dic, None,
);
entries.push(FuncEntry {
rva,
return_type,
func_name,
params,
method_info_rva: mi_rva,
});
} else if mi_rva.is_some() {
let func_name = format!("{}$${}", fix_name_scaffold(&type_name), fix_name_scaffold(&method_name_raw));
let (return_type, params) = Self::build_func_signature(
executor, metadata, il2cpp, &method_def, &type_def,
type_def_index, &struct_name_dic, None,
);
entries.push(FuncEntry {
rva: 0,
return_type,
func_name,
params,
method_info_rva: mi_rva,
});
}
if let Some(spec_indices) = il2cpp.method_definition_method_specs.get(&method_index).cloned() {
for spec_idx in &spec_indices {
let spec_ptr = il2cpp.method_spec_generic_method_pointers.get(spec_idx).copied().unwrap_or(0);
if spec_ptr == 0 { continue; }
let spec_rva = il2cpp.get_rva(spec_ptr);
if !seen_rvas.insert(spec_rva) { continue; }
let (spec_type_name, spec_method_name) = executor.get_method_spec_name(*spec_idx, metadata, il2cpp, true);
let func_name = format!("{}$${}", fix_name_scaffold(&spec_type_name), fix_name_scaffold(&spec_method_name));
let (class_inst, method_inst) = executor.get_method_spec_generic_context(*spec_idx, il2cpp);
let generic_context = Il2CppGenericContext { class_inst, method_inst };
let (return_type, params) = Self::build_func_signature(
executor, metadata, il2cpp, &method_def, &type_def,
type_def_index, &struct_name_dic, Some(&generic_context),
);
entries.push(FuncEntry {
rva: spec_rva,
return_type,
func_name,
params,
method_info_rva: None,
});
}
}
}
}
}
let mut header = String::with_capacity(entries.len() * 120 + 2048);
writeln!(header, "// IL2CPP Function Pointers").ok();
writeln!(header, "// Generated by Il2CppDumper - Rust Edition").ok();
writeln!(header, "//").ok();
writeln!(header, "// Usage: #include this file after setting il2cpp_base to the base address").ok();
writeln!(header, "// of libil2cpp.so in memory.").ok();
writeln!(header, "").ok();
writeln!(header, "#pragma once").ok();
writeln!(header, "").ok();
writeln!(header, "#ifndef DO_APP_FUNC").ok();
writeln!(header, "#define DO_APP_FUNC(offset, rettype, name, args) \\").ok();
writeln!(header, " typedef rettype (*name##_t) args; \\").ok();
writeln!(header, " static name##_t name = (name##_t)(il2cpp_base + offset);").ok();
writeln!(header, "#endif").ok();
writeln!(header, "").ok();
writeln!(header, "#ifndef DO_APP_FUNC_METHODINFO").ok();
writeln!(header, "#define DO_APP_FUNC_METHODINFO(offset, name) \\").ok();
writeln!(header, " static struct MethodInfo** name = (struct MethodInfo**)(il2cpp_base + offset);").ok();
writeln!(header, "#endif").ok();
writeln!(header, "").ok();
writeln!(header, "// ******************************************************************************").ok();
writeln!(header, "// * Application method definitions").ok();
writeln!(header, "// ******************************************************************************").ok();
writeln!(header, "").ok();
for entry in &entries {
if entry.rva > 0 {
writeln!(header, "DO_APP_FUNC(0x{:X}, {}, {}, ({}));",
entry.rva,
entry.return_type,
entry.func_name,
entry.params,
).ok();
}
if let Some(mi_rva) = entry.method_info_rva {
writeln!(header, "DO_APP_FUNC_METHODINFO(0x{:X}, {}__MethodInfo);",
mi_rva,
entry.func_name,
).ok();
}
}
Ok(header)
}
fn collect_method_info_addresses(
map: &mut HashMap<(usize, usize), u64>,
executor: &mut Il2CppExecutor,
metadata: &mut Metadata,
il2cpp: &mut Il2Cpp,
type_def_image_names: &HashMap<usize, String>,
) {
if il2cpp.version >= 27.0 {
Self::collect_method_info_v27(map, executor, metadata, il2cpp, type_def_image_names);
} else if il2cpp.version > 16.0 {
Self::collect_method_info_pre_v27(map, metadata, il2cpp);
}
}
fn collect_method_info_pre_v27(
map: &mut HashMap<(usize, usize), u64>,
metadata: &Metadata,
il2cpp: &Il2Cpp,
) {
if metadata.metadata_usage_dic.is_empty() { return; }
let usage_dic = metadata.metadata_usage_dic.clone();
for (usage_type, entries) in &usage_dic {
if *usage_type != 3 { continue; }
for (dest_index, source_index) in entries {
let dest = *dest_index as usize;
if dest >= il2cpp.metadata_usages.len() { continue; }
let address = il2cpp.metadata_usages[dest];
if address == 0 { continue; }
let rva = il2cpp.get_rva(address);
let src = *source_index as usize;
if let Some(method_def) = metadata.method_defs.get(src) {
let td_idx = method_def.declaring_type as usize;
map.insert((td_idx, src), rva);
}
}
}
}
fn collect_method_info_v27(
map: &mut HashMap<(usize, usize), u64>,
_executor: &mut Il2CppExecutor,
metadata: &mut Metadata,
il2cpp: &mut Il2Cpp,
_type_def_image_names: &HashMap<usize, String>,
) {
let pointer_size = if il2cpp.is_32bit { 4u64 } else { 8u64 };
let data_sections = il2cpp.data_sections.clone();
for sec in &data_sections {
let sec_end = std::cmp::min(sec.offset_end, il2cpp.stream.len() as u64).saturating_sub(pointer_size);
let mut pos = sec.offset;
while pos < sec_end {
il2cpp.stream.set_position(pos);
let metadata_value = if il2cpp.is_32bit {
il2cpp.stream.read_u32().unwrap_or(0) as u64
} else {
il2cpp.stream.read_u64().unwrap_or(0)
};
let saved_pos = il2cpp.stream.position();
pos = saved_pos;
if metadata_value >= u32::MAX as u64 { continue; }
let encoded_token = metadata_value as u32;
let usage = (encoded_token & 0xE0000000) >> 29;
if usage != 3 { continue; }
let decoded_index = (encoded_token & 0x1FFFFFFE) >> 1;
let expected = ((usage << 29) | (decoded_index << 1)) + 1;
if metadata_value != expected as u64 { continue; }
let addr = pos - pointer_size;
let va = il2cpp.map_rtva(addr);
let rva = il2cpp.get_rva(va);
if let Some(method_def) = metadata.method_defs.get(decoded_index as usize) {
let td_idx = method_def.declaring_type as usize;
map.insert((td_idx, decoded_index as usize), rva);
}
if il2cpp.stream.position() != saved_pos {
il2cpp.stream.set_position(saved_pos);
}
}
}
}
fn build_func_signature(
executor: &mut Il2CppExecutor,
metadata: &mut Metadata,
il2cpp: &mut Il2Cpp,
method_def: &Il2CppMethodDefinition,
type_def: &Il2CppTypeDefinition,
type_def_index: usize,
struct_name_dic: &HashMap<usize, String>,
generic_context: Option<&Il2CppGenericContext>,
) -> (String, String) {
let method_return_type = il2cpp.types[method_def.return_type as usize].clone();
let return_type = Self::parse_scaffolding_type(
&method_return_type, struct_name_dic, executor, metadata, il2cpp, generic_context,
);
let return_c = if method_return_type.byref == 1 {
format!("{}*", return_type)
} else {
return_type
};
let mut param_strs = Vec::new();
let is_static = (method_def.flags as u32 & 0x0010) != 0;
if !is_static {
if type_def.is_value_type() {
let base_name = struct_name_dic.get(&type_def_index)
.map(|s| s.as_str())
.unwrap_or("Il2CppObject");
param_strs.push(format!("{}* __this", base_name));
} else {
let byval_type = il2cpp.types[type_def.byval_type_index as usize].clone();
let this_type = Self::parse_scaffolding_type(
&byval_type, struct_name_dic, executor, metadata, il2cpp, generic_context,
);
if this_type.ends_with('*') {
param_strs.push(format!("{} __this", this_type));
} else {
param_strs.push(format!("{}* __this", this_type));
}
}
} else if il2cpp.version <= 24.0 {
param_strs.push("Il2CppObject* __this".to_string());
}
for j in 0..method_def.parameter_count as usize {
let param_def = metadata.parameter_defs[method_def.parameter_start as usize + j].clone();
let param_name = metadata.get_string_from_index(param_def.name_index)
.unwrap_or_else(|_| "param".to_string());
let param_type = il2cpp.types[param_def.type_index as usize].clone();
let param_c = Self::parse_scaffolding_type(
¶m_type, struct_name_dic, executor, metadata, il2cpp, generic_context,
);
let param_c_final = if param_type.byref == 1 {
format!("{}*", param_c)
} else {
param_c
};
param_strs.push(format!("{} {}", param_c_final, fix_name_scaffold(¶m_name)));
}
param_strs.push("const MethodInfo* method".to_string());
(return_c, param_strs.join(", "))
}
fn parse_scaffolding_type(
il2cpp_type: &Il2CppType,
struct_name_dic: &HashMap<usize, String>,
executor: &mut Il2CppExecutor,
metadata: &mut Metadata,
il2cpp: &mut Il2Cpp,
context: Option<&Il2CppGenericContext>,
) -> String {
let te = Il2CppTypeEnum::from_u8(il2cpp_type.type_enum);
match te {
Some(Il2CppTypeEnum::Void) => "void".to_string(),
Some(Il2CppTypeEnum::Boolean) => "bool".to_string(),
Some(Il2CppTypeEnum::Char) => "uint16_t".to_string(),
Some(Il2CppTypeEnum::I1) => "int8_t".to_string(),
Some(Il2CppTypeEnum::U1) => "uint8_t".to_string(),
Some(Il2CppTypeEnum::I2) => "int16_t".to_string(),
Some(Il2CppTypeEnum::U2) => "uint16_t".to_string(),
Some(Il2CppTypeEnum::I4) => "int32_t".to_string(),
Some(Il2CppTypeEnum::U4) => "uint32_t".to_string(),
Some(Il2CppTypeEnum::I8) => "int64_t".to_string(),
Some(Il2CppTypeEnum::U8) => "uint64_t".to_string(),
Some(Il2CppTypeEnum::R4) => "float".to_string(),
Some(Il2CppTypeEnum::R8) => "double".to_string(),
Some(Il2CppTypeEnum::String) => "System_String_o*".to_string(),
Some(Il2CppTypeEnum::I) => "intptr_t".to_string(),
Some(Il2CppTypeEnum::U) => "uintptr_t".to_string(),
Some(Il2CppTypeEnum::Object) | Some(Il2CppTypeEnum::TypedByRef) => "Il2CppObject*".to_string(),
Some(Il2CppTypeEnum::ValueType) => {
let klass_idx = il2cpp_type.klass_index() as usize;
if let Some(td) = metadata.type_defs.get(klass_idx) {
if td.is_enum() {
if let Some(elem_type) = il2cpp.types.get(td.element_type_index as usize).cloned() {
return Self::parse_scaffolding_type(&elem_type, struct_name_dic, executor, metadata, il2cpp, context);
}
}
if let Some(sn) = struct_name_dic.get(&klass_idx) {
return format!("{}", sn);
}
}
"Il2CppObject*".to_string()
}
Some(Il2CppTypeEnum::Class) => {
let klass_idx = il2cpp_type.klass_index() as usize;
if let Some(sn) = struct_name_dic.get(&klass_idx) {
format!("{}_o*", sn)
} else {
"Il2CppObject*".to_string()
}
}
Some(Il2CppTypeEnum::SzArray) | Some(Il2CppTypeEnum::Array) => {
"Il2CppArray*".to_string()
}
Some(Il2CppTypeEnum::GenericInst) => {
let generic_class_ptr = il2cpp_type.generic_class();
if generic_class_ptr != 0 {
if let Ok(generic_class) = il2cpp.read_generic_class(generic_class_ptr) {
if let Some((td, td_idx)) = executor.get_generic_class_type_definition(&generic_class, metadata, il2cpp) {
if let Some(sn) = struct_name_dic.get(&td_idx) {
if td.is_value_type() {
if td.is_enum() {
if let Some(elem) = il2cpp.types.get(td.element_type_index as usize).cloned() {
return Self::parse_scaffolding_type(&elem, struct_name_dic, executor, metadata, il2cpp, context);
}
}
return sn.clone();
} else {
return format!("{}_o*", sn);
}
}
}
}
}
"Il2CppObject*".to_string()
}
Some(Il2CppTypeEnum::Var) => {
if let Some(ctx) = context {
let gp = executor.get_generic_parameter_from_type(il2cpp_type, metadata, il2cpp);
if let Some(gp) = gp {
if let Some(resolved) = StructGenerator::resolve_generic_type_var_pub(il2cpp, ctx.class_inst, gp.num as u32) {
return Self::parse_scaffolding_type(&resolved, struct_name_dic, executor, metadata, il2cpp, None);
}
}
}
"Il2CppObject*".to_string()
}
Some(Il2CppTypeEnum::MVar) => {
if let Some(ctx) = context {
let gp = executor.get_generic_parameter_from_type(il2cpp_type, metadata, il2cpp);
if let Some(gp) = gp {
if ctx.method_inst == 0 && ctx.class_inst != 0 {
if let Some(resolved) = StructGenerator::resolve_generic_type_var_pub(il2cpp, ctx.class_inst, gp.num as u32) {
return Self::parse_scaffolding_type(&resolved, struct_name_dic, executor, metadata, il2cpp, None);
}
} else {
if let Some(resolved) = StructGenerator::resolve_generic_type_var_pub(il2cpp, ctx.method_inst, gp.num as u32) {
return Self::parse_scaffolding_type(&resolved, struct_name_dic, executor, metadata, il2cpp, None);
}
}
}
}
"Il2CppObject*".to_string()
}
Some(Il2CppTypeEnum::Ptr) => {
if il2cpp_type.datapoint != 0 {
if let Some(ori_type) = il2cpp.types.get(il2cpp_type.datapoint as usize).cloned() {
let inner = Self::parse_scaffolding_type(&ori_type, struct_name_dic, executor, metadata, il2cpp, context);
return format!("{}*", inner);
}
}
"void*".to_string()
}
_ => "Il2CppObject*".to_string(),
}
}
}
impl CppScaffolding {
pub fn write_project(
executor: &mut Il2CppExecutor,
metadata: &mut Metadata,
il2cpp: &mut Il2Cpp,
type_header_text: &str,
api_header_text: &str,
project_root: &Path,
) -> Result<()> {
let appdata_dir = project_root.join("appdata");
let framework_dir = project_root.join("framework");
let user_dir = project_root.join("user");
fs::create_dir_all(&appdata_dir).ok();
fs::create_dir_all(&framework_dir).ok();
fs::create_dir_all(&user_dir).ok();
let functions_header = Self::build(executor, metadata, il2cpp).unwrap_or_default();
fs::write(appdata_dir.join("il2cpp-functions.h"), Self::wrap_header("IL2CPP application method pointers", &functions_header)).ok();
let types_header_content = Self::wrap_types_header(il2cpp, type_header_text);
fs::write(appdata_dir.join("il2cpp-types.h"), types_header_content).ok();
let api_filtered = Self::filter_api_header(api_header_text, &il2cpp.api_export_rvas);
fs::write(appdata_dir.join("il2cpp-api-functions.h"), Self::wrap_header("IL2CPP API function declarations", &api_filtered)).ok();
let api_ptr_header = Self::build_api_function_ptr_header(&il2cpp.api_export_rvas);
fs::write(appdata_dir.join("il2cpp-api-functions-ptr.h"), Self::wrap_header("IL2CPP API function pointers", &api_ptr_header)).ok();
let types_ptr_header = Self::build_types_ptr_header(executor, metadata, il2cpp);
fs::write(appdata_dir.join("il2cpp-types-ptr.h"), Self::wrap_header("IL2CPP application type definition addresses", &types_ptr_header)).ok();
let md_ver = il2cpp.version;
let md_int = (md_ver * 10.0).round() as i32;
let version_header = format!("#pragma once\n\n#define __IL2CPP_METADATA_VERSION {md_int}\n");
fs::write(appdata_dir.join("il2cpp-metadata-version.h"), version_header).ok();
fs::write(framework_dir.join("dllmain.cpp"), TEMPLATE_DLLMAIN_CPP).ok();
fs::write(framework_dir.join("helpers.cpp"), TEMPLATE_HELPERS_CPP).ok();
fs::write(framework_dir.join("helpers.h"), TEMPLATE_HELPERS_H).ok();
fs::write(framework_dir.join("il2cpp-appdata.h"), TEMPLATE_IL2CPP_APPDATA_H).ok();
fs::write(framework_dir.join("il2cpp-init.cpp"), TEMPLATE_IL2CPP_INIT_CPP).ok();
fs::write(framework_dir.join("il2cpp-init.h"), TEMPLATE_IL2CPP_INIT_H).ok();
fs::write(framework_dir.join("pch-il2cpp.cpp"), TEMPLATE_PCH_IL2CPP_CPP).ok();
fs::write(framework_dir.join("pch-il2cpp.h"), TEMPLATE_PCH_IL2CPP_H).ok();
Self::write_if_not_exists(&user_dir.join("main.cpp"), TEMPLATE_MAIN_CPP);
Self::write_if_not_exists(&user_dir.join("main.h"), TEMPLATE_MAIN_H);
let project_name = "IL2CppDLL";
let project_guid = Self::make_guid(0);
let filter_guid1 = Self::make_guid(1);
let filter_guid2 = Self::make_guid(2);
let filter_guid3 = Self::make_guid(3);
let solution_guid = Self::make_guid(4);
let project_file = format!("{project_name}.vcxproj");
let filters_file = format!("{project_file}.filters");
let solution_file = format!("{project_name}.sln");
let vcxproj = TEMPLATE_VCXPROJ.replace("%PROJECTGUID%", &project_guid);
Self::write_if_not_exists(&project_root.join(&project_file), &vcxproj);
let filters = TEMPLATE_VCXPROJ_FILTERS
.replace("%GUID1%", &filter_guid1)
.replace("%GUID2%", &filter_guid2)
.replace("%GUID3%", &filter_guid3);
Self::write_if_not_exists(&project_root.join(&filters_file), &filters);
let sln = TEMPLATE_SLN
.replace("%PROJECTGUID%", &project_guid)
.replace("%PROJECTNAME%", project_name)
.replace("%PROJECTFILE%", &project_file)
.replace("%SOLUTIONGUID%", &solution_guid);
Self::write_if_not_exists(&project_root.join(&solution_file), &sln);
Ok(())
}
fn write_if_not_exists(path: &Path, contents: &str) {
if !path.exists() {
let _ = fs::write(path, contents);
}
}
fn make_guid(seed: u64) -> String {
let h = seed.wrapping_mul(0x9E3779B97F4A7C15) ^ 0xDEADBEEFCAFEBABE;
let a = (h & 0xFFFFFFFF) as u32;
let b = ((h >> 32) & 0xFFFF) as u16;
let c = ((h >> 48) & 0xFFFF) as u16;
let d = ((seed.wrapping_add(1)).wrapping_mul(0xBF58476D1CE4E5B9)) ^ 0xC0FFEE00C0FFEE00;
format!(
"{a:08X}-{b:04X}-{c:04X}-{:04X}-{:012X}",
((d >> 48) & 0xFFFF) as u16,
d & 0x0000_FFFF_FFFF_FFFF
)
}
fn wrap_header(section: &str, body: &str) -> String {
let mut out = String::with_capacity(body.len() + 256);
writeln!(out, "// Generated C++ file by Il2CppDumper - Rust Edition").ok();
writeln!(out, "// * {section}").ok();
writeln!(out).ok();
out.push_str(body);
if !out.ends_with('\n') { out.push('\n'); }
out
}
fn wrap_types_header(il2cpp: &Il2Cpp, type_header_text: &str) -> String {
let bits = if il2cpp.is_32bit { 32 } else { 64 };
let mut out = String::with_capacity(type_header_text.len() + 4096);
writeln!(out, "// Generated C++ file by Il2CppDumper - Rust Edition").ok();
writeln!(out).ok();
writeln!(out, "#pragma once").ok();
writeln!(out).ok();
writeln!(out, "#if defined(_IDACLANG_) || defined(_BINARYNINJA_)").ok();
writeln!(out, "#define IS_LIBCLANG_DECOMPILER").ok();
writeln!(out, "#endif").ok();
writeln!(out).ok();
writeln!(out, "#if defined(_GHIDRA_) || defined(_IDA_) || defined(IS_LIBCLANG_DECOMPILER)").ok();
writeln!(out, "#define IS_DECOMPILER").ok();
writeln!(out, "#endif").ok();
writeln!(out).ok();
writeln!(out, "#if defined(_GHIDRA_) || defined(_IDA_)").ok();
writeln!(out, "typedef unsigned __int8 uint8_t;").ok();
writeln!(out, "typedef unsigned __int16 uint16_t;").ok();
writeln!(out, "typedef unsigned __int32 uint32_t;").ok();
writeln!(out, "typedef unsigned __int64 uint64_t;").ok();
writeln!(out, "typedef __int8 int8_t;").ok();
writeln!(out, "typedef __int16 int16_t;").ok();
writeln!(out, "typedef __int32 int32_t;").ok();
writeln!(out, "typedef __int64 int64_t;").ok();
writeln!(out, "#endif").ok();
writeln!(out).ok();
writeln!(out, "#if defined(IS_LIBCLANG_DECOMPILER)").ok();
writeln!(out, "typedef unsigned char uint8_t;").ok();
writeln!(out, "typedef unsigned short uint16_t;").ok();
writeln!(out, "typedef unsigned int uint32_t;").ok();
writeln!(out, "typedef unsigned long uint64_t;").ok();
writeln!(out, "typedef char int8_t;").ok();
writeln!(out, "typedef short int16_t;").ok();
writeln!(out, "typedef int int32_t;").ok();
writeln!(out, "typedef long int64_t;").ok();
writeln!(out).ok();
writeln!(out, "#ifdef linux").ok();
writeln!(out, "#undef linux").ok();
writeln!(out, "#endif").ok();
writeln!(out, "#endif").ok();
writeln!(out).ok();
writeln!(out, "#if defined(_GHIDRA_) || defined(IS_LIBCLANG_DECOMPILER)").ok();
writeln!(out, "typedef int{bits}_t intptr_t;").ok();
writeln!(out, "typedef uint{bits}_t uintptr_t;").ok();
writeln!(out, "typedef uint{bits}_t size_t;").ok();
writeln!(out, "#endif").ok();
writeln!(out).ok();
writeln!(out, "#ifndef IS_DECOMPILER").ok();
writeln!(out, "#define _CPLUSPLUS_").ok();
writeln!(out, "#endif").ok();
writeln!(out).ok();
writeln!(out, "// ******************************************************************************").ok();
writeln!(out, "// * IL2CPP internal types").ok();
writeln!(out, "// ******************************************************************************").ok();
writeln!(out).ok();
out.push_str(type_header_text);
if !out.ends_with('\n') { out.push('\n'); }
writeln!(out).ok();
writeln!(out, "#ifndef IS_DECOMPILER").ok();
writeln!(out, "namespace app {{").ok();
writeln!(out, "#endif").ok();
writeln!(out).ok();
writeln!(out, "// Application types are emitted through il2cpp.h (#include \"../../il2cpp.h\") separately.").ok();
writeln!(out).ok();
writeln!(out, "#ifndef IS_DECOMPILER").ok();
writeln!(out, "}}").ok();
writeln!(out, "#endif").ok();
out
}
fn filter_api_header(text: &str, available: &HashMap<String, u64>) -> String {
if available.is_empty() {
return text.to_string();
}
let mut out = String::with_capacity(text.len());
for line in text.lines() {
let trimmed = line.trim_start();
if trimmed.starts_with("DO_API") || trimmed.starts_with("DO_API_NO_RETURN") {
if let Some(fn_name) = Self::extract_api_name(trimmed) {
if available.contains_key(&fn_name) {
out.push_str(line);
out.push('\n');
}
} else {
out.push_str(line);
out.push('\n');
}
} else {
out.push_str(line);
out.push('\n');
}
}
out
}
fn extract_api_name(line: &str) -> Option<String> {
let paren = line.find('(')?;
let tail = &line[paren + 1..];
let mut depth = 1;
let mut end = 0usize;
for (i, c) in tail.char_indices() {
match c {
'(' => depth += 1,
')' => {
depth -= 1;
if depth == 0 { end = i; break; }
}
_ => {}
}
}
let args = &tail[..end];
let parts: Vec<&str> = args.split(',').collect();
if parts.len() < 2 { return None; }
Some(parts[1].trim().to_string())
}
fn build_api_function_ptr_header(exports: &HashMap<String, u64>) -> String {
let mut names: Vec<(&String, &u64)> = exports.iter().collect();
names.sort_by(|a, b| a.0.cmp(b.0));
let mut out = String::with_capacity(exports.len() * 48);
out.push_str("#pragma once\n\n");
for (name, rva) in names {
writeln!(out, "#define {name}_ptr 0x{rva:08X}").ok();
}
out
}
fn build_types_ptr_header(
executor: &mut Il2CppExecutor,
metadata: &mut Metadata,
il2cpp: &mut Il2Cpp,
) -> String {
let struct_name_dic = StructGenerator::build_struct_name_dic_pub(executor, metadata, il2cpp);
let mut out = String::with_capacity(metadata.type_defs.len() * 64);
out.push_str("#pragma once\n\n");
let type_defs_len = metadata.type_defs.len();
for td_idx in 0..type_defs_len {
let name = match struct_name_dic.get(&td_idx) {
Some(n) => n.clone(),
None => continue,
};
if name.is_empty() { continue; }
writeln!(out, "DO_TYPEDEF(0x00000000, {name});").ok();
}
out
}
}
const TEMPLATE_DLLMAIN_CPP: &str = r#"// Generated C++ file by Il2CppDumper - Rust Edition
// DLL entry point
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "il2cpp-init.h"
#include "main.h"
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
init_il2cpp();
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) Run, NULL, 0, NULL);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
"#;
const TEMPLATE_HELPERS_CPP: &str = r#"// Generated C++ file by Il2CppDumper - Rust Edition
// Helper functions
#include "pch-il2cpp.h"
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <string>
#include <codecvt>
#include "helpers.h"
extern const LPCWSTR LOG_FILE;
uintptr_t il2cppi_get_base_address() {
return (uintptr_t) GetModuleHandleW(L"GameAssembly.dll");
}
void il2cppi_log_write(std::string text) {
HANDLE hfile = CreateFileW(LOG_FILE, FILE_APPEND_DATA, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hfile == INVALID_HANDLE_VALUE)
MessageBoxW(0, L"Could not open log file", 0, 0);
DWORD written;
WriteFile(hfile, text.c_str(), (DWORD) text.length(), &written, NULL);
WriteFile(hfile, "\r\n", 2, &written, NULL);
CloseHandle(hfile);
}
void il2cppi_new_console() {
AllocConsole();
freopen_s((FILE**) stdout, "CONOUT$", "w", stdout);
}
#if _MSC_VER >= 1920
std::string il2cppi_to_string(Il2CppString* str) {
std::u16string u16(reinterpret_cast<const char16_t*>(str->chars));
return std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.to_bytes(u16);
}
std::string il2cppi_to_string(app::String* str) {
return il2cppi_to_string(reinterpret_cast<Il2CppString*>(str));
}
#endif
"#;
const TEMPLATE_HELPERS_H: &str = r#"// Generated C++ file by Il2CppDumper - Rust Edition
// Helper functions
#pragma once
#include <string>
#include <sstream>
#include <iomanip>
#include "il2cpp-metadata-version.h"
uintptr_t il2cppi_get_base_address();
void il2cppi_log_write(std::string text);
void il2cppi_new_console();
#if _MSC_VER >= 1920
std::string il2cppi_to_string(Il2CppString* str);
std::string il2cppi_to_string(app::String* str);
#endif
template<typename T> bool il2cppi_is_initialized(T* metadataItem) {
#if __IL2CPP_METADATA_VERSION < 270
return *metadataItem != 0;
#else
return !((uintptr_t) *metadataItem & 1);
#endif
}
template<typename T> std::string to_hex_string(T i) {
std::stringstream stream;
stream << "0x" << std::setfill('0') << std::setw(sizeof(T) * 2) << std::hex << i;
return stream.str();
}
"#;
const TEMPLATE_IL2CPP_APPDATA_H: &str = r#"// Generated C++ file by Il2CppDumper - Rust Edition
// IL2CPP application data
#pragma once
#include <cstdint>
#include "il2cpp-types.h"
#include "il2cpp-api-functions-ptr.h"
#define DO_API(r, n, p) extern r (*n) p
#include "il2cpp-api-functions.h"
#undef DO_API
#define DO_APP_FUNC(a, r, n, p) extern r (*n) p
#define DO_APP_FUNC_METHODINFO(a, n) extern struct MethodInfo ** n
namespace app {
#include "il2cpp-functions.h"
}
#undef DO_APP_FUNC
#undef DO_APP_FUNC_METHODINFO
#define DO_TYPEDEF(a, n) extern n ## __Class** n ## __TypeInfo
namespace app {
#include "il2cpp-types-ptr.h"
}
#undef DO_TYPEDEF
"#;
const TEMPLATE_IL2CPP_INIT_CPP: &str = r#"// Generated C++ file by Il2CppDumper - Rust Edition
// IL2CPP application initializer
#include "pch-il2cpp.h"
#include "il2cpp-appdata.h"
#include "il2cpp-init.h"
#include "helpers.h"
#define DO_API(r, n, p) r (*n) p
#include "il2cpp-api-functions.h"
#undef DO_API
#define DO_APP_FUNC(a, r, n, p) r (*n) p
#define DO_APP_FUNC_METHODINFO(a, n) struct MethodInfo ** n
namespace app {
#include "il2cpp-functions.h"
}
#undef DO_APP_FUNC
#undef DO_APP_FUNC_METHODINFO
#define DO_TYPEDEF(a, n) n ## __Class** n ## __TypeInfo
namespace app {
#include "il2cpp-types-ptr.h"
}
#undef DO_TYPEDEF
void init_il2cpp()
{
uintptr_t baseAddress = il2cppi_get_base_address();
using namespace app;
#define DO_API(r, n, p) n = (r (*) p)(baseAddress + n ## _ptr)
#include "il2cpp-api-functions.h"
#undef DO_API
#define DO_APP_FUNC(a, r, n, p) n = (r (*) p)(baseAddress + a)
#define DO_APP_FUNC_METHODINFO(a, n) n = (struct MethodInfo **)(baseAddress + a)
#include "il2cpp-functions.h"
#undef DO_APP_FUNC
#undef DO_APP_FUNC_METHODINFO
#define DO_TYPEDEF(a, n) n ## __TypeInfo = (n ## __Class**) (baseAddress + a);
#include "il2cpp-types-ptr.h"
#undef DO_TYPEDEF
}
"#;
const TEMPLATE_IL2CPP_INIT_H: &str = r#"// Generated C++ file by Il2CppDumper - Rust Edition
// IL2CPP application initializer
#pragma once
void init_il2cpp();
"#;
const TEMPLATE_PCH_IL2CPP_CPP: &str = r#"// pch.cpp: source file corresponding to the pre-compiled header
#include "pch-il2cpp.h"
"#;
const TEMPLATE_PCH_IL2CPP_H: &str = r#"// pch.h: This is a precompiled header file.
#ifndef PCH_IL2CPP_H
#define PCH_IL2CPP_H
#endif
"#;
const TEMPLATE_MAIN_CPP: &str = r#"// Generated C++ file by Il2CppDumper - Rust Edition
// Custom injected code entry point
#include "pch-il2cpp.h"
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <iostream>
#include "il2cpp-appdata.h"
#include "helpers.h"
using namespace app;
extern const LPCWSTR LOG_FILE = L"il2cpp-log.txt";
void Run()
{
il2cpp_thread_attach(il2cpp_domain_get());
}
"#;
const TEMPLATE_MAIN_H: &str = r#"// Generated C++ file by Il2CppDumper - Rust Edition
// Custom injected code entry point
#pragma once
void Run();
"#;
const TEMPLATE_VCXPROJ: &str = r#"<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<ProjectGuid>{%PROJECTGUID%}</ProjectGuid>
<RootNamespace>IL2CppDLL</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ItemDefinitionGroup>
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch-il2cpp.h</PrecompiledHeaderFile>
<LanguageStandard>stdcpp17</LanguageStandard>
<AdditionalIncludeDirectories>framework;appdata;user;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="appdata\il2cpp-types.h" />
<ClInclude Include="appdata\il2cpp-types-ptr.h" />
<ClInclude Include="appdata\il2cpp-functions.h" />
<ClInclude Include="appdata\il2cpp-api-functions.h" />
<ClInclude Include="appdata\il2cpp-api-functions-ptr.h" />
<ClInclude Include="appdata\il2cpp-metadata-version.h" />
<ClInclude Include="framework\helpers.h" />
<ClInclude Include="framework\il2cpp-appdata.h" />
<ClInclude Include="framework\il2cpp-init.h" />
<ClInclude Include="framework\pch-il2cpp.h" />
<ClInclude Include="user\main.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="framework\dllmain.cpp" />
<ClCompile Include="framework\helpers.cpp" />
<ClCompile Include="framework\il2cpp-init.cpp" />
<ClCompile Include="framework\pch-il2cpp.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="user\main.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>
"#;
const TEMPLATE_VCXPROJ_FILTERS: &str = r#"<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="appdata">
<UniqueIdentifier>{%GUID1%}</UniqueIdentifier>
</Filter>
<Filter Include="framework">
<UniqueIdentifier>{%GUID2%}</UniqueIdentifier>
</Filter>
<Filter Include="user">
<UniqueIdentifier>{%GUID3%}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="appdata\il2cpp-types.h"><Filter>appdata</Filter></ClInclude>
<ClInclude Include="appdata\il2cpp-types-ptr.h"><Filter>appdata</Filter></ClInclude>
<ClInclude Include="appdata\il2cpp-functions.h"><Filter>appdata</Filter></ClInclude>
<ClInclude Include="appdata\il2cpp-api-functions.h"><Filter>appdata</Filter></ClInclude>
<ClInclude Include="appdata\il2cpp-api-functions-ptr.h"><Filter>appdata</Filter></ClInclude>
<ClInclude Include="appdata\il2cpp-metadata-version.h"><Filter>appdata</Filter></ClInclude>
<ClInclude Include="framework\helpers.h"><Filter>framework</Filter></ClInclude>
<ClInclude Include="framework\il2cpp-appdata.h"><Filter>framework</Filter></ClInclude>
<ClInclude Include="framework\il2cpp-init.h"><Filter>framework</Filter></ClInclude>
<ClInclude Include="framework\pch-il2cpp.h"><Filter>framework</Filter></ClInclude>
<ClInclude Include="user\main.h"><Filter>user</Filter></ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="framework\dllmain.cpp"><Filter>framework</Filter></ClCompile>
<ClCompile Include="framework\helpers.cpp"><Filter>framework</Filter></ClCompile>
<ClCompile Include="framework\il2cpp-init.cpp"><Filter>framework</Filter></ClCompile>
<ClCompile Include="framework\pch-il2cpp.cpp"><Filter>framework</Filter></ClCompile>
<ClCompile Include="user\main.cpp"><Filter>user</Filter></ClCompile>
</ItemGroup>
</Project>
"#;
const TEMPLATE_SLN: &str = r#"Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "%PROJECTNAME%", "%PROJECTFILE%", "{%PROJECTGUID%}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{%PROJECTGUID%}.Debug|x64.ActiveCfg = Debug|x64
{%PROJECTGUID%}.Debug|x64.Build.0 = Debug|x64
{%PROJECTGUID%}.Release|x64.ActiveCfg = Release|x64
{%PROJECTGUID%}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {%SOLUTIONGUID%}
EndGlobalSection
EndGlobal
"#;
fn fix_name_scaffold(name: &str) -> String {
crate::utils::sanitize_cpp_identifier(
name,
crate::utils::NameSanitizerOptions {
allow_dollar: true,
avoid_double_underscore_prefix: true,
},
)
}