Skip to content
86 changes: 84 additions & 2 deletions src/game_engine/unity/mono/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,31 @@

#[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,

Check warning on line 28 in src/game_engine/unity/mono/class.rs

View workflow job for this annotation

GitHub Actions / Test (Host)

variant `GC_FILTER` should have an upper camel case name

Check warning on line 28 in src/game_engine/unity/mono/class.rs

View workflow job for this annotation

GitHub Actions / Test (Host)

variant `GC_FILTER` should have an upper camel case name

Check warning on line 28 in src/game_engine/unity/mono/class.rs

View workflow job for this annotation

GitHub Actions / Check clippy lints

variant `GC_FILTER` should have an upper camel case name

Check warning on line 28 in src/game_engine/unity/mono/class.rs

View workflow job for this annotation

GitHub Actions / build (wasm32-unknown-unknown, stable)

variant `GC_FILTER` should have an upper camel case name

Check warning on line 28 in src/game_engine/unity/mono/class.rs

View workflow job for this annotation

GitHub Actions / build (wasm32-wasip1, nightly)

variant `GC_FILTER` should have an upper camel case name

Check warning on line 28 in src/game_engine/unity/mono/class.rs

View workflow job for this annotation

GitHub Actions / build (wasm32-unknown-unknown, nightly)

variant `GC_FILTER` should have an upper camel case name

Check warning on line 28 in src/game_engine/unity/mono/class.rs

View workflow job for this annotation

GitHub Actions / build (wasm32-wasip1, stable)

variant `GC_FILTER` should have an upper camel case name

#[default]
Unknown,
}

/// A .NET class that is part of an [`Image`](Image).
#[derive(Copy, Clone)]
Expand Down Expand Up @@ -36,6 +61,63 @@
.and_then(|addr| process.read(addr))
}

fn class_kind(&self, process: &Process, module: &Module) -> Result<MonoTypeKind, Error> {
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::<u8>(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::<MonoTypeKind>().read() };

Ok(kind)
}
Comment on lines +66 to +81

@mitchell-merry mitchell-merry May 10, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Er, my rust noob-ness is showing here. I'm trying to read just these three bits into a MonoTypeKind, but I suspect this can be done much simpler than I've done it here... could use help on that

Maybe I should drop the enum and just use a u8?

// See https://github.com/mono/mono/blob/0f53e9e151d92944cacab3e24ac359410c606df6/mono/metadata/class-private-definition.h#L28
Version::V3 => {
process.read::<MonoTypeKind>(self.class + module.offsets.class.class_kind)
}
_ => Err(Error {}),
}
}

fn field_count(&self, process: &Process, module: &Module) -> Result<i32, Error> {
match module.version {
Version::V1 | Version::V1Cattrs => {
process.read::<i32>(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::<i32>(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,
Expand All @@ -61,8 +143,8 @@
// Prepare for next iteration
this_class = class.get_parent(process, module);

let field_count = process
.read::<i32>(class.class + module.offsets.class.field_count)
let field_count = class
.field_count(process, module)
.ok()
.filter(|&val| val > 0)
.unwrap_or_default();
Expand Down
1 change: 1 addition & 0 deletions src/game_engine/unity/mono/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading
Loading