use std::fs;
use std::process;
use std::time::Instant;
use clap::Parser;
use console::{style, Emoji};
use dialoguer::{Input, Select};
use indicatif::{ProgressBar, ProgressStyle};
use il2cpp_dumper::config::Config;
use il2cpp_dumper::error::Result;
use il2cpp_dumper::il2cpp::metadata::Metadata;
use il2cpp_dumper::il2cpp::base::{Il2Cpp, VaSegment};
use il2cpp_dumper::executor::Il2CppExecutor;
use il2cpp_dumper::output::decompiler::Il2CppDecompiler;
use il2cpp_dumper::output::struct_generator::StructGenerator;
use il2cpp_dumper::formats::elf::Elf;
use il2cpp_dumper::formats::pe::Pe;
use il2cpp_dumper::formats::macho::MachO;
use il2cpp_dumper::formats::nso::Nso;
use il2cpp_dumper::formats::wasm::Wasm;
const MAGIC_METADATA: u32 = 0xFAB11BAF;
const MAGIC_ELF: u32 = 0x464C457F;
const MAGIC_PE: u16 = 0x5A4D;
const MAGIC_MACHO32: u32 = 0xFEEDFACE;
const MAGIC_MACHO64: u32 = 0xFEEDFACF;
const MAGIC_MACHOFAT: u32 = 0xBEBAFECA;
const MAGIC_NSO: u32 = 0x304F534E;
const MAGIC_WASM: u32 = 0x6D736100;
static SPARKLE: Emoji<'_, '_> = Emoji("✨ ", "* ");
static PACKAGE: Emoji<'_, '_> = Emoji("📦 ", "");
static GEAR: Emoji<'_, '_> = Emoji("⚙️ ", "");
static MAG: Emoji<'_, '_> = Emoji("🔍 ", "");
static LOCK: Emoji<'_, '_> = Emoji("🔓 ", "");
static SHIELD: Emoji<'_, '_> = Emoji("🛡️ ", "");
static WARN: Emoji<'_, '_> = Emoji("⚠️ ", "! ");
static FOLDER: Emoji<'_, '_> = Emoji("📂 ", "");
static ROCKET: Emoji<'_, '_> = Emoji("🚀 ", "");
const BANNER: &str = r#"
╦╦ ╔═╗╔═╗╔═╗ ╔╦╗╦ ╦╔╦╗╔═╗╔═╗╦═╗
║║ ╠═╝║ ╠═╝ ║║║ ║║║║╠═╝║╣ ╠╦╝
╩╩═╝╚ ╚═╝╩ ═╩╝╚═╝╩ ╩╩ ╚═╝╩╚═
"#;
#[derive(Parser)]
#[command(name = "il2cpp_dumper", version, about = "IL2CPP Dumper - Rust Port")]
struct Cli {
il2cpp_binary: String,
metadata: String,
#[arg(default_value = ".")]
output_dir: String,
#[arg(long)]
config: Option<String>,
}
fn spinner(msg: &str) -> ProgressBar {
let pb = ProgressBar::new_spinner();
pb.set_style(
ProgressStyle::with_template("{spinner:.cyan} {msg} {elapsed:.dim}")
.unwrap()
.tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏", "✓"]),
);
pb.set_message(msg.to_string());
pb.enable_steady_tick(std::time::Duration::from_millis(80));
pb
}
fn print_banner() {
let version = env!("CARGO_PKG_VERSION");
println!();
println!(
" {} {}",
style("Rodroid").magenta().bold(),
style("Il2CppDumper").cyan().bold()
);
for line in BANNER.lines() {
println!(" {}", style(line).cyan().bold());
}
println!(
" {}{}",
style(" Version").dim(),
style(format!(" v{version}")).magenta().bold()
);
println!(
" {}",
style("─────────────────────────────────────").dim()
);
println!();
}
fn print_info(label: &str, value: &str) {
println!(
" {} {}",
style(format!("{label}:")).green().bold(),
style(value).cyan()
);
}
fn print_address(label: &str, addr: u64) {
println!(
" {} {}{}",
style(format!("{label}:")).green().bold(),
style("0x").dim(),
style(format!("{addr:x}")).yellow().bold()
);
}
fn print_warn(msg: &str) {
println!(" {} {}", WARN, style(msg).yellow());
}
fn print_success(msg: &str) {
println!(" {} {}", style("✓").green().bold(), style(msg).green());
}
fn print_detection(label: &str) {
println!(
" {} {}{}",
MAG,
style("Detected ").dim(),
style(label).white().bold()
);
}
fn read_magic_u32(data: &[u8]) -> u32 {
if data.len() < 4 { return 0; }
u32::from_le_bytes([data[0], data[1], data[2], data[3]])
}
fn read_magic_u16(data: &[u8]) -> u16 {
if data.len() < 2 { return 0; }
u16::from_le_bytes([data[0], data[1]])
}
fn is_valid_metadata_version(data: &[u8]) -> bool {
if data.len() < 8 { return false; }
let ver = i32::from_le_bytes([data[4], data[5], data[6], data[7]]);
ver > 0 && ver < 200
}
fn try_decrypt_metadata(data: &mut Vec<u8>) -> Option<String> {
if data.len() < 16 {
return None;
}
let magic = MAGIC_METADATA.to_le_bytes();
let k1 = magic[0] ^ data[0];
if k1 != 0 && (1..4).all(|i| (magic[i] ^ data[i]) == k1) {
let mut test = data.clone();
for b in test.iter_mut() { *b ^= k1; }
if is_valid_metadata_version(&test) {
*data = test;
return Some(format!("Single-byte XOR (key: 0x{k1:02X})"));
}
}
let key4: [u8; 4] = std::array::from_fn(|i| magic[i] ^ data[i]);
if key4 != [0u8; 4] && !key4.windows(2).all(|w| w[0] == w[1]) {
let mut test = data.clone();
for (i, b) in test.iter_mut().enumerate() { *b ^= key4[i % 4]; }
if is_valid_metadata_version(&test) {
*data = test;
return Some(format!("4-byte XOR (key: {:02X}{:02X}{:02X}{:02X})",
key4[0], key4[1], key4[2], key4[3]));
}
}
let key8: [u8; 8] = std::array::from_fn(|i| {
if i < 4 { magic[i] ^ data[i] } else { data[i] }
});
if key8[0..4] != [0u8; 4] {
let expected_ver_bytes: Vec<u8> = (4..8).map(|i| data[i] ^ key8[i]).collect();
let test_ver = i32::from_le_bytes([expected_ver_bytes[0], expected_ver_bytes[1], expected_ver_bytes[2], expected_ver_bytes[3]]);
if test_ver > 0 && test_ver < 200 {
let mut test = data.clone();
for (i, b) in test.iter_mut().enumerate() { *b ^= key8[i % 8]; }
if is_valid_metadata_version(&test) {
*data = test;
return Some(format!("8-byte XOR (key: {:02X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X})",
key8[0], key8[1], key8[2], key8[3], key8[4], key8[5], key8[6], key8[7]));
}
}
}
for key_len in [16usize, 32, 64, 128, 256] {
if data.len() < key_len * 2 { continue; }
let key: Vec<u8> = (0..key_len).map(|i| if i < 4 { magic[i] ^ data[i] } else { data[i] ^ 0 }).collect();
if key[0..4] == [0u8; 4] { continue; }
let mut test = data[..8].to_vec();
for (i, b) in test.iter_mut().enumerate() { *b ^= key[i % key_len]; }
if &test[0..4] == &magic && is_valid_metadata_version(&test) {
for (i, b) in data.iter_mut().enumerate() { *b ^= key[i % key_len]; }
return Some(format!("{key_len}-byte rolling XOR key"));
}
}
{
let mut test = data.clone();
for (i, b) in test.iter_mut().enumerate() {
*b ^= key4[i % 4] ^ (i as u8);
}
if &test[0..4] == &magic && is_valid_metadata_version(&test) {
*data = test;
return Some(format!("Position-dependent XOR (base key: {:02X}{:02X}{:02X}{:02X})",
key4[0], key4[1], key4[2], key4[3]));
}
}
{
let mut test = data.clone();
for (i, b) in test.iter_mut().enumerate() {
*b ^= key4[i % 4] ^ ((i & 0xFF) as u8);
}
if &test[0..4] == &magic && is_valid_metadata_version(&test) {
*data = test;
return Some(format!("Masked position XOR (base key: {:02X}{:02X}{:02X}{:02X})",
key4[0], key4[1], key4[2], key4[3]));
}
}
{
let header_size = 256usize.min(data.len());
let mut test = data.clone();
for i in 0..header_size {
test[i] ^= key4[i % 4];
}
if &test[0..4] == &magic && is_valid_metadata_version(&test) {
*data = test;
return Some(format!("Header-only XOR ({header_size} bytes, key: {:02X}{:02X}{:02X}{:02X})",
key4[0], key4[1], key4[2], key4[3]));
}
}
None
}
fn detect_unity_version(data: &[u8]) -> Option<String> {
let mut best: Option<String> = None;
let mut i = 0;
while i + 12 < data.len() {
if data[i] == b'2' && data[i + 1] == b'0'
&& data[i + 2].is_ascii_digit() && data[i + 3].is_ascii_digit()
&& data[i + 4] == b'.'
&& data[i + 5].is_ascii_digit()
{
let max_len = std::cmp::min(24, data.len() - i);
let end = data[i..i + max_len].iter().position(|&b| {
!b.is_ascii_alphanumeric() && b != b'.'
}).unwrap_or(max_len);
let candidate = &data[i..i + end];
if candidate.len() >= 8 && candidate.len() <= 20 {
if let Ok(s) = std::str::from_utf8(candidate) {
if s.chars().filter(|c| *c == '.').count() == 2
&& (s.contains('f') || s.contains('b') || s.contains('a') || s.contains('p'))
&& s.ends_with(|c: char| c.is_ascii_digit())
{
if best.as_ref().map_or(true, |prev| s > prev.as_str()) {
best = Some(s.to_string());
}
}
}
}
i += end.max(1);
} else {
i += 1;
}
}
best
}
fn validate_hex(input: &String) -> std::result::Result<(), String> {
let trimmed = input.trim().trim_start_matches("0x").trim_start_matches("0X");
if trimmed.is_empty() {
return Err("Address cannot be empty".into());
}
u64::from_str_radix(trimmed, 16)
.map(|_| ())
.map_err(|_| format!("'{}' is not a valid hex address", input))
}
fn parse_hex_input(input: &str) -> u64 {
let trimmed = input.trim().trim_start_matches("0x").trim_start_matches("0X");
u64::from_str_radix(trimmed, 16).unwrap_or(0)
}
fn prompt_dump_address() -> Option<u64> {
println!();
println!(
" {} {}",
WARN,
style("This appears to be a memory dump file.").yellow().bold()
);
println!(
" {}",
style(" Enter the il2cpp dump base address, or 0 to skip.").dim()
);
println!();
let input: String = Input::new()
.with_prompt(format!(" {} Dump base address (hex)", SHIELD))
.default("0".into())
.validate_with(validate_hex)
.interact_text()
.unwrap_or_else(|_| "0".into());
let addr = parse_hex_input(&input);
if addr != 0 {
Some(addr)
} else {
None
}
}
fn prompt_manual_addresses() -> Result<(u64, u64)> {
println!();
println!(
" {} {}",
style("✗").red().bold(),
style("Auto-detection failed. Manual input required.").red()
);
println!(
" {}",
style(" Provide the registration addresses in hexadecimal.").dim()
);
println!();
let cr_input: String = Input::new()
.with_prompt(format!(" {} CodeRegistration (hex)", style("→").cyan()))
.validate_with(|input: &String| {
let trimmed = input.trim().trim_start_matches("0x").trim_start_matches("0X");
if trimmed.is_empty() || trimmed == "0" {
return Err("CodeRegistration address is required".to_string());
}
validate_hex(input)
})
.interact_text()
.map_err(|_| il2cpp_dumper::error::Error::Other("Failed to read CodeRegistration input".into()))?;
let mr_input: String = Input::new()
.with_prompt(format!(" {} MetadataRegistration (hex)", style("→").cyan()))
.validate_with(|input: &String| {
let trimmed = input.trim().trim_start_matches("0x").trim_start_matches("0X");
if trimmed.is_empty() || trimmed == "0" {
return Err("MetadataRegistration address is required".to_string());
}
validate_hex(input)
})
.interact_text()
.map_err(|_| il2cpp_dumper::error::Error::Other("Failed to read MetadataRegistration input".into()))?;
let cr = parse_hex_input(&cr_input);
let mr = parse_hex_input(&mr_input);
println!();
print_address("CodeRegistration", cr);
print_address("MetadataRegistration", mr);
println!();
Ok((cr, mr))
}
fn init_elf(data: Vec<u8>, metadata: &Metadata, config: &Config) -> Result<Il2Cpp> {
let is_64 = data.len() > 4 && data[4] == 2;
print_detection(&format!("ELF{} format", if is_64 { "64" } else { "32" }));
let mut elf = Elf::new(data, !is_64)?;
let version = if config.force_il2cpp_version {
config.force_version
} else {
metadata.version
};
elf.set_properties(version, metadata.metadata_usages_count as u64);
print_info("IL2CPP Version", &elf.stream.version.to_string());
if config.force_dump || elf.check_dump() {
if let Some(addr) = prompt_dump_address() {
elf.stream.image_base = addr;
elf.is_dumped = true;
if !config.no_redirected_pointer {
elf.load()?;
}
}
}
let sp = spinner("Searching for registrations...");
let method_count = metadata.method_defs.iter().filter(|m| m.method_index >= 0).count();
let type_count = metadata.type_defs.len();
let image_count = metadata.image_defs.len();
let mut helper = elf.get_section_helper(method_count, type_count, image_count);
let code_reg = helper.find_code_registration();
let metadata_reg = helper.find_metadata_registration();
sp.finish_and_clear();
if let Some(cr) = code_reg {
print_address("CodeRegistration", cr);
}
if let Some(mr) = metadata_reg {
print_address("MetadataRegistration", mr);
}
let mut found = elf.auto_plus_init(code_reg, metadata_reg)?;
if !found {
if let Ok(Some((cr, mr))) = elf.symbol_search() {
print_detection("Symbol table");
print_address("CodeRegistration", cr);
print_address("MetadataRegistration", mr);
elf.init(cr, mr)?;
found = true;
}
}
if !found {
if let Some((cr, mr)) = elf.search_arm32(version) {
print_detection("ARM32 search pattern");
print_address("CodeRegistration", cr);
print_address("MetadataRegistration", mr);
elf.init(cr, mr)?;
found = true;
}
}
if !found {
let (cr, mr) = prompt_manual_addresses()?;
elf.init(cr, mr)?;
}
let elf_exports = elf.list_exported_symbols().unwrap_or_default();
let mut il2cpp = Il2Cpp::from_elf(&elf);
il2cpp.exported_symbols = elf_exports.iter().map(|(n, _)| n.clone()).collect();
for (name, addr) in elf_exports {
if name.starts_with("il2cpp_") || name.starts_with("mono_") {
let rva = il2cpp.get_rva(addr);
il2cpp.api_export_rvas.insert(name, rva);
}
}
Ok(il2cpp)
}
fn init_pe(data: Vec<u8>, metadata: &Metadata, config: &Config) -> Result<Il2Cpp> {
let mut pe = Pe::new(data)?;
print_detection(&format!("PE{} format", if pe.is_32bit { "32" } else { "64" }));
let version = if config.force_il2cpp_version {
config.force_version
} else {
metadata.version
};
pe.stream.version = version;
pe.stream.is_32bit = pe.is_32bit;
print_info("IL2CPP Version", &version.to_string());
if config.force_dump || pe.check_dump() {
if let Some(addr) = prompt_dump_address() {
pe.stream.image_base = addr;
}
}
let sp = spinner("Searching for registrations...");
let method_count = metadata.method_defs.iter().filter(|m| m.method_index >= 0).count();
let type_count = metadata.type_defs.len();
let image_count = metadata.image_defs.len();
let mu_count = metadata.metadata_usages_count;
let mut cr_addr = 0u64;
let mut mr_addr = 0u64;
if let Ok(Some((cr, mr))) = pe.symbol_search() {
sp.finish_and_clear();
print_detection("Symbol table");
print_address("CodeRegistration", cr);
print_address("MetadataRegistration", mr);
cr_addr = cr;
mr_addr = mr;
}
if cr_addr == 0 || mr_addr == 0 {
let mut helper = pe.get_section_helper(method_count, type_count, mu_count, image_count, version);
let code_reg = helper.find_code_registration();
let metadata_reg = helper.find_metadata_registration();
sp.finish_and_clear();
if let (Some(cr), Some(mr)) = (code_reg, metadata_reg) {
print_address("CodeRegistration", cr);
print_address("MetadataRegistration", mr);
cr_addr = cr;
mr_addr = mr;
}
} else {
sp.finish_and_clear();
}
if cr_addr == 0 || mr_addr == 0 {
let (cr, mr) = prompt_manual_addresses()?;
cr_addr = cr;
mr_addr = mr;
}
let pe_image_base = pe.image_base();
let va_segments: Vec<VaSegment> = pe.sections.iter().map(|s| {
VaSegment {
vaddr: s.virtual_address as u64 + pe_image_base,
memsz: s.virtual_size as u64,
offset: s.pointer_to_raw_data as u64,
}
}).collect();
let mut il2cpp = Il2Cpp::new(pe.stream.clone(), version, pe.is_32bit);
il2cpp.va_segments = va_segments;
il2cpp.image_base = pe_image_base;
il2cpp.is_pe = true;
il2cpp.arch = Some(if pe.is_32bit {
il2cpp_dumper::disassembler::Architecture::X86
} else {
il2cpp_dumper::disassembler::Architecture::X64
});
il2cpp.init(cr_addr, mr_addr, &|addr| pe.map_vatr(addr))?;
if let Ok(exports) = pe.list_exported_symbols() {
il2cpp.exported_symbols = exports.iter().map(|(n, _)| n.clone()).collect();
for (name, rva) in exports {
if name.starts_with("il2cpp_") || name.starts_with("mono_") {
il2cpp.api_export_rvas.insert(name, rva);
}
}
}
Ok(il2cpp)
}
fn init_macho_fat(data: Vec<u8>, metadata: &Metadata, config: &Config) -> Result<Il2Cpp> {
use il2cpp_dumper::formats::macho::{parse_fat, extract_fat_slice, MH_MAGIC_64};
let arches = parse_fat(&data)?;
print_detection(&format!("Fat Mach-O with {} architectures", arches.len()));
println!();
let items: Vec<String> = arches
.iter()
.enumerate()
.map(|(i, arch)| {
if arch.magic == MH_MAGIC_64 {
format!("{}. 64-bit", i + 1)
} else {
format!("{}. 32-bit", i + 1)
}
})
.collect();
let selection = Select::new()
.with_prompt(format!(" {} Select target architecture", GEAR))
.items(&items)
.default(0)
.interact()
.unwrap_or(0);
let slice = extract_fat_slice(&data, &arches[selection])?;
init_macho(slice, metadata, config)
}
fn init_macho(data: Vec<u8>, metadata: &Metadata, config: &Config) -> Result<Il2Cpp> {
let magic = read_magic_u32(&data);
let is_64 = magic == MAGIC_MACHO64;
print_detection(&format!("Mach-O {} format", if is_64 { "64-bit" } else { "32-bit" }));
let mut macho = MachO::new(data, !is_64)?;
let version = if config.force_il2cpp_version {
config.force_version
} else {
metadata.version
};
macho.stream.version = version;
print_info("IL2CPP Version", &version.to_string());
let sp = spinner("Searching for registrations...");
let method_count = metadata.method_defs.iter().filter(|m| m.method_index >= 0).count();
let type_count = metadata.type_defs.len();
let image_count = metadata.image_defs.len();
let mu_count = metadata.metadata_usages_count;
let mut cr_addr = 0u64;
let mut mr_addr = 0u64;
if let Some((cr, mr)) = macho.symbol_search() {
sp.finish_and_clear();
print_detection("Symbol table");
print_address("CodeRegistration", cr);
print_address("MetadataRegistration", mr);
cr_addr = cr;
mr_addr = mr;
}
if cr_addr == 0 || mr_addr == 0 {
if let Some((cr, mr)) = macho.search_mod_init_func(version) {
sp.finish_and_clear();
print_detection("__mod_init_func section");
print_address("CodeRegistration", cr);
print_address("MetadataRegistration", mr);
cr_addr = cr;
mr_addr = mr;
}
}
if cr_addr == 0 || mr_addr == 0 {
let mut helper = macho.get_section_helper(method_count, type_count, mu_count, image_count, version);
let code_reg = helper.find_code_registration();
let metadata_reg = helper.find_metadata_registration();
sp.finish_and_clear();
if let (Some(cr), Some(mr)) = (code_reg, metadata_reg) {
print_address("CodeRegistration", cr);
print_address("MetadataRegistration", mr);
cr_addr = cr;
mr_addr = mr;
}
} else {
sp.finish_and_clear();
}
if cr_addr == 0 || mr_addr == 0 {
let (cr, mr) = prompt_manual_addresses()?;
cr_addr = cr;
mr_addr = mr;
}
let va_segments: Vec<VaSegment> = macho.segments.iter().map(|s| {
VaSegment {
vaddr: s.vmaddr,
memsz: s.vmsize,
offset: s.fileoff,
}
}).collect();
let mut il2cpp = Il2Cpp::new(macho.stream.clone(), version, macho.is_32bit);
il2cpp.va_segments = va_segments;
il2cpp.init(cr_addr, mr_addr, &|addr| macho.map_vatr(addr))?;
if macho.is_32bit {
for ptr in il2cpp.method_pointers.iter_mut() {
if *ptr > 0 { *ptr -= 1; }
}
for ptr in il2cpp.custom_attribute_generators.iter_mut() {
if *ptr > 0 { *ptr -= 1; }
}
}
let macho_exports = macho.list_exported_symbols();
il2cpp.exported_symbols = macho_exports.iter().map(|(n, _)| n.clone()).collect();
for (name, addr) in macho_exports {
if name.starts_with("il2cpp_") || name.starts_with("mono_") {
let rva = il2cpp.get_rva(addr);
il2cpp.api_export_rvas.insert(name, rva);
}
}
Ok(il2cpp)
}
fn init_nso(data: Vec<u8>, metadata: &Metadata, config: &Config) -> Result<Il2Cpp> {
print_detection("NSO (Nintendo Switch) format");
let mut nso = Nso::new(data)?;
let version = if config.force_il2cpp_version {
config.force_version
} else {
metadata.version
};
print_info("IL2CPP Version", &version.to_string());
let sp = spinner("Searching for registrations...");
let method_count = metadata.method_defs.iter().filter(|m| m.method_index >= 0).count();
let type_count = metadata.type_defs.len();
let image_count = metadata.image_defs.len();
let mu_count = metadata.metadata_usages_count;
let mut helper = nso.get_section_helper(method_count, type_count, mu_count, image_count, version);
let code_reg = helper.find_code_registration();
let metadata_reg = helper.find_metadata_registration();
sp.finish_and_clear();
let (cr_addr, mr_addr) = if let (Some(cr), Some(mr)) = (code_reg, metadata_reg) {
print_address("CodeRegistration", cr);
print_address("MetadataRegistration", mr);
(cr, mr)
} else {
prompt_manual_addresses()?
};
let stream_len = nso.stream.data().len() as u64;
let mut il2cpp = Il2Cpp::new(nso.stream.clone(), version, nso.is_32bit);
il2cpp.va_segments = vec![VaSegment { vaddr: 0, memsz: stream_len, offset: 0 }];
il2cpp.init(cr_addr, mr_addr, &|addr| nso.map_vatr(addr))?;
if let Ok(nso_exports) = nso.list_exported_symbols() {
il2cpp.exported_symbols = nso_exports.iter().map(|(n, _)| n.clone()).collect();
for (name, addr) in nso_exports {
if name.starts_with("il2cpp_") || name.starts_with("mono_") {
let rva = il2cpp.get_rva(addr);
il2cpp.api_export_rvas.insert(name, rva);
}
}
}
Ok(il2cpp)
}
fn init_wasm(data: Vec<u8>, metadata: &Metadata, config: &Config) -> Result<Il2Cpp> {
print_detection("WebAssembly (WASM) format");
let wasm = Wasm::new(data)?;
let version = if config.force_il2cpp_version {
config.force_version
} else {
metadata.version
};
print_info("IL2CPP Version", &version.to_string());
let sp = spinner("Searching for registrations...");
let method_count = metadata.method_defs.iter().filter(|m| m.method_index >= 0).count();
let type_count = metadata.type_defs.len();
let image_count = metadata.image_defs.len();
let mu_count = metadata.metadata_usages_count;
let mut helper = wasm.get_section_helper(method_count, type_count, mu_count, image_count, version);
let code_reg = helper.find_code_registration();
let metadata_reg = helper.find_metadata_registration();
sp.finish_and_clear();
let (cr_addr, mr_addr) = if let (Some(cr), Some(mr)) = (code_reg, metadata_reg) {
print_address("CodeRegistration", cr);
print_address("MetadataRegistration", mr);
(cr, mr)
} else {
prompt_manual_addresses()?
};
let stream_len = wasm.stream.data().len() as u64;
let mut il2cpp = Il2Cpp::new(wasm.stream.clone(), version, wasm.is_32bit);
il2cpp.va_segments = vec![VaSegment { vaddr: 0, memsz: stream_len, offset: 0 }];
il2cpp.init(cr_addr, mr_addr, &|addr| wasm.map_vatr(addr))?;
Ok(il2cpp)
}
fn detect_format(data: &[u8]) -> &'static str {
let magic32 = read_magic_u32(data);
let magic16 = read_magic_u16(data);
match magic32 {
MAGIC_ELF => "elf",
MAGIC_MACHO32 | MAGIC_MACHO64 => "macho",
MAGIC_MACHOFAT => "macho_fat",
MAGIC_NSO => "nso",
MAGIC_WASM => "wasm",
_ if magic16 == MAGIC_PE => "pe",
_ => "unknown",
}
}
fn format_file_size(bytes: u64) -> String {
const KB: u64 = 1024;
const MB: u64 = 1024 * KB;
const GB: u64 = 1024 * MB;
if bytes >= GB {
format!("{:.2} GB", bytes as f64 / GB as f64)
} else if bytes >= MB {
format!("{:.2} MB", bytes as f64 / MB as f64)
} else if bytes >= KB {
format!("{:.1} KB", bytes as f64 / KB as f64)
} else {
format!("{bytes} B")
}
}
fn run() -> Result<()> {
let start_time = Instant::now();
let cli = Cli::parse();
print_banner();
let config = if let Some(config_path) = &cli.config {
Config::load_from_file(config_path).unwrap_or_else(|e| {
print_warn(&format!("Failed to load config from {}: {}", config_path, e));
Config::default()
})
} else if std::path::Path::new("config.json").exists() {
Config::load_from_file("config.json").unwrap_or_else(|e| {
print_warn(&format!("Failed to load config.json: {}", e));
Config::default()
})
} else {
Config::default()
};
let base_dir = std::path::Path::new(&cli.output_dir);
let mut dump_num = 0u32;
while base_dir.join(format!("Dump{dump_num}")).exists() {
dump_num += 1;
}
let output_dir = base_dir.join(format!("Dump{dump_num}")).to_string_lossy().to_string();
fs::create_dir_all(&output_dir).ok();
println!(
" {}{} {}",
FOLDER,
style("Output").dim(),
style(&output_dir).white().bold().underlined()
);
println!();
println!(
" {}",
style("─── Binary Analysis ───────────────────").dim()
);
println!();
let sp = spinner("Loading IL2CPP binary...");
let il2cpp_bytes = fs::read(&cli.il2cpp_binary)?;
let binary_size = il2cpp_bytes.len() as u64;
sp.finish_and_clear();
print_success(&format!(
"Binary loaded: {} ({})",
style(&cli.il2cpp_binary).white().bold(),
style(format_file_size(binary_size)).cyan()
));
let unity_version_str = detect_unity_version(&il2cpp_bytes);
if let Some(ref uv) = unity_version_str {
print_info("Unity Version", uv);
}
let sp = spinner("Loading metadata...");
let mut metadata_bytes = fs::read(&cli.metadata)?;
let metadata_size = metadata_bytes.len() as u64;
sp.finish_and_clear();
print_success(&format!(
"Metadata loaded: {} ({})",
style(&cli.metadata).white().bold(),
style(format_file_size(metadata_size)).cyan()
));
let metadata_magic = read_magic_u32(&metadata_bytes);
if metadata_magic != MAGIC_METADATA {
match try_decrypt_metadata(&mut metadata_bytes) {
Some(scheme) => {
println!(
" {} {}{}",
LOCK,
style("Decrypted metadata: ").dim(),
style(&scheme).yellow().bold()
);
}
None => return Err(il2cpp_dumper::error::Error::Other(
format!("Invalid metadata file (magic: 0x{metadata_magic:08X}). Encryption not recognized.")
)),
}
}
let sp = spinner("Parsing metadata structures...");
let mut metadata = Metadata::new_with_unity_version(
metadata_bytes,
unity_version_str.as_deref(),
)?;
sp.finish_and_clear();
print_info("Metadata Version", &metadata.version.to_string());
print_info("Type Definitions", &format!("{}", metadata.type_defs.len()));
print_info("Method Definitions", &format!("{}", metadata.method_defs.len()));
println!();
println!(
" {}",
style("─── Format Detection ──────────────────").dim()
);
println!();
let format = detect_format(&il2cpp_bytes);
let mut il2cpp = match format {
"elf" => init_elf(il2cpp_bytes, &metadata, &config)?,
"pe" => init_pe(il2cpp_bytes, &metadata, &config)?,
"macho" => init_macho(il2cpp_bytes, &metadata, &config)?,
"macho_fat" => init_macho_fat(il2cpp_bytes, &metadata, &config)?,
"nso" => init_nso(il2cpp_bytes, &metadata, &config)?,
"wasm" => init_wasm(il2cpp_bytes, &metadata, &config)?,
_ => {
let magic = read_magic_u32(&il2cpp_bytes);
return Err(il2cpp_dumper::error::Error::Other(
format!("Unsupported binary format (magic: 0x{magic:08X})")
));
}
};
if il2cpp.version >= 27.0 && il2cpp.is_dumped {
if let Some(type_def) = metadata.type_defs.first() {
let byval_idx = type_def.byval_type_index as usize;
if byval_idx < il2cpp.types.len() {
let il2cpp_type = &il2cpp.types[byval_idx];
let type_handle = il2cpp_type.type_handle();
il2cpp.image_base = type_handle.wrapping_sub(metadata.header.type_definitions_offset as u64);
}
}
}
println!();
println!(
" {}",
style("─── Output Generation ─────────────────").dim()
);
println!();
let sp_dump = spinner("Generating dump.cs...");
let mut executor = Il2CppExecutor::new(&metadata, &mut il2cpp)?;
Il2CppDecompiler::decompile(&mut executor, &mut metadata, &mut il2cpp, &config, &output_dir, |msg| {
sp_dump.set_message(msg.to_string());
})?;
sp_dump.finish_and_clear();
print_success("dump.cs generated");
let mut generated_files: Vec<String> = vec!["dump.cs".into()];
if config.generate_struct {
let sp = spinner("Generating structs...");
StructGenerator::write_all(&mut executor, &mut metadata, &mut il2cpp, &config, &output_dir)?;
il2cpp_dumper::output::embedded_scripts::write_scripts(std::path::Path::new(&output_dir))?;
sp.finish_and_clear();
print_success("script.json, il2cpp.h, il2cpp-functions.h, stringliteral.json generated");
generated_files.extend(["script.json".into(), "il2cpp.h".into(), "il2cpp-functions.h".into(), "stringliteral.json".into()]);
}
if config.generate_dummy_dll {
let sp = spinner("Generating dummy DLLs...");
il2cpp_dumper::output::dummy_assembly_generator::generate_dummy_dlls(
&mut executor, &mut metadata, &mut il2cpp, &config, &output_dir,
)?;
sp.finish_and_clear();
print_success("Dummy DLL files generated");
generated_files.push("DummyDll/*.dll".into());
}
if config.generate_generics_dump {
let sp = spinner("Generating generics dump...");
let generics_path = std::path::Path::new(&output_dir).join("generics_dump.txt");
if let Err(e) = il2cpp_dumper::output::generics::dump_generics(
&generics_path.to_string_lossy(), &mut metadata, &mut il2cpp, &mut executor, &config
) {
sp.finish_and_clear();
print_warn(&format!("Failed to generate generics_dump.txt: {}", e));
} else {
sp.finish_and_clear();
print_success("generics_dump.txt generated");
generated_files.push("generics_dump.txt".into());
}
}
let elapsed = start_time.elapsed();
println!();
println!(
" {}",
style("═══════════════════════════════════════").green()
);
println!(
" {} {}",
SPARKLE,
style("All tasks completed successfully!").green().bold()
);
println!(
" {}",
style("═══════════════════════════════════════").green()
);
println!();
println!(
" {}{} {}",
FOLDER,
style("Output Directory:").dim(),
style(&output_dir).white().bold().underlined()
);
println!(
" {}{} {}",
PACKAGE,
style("Generated Files:").dim(),
style(generated_files.join(", ")).cyan()
);
println!(
" {}{} {}",
ROCKET,
style("Elapsed:").dim(),
style(format!("{:.2}s", elapsed.as_secs_f64())).magenta().bold()
);
println!(
" {}",
style("───────────────────────────────────────").dim()
);
println!();
Ok(())
}
fn main() {
if let Err(e) = run() {
eprintln!();
eprintln!(
" {} {}",
style("✗ ERROR:").red().bold(),
style(&e).red()
);
eprintln!();
process::exit(1);
}
}