use std::fs::File;
use std::io::{BufWriter, Write};
use std::collections::{HashMap, BTreeMap};
use crate::error::Result;
use crate::il2cpp::base::Il2Cpp;
use crate::il2cpp::metadata::Metadata;
use crate::executor::Il2CppExecutor;
use crate::executor::custom_attribute_reader;
use crate::config::Config;
fn safe_truncate(s: &str, max_bytes: usize) -> &str {
if s.len() <= max_bytes {
return s;
}
let mut end = max_bytes;
while end > 0 && !s.is_char_boundary(end) {
end -= 1;
}
&s[..end]
}
pub fn dump_generics(
output_path: &str,
metadata: &mut Metadata,
il2cpp: &mut Il2Cpp,
executor: &mut Il2CppExecutor,
config: &Config,
) -> Result<()> {
let mut file = BufWriter::with_capacity(1 << 20, File::create(output_path)?);
writeln!(file, "// ============================================================================")?;
writeln!(file, "// IL2CPP Advanced Dump - Runtime Generic Contexts, Attributes & Cross-References")?;
writeln!(file, "// Generated by Il2CppDumper Rust v4.0")?;
writeln!(file, "// ============================================================================\n")?;
let type_def_image_map = build_type_def_image_map(metadata);
let method_def_owner_map = build_method_def_owner_map(metadata);
if config.dump_generics_rgctx {
dump_rgctx_section(&mut file, metadata, il2cpp, executor, &type_def_image_map)?;
}
if config.dump_generics_method_specs {
dump_method_specs_section(&mut file, metadata, il2cpp, executor)?;
}
if config.dump_generics_custom_attributes {
dump_custom_attributes_section(&mut file, metadata, il2cpp, executor, &type_def_image_map)?;
}
if config.dump_generics_string_literals {
dump_string_literals_section(&mut file, metadata)?;
}
if config.dump_generics_metadata_usages {
dump_metadata_usages_section(&mut file, metadata, il2cpp, executor, &type_def_image_map, &method_def_owner_map)?;
}
if config.dump_generics_vtables {
dump_vtable_section(&mut file, metadata, il2cpp, executor, &type_def_image_map)?;
}
if config.dump_generics_interfaces {
dump_interface_section(&mut file, metadata, il2cpp, executor, &type_def_image_map)?;
}
file.flush()?;
Ok(())
}
fn build_type_def_image_map(metadata: &mut Metadata) -> HashMap<usize, (usize, String)> {
let mut map = HashMap::new();
for (img_idx, image_def) in metadata.image_defs.clone().iter().enumerate() {
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_idx in image_def.type_start as usize..type_end {
map.insert(type_idx, (img_idx, image_name.clone()));
}
}
map
}
fn build_method_def_owner_map(metadata: &Metadata) -> HashMap<usize, usize> {
let mut map = HashMap::new();
for (td_idx, td) in metadata.type_defs.iter().enumerate() {
let method_end = td.method_start as usize + td.method_count as usize;
for m_idx in td.method_start as usize..method_end {
map.insert(m_idx, td_idx);
}
}
map
}
fn dump_rgctx_section(
file: &mut BufWriter<File>,
metadata: &mut Metadata,
il2cpp: &mut Il2Cpp,
executor: &mut Il2CppExecutor,
type_def_image_map: &HashMap<usize, (usize, String)>,
) -> Result<()> {
writeln!(file, "╔══════════════════════════════════════════════════════════════════════════════╗")?;
writeln!(file, "║ SECTION 1: Runtime Generic Contexts (RGCTX) ║")?;
writeln!(file, "║ Maps concrete generic instantiations used by the IL2CPP runtime ║")?;
writeln!(file, "╚══════════════════════════════════════════════════════════════════════════════╝\n")?;
let mut total_rgctx_entries = 0u64;
for image in &metadata.image_defs.clone() {
let image_name = metadata.get_string_from_index(image.name_index).unwrap_or_default();
let mut image_buffer = String::new();
let type_end = image.type_start as usize + image.type_count as usize;
for type_idx in image.type_start as usize..type_end {
let type_def = metadata.type_defs[type_idx].clone();
if let Some(rgctxs) = executor.get_rgctx_definition_for_type(&image_name, &type_def, metadata, il2cpp) {
if !rgctxs.is_empty() {
let type_name = executor.get_type_def_name(&type_def, type_idx, metadata, il2cpp, true, true);
image_buffer.push_str(&format!(" Type: {} (Token: 0x{:08X})\n", type_name, type_def.token));
for (i, rgctx) in rgctxs.iter().enumerate() {
let formatted = format_rgctx(rgctx, metadata, il2cpp, executor);
image_buffer.push_str(&format!(" [{:3}] {} -> {}\n", i, get_rgctx_type_name(rgctx.rgctx_type), formatted));
total_rgctx_entries += 1;
}
image_buffer.push('\n');
}
}
let method_end = type_def.method_start as usize + type_def.method_count as usize;
for method_idx in type_def.method_start as usize..method_end {
if method_idx >= metadata.method_defs.len() { break; }
let method_def = metadata.method_defs[method_idx].clone();
if let Some(rgctxs) = executor.get_rgctx_definition_for_method(&image_name, &method_def, metadata, il2cpp) {
if !rgctxs.is_empty() {
let type_name = executor.get_type_def_name(&type_def, type_idx, metadata, il2cpp, true, true);
let method_name = metadata.get_string_from_index(method_def.name_index as i32).unwrap_or_else(|_| "?".to_string());
let img_name = type_def_image_map.get(&type_idx)
.map(|(_, n)| n.clone()).unwrap_or_default();
let method_ptr = il2cpp.get_method_pointer(&img_name, &method_def);
let rva_str = if method_ptr > 0 {
format!(" @ 0x{:08X}", il2cpp.get_rva(method_ptr))
} else {
String::new()
};
image_buffer.push_str(&format!(
" Method: {}::{} (Token: 0x{:08X}){}\n",
type_name, method_name, method_def.token, rva_str
));
for (i, rgctx) in rgctxs.iter().enumerate() {
let formatted = format_rgctx(rgctx, metadata, il2cpp, executor);
image_buffer.push_str(&format!(" [{:3}] {} -> {}\n", i, get_rgctx_type_name(rgctx.rgctx_type), formatted));
total_rgctx_entries += 1;
}
image_buffer.push('\n');
}
}
}
}
if !image_buffer.is_empty() {
writeln!(file, "─── Image: {} ───", image_name)?;
write!(file, "{}", image_buffer)?;
}
}
writeln!(file, "Total RGCTX entries: {}\n", total_rgctx_entries)?;
Ok(())
}
fn dump_method_specs_section(
file: &mut BufWriter<File>,
metadata: &mut Metadata,
il2cpp: &mut Il2Cpp,
executor: &mut Il2CppExecutor,
) -> Result<()> {
writeln!(file, "╔══════════════════════════════════════════════════════════════════════════════╗")?;
writeln!(file, "║ SECTION 2: Instantiated Generic Methods (MethodSpecs) ║")?;
writeln!(file, "║ Every concrete generic method instantiation with its native address ║")?;
writeln!(file, "╚══════════════════════════════════════════════════════════════════════════════╝\n")?;
let mut resolved_count = 0u64;
let total = il2cpp.method_specs.len();
for i in 0..total {
let (type_name, method_name) = executor.get_method_spec_name(i, metadata, il2cpp, true);
let ptr = il2cpp.method_spec_generic_method_pointers
.get(&i).copied()
.unwrap_or(0);
if ptr != 0 {
let offset = il2cpp.get_rva(ptr);
writeln!(file, " 0x{:08X} | {}::{}", offset, type_name, method_name)?;
resolved_count += 1;
} else {
writeln!(file, " | {}::{}", type_name, method_name)?;
}
}
writeln!(file, "\nTotal MethodSpecs: {} ({} with resolved addresses)\n", total, resolved_count)?;
Ok(())
}
fn dump_custom_attributes_section(
file: &mut BufWriter<File>,
metadata: &mut Metadata,
il2cpp: &mut Il2Cpp,
executor: &mut Il2CppExecutor,
_type_def_image_map: &HashMap<usize, (usize, String)>,
) -> Result<()> {
writeln!(file, "╔══════════════════════════════════════════════════════════════════════════════╗")?;
writeln!(file, "║ SECTION 3: Custom Attributes ║")?;
writeln!(file, "║ Reconstructed [SerializeField], [Header], [Range] etc. for every type ║")?;
writeln!(file, "╚══════════════════════════════════════════════════════════════════════════════╝\n")?;
let mut attr_count = 0u64;
for (img_idx, image) in metadata.image_defs.clone().iter().enumerate() {
let image_name = metadata.get_string_from_index(image.name_index).unwrap_or_default();
let mut image_buffer = String::new();
let type_end = image.type_start as usize + image.type_count as usize;
for type_idx in image.type_start as usize..type_end {
let type_def = metadata.type_defs[type_idx].clone();
let type_name = executor.get_type_def_name(&type_def, type_idx, metadata, il2cpp, true, true);
let mut type_buffer = String::new();
let mut type_has_attrs = false;
let type_attrs = collect_attributes(metadata, img_idx, type_def.custom_attribute_index, type_def.token);
if !type_attrs.is_empty() {
type_buffer.push_str(&format!(" [Type] {}\n", type_attrs));
type_has_attrs = true;
attr_count += 1;
}
for field_offset in 0..type_def.field_count as usize {
let field_idx = type_def.field_start as usize + field_offset;
if field_idx >= metadata.field_defs.len() { break; }
let field_def = metadata.field_defs[field_idx].clone();
let field_attrs = collect_attributes(metadata, img_idx, field_def.custom_attribute_index, field_def.token);
if !field_attrs.is_empty() {
let field_name = metadata.get_string_from_index(field_def.name_index).unwrap_or_else(|_| "?".to_string());
type_buffer.push_str(&format!(" [Field] {} -> {}\n", field_name, field_attrs));
type_has_attrs = true;
attr_count += 1;
}
}
let method_end = type_def.method_start as usize + type_def.method_count as usize;
for method_idx in type_def.method_start as usize..method_end {
if method_idx >= metadata.method_defs.len() { break; }
let method_def = metadata.method_defs[method_idx].clone();
let method_attrs = collect_attributes(metadata, img_idx, method_def.custom_attribute_index, method_def.token);
if !method_attrs.is_empty() {
let method_name = metadata.get_string_from_index(method_def.name_index as i32).unwrap_or_else(|_| "?".to_string());
type_buffer.push_str(&format!(" [Method] {} -> {}\n", method_name, method_attrs));
type_has_attrs = true;
attr_count += 1;
}
}
for prop_offset in 0..type_def.property_count as usize {
let prop_idx = type_def.property_start as usize + prop_offset;
if prop_idx >= metadata.property_defs.len() { break; }
let prop_def = metadata.property_defs[prop_idx].clone();
let prop_attrs = collect_attributes(metadata, img_idx, prop_def.custom_attribute_index, prop_def.token);
if !prop_attrs.is_empty() {
let prop_name = metadata.get_string_from_index(prop_def.name_index).unwrap_or_else(|_| "?".to_string());
type_buffer.push_str(&format!(" [Property] {} -> {}\n", prop_name, prop_attrs));
type_has_attrs = true;
attr_count += 1;
}
}
if type_has_attrs {
image_buffer.push_str(&format!(" {}\n", type_name));
image_buffer.push_str(&type_buffer);
image_buffer.push('\n');
}
}
if !image_buffer.is_empty() {
writeln!(file, "─── Image: {} ───", image_name)?;
write!(file, "{}", image_buffer)?;
}
}
writeln!(file, "Total attributed members: {}\n", attr_count)?;
Ok(())
}
fn collect_attributes(
metadata: &mut Metadata,
image_index: usize,
custom_attribute_index: i32,
token: u32,
) -> String {
let attr_idx = match metadata.get_custom_attribute_index(image_index, custom_attribute_index, token) {
Some(idx) => idx,
None => return String::new(),
};
if metadata.version >= 29.0 {
let mut buf = String::new();
custom_attribute_reader::format_custom_attribute_data(&mut buf, metadata, attr_idx, "");
buf.trim().to_string()
} else {
let start = attr_idx;
if start >= metadata.attribute_type_ranges.len() {
return String::new();
}
let range = metadata.attribute_type_ranges[start].clone();
let mut names = Vec::new();
for j in 0..range.count as usize {
let type_idx = range.start as usize + j;
if type_idx >= metadata.attribute_types.len() { break; }
let attr_type_index = metadata.attribute_types[type_idx];
if attr_type_index >= 0 && (attr_type_index as usize) < metadata.type_defs.len() {
let td = &metadata.type_defs[attr_type_index as usize];
let name = metadata.get_string_from_index(td.name_index)
.unwrap_or_else(|_| "?".to_string());
names.push(format!("[{}]", name.replace("Attribute", "")));
}
}
names.join(" ")
}
}
fn dump_string_literals_section(
file: &mut BufWriter<File>,
metadata: &mut Metadata,
) -> Result<()> {
writeln!(file, "╔══════════════════════════════════════════════════════════════════════════════╗")?;
writeln!(file, "║ SECTION 4: String Literal Table ║")?;
writeln!(file, "║ Every hardcoded string in the game binary with its index ║")?;
writeln!(file, "╚══════════════════════════════════════════════════════════════════════════════╝\n")?;
let total = metadata.string_literals.len();
let mut dumped = 0u64;
for i in 0..total {
if let Ok(value) = metadata.get_string_literal_from_index(i) {
if value.is_empty() { continue; }
let escaped = value
.replace('\\', "\\\\")
.replace('\n', "\\n")
.replace('\r', "\\r")
.replace('\t', "\\t")
.replace('\0', "\\0");
let truncated = if escaped.len() > 200 {
format!("{}...", safe_truncate(&escaped, 200))
} else {
escaped
};
writeln!(file, " [{:6}] \"{}\"", i, truncated)?;
dumped += 1;
}
}
writeln!(file, "\nTotal string literals: {} ({} non-empty)\n", total, dumped)?;
Ok(())
}
fn dump_metadata_usages_section(
file: &mut BufWriter<File>,
metadata: &mut Metadata,
il2cpp: &mut Il2Cpp,
executor: &mut Il2CppExecutor,
type_def_image_map: &HashMap<usize, (usize, String)>,
method_def_owner_map: &HashMap<usize, usize>,
) -> Result<()> {
writeln!(file, "╔══════════════════════════════════════════════════════════════════════════════╗")?;
writeln!(file, "║ SECTION 5: Metadata Usage Cross-References ║")?;
writeln!(file, "║ Maps binary addresses to the types, methods, fields and strings they use ║")?;
writeln!(file, "╚══════════════════════════════════════════════════════════════════════════════╝\n")?;
if il2cpp.version >= 27.0 {
dump_v27_metadata_usages(file, metadata, il2cpp, executor, type_def_image_map, method_def_owner_map)?;
} else if il2cpp.version > 16.0 {
dump_legacy_metadata_usages(file, metadata, il2cpp, executor, type_def_image_map, method_def_owner_map)?;
} else {
writeln!(file, " (Not available for IL2CPP version < 16)\n")?;
}
Ok(())
}
fn dump_vtable_section(
file: &mut BufWriter<File>,
metadata: &mut Metadata,
il2cpp: &mut Il2Cpp,
executor: &mut Il2CppExecutor,
type_def_image_map: &HashMap<usize, (usize, String)>,
) -> Result<()> {
writeln!(file, "╔══════════════════════════════════════════════════════════════════════════════╗")?;
writeln!(file, "║ SECTION 6: Virtual Method Tables (VTables) ║")?;
writeln!(file, "║ Full virtual dispatch tables showing which methods override which slots ║")?;
writeln!(file, "╚══════════════════════════════════════════════════════════════════════════════╝\n")?;
let mut total_types_with_vtable = 0u64;
let mut total_vtable_slots = 0u64;
for (_img_idx, image) in metadata.image_defs.clone().iter().enumerate() {
let image_name = metadata.get_string_from_index(image.name_index).unwrap_or_default();
let mut image_buffer = String::new();
let type_end = image.type_start as usize + image.type_count as usize;
for type_idx in image.type_start as usize..type_end {
let type_def = metadata.type_defs[type_idx].clone();
if type_def.vtable_count == 0 { continue; }
let type_name = executor.get_type_def_name(&type_def, type_idx, metadata, il2cpp, true, true);
let parent_name = if type_def.parent_index >= 0 && (type_def.parent_index as usize) < il2cpp.types.len() {
let parent_type = il2cpp.types[type_def.parent_index as usize].clone();
executor.get_type_name(&parent_type, metadata, il2cpp, true, false)
} else {
String::new()
};
let parent_str = if !parent_name.is_empty() {
format!(" : {}", parent_name)
} else {
String::new()
};
let mut slot_dic: BTreeMap<u16, (String, String, u64)> = BTreeMap::new();
for i in 0..type_def.vtable_count as usize {
let vtable_index = type_def.vtable_start as usize + i;
if vtable_index >= metadata.vtable_methods.len() { continue; }
let encoded = metadata.vtable_methods[vtable_index];
let usage = (encoded & 0xE0000000) >> 29;
let index = if metadata.version >= 27.0 {
(encoded & 0x1FFFFFFE) >> 1
} else {
encoded & 0x1FFFFFFF
};
if usage == 6 {
if (index as usize) < il2cpp.method_specs.len() {
let spec = il2cpp.method_specs[index as usize].clone();
let (spec_type_name, spec_method_name) = executor.get_method_spec_name(index as usize, metadata, il2cpp, true);
if let Some(md) = metadata.method_defs.get(spec.method_definition_index as usize).cloned() {
if md.slot != 0xFFFF {
let ptr = il2cpp.method_spec_generic_method_pointers
.get(&(index as usize)).copied()
.filter(|p| *p > 0)
.map(|p| il2cpp.get_rva(p))
.unwrap_or(0);
slot_dic.insert(md.slot, (spec_type_name, spec_method_name, ptr));
}
}
}
} else {
if let Some(md) = metadata.method_defs.get(index as usize).cloned() {
if md.slot != 0xFFFF {
let declaring_type_idx = md.declaring_type as usize;
let declaring_name = if let Some(td) = metadata.type_defs.get(declaring_type_idx).cloned() {
executor.get_type_def_name(&td, declaring_type_idx, metadata, il2cpp, true, true)
} else {
"?".to_string()
};
let method_name = metadata.get_string_from_index(md.name_index as i32).unwrap_or_else(|_| "?".to_string());
let img_name = type_def_image_map.get(&declaring_type_idx)
.map(|(_, n)| n.clone()).unwrap_or_default();
let method_ptr = il2cpp.get_method_pointer(&img_name, &md);
let rva = if method_ptr > 0 { il2cpp.get_rva(method_ptr) } else { 0 };
slot_dic.insert(md.slot, (declaring_name, method_name, rva));
}
}
}
}
if !slot_dic.is_empty() {
image_buffer.push_str(&format!(" {} (Token: 0x{:08X}){} [{} slots]\n",
type_name, type_def.token, parent_str, slot_dic.len()));
for (slot, (declaring, method, rva)) in &slot_dic {
let rva_str = if *rva > 0 {
format!(" @ 0x{:08X}", rva)
} else {
String::new()
};
image_buffer.push_str(&format!(" [{:4}] {}::{}{}\n", slot, declaring, method, rva_str));
total_vtable_slots += 1;
}
image_buffer.push('\n');
total_types_with_vtable += 1;
}
}
if !image_buffer.is_empty() {
writeln!(file, "─── Image: {} ───", image_name)?;
write!(file, "{}", image_buffer)?;
}
}
writeln!(file, "Total types with VTables: {} ({} total slots)\n", total_types_with_vtable, total_vtable_slots)?;
Ok(())
}
fn dump_interface_section(
file: &mut BufWriter<File>,
metadata: &mut Metadata,
il2cpp: &mut Il2Cpp,
executor: &mut Il2CppExecutor,
_type_def_image_map: &HashMap<usize, (usize, String)>,
) -> Result<()> {
writeln!(file, "╔══════════════════════════════════════════════════════════════════════════════╗")?;
writeln!(file, "║ SECTION 7: Interface Implementation Tables ║")?;
writeln!(file, "║ Which interfaces each class implements and at what vtable offsets ║")?;
writeln!(file, "╚══════════════════════════════════════════════════════════════════════════════╝\n")?;
let mut total_types_with_interfaces = 0u64;
let mut total_interface_impls = 0u64;
for (_img_idx, image) in metadata.image_defs.clone().iter().enumerate() {
let image_name = metadata.get_string_from_index(image.name_index).unwrap_or_default();
let mut image_buffer = String::new();
let type_end = image.type_start as usize + image.type_count as usize;
for type_idx in image.type_start as usize..type_end {
let type_def = metadata.type_defs[type_idx].clone();
if type_def.interfaces_count == 0 && type_def.interface_offsets_count == 0 { continue; }
let type_name = executor.get_type_def_name(&type_def, type_idx, metadata, il2cpp, true, true);
let mut type_buffer = String::new();
let mut has_data = false;
if type_def.interfaces_count > 0 {
type_buffer.push_str(" Implements:\n");
for i in 0..type_def.interfaces_count as usize {
let iface_idx = type_def.interfaces_start as usize + i;
if iface_idx >= metadata.interface_indices.len() { break; }
let interface_type_index = metadata.interface_indices[iface_idx];
if interface_type_index >= 0 && (interface_type_index as usize) < il2cpp.types.len() {
let iface_type = il2cpp.types[interface_type_index as usize].clone();
let iface_name = executor.get_type_name(&iface_type, metadata, il2cpp, true, false);
type_buffer.push_str(&format!(" - {}\n", iface_name));
total_interface_impls += 1;
has_data = true;
}
}
}
if type_def.interface_offsets_count > 0 {
type_buffer.push_str(" VTable Offsets:\n");
for i in 0..type_def.interface_offsets_count as usize {
let offset_idx = type_def.interface_offsets_start as usize + i;
if offset_idx >= metadata.interface_offsets.len() { break; }
let iface_offset = metadata.interface_offsets[offset_idx].clone();
if iface_offset.type_index >= 0 && (iface_offset.type_index as usize) < il2cpp.types.len() {
let iface_type = il2cpp.types[iface_offset.type_index as usize].clone();
let iface_name = executor.get_type_name(&iface_type, metadata, il2cpp, true, false);
type_buffer.push_str(&format!(" [{:4}] {}\n", iface_offset.offset, iface_name));
has_data = true;
}
}
}
if has_data {
image_buffer.push_str(&format!(" {} (Token: 0x{:08X})\n", type_name, type_def.token));
image_buffer.push_str(&type_buffer);
image_buffer.push('\n');
total_types_with_interfaces += 1;
}
}
if !image_buffer.is_empty() {
writeln!(file, "─── Image: {} ───", image_name)?;
write!(file, "{}", image_buffer)?;
}
}
writeln!(file, "Total types with interfaces: {} ({} total implementations)\n",
total_types_with_interfaces, total_interface_impls)?;
Ok(())
}
fn dump_legacy_metadata_usages(
file: &mut BufWriter<File>,
metadata: &mut Metadata,
il2cpp: &mut Il2Cpp,
executor: &mut Il2CppExecutor,
type_def_image_map: &HashMap<usize, (usize, String)>,
_method_def_owner_map: &HashMap<usize, usize>,
) -> Result<()> {
if metadata.metadata_usage_dic.is_empty() {
writeln!(file, " (No metadata usages found)\n")?;
return Ok(());
}
let usage_dic = metadata.metadata_usage_dic.clone();
let mut type_info_entries = Vec::new();
let mut type_ref_entries = Vec::new();
let mut method_def_entries = Vec::new();
let mut field_ref_entries = Vec::new();
let mut string_lit_entries = Vec::new();
let mut method_spec_entries = Vec::new();
for (usage_type, entries) in &usage_dic {
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;
match *usage_type {
1 => {
if src < il2cpp.types.len() {
let type_ref = il2cpp.types[src].clone();
let type_name = executor.get_type_name(&type_ref, metadata, il2cpp, true, false);
type_info_entries.push(format!(" 0x{:08X} | {}_TypeInfo", rva, type_name));
}
}
2 => {
if src < il2cpp.types.len() {
let type_ref = il2cpp.types[src].clone();
let type_name = executor.get_type_name(&type_ref, metadata, il2cpp, true, false);
type_ref_entries.push(format!(" 0x{:08X} | {}_var (Il2CppType*)", rva, type_name));
}
}
3 => {
if let Some(method_def) = metadata.method_defs.get(src).cloned() {
if let Some(type_def) = metadata.type_defs.get(method_def.declaring_type as usize).cloned() {
let td_idx = method_def.declaring_type as usize;
let type_name = executor.get_type_def_name(&type_def, td_idx, metadata, il2cpp, true, true);
let method_name = metadata.get_string_from_index(method_def.name_index as i32).unwrap_or_else(|_| "?".to_string());
let img_name = type_def_image_map.get(&td_idx).map(|(_, n)| n.clone()).unwrap_or_default();
let method_pointer = il2cpp.get_method_pointer(&img_name, &method_def);
let target_str = if method_pointer > 0 {
format!(" -> 0x{:08X}", il2cpp.get_rva(method_pointer))
} else {
String::new()
};
method_def_entries.push(format!(" 0x{:08X} | {}.{}(){}", rva, type_name, method_name, target_str));
}
}
}
4 => {
if src < metadata.field_refs.len() {
let field_ref = metadata.field_refs[src].clone();
if (field_ref.type_index as usize) < il2cpp.types.len() {
let il2cpp_type = il2cpp.types[field_ref.type_index as usize].clone();
let type_name = executor.get_type_name(&il2cpp_type, metadata, il2cpp, true, false);
let klass_idx = il2cpp_type.klass_index() as usize;
if let Some(td) = metadata.type_defs.get(klass_idx) {
let field_idx = td.field_start as usize + field_ref.field_index as usize;
if let Some(fd) = metadata.field_defs.get(field_idx) {
let field_name = metadata.get_string_from_index(fd.name_index).unwrap_or_default();
field_ref_entries.push(format!(" 0x{:08X} | {}.{}", rva, type_name, field_name));
}
}
}
}
}
5 => {
if let Ok(string_literal) = metadata.get_string_literal_from_index(src) {
let escaped = string_literal.replace('\n', "\\n").replace('\r', "\\r");
let truncated = if escaped.len() > 120 {
format!("{}...", safe_truncate(&escaped, 120))
} else {
escaped
};
string_lit_entries.push(format!(" 0x{:08X} | [{}] \"{}\"", rva, src, truncated));
}
}
6 => {
if src < il2cpp.method_specs.len() {
let (spec_type_name, spec_method_name) = executor.get_method_spec_name(src, metadata, il2cpp, true);
let method_address = il2cpp.method_spec_generic_method_pointers
.get(&src).copied()
.filter(|p| *p > 0)
.map(|p| il2cpp.get_rva(p))
.unwrap_or(0);
let target_str = if method_address > 0 {
format!(" -> 0x{:08X}", method_address)
} else {
String::new()
};
method_spec_entries.push(format!(" 0x{:08X} | {}.{}(){}", rva, spec_type_name, spec_method_name, target_str));
}
}
_ => {}
}
}
}
write_usage_subsection(file, "Type Info References (Il2CppClass*)", &type_info_entries)?;
write_usage_subsection(file, "Type Ref References (Il2CppType*)", &type_ref_entries)?;
write_usage_subsection(file, "Method Definition References", &method_def_entries)?;
write_usage_subsection(file, "Field References", &field_ref_entries)?;
write_usage_subsection(file, "String Literal Cross-References", &string_lit_entries)?;
write_usage_subsection(file, "Generic Method (MethodSpec) References", &method_spec_entries)?;
let total = type_info_entries.len() + type_ref_entries.len() + method_def_entries.len()
+ field_ref_entries.len() + string_lit_entries.len() + method_spec_entries.len();
writeln!(file, "Total metadata usage cross-references: {}\n", total)?;
Ok(())
}
fn dump_v27_metadata_usages(
file: &mut BufWriter<File>,
metadata: &mut Metadata,
il2cpp: &mut Il2Cpp,
executor: &mut Il2CppExecutor,
type_def_image_map: &HashMap<usize, (usize, String)>,
_method_def_owner_map: &HashMap<usize, usize>,
) -> Result<()> {
let pointer_size = if il2cpp.is_32bit { 4u64 } else { 8u64 };
let data_sections = il2cpp.data_sections.clone();
let mut type_info_entries = Vec::new();
let mut type_ref_entries = Vec::new();
let mut method_def_entries = Vec::new();
let mut field_ref_entries = Vec::new();
let mut string_lit_entries = Vec::new();
let mut method_spec_entries = Vec::new();
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 == 0 || usage > 6 { 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);
if va == 0 { continue; }
let rva = il2cpp.get_rva(va);
match usage {
1 => {
if (decoded_index as usize) < il2cpp.types.len() {
let type_ref = il2cpp.types[decoded_index as usize].clone();
let type_name = executor.get_type_name(&type_ref, metadata, il2cpp, true, false);
type_info_entries.push(format!(" 0x{:08X} | {}_TypeInfo", rva, type_name));
}
}
2 => {
if (decoded_index as usize) < il2cpp.types.len() {
let type_ref = il2cpp.types[decoded_index as usize].clone();
let type_name = executor.get_type_name(&type_ref, metadata, il2cpp, true, false);
type_ref_entries.push(format!(" 0x{:08X} | {}_var (Il2CppType*)", rva, type_name));
}
}
3 => {
if let Some(method_def) = metadata.method_defs.get(decoded_index as usize).cloned() {
if let Some(type_def) = metadata.type_defs.get(method_def.declaring_type as usize).cloned() {
let td_idx = method_def.declaring_type as usize;
let type_name = executor.get_type_def_name(&type_def, td_idx, metadata, il2cpp, true, true);
let method_name = metadata.get_string_from_index(method_def.name_index as i32).unwrap_or_else(|_| "?".to_string());
let img_name = type_def_image_map.get(&td_idx).map(|(_, n)| n.clone()).unwrap_or_default();
let method_pointer = il2cpp.get_method_pointer(&img_name, &method_def);
let target_str = if method_pointer > 0 {
format!(" -> 0x{:08X}", il2cpp.get_rva(method_pointer))
} else {
String::new()
};
method_def_entries.push(format!(" 0x{:08X} | {}.{}(){}", rva, type_name, method_name, target_str));
}
}
}
4 => {
if (decoded_index as usize) < metadata.field_refs.len() {
let field_ref = metadata.field_refs[decoded_index as usize].clone();
if (field_ref.type_index as usize) < il2cpp.types.len() {
let il2cpp_type = il2cpp.types[field_ref.type_index as usize].clone();
let type_name = executor.get_type_name(&il2cpp_type, metadata, il2cpp, true, false);
let klass_idx = il2cpp_type.klass_index() as usize;
if let Some(td) = metadata.type_defs.get(klass_idx) {
let field_idx = td.field_start as usize + field_ref.field_index as usize;
if let Some(fd) = metadata.field_defs.get(field_idx) {
let field_name = metadata.get_string_from_index(fd.name_index).unwrap_or_default();
field_ref_entries.push(format!(" 0x{:08X} | {}.{}", rva, type_name, field_name));
}
}
}
}
}
5 => {
if let Ok(string_literal) = metadata.get_string_literal_from_index(decoded_index as usize) {
let escaped = string_literal.replace('\n', "\\n").replace('\r', "\\r");
let truncated = if escaped.len() > 120 {
format!("{}...", safe_truncate(&escaped, 120))
} else {
escaped
};
string_lit_entries.push(format!(" 0x{:08X} | [{}] \"{}\"", rva, decoded_index, truncated));
}
}
6 => {
if (decoded_index as usize) < il2cpp.method_specs.len() {
let (spec_type_name, spec_method_name) = executor.get_method_spec_name(decoded_index as usize, metadata, il2cpp, true);
let method_address = il2cpp.method_spec_generic_method_pointers
.get(&(decoded_index as usize)).copied()
.filter(|p| *p > 0)
.map(|p| il2cpp.get_rva(p))
.unwrap_or(0);
let target_str = if method_address > 0 {
format!(" -> 0x{:08X}", method_address)
} else {
String::new()
};
method_spec_entries.push(format!(" 0x{:08X} | {}.{}(){}", rva, spec_type_name, spec_method_name, target_str));
}
}
_ => {}
}
if il2cpp.stream.position() != saved_pos {
il2cpp.stream.set_position(saved_pos);
}
}
}
write_usage_subsection(file, "Type Info References (Il2CppClass*)", &type_info_entries)?;
write_usage_subsection(file, "Type Ref References (Il2CppType*)", &type_ref_entries)?;
write_usage_subsection(file, "Method Definition References", &method_def_entries)?;
write_usage_subsection(file, "Field References", &field_ref_entries)?;
write_usage_subsection(file, "String Literal Cross-References", &string_lit_entries)?;
write_usage_subsection(file, "Generic Method (MethodSpec) References", &method_spec_entries)?;
let total = type_info_entries.len() + type_ref_entries.len() + method_def_entries.len()
+ field_ref_entries.len() + string_lit_entries.len() + method_spec_entries.len();
writeln!(file, "Total metadata usage cross-references: {}\n", total)?;
Ok(())
}
fn write_usage_subsection(
file: &mut BufWriter<File>,
title: &str,
entries: &[String],
) -> Result<()> {
if entries.is_empty() { return Ok(()); }
writeln!(file, "── {} ({}) ──", title, entries.len())?;
for entry in entries {
writeln!(file, "{}", entry)?;
}
writeln!(file)?;
Ok(())
}
fn get_rgctx_type_name(t: i64) -> &'static str {
match t {
1 => "TYPE ",
2 => "CLASS ",
3 => "METHOD ",
4 => "ARRAY ",
5 => "CONSTRAINED",
_ => "UNKNOWN ",
}
}
fn format_rgctx(
rgctx: &crate::il2cpp::structures::Il2CppRGCTXDefinition,
metadata: &mut Metadata,
il2cpp: &mut Il2Cpp,
executor: &mut Il2CppExecutor,
) -> String {
let t = rgctx.rgctx_type;
if t == 1 || t == 2 || t == 4 {
let type_idx = rgctx.type_index();
if type_idx >= 0 && (type_idx as usize) < il2cpp.types.len() {
let il2cpp_type = il2cpp.types[type_idx as usize].clone();
return executor.get_type_name(&il2cpp_type, metadata, il2cpp, true, false);
}
return format!("InvalidTypeIndex({})", type_idx);
} else if t == 3 || t == 5 {
let method_idx = rgctx.method_index();
if method_idx >= 0 && (method_idx as usize) < il2cpp.method_specs.len() {
let (t_name, m_name) = executor.get_method_spec_name(method_idx as usize, metadata, il2cpp, true);
return format!("{}::{}", t_name, m_name);
}
return format!("InvalidMethodIndex({})", method_idx);
}
format!("UnknownData({})", rgctx.rgctx_type)
}