GracefulDumper/dumpcs-gen/src/lib.rs
2025-01-05 13:58:39 +03:00

190 lines
5.8 KiB
Rust

use il2cpp::vm::{attributes::*, Il2cppField, Il2cppMethod};
use std::io::{self, Write};
use il2cpp::vm::{Il2cppDomain, Il2cppString};
unsafe fn write_assemblies_list<W: Write>(out: &mut W, domain: &Il2cppDomain) -> io::Result<()> {
for assembly in domain.assemblies() {
let image = assembly.image();
writeln!(
out,
"// Assembly {}, class count: {}",
image.name(),
image.get_class_count()
)?;
}
writeln!(out)
}
pub unsafe fn dump<W: Write>(out: &mut W) -> io::Result<()> {
il2cpp::ffi::il2cpp_gc_disable();
let domain = Il2cppDomain::get();
// First write assemblies list on top of dump.cs
write_assemblies_list(out, &domain)?;
for assembly in domain.assemblies() {
let image = assembly.image();
let image_name = image.name();
for i in 0..image.get_class_count() {
let class = image.get_class(i);
class.init();
class.init_methods();
let namespace = class.namespace();
writeln!(out, "// Assembly: {image_name}, Namespace: {namespace}")?;
write!(out, "class {}", class.il2cpp_type().name())?;
if let Some(parent) = class.parent_class() {
write!(out, " : {}", parent.il2cpp_type().name())?;
}
for (i, interface) in class.interfaces().into_iter().enumerate() {
if i == 0 && class.parent_class().is_none() {
write!(out, " : {}", interface.il2cpp_type().name())?;
} else {
write!(out, ", {}", interface.il2cpp_type().name())?;
}
}
writeln!(out, " {{ // Token: 0x{:X}", class.token())?;
class
.fields()
.iter()
.try_for_each(|field| write_class_field(out, field))?;
writeln!(out)?;
class
.methods()
.iter()
.try_for_each(|method| write_class_method(out, method))?;
writeln!(out, "}}\n")?;
}
}
Ok(())
}
fn write_class_field<W: Write>(out: &mut W, field: &Il2cppField) -> io::Result<()> {
let field_type = field.il2cpp_type();
let attrs = field_type.attrs();
write!(out, " ")?;
prepend_field_modifiers(out, attrs)?;
write!(out, "{} {}", field_type.name(), field.name())?;
if attrs & FIELD_ATTRIBUTE_LITERAL != 0 {
let value = field.static_get_value();
match field_type.type_enum() {
0x3 => write!(out, " = '{}'", char::from(value as u8))?,
0xC => write!(out, " = {}", f32::from_bits(value as u32))?,
0xD => write!(out, " = {}", f64::from_bits(value as u64))?,
0xE => write!(
out,
" = \"{}\"",
Il2cppString(value as *const u8).to_string()
)?,
_ => write!(out, " = {value}")?,
}
}
writeln!(
out,
"; // Offset: 0x{:X}, Token: 0x{:X}",
field.offset(),
field.token()
)
}
fn write_class_method<W: Write>(out: &mut W, method: &Il2cppMethod) -> io::Result<()> {
write!(
out,
" // RVA: 0x{:X}\n ",
(method.address() != 0)
.then_some(method.address() - il2cpp::ffi::base())
.unwrap_or(0)
)?;
prepend_method_modifiers(out, method.attrs())?;
write!(out, "{} {}(", method.return_type().name(), method.name())?;
for i in 0..method.arg_count() {
if i != 0 {
write!(out, ", ")?;
}
write!(out, "{} {}", method.arg_type(i).name(), method.arg_name(i))?;
}
writeln!(out, ") {{}}\n")
}
fn prepend_field_modifiers<W: Write>(out: &mut W, attrs: u32) -> io::Result<()> {
match attrs & FIELD_ATTRIBUTE_FIELD_ACCESS_MASK {
FIELD_ATTRIBUTE_PUBLIC => write!(out, "public "),
FIELD_ATTRIBUTE_PRIVATE => write!(out, "private "),
FIELD_ATTRIBUTE_FAMILY => write!(out, "protected "),
FIELD_ATTRIBUTE_ASSEMBLY | FIELD_ATTRIBUTE_FAM_AND_ASSEM => {
write!(out, "internal ")
}
FIELD_ATTRIBUTE_FAM_OR_ASSEM => write!(out, "protected internal "),
_ => Ok(()),
}?;
if attrs & FIELD_ATTRIBUTE_LITERAL != 0 {
write!(out, "const ")?;
} else if attrs & FIELD_ATTRIBUTE_STATIC != 0 {
write!(out, "static ")?;
}
if attrs & FIELD_ATTRIBUTE_INIT_ONLY != 0 {
write!(out, "readonly ")?;
}
Ok(())
}
fn prepend_method_modifiers<W: Write>(out: &mut W, attrs: u32) -> io::Result<()> {
match attrs & METHOD_ATTRIBUTE_MEMBER_ACCESS_MASK {
METHOD_ATTRIBUTE_PUBLIC => write!(out, "public "),
METHOD_ATTRIBUTE_PRIVATE => write!(out, "private "),
METHOD_ATTRIBUTE_FAMILY => write!(out, "protected "),
METHOD_ATTRIBUTE_ASSEM | METHOD_ATTRIBUTE_FAM_AND_ASSEM => {
write!(out, "internal ")
}
METHOD_ATTRIBUTE_FAM_OR_ASSEM => write!(out, "protected internal "),
_ => Ok(()),
}?;
if attrs & METHOD_ATTRIBUTE_STATIC != 0 {
write!(out, "static ")?;
}
if attrs & METHOD_ATTRIBUTE_ABSTRACT != 0 {
write!(out, "abstract ")?;
if attrs & METHOD_ATTRIBUTE_VTABLE_LAYOUT_MASK == METHOD_ATTRIBUTE_REUSE_SLOT {
write!(out, "override ")?;
}
} else if attrs & METHOD_ATTRIBUTE_FINAL != 0 {
if attrs & METHOD_ATTRIBUTE_VTABLE_LAYOUT_MASK == METHOD_ATTRIBUTE_REUSE_SLOT {
write!(out, "sealed override ")?;
}
} else if attrs & METHOD_ATTRIBUTE_VIRTUAL != 0 {
if attrs & METHOD_ATTRIBUTE_VTABLE_LAYOUT_MASK == METHOD_ATTRIBUTE_NEW_SLOT {
write!(out, "virtual ")?;
} else {
write!(out, "override ")?;
}
}
if attrs & METHOD_ATTRIBUTE_PINVOKE_IMPL != 0 {
write!(out, "extern ")?;
}
Ok(())
}