use std::collections::HashMap;
use crate::il2cpp::metadata::Metadata;
use crate::il2cpp::base::Il2Cpp;
use crate::il2cpp::enums::Il2CppTypeEnum;
use crate::il2cpp::structures::*;
use crate::executor::Il2CppExecutor;
pub struct MangledNameBuilder {
buf: String,
substitution_map: HashMap<String, usize>,
current_sub_index: usize,
}
impl MangledNameBuilder {
fn new() -> Self {
Self {
buf: String::from("_Z"),
substitution_map: HashMap::new(),
current_sub_index: 0,
}
}
fn begin_name(&mut self) { self.buf.push('N'); }
fn begin_generics(&mut self) { self.buf.push('I'); }
fn write_end(&mut self) { self.buf.push('E'); }
fn write_identifier(&mut self, id: &str) {
let sanitized = crate::utils::sanitize_mangled_identifier_chars(id);
self.buf.push_str(&sanitized.len().to_string());
self.buf.push_str(&sanitized);
}
fn skip_sub_index(&mut self) {
self.current_sub_index += 1;
}
fn try_write_substitution(&mut self, key: &str) -> bool {
if let Some(&index) = self.substitution_map.get(key) {
self.buf.push('S');
if index > 0 {
let mut idx = index - 1;
const BASE36: &[u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
if idx == 0 {
self.buf.push(BASE36[0] as char);
} else {
let mut chars: Vec<char> = Vec::new();
while idx > 0 {
chars.push(BASE36[idx % 36] as char);
idx /= 36;
}
chars.reverse();
for c in chars { self.buf.push(c); }
}
}
self.buf.push('_');
return true;
}
false
}
fn register_substitution(&mut self, key: String) {
self.substitution_map.insert(key, self.current_sub_index);
self.current_sub_index += 1;
}
fn finish(self) -> String { self.buf }
fn base_clean(name: &str) -> &str {
if let Some(pos) = name.find('`') { &name[..pos] } else { name }
}
fn type_def_key(td: &Il2CppTypeDefinition, td_index_guess: Option<usize>) -> String {
match td_index_guess {
Some(i) => format!("td#{i}::{}::{}", td.namespace_index, td.name_index),
None => format!("td::{}::{}", td.namespace_index, td.name_index),
}
}
fn write_type_name_from_td(
&mut self,
td: &Il2CppTypeDefinition,
_td_index: usize,
metadata: &mut Metadata,
il2cpp: &mut Il2Cpp,
executor: &mut Il2CppExecutor,
generic_inst: Option<&Il2CppGenericInst>,
) {
let ns = metadata.get_string_from_index(td.namespace_index).unwrap_or_default();
for part in ns.split('.') {
if !part.is_empty() {
self.write_identifier(part);
self.skip_sub_index();
}
}
if td.declaring_type_index != -1 {
if let Some(declaring_type) = il2cpp.types.get(td.declaring_type_index as usize).cloned() {
let decl_td_idx_opt = match Il2CppTypeEnum::from_u8(declaring_type.type_enum) {
Some(Il2CppTypeEnum::ValueType) | Some(Il2CppTypeEnum::Class) => {
Some(declaring_type.klass_index() as usize)
}
_ => None,
};
if let Some(dt_idx) = decl_td_idx_opt {
if let Some(decl_td) = metadata.type_defs.get(dt_idx).cloned() {
let decl_name_raw = metadata.get_string_from_index(decl_td.name_index).unwrap_or_default();
let decl_clean = Self::base_clean(&decl_name_raw);
self.write_identifier(decl_clean);
self.skip_sub_index();
} else {
let decl_name = executor.get_type_name(&declaring_type, metadata, il2cpp, false, false);
self.write_identifier(&decl_name);
self.skip_sub_index();
}
} else {
let decl_name = executor.get_type_name(&declaring_type, metadata, il2cpp, false, false);
self.write_identifier(&decl_name);
self.skip_sub_index();
}
}
}
let name_raw = metadata.get_string_from_index(td.name_index).unwrap_or_default();
let name_clean = Self::base_clean(&name_raw).to_string();
self.write_identifier(&name_clean);
if let Some(inst) = generic_inst {
self.skip_sub_index();
self.write_generic_inst(inst, il2cpp, metadata, executor);
}
}
fn write_complex_type_from_td(
&mut self,
td: &Il2CppTypeDefinition,
td_index: usize,
metadata: &mut Metadata,
il2cpp: &mut Il2Cpp,
executor: &mut Il2CppExecutor,
generic_inst: Option<&Il2CppGenericInst>,
) {
let key_base = Self::type_def_key(td, Some(td_index));
let key = match generic_inst {
Some(inst) => format!("{key_base}::gi@{:x}", inst as *const _ as usize),
None => key_base,
};
if self.try_write_substitution(&key) {
return;
}
self.begin_name();
self.write_type_name_from_td(td, td_index, metadata, il2cpp, executor, generic_inst);
self.write_end();
self.register_substitution(key);
}
fn write_nested_system_type(
&mut self,
namespace: &str,
type_name: &str,
) {
let key = format!("sys::{namespace}::{type_name}");
if self.try_write_substitution(&key) {
return;
}
self.begin_name();
for part in namespace.split('.') {
if !part.is_empty() {
self.write_identifier(part);
self.skip_sub_index();
}
}
self.write_identifier(type_name);
self.write_end();
self.register_substitution(key);
}
fn write_generic_inst(
&mut self,
inst: &Il2CppGenericInst,
il2cpp: &mut Il2Cpp,
metadata: &mut Metadata,
executor: &mut Il2CppExecutor,
) {
self.begin_generics();
if let Ok(pointers) = il2cpp.read_ptr_array(inst.type_argv, inst.type_argc) {
for ptr in &pointers {
if let Some(t) = il2cpp.get_il2cpp_type(*ptr).cloned() {
self.write_type(&t, metadata, il2cpp, executor);
} else {
self.buf.push('v');
}
}
}
self.write_end();
}
fn write_type(
&mut self,
il2cpp_type: &Il2CppType,
metadata: &mut Metadata,
il2cpp: &mut Il2Cpp,
executor: &mut Il2CppExecutor,
) {
let te = Il2CppTypeEnum::from_u8(il2cpp_type.type_enum);
if let Some(Il2CppTypeEnum::Void) = te {
self.buf.push('v');
return;
}
let mut is_nested_type_element = false;
if il2cpp_type.byref == 1 {
self.buf.push('R');
is_nested_type_element = true;
}
match te {
Some(Il2CppTypeEnum::Ptr) => {
self.buf.push('P');
is_nested_type_element = true;
if il2cpp_type.datapoint != 0 {
if let Some(inner) = il2cpp.types.get(il2cpp_type.datapoint as usize).cloned() {
self.write_type(&inner, metadata, il2cpp, executor);
} else {
self.buf.push('v');
}
} else {
self.buf.push('v');
}
if is_nested_type_element { self.skip_sub_index(); }
return;
}
Some(Il2CppTypeEnum::SzArray) | Some(Il2CppTypeEnum::Array) => {
self.buf.push_str("A_");
is_nested_type_element = true;
if il2cpp_type.datapoint != 0 {
if let Some(element) = il2cpp.types.get(il2cpp_type.datapoint as usize).cloned() {
self.write_type(&element, metadata, il2cpp, executor);
} else {
self.buf.push('v');
}
} else {
self.buf.push('v');
}
if is_nested_type_element { self.skip_sub_index(); }
return;
}
_ => {}
}
match te {
Some(Il2CppTypeEnum::Boolean) => self.buf.push('b'),
Some(Il2CppTypeEnum::Char) => self.buf.push('w'),
Some(Il2CppTypeEnum::I1) => self.buf.push('a'),
Some(Il2CppTypeEnum::U1) => self.buf.push('h'),
Some(Il2CppTypeEnum::I2) => self.buf.push('s'),
Some(Il2CppTypeEnum::U2) => self.buf.push('t'),
Some(Il2CppTypeEnum::I4) => self.buf.push('i'),
Some(Il2CppTypeEnum::U4) => self.buf.push('j'),
Some(Il2CppTypeEnum::I8) => self.buf.push('l'),
Some(Il2CppTypeEnum::U8) => self.buf.push('m'),
Some(Il2CppTypeEnum::R4) => self.buf.push('f'),
Some(Il2CppTypeEnum::R8) => self.buf.push('d'),
Some(Il2CppTypeEnum::I) | Some(Il2CppTypeEnum::U) => {
self.buf.push_str("Pv");
}
Some(Il2CppTypeEnum::String) => {
self.write_nested_system_type("System", "String");
}
Some(Il2CppTypeEnum::Object) => {
self.write_nested_system_type("System", "Object");
}
Some(Il2CppTypeEnum::TypedByRef) => {
self.write_nested_system_type("System", "TypedReference");
}
Some(Il2CppTypeEnum::ValueType) | Some(Il2CppTypeEnum::Class) => {
let klass_idx = il2cpp_type.klass_index() as usize;
if let Some(td) = metadata.type_defs.get(klass_idx).cloned() {
self.write_complex_type_from_td(&td, klass_idx, metadata, il2cpp, executor, None);
} else {
self.buf.push('v');
}
}
Some(Il2CppTypeEnum::GenericInst) => {
let generic_class_ptr = il2cpp_type.generic_class();
let mut wrote = false;
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) {
let key = format!(
"td#{td_idx}::gi_ctx@0x{:x}",
generic_class.context.class_inst
);
if self.try_write_substitution(&key) {
wrote = true;
} else if let Ok(inst) = il2cpp.read_generic_inst(generic_class.context.class_inst) {
self.begin_name();
self.write_type_name_from_td(&td, td_idx, metadata, il2cpp, executor, Some(&inst));
self.write_end();
self.register_substitution(key);
wrote = true;
}
}
}
}
if !wrote { self.buf.push('v'); }
}
Some(Il2CppTypeEnum::Var) | Some(Il2CppTypeEnum::MVar) => {
self.buf.push('v');
}
_ => {
self.buf.push('v');
}
}
if is_nested_type_element {
self.skip_sub_index();
}
}
pub fn mangle_method(
executor: &mut Il2CppExecutor,
metadata: &mut Metadata,
il2cpp: &mut Il2Cpp,
type_def: &Il2CppTypeDefinition,
type_def_index: usize,
method_def: &Il2CppMethodDefinition,
) -> String {
Self::mangle_method_inner(
executor, metadata, il2cpp, type_def, type_def_index, method_def, None, "",
)
}
pub fn mangle_method_info(
executor: &mut Il2CppExecutor,
metadata: &mut Metadata,
il2cpp: &mut Il2Cpp,
type_def: &Il2CppTypeDefinition,
type_def_index: usize,
method_def: &Il2CppMethodDefinition,
) -> String {
Self::mangle_method_inner(
executor, metadata, il2cpp, type_def, type_def_index, method_def, None, "MethodInfo",
)
}
pub fn mangle_method_spec(
executor: &mut Il2CppExecutor,
metadata: &mut Metadata,
il2cpp: &mut Il2Cpp,
type_def: &Il2CppTypeDefinition,
type_def_index: usize,
method_def: &Il2CppMethodDefinition,
generic_context: &Il2CppGenericContext,
) -> String {
Self::mangle_method_inner(
executor, metadata, il2cpp, type_def, type_def_index, method_def,
Some(generic_context), "",
)
}
pub fn mangle_type_info(
executor: &mut Il2CppExecutor,
metadata: &mut Metadata,
il2cpp: &mut Il2Cpp,
type_def: &Il2CppTypeDefinition,
type_def_index: usize,
) -> String {
Self::mangle_data(executor, metadata, il2cpp, type_def, type_def_index, "TypeInfo")
}
pub fn mangle_type_ref(
executor: &mut Il2CppExecutor,
metadata: &mut Metadata,
il2cpp: &mut Il2Cpp,
type_def: &Il2CppTypeDefinition,
type_def_index: usize,
) -> String {
Self::mangle_data(executor, metadata, il2cpp, type_def, type_def_index, "TypeRef")
}
fn mangle_data(
executor: &mut Il2CppExecutor,
metadata: &mut Metadata,
il2cpp: &mut Il2Cpp,
type_def: &Il2CppTypeDefinition,
type_def_index: usize,
prefix: &str,
) -> String {
let mut b = MangledNameBuilder::new();
b.begin_name();
if !prefix.is_empty() {
b.write_identifier(prefix);
b.skip_sub_index();
}
b.write_type_name_from_td(type_def, type_def_index, metadata, il2cpp, executor, None);
let key = Self::type_def_key(type_def, Some(type_def_index));
b.register_substitution(key);
b.write_end();
b.finish()
}
fn method_has_generic_params(method_def: &Il2CppMethodDefinition) -> bool {
method_def.generic_container_index >= 0
}
fn mangle_method_inner(
executor: &mut Il2CppExecutor,
metadata: &mut Metadata,
il2cpp: &mut Il2Cpp,
type_def: &Il2CppTypeDefinition,
type_def_index: usize,
method_def: &Il2CppMethodDefinition,
generic_context: Option<&Il2CppGenericContext>,
prefix: &str,
) -> String {
let mut b = MangledNameBuilder::new();
b.begin_name();
if !prefix.is_empty() {
b.write_identifier(prefix);
b.skip_sub_index();
}
let class_inst_opt = generic_context.and_then(|ctx| {
if ctx.class_inst != 0 {
il2cpp.read_generic_inst(ctx.class_inst).ok()
} else {
None
}
});
b.write_type_name_from_td(type_def, type_def_index, metadata, il2cpp, executor, class_inst_opt.as_ref());
let td_key_base = Self::type_def_key(type_def, Some(type_def_index));
let td_key = match &class_inst_opt {
Some(_) => match generic_context {
Some(ctx) => format!("{td_key_base}::gi_ctx@0x{:x}", ctx.class_inst),
None => td_key_base,
},
None => td_key_base,
};
b.register_substitution(td_key);
let method_name = metadata.get_string_from_index(method_def.name_index as i32).unwrap_or_default();
match method_name.as_str() {
".ctor" => b.buf.push_str("C1"),
".cctor" => b.write_identifier("cctor"),
_ => b.write_identifier(&method_name),
}
let method_inst_opt = generic_context.and_then(|ctx| {
if ctx.method_inst != 0 {
il2cpp.read_generic_inst(ctx.method_inst).ok()
} else {
None
}
});
let is_generic_method = method_inst_opt.is_some() || Self::method_has_generic_params(method_def);
if let Some(inst) = &method_inst_opt {
b.skip_sub_index();
b.write_generic_inst(inst, il2cpp, metadata, executor);
}
b.write_end();
let has_return_first = is_generic_method && prefix.is_empty();
if has_return_first {
if let Some(ret_type) = il2cpp.types.get(method_def.return_type as usize).cloned() {
b.write_type(&ret_type, metadata, il2cpp, executor);
} else {
b.buf.push('v');
}
}
if prefix == "MethodInfo" {
b.buf.push('v');
} else if method_def.parameter_count == 0 {
if !has_return_first {
b.buf.push('v');
} else {
}
} else {
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_type = il2cpp.types[param_def.type_index as usize].clone();
b.write_type(¶m_type, metadata, il2cpp, executor);
}
}
b.skip_sub_index();
b.finish()
}
}