Skip to content

Commit ed5e832

Browse files
authored
Merge pull request #105 from AlexKnauth/mono-4-linux
Linux support for Unity/Mono
2 parents 97795a8 + 0815f3c commit ed5e832

File tree

3 files changed

+236
-54
lines changed

3 files changed

+236
-54
lines changed

src/file_format/elf.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,6 +1005,13 @@ pub fn is_64_bit(process: &Process, module_address: Address) -> Option<bool> {
10051005
}
10061006
}
10071007

1008+
/// Checks if a given ELF module is 64-bit or 32-bit
1009+
pub fn pointer_size(process: &Process, module_address: Address) -> Option<PointerSize> {
1010+
let header = process.read::<Header>(module_address).ok()?;
1011+
let info = Info::parse(bytemuck::bytes_of(&header))?;
1012+
info.bitness.pointer_size()
1013+
}
1014+
10081015
#[derive(Debug, Copy, Clone, Pod, Zeroable)]
10091016
#[repr(C)]
10101017
struct ProgramHeader32 {

src/game_engine/unity/mono.rs

Lines changed: 174 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22
//! backend.
33
44
use crate::{
5-
file_format::pe, future::retry, signature::Signature, string::ArrayCString, Address, Address32,
6-
Address64, Error, PointerSize, Process,
5+
file_format::{elf, pe},
6+
future::retry,
7+
signature::Signature,
8+
string::ArrayCString,
9+
Address, Address32, Address64, Error, PointerSize, Process,
710
};
811
use core::{
912
array,
@@ -40,34 +43,61 @@ impl Module {
4043
/// correct for this function to work. If you don't know the version in
4144
/// advance, use [`attach_auto_detect`](Self::attach_auto_detect) instead.
4245
pub fn attach(process: &Process, version: Version) -> Option<Self> {
43-
let module = ["mono.dll", "mono-2.0-bdwgc.dll"]
44-
.iter()
45-
.find_map(|&name| process.get_module_address(name).ok())?;
46-
47-
let pointer_size = match pe::MachineType::read(process, module)? {
48-
pe::MachineType::X86_64 => PointerSize::Bit64,
49-
_ => PointerSize::Bit32,
46+
#[allow(unused)]
47+
let (module_range, format) = [
48+
("mono.dll", BinaryFormat::PE),
49+
("libmono.so", BinaryFormat::ELF),
50+
("mono-2.0-bdwgc.dll", BinaryFormat::PE),
51+
("libmonobdwgc-2.0.so", BinaryFormat::ELF),
52+
]
53+
.into_iter()
54+
.find_map(|(name, format)| Some((process.get_module_range(name).ok()?, format)))?;
55+
let module = module_range.0;
56+
57+
let pointer_size = match format {
58+
BinaryFormat::PE => pe::MachineType::read(process, module)?.pointer_size()?,
59+
BinaryFormat::ELF => elf::pointer_size(process, module)?,
5060
};
5161

52-
let offsets = Offsets::new(version, pointer_size)?;
53-
54-
let root_domain_function_address = pe::symbols(process, module)
55-
.find(|symbol| {
56-
symbol
57-
.get_name::<25>(process)
58-
.is_ok_and(|name| name.matches("mono_assembly_foreach"))
59-
})?
60-
.address;
62+
let offsets = Offsets::new(version, pointer_size, format)?;
63+
64+
let root_domain_function_address = match format {
65+
BinaryFormat::PE => {
66+
pe::symbols(process, module)
67+
.find(|symbol| {
68+
symbol
69+
.get_name::<25>(process)
70+
.is_ok_and(|name| name.matches("mono_assembly_foreach"))
71+
})?
72+
.address
73+
}
74+
BinaryFormat::ELF => {
75+
elf::symbols(process, module)
76+
.find(|symbol| {
77+
symbol
78+
.get_name::<25>(process)
79+
.is_ok_and(|name| name.matches("mono_assembly_foreach"))
80+
})?
81+
.address
82+
}
83+
};
6184

62-
let assemblies: Address = match pointer_size {
63-
PointerSize::Bit64 => {
85+
let assemblies: Address = match (pointer_size, format) {
86+
(PointerSize::Bit64, BinaryFormat::PE) => {
6487
const SIG_MONO_64: Signature<3> = Signature::new("48 8B 0D");
6588
let scan_address: Address = SIG_MONO_64
6689
.scan_process_range(process, (root_domain_function_address, 0x100))?
6790
+ 3;
6891
scan_address + 0x4 + process.read::<i32>(scan_address).ok()?
6992
}
70-
PointerSize::Bit32 => {
93+
(PointerSize::Bit64, BinaryFormat::ELF) => {
94+
const SIG_MONO_64_ELF: Signature<3> = Signature::new("48 8B 3D");
95+
let scan_address: Address = SIG_MONO_64_ELF
96+
.scan_process_range(process, (root_domain_function_address, 0x100))?
97+
+ 3;
98+
scan_address + 0x4 + process.read::<i32>(scan_address).ok()?
99+
}
100+
(PointerSize::Bit32, BinaryFormat::PE) => {
71101
const SIG_32_1: Signature<2> = Signature::new("FF 35");
72102
const SIG_32_2: Signature<2> = Signature::new("8B 0D");
73103

@@ -754,9 +784,13 @@ struct Offsets {
754784
}
755785

756786
impl Offsets {
757-
const fn new(version: Version, pointer_size: PointerSize) -> Option<&'static Self> {
758-
match pointer_size {
759-
PointerSize::Bit64 => match version {
787+
const fn new(
788+
version: Version,
789+
pointer_size: PointerSize,
790+
format: BinaryFormat,
791+
) -> Option<&'static Self> {
792+
match (pointer_size, format) {
793+
(PointerSize::Bit64, BinaryFormat::PE) => match version {
760794
Version::V1 => Some(&Self {
761795
monoassembly_aname: 0x10,
762796
monoassembly_image: 0x58,
@@ -799,6 +833,8 @@ impl Offsets {
799833
monovtable_vtable: 0x48,
800834
monoclassfieldalignment: 0x20,
801835
}),
836+
// 64-bit PE V2 matches Unity2019_4_2020_3_x64_PE_Offsets from
837+
// https://github.com/hackf5/unityspy/blob/master/src/HackF5.UnitySpy/Offsets/MonoLibraryOffsets.cs#L49
802838
Version::V2 => Some(&Self {
803839
monoassembly_aname: 0x10,
804840
monoassembly_image: 0x60,
@@ -842,7 +878,95 @@ impl Offsets {
842878
monoclassfieldalignment: 0x20,
843879
}),
844880
},
845-
PointerSize::Bit32 => match version {
881+
(PointerSize::Bit64, BinaryFormat::ELF) => match version {
882+
Version::V1 => Some(&Self {
883+
monoassembly_aname: 0x10,
884+
monoassembly_image: 0x58,
885+
monoimage_class_cache: 0x3D0,
886+
monointernalhashtable_table: 0x20,
887+
monointernalhashtable_size: 0x18,
888+
monoclassdef_next_class_cache: 0xF8,
889+
monoclassdef_klass: 0x0,
890+
monoclass_name: 0x40,
891+
monoclass_name_space: 0x48,
892+
monoclass_fields: 0xA0,
893+
monoclassdef_field_count: 0x8C,
894+
monoclass_runtime_info: 0xF0,
895+
monoclass_vtable_size: 0x18, // MonoVtable.data
896+
monoclass_parent: 0x28,
897+
monoclassfield_name: 0x8,
898+
monoclassfield_offset: 0x18,
899+
monoclassruntimeinfo_domain_vtables: 0x8,
900+
monovtable_vtable: 0x48,
901+
monoclassfieldalignment: 0x20,
902+
}),
903+
Version::V1Cattrs => Some(&Self {
904+
monoassembly_aname: 0x10,
905+
monoassembly_image: 0x58,
906+
monoimage_class_cache: 0x3D0,
907+
monointernalhashtable_table: 0x20,
908+
monointernalhashtable_size: 0x18,
909+
monoclassdef_next_class_cache: 0x100,
910+
monoclassdef_klass: 0x0,
911+
monoclass_name: 0x48,
912+
monoclass_name_space: 0x50,
913+
monoclass_fields: 0xA8,
914+
monoclassdef_field_count: 0x94,
915+
monoclass_runtime_info: 0xF8,
916+
monoclass_vtable_size: 0x18, // MonoVtable.data
917+
monoclass_parent: 0x28,
918+
monoclassfield_name: 0x8,
919+
monoclassfield_offset: 0x18,
920+
monoclassruntimeinfo_domain_vtables: 0x8,
921+
monovtable_vtable: 0x48,
922+
monoclassfieldalignment: 0x20,
923+
}),
924+
// 64-bit ELF V2 happens to match Unity2019_4_2020_3_x64_MachO_Offsets from
925+
// https://github.com/hackf5/unityspy/blob/master/src/HackF5.UnitySpy/Offsets/MonoLibraryOffsets.cs#L86
926+
Version::V2 => Some(&Self {
927+
monoassembly_aname: 0x10,
928+
monoassembly_image: 0x60,
929+
monoimage_class_cache: 0x4C0,
930+
monointernalhashtable_table: 0x20,
931+
monointernalhashtable_size: 0x18,
932+
monoclassdef_next_class_cache: 0x100,
933+
monoclassdef_klass: 0x0,
934+
monoclass_name: 0x40,
935+
monoclass_name_space: 0x48,
936+
monoclass_fields: 0x90,
937+
monoclassdef_field_count: 0xF8,
938+
monoclass_runtime_info: 0xC8,
939+
monoclass_vtable_size: 0x54,
940+
monoclass_parent: 0x28,
941+
monoclassfield_name: 0x8,
942+
monoclassfield_offset: 0x18,
943+
monoclassruntimeinfo_domain_vtables: 0x8,
944+
monovtable_vtable: 0x40,
945+
monoclassfieldalignment: 0x20,
946+
}),
947+
Version::V3 => Some(&Self {
948+
monoassembly_aname: 0x10,
949+
monoassembly_image: 0x60,
950+
monoimage_class_cache: 0x4D0,
951+
monointernalhashtable_table: 0x20,
952+
monointernalhashtable_size: 0x18,
953+
monoclassdef_next_class_cache: 0x100,
954+
monoclassdef_klass: 0x0,
955+
monoclass_name: 0x40,
956+
monoclass_name_space: 0x48,
957+
monoclass_fields: 0x90,
958+
monoclassdef_field_count: 0xF8,
959+
monoclass_runtime_info: 0xC8,
960+
monoclass_vtable_size: 0x54,
961+
monoclass_parent: 0x28,
962+
monoclassfield_name: 0x8,
963+
monoclassfield_offset: 0x18,
964+
monoclassruntimeinfo_domain_vtables: 0x8,
965+
monovtable_vtable: 0x48,
966+
monoclassfieldalignment: 0x20,
967+
}),
968+
},
969+
(PointerSize::Bit32, BinaryFormat::PE) => match version {
846970
Version::V1 => Some(&Self {
847971
monoassembly_aname: 0x8,
848972
monoassembly_image: 0x40,
@@ -885,6 +1009,8 @@ impl Offsets {
8851009
monovtable_vtable: 0x28,
8861010
monoclassfieldalignment: 0x10,
8871011
}),
1012+
// 32-bit PE V2 matches Unity2018_4_10_x86_PE_Offsets from
1013+
// https://github.com/hackf5/unityspy/blob/master/src/HackF5.UnitySpy/Offsets/MonoLibraryOffsets.cs#L12
8881014
Version::V2 => Some(&Self {
8891015
monoassembly_aname: 0x8,
8901016
monoassembly_image: 0x44,
@@ -933,6 +1059,13 @@ impl Offsets {
9331059
}
9341060
}
9351061

1062+
#[derive(Copy, Clone, PartialEq, Hash, Debug)]
1063+
#[non_exhaustive]
1064+
enum BinaryFormat {
1065+
PE,
1066+
ELF,
1067+
}
1068+
9361069
/// The version of Mono that was used for the game. These don't correlate to the
9371070
/// Mono version numbers.
9381071
#[derive(Copy, Clone, PartialEq, Hash, Debug)]
@@ -948,7 +1081,9 @@ pub enum Version {
9481081
}
9491082

9501083
fn detect_version(process: &Process) -> Option<Version> {
951-
if process.get_module_address("mono.dll").is_ok() {
1084+
if process.get_module_address("mono.dll").is_ok()
1085+
|| process.get_module_address("libmono.so").is_ok()
1086+
{
9521087
// If the module mono.dll is present, then it's either V1 or V1Cattrs.
9531088
// In order to distinguish between them, we check the first class listed in the
9541089
// default Assembly-CSharp image and check for the pointer to its name, assuming it's using V1.
@@ -972,11 +1107,19 @@ fn detect_version(process: &Process) -> Option<Version> {
9721107
});
9731108
}
9741109

975-
let unity_module = {
976-
let address = process.get_module_address("UnityPlayer.dll").ok()?;
977-
let range = pe::read_size_of_image(process, address)? as u64;
978-
(address, range)
979-
};
1110+
let unity_module = [
1111+
("UnityPlayer.dll", BinaryFormat::PE),
1112+
("UnityPlayer.so", BinaryFormat::ELF),
1113+
]
1114+
.into_iter()
1115+
.find_map(|(name, format)| match format {
1116+
BinaryFormat::PE => {
1117+
let address = process.get_module_address(name).ok()?;
1118+
let range = pe::read_size_of_image(process, address)? as u64;
1119+
Some((address, range))
1120+
}
1121+
BinaryFormat::ELF => process.get_module_range(name).ok(),
1122+
})?;
9801123

9811124
const SIG_202X: Signature<6> = Signature::new("00 32 30 32 ?? 2E");
9821125

0 commit comments

Comments
 (0)