diff --git a/src/game_engine/unity/mono/class.rs b/src/game_engine/unity/mono/class.rs index 2ffe93c..be2b76f 100644 --- a/src/game_engine/unity/mono/class.rs +++ b/src/game_engine/unity/mono/class.rs @@ -5,6 +5,31 @@ use crate::{future::retry, string::ArrayCString, Address, Error, Process}; #[cfg(feature = "derive")] pub use asr_derive::MonoClass as Class; +use bytemuck::CheckedBitPattern; + +/// The kind of MonoClass. +/// See https://github.com/mono/mono/blob/0f53e9e151d92944cacab3e24ac359410c606df6/mono/metadata/class-internals.h#L267 +#[derive(CheckedBitPattern, Copy, Clone, Debug, Default, PartialEq, Eq)] +#[repr(u8)] +#[allow(unused)] +enum MonoTypeKind { + /// Non-generic type + DEF = 1, + /// Generic type definition + GTD = 2, + /// Generic instantiation + GINST = 3, + /// Generic parameter + GPARAM = 4, + /// vector or array + ARRAY = 5, + /// pointer or function pointer + POINTER = 6, + GC_FILTER = 0xAC, + + #[default] + Unknown, +} /// A .NET class that is part of an [`Image`](Image). #[derive(Copy, Clone)] @@ -36,6 +61,63 @@ impl Class { .and_then(|addr| process.read(addr)) } + fn class_kind(&self, process: &Process, module: &Module) -> Result { + match module.version { + // See https://github.com/mono/mono/blob/337052f86112fc0dc8435c5c4a2de43b399a14bb/mono/metadata/class-internals.h#L327 + Version::V2 => { + // TODO I feel like I'm doing this very poorly + + let byte = + process.read::(self.class + module.offsets.class.class_kind)? & 0x7u8; + + if !MonoTypeKind::is_valid_bit_pattern(&byte) { + return Err(Error {}); + } + + // SAFETY: We just checked if this was valid + let kind: MonoTypeKind = unsafe { (&raw const byte).cast::().read() }; + + Ok(kind) + } + // See https://github.com/mono/mono/blob/0f53e9e151d92944cacab3e24ac359410c606df6/mono/metadata/class-private-definition.h#L28 + Version::V3 => { + process.read::(self.class + module.offsets.class.class_kind) + } + _ => Err(Error {}), + } + } + + fn field_count(&self, process: &Process, module: &Module) -> Result { + match module.version { + Version::V1 | Version::V1Cattrs => { + process.read::(self.class + module.offsets.class.field_count) + } + Version::V2 | Version::V3 => { + let class_kind = self.class_kind(process, module)?; + + // See https://github.com/mono/mono/blob/0f53e9e151d92944cacab3e24ac359410c606df6/mono/metadata/class-accessors.c#L216 + match class_kind { + MonoTypeKind::DEF | MonoTypeKind::GTD => { + process.read::(self.class + module.offsets.class.field_count) + } + MonoTypeKind::GINST => { + let generic_class = process.read_pointer( + self.class + module.offsets.class.generic_class, + module.get_pointer_size(), + )?; + let container_class = Class { + class: process + .read_pointer(generic_class + 0x0, module.get_pointer_size())?, + }; + + container_class.field_count(process, module) + } + _ => Ok(0), + } + } + } + } + fn fields<'a>( &'a self, process: &'a Process, @@ -61,8 +143,8 @@ impl Class { // Prepare for next iteration this_class = class.get_parent(process, module); - let field_count = process - .read::(class.class + module.offsets.class.field_count) + let field_count = class + .field_count(process, module) .ok() .filter(|&val| val > 0) .unwrap_or_default(); diff --git a/src/game_engine/unity/mono/mod.rs b/src/game_engine/unity/mono/mod.rs index 56b44f4..338caa7 100644 --- a/src/game_engine/unity/mono/mod.rs +++ b/src/game_engine/unity/mono/mod.rs @@ -146,6 +146,7 @@ impl Module { check_pos: Some(7), check_byte: 0xF9, }; + if let Some(scan_address) = SIG_MONO_X86_64_MACHO .scan_process_range(process, (root_domain_function_address, 0x100)) .map(|a| a + 3) diff --git a/src/game_engine/unity/mono/offsets.rs b/src/game_engine/unity/mono/offsets.rs index dbfb5e5..cb5767d 100644 --- a/src/game_engine/unity/mono/offsets.rs +++ b/src/game_engine/unity/mono/offsets.rs @@ -37,6 +37,8 @@ impl MonoOffsets { runtime_info: 0xD0, field_count: 0x100, next_class_cache: 0x108, + class_kind: 0x1B, + generic_class: 0xF0, }, field: FieldInfoOffsets { name: 0x8, @@ -65,6 +67,8 @@ impl MonoOffsets { runtime_info: 0x7C, field_count: 0x9C, next_class_cache: 0xA0, + class_kind: 0xF, + generic_class: 0x70, }, field: FieldInfoOffsets { name: 0x4, @@ -93,6 +97,8 @@ impl MonoOffsets { runtime_info: 0xD0, field_count: 0x100, next_class_cache: 0x108, + class_kind: 0x2A, + generic_class: 0xF0, }, field: FieldInfoOffsets { name: 0x8, @@ -121,6 +127,8 @@ impl MonoOffsets { runtime_info: 0x84, field_count: 0xA4, next_class_cache: 0xA8, + class_kind: 0x1E, + generic_class: 0x94, }, field: FieldInfoOffsets { name: 0x4, @@ -149,6 +157,9 @@ impl MonoOffsets { runtime_info: 0x100, field_count: 0x9C, next_class_cache: 0x108, + // Not a thing in V1/V1Cattrs + class_kind: 0x0, + generic_class: 0x0, }, field: FieldInfoOffsets { name: 0x8, @@ -177,6 +188,9 @@ impl MonoOffsets { runtime_info: 0xA8, field_count: 0x68, next_class_cache: 0xAC, + // Not a thing in V1/V1Cattrs + class_kind: 0x0, + generic_class: 0x0, }, field: FieldInfoOffsets { name: 0x4, @@ -205,6 +219,9 @@ impl MonoOffsets { runtime_info: 0xF8, field_count: 0x94, next_class_cache: 0x100, + // Not a thing in V1/V1Cattrs + class_kind: 0x0, + generic_class: 0x0, }, field: FieldInfoOffsets { name: 0x8, @@ -233,6 +250,9 @@ impl MonoOffsets { runtime_info: 0xA4, field_count: 0x64, next_class_cache: 0xA8, + // Not a thing in V1/V1Cattrs + class_kind: 0x0, + generic_class: 0x0, }, field: FieldInfoOffsets { name: 0x4, @@ -241,118 +261,134 @@ impl MonoOffsets { }, v_table: MonoVTableOffsets { vtable: 0x28 }, }), - (BinaryFormat::ELF | BinaryFormat::MachO, Version::V3, PointerSize::Bit64) => Some(&Self { - assembly: AssemblyOffsets { - aname: 0x10, - image: 0x60, - }, - image: ImageOffsets { class_cache: 0x4D0 }, - hash_table: HashTableOffsets { - size: 0x18, - table: 0x20, - }, - class: ClassOffsets { - parent: 0x28, - image: 0x38, - name: 0x40, - namespace: 0x48, - vtable_size: 0x54, - fields: 0x90, - runtime_info: 0xC8, - field_count: 0xF8, - next_class_cache: 0x100, - }, - field: FieldInfoOffsets { - name: 0x8, - offset: 0x18, - alignment: 0x20, - }, - v_table: MonoVTableOffsets { vtable: 0x48 }, - }), - (BinaryFormat::ELF | BinaryFormat::MachO, Version::V2, PointerSize::Bit64) => Some(&Self { - assembly: AssemblyOffsets { - aname: 0x10, - image: 0x60, - }, - image: ImageOffsets { class_cache: 0x4C0 }, - hash_table: HashTableOffsets { - size: 0x18, - table: 0x20, - }, - class: ClassOffsets { - parent: 0x28, - image: 0x38, - name: 0x40, - namespace: 0x48, - vtable_size: 0x54, - fields: 0x90, - runtime_info: 0xC8, - field_count: 0xF8, - next_class_cache: 0x100, - }, - field: FieldInfoOffsets { - name: 0x8, - offset: 0x18, - alignment: 0x20, - }, - v_table: MonoVTableOffsets { vtable: 0x40 }, - }), - (BinaryFormat::ELF | BinaryFormat::MachO, Version::V1Cattrs, PointerSize::Bit64) => Some(&Self { - assembly: AssemblyOffsets { - aname: 0x10, - image: 0x58, - }, - image: ImageOffsets { class_cache: 0x3D0 }, - hash_table: HashTableOffsets { - size: 0x18, - table: 0x20, - }, - class: ClassOffsets { - parent: 0x28, - image: 0x40, - name: 0x48, - namespace: 0x50, - vtable_size: 0x18, - fields: 0xA8, - runtime_info: 0xF8, - field_count: 0x94, - next_class_cache: 0x100, - }, - field: FieldInfoOffsets { - name: 0x8, - offset: 0x18, - alignment: 0x20, - }, - v_table: MonoVTableOffsets { vtable: 0x48 }, - }), - (BinaryFormat::ELF | BinaryFormat::MachO, Version::V1, PointerSize::Bit64) => Some(&Self { - assembly: AssemblyOffsets { - aname: 0x10, - image: 0x58, - }, - image: ImageOffsets { class_cache: 0x3D0 }, - hash_table: HashTableOffsets { - size: 0x18, - table: 0x20, - }, - class: ClassOffsets { - parent: 0x28, - image: 0x38, - name: 0x40, - namespace: 0x48, - vtable_size: 0x18, - fields: 0xA0, - runtime_info: 0xF0, - field_count: 0x8C, - next_class_cache: 0xF8, - }, - field: FieldInfoOffsets { - name: 0x8, - offset: 0x18, - alignment: 0x20, - }, - v_table: MonoVTableOffsets { vtable: 0x48 }, - }), + (BinaryFormat::ELF | BinaryFormat::MachO, Version::V3, PointerSize::Bit64) => { + Some(&Self { + assembly: AssemblyOffsets { + aname: 0x10, + image: 0x60, + }, + image: ImageOffsets { class_cache: 0x4D0 }, + hash_table: HashTableOffsets { + size: 0x18, + table: 0x20, + }, + class: ClassOffsets { + parent: 0x28, + image: 0x38, + name: 0x40, + namespace: 0x48, + vtable_size: 0x54, + fields: 0x90, + runtime_info: 0xC8, + field_count: 0xF8, + next_class_cache: 0x100, + class_kind: 0x1B, + generic_class: 0xB0, + }, + field: FieldInfoOffsets { + name: 0x8, + offset: 0x18, + alignment: 0x20, + }, + v_table: MonoVTableOffsets { vtable: 0x48 }, + }) + } + (BinaryFormat::ELF | BinaryFormat::MachO, Version::V2, PointerSize::Bit64) => { + Some(&Self { + assembly: AssemblyOffsets { + aname: 0x10, + image: 0x60, + }, + image: ImageOffsets { class_cache: 0x4C0 }, + hash_table: HashTableOffsets { + size: 0x18, + table: 0x20, + }, + class: ClassOffsets { + parent: 0x28, + image: 0x38, + name: 0x40, + namespace: 0x48, + vtable_size: 0x54, + fields: 0x90, + runtime_info: 0xC8, + field_count: 0xF8, + next_class_cache: 0x100, + class_kind: 0x24, + generic_class: 0xF0, + }, + field: FieldInfoOffsets { + name: 0x8, + offset: 0x18, + alignment: 0x20, + }, + v_table: MonoVTableOffsets { vtable: 0x40 }, + }) + } + (BinaryFormat::ELF | BinaryFormat::MachO, Version::V1Cattrs, PointerSize::Bit64) => { + Some(&Self { + assembly: AssemblyOffsets { + aname: 0x10, + image: 0x58, + }, + image: ImageOffsets { class_cache: 0x3D0 }, + hash_table: HashTableOffsets { + size: 0x18, + table: 0x20, + }, + class: ClassOffsets { + parent: 0x28, + image: 0x40, + name: 0x48, + namespace: 0x50, + vtable_size: 0x18, + fields: 0xA8, + runtime_info: 0xF8, + field_count: 0x94, + next_class_cache: 0x100, + class_kind: 0x0, + generic_class: 0x0, + }, + field: FieldInfoOffsets { + name: 0x8, + offset: 0x18, + alignment: 0x20, + }, + v_table: MonoVTableOffsets { vtable: 0x48 }, + }) + } + (BinaryFormat::ELF | BinaryFormat::MachO, Version::V1, PointerSize::Bit64) => { + Some(&Self { + assembly: AssemblyOffsets { + aname: 0x10, + image: 0x58, + }, + image: ImageOffsets { class_cache: 0x3D0 }, + hash_table: HashTableOffsets { + size: 0x18, + table: 0x20, + }, + class: ClassOffsets { + parent: 0x28, + image: 0x38, + name: 0x40, + namespace: 0x48, + vtable_size: 0x18, + fields: 0xA0, + runtime_info: 0xF0, + field_count: 0x8C, + next_class_cache: 0xF8, + class_kind: 0x0, + generic_class: 0x0, + }, + field: FieldInfoOffsets { + name: 0x8, + offset: 0x18, + alignment: 0x20, + }, + v_table: MonoVTableOffsets { vtable: 0x48 }, + }) + } _ => None, } } @@ -381,7 +417,9 @@ pub(super) struct ClassOffsets { pub(super) vtable_size: u8, // On mono V1 and V1_cattrs, this offset represents MonoVTable.data pub(super) fields: u8, pub(super) runtime_info: u16, + pub(super) class_kind: u8, pub(super) field_count: u16, + pub(super) generic_class: u8, pub(super) next_class_cache: u16, }