diff --git a/src/file_format/elf.rs b/src/file_format/elf.rs index e5a28dd..179bfed 100644 --- a/src/file_format/elf.rs +++ b/src/file_format/elf.rs @@ -1005,6 +1005,13 @@ pub fn is_64_bit(process: &Process, module_address: Address) -> Option { } } +/// Checks if a given ELF module is 64-bit or 32-bit +pub fn pointer_size(process: &Process, module_address: Address) -> Option { + let header = process.read::
(module_address).ok()?; + let info = Info::parse(bytemuck::bytes_of(&header))?; + info.bitness.pointer_size() +} + #[derive(Debug, Copy, Clone, Pod, Zeroable)] #[repr(C)] struct ProgramHeader32 { diff --git a/src/file_format/macho.rs b/src/file_format/macho.rs new file mode 100644 index 0000000..4a7c7ee --- /dev/null +++ b/src/file_format/macho.rs @@ -0,0 +1,177 @@ +//! Support for parsing Mach-O format + +#[cfg(feature = "alloc")] +use core::iter::FusedIterator; + +#[cfg(feature = "alloc")] +use alloc::collections::BTreeMap; + +#[cfg(feature = "alloc")] +use crate::{string::ArrayCString, Error}; +use crate::{Address, PointerSize, Process}; + +// Magic mach-o header constants from: +// https://opensource.apple.com/source/xnu/xnu-4570.71.2/EXTERNAL_HEADERS/mach-o/loader.h.auto.html +const MH_MAGIC_32: u32 = 0xfeedface; +const MH_CIGAM_32: u32 = 0xcefaedfe; +const MH_MAGIC_64: u32 = 0xfeedfacf; +const MH_CIGAM_64: u32 = 0xcffaedfe; + +/// Checks if a given Mach-O module is 64-bit or 32-bit +pub fn pointer_size(process: &Process, range: (Address, u64)) -> Option { + match process.read::(scan_macho_page(process, range)?).ok()? { + MH_MAGIC_64 | MH_CIGAM_64 => Some(PointerSize::Bit64), + MH_MAGIC_32 | MH_CIGAM_32 => Some(PointerSize::Bit32), + _ => None, + } +} + +/// Scans the range for a page that begins with Mach-O Magic +fn scan_macho_page(process: &Process, range: (Address, u64)) -> Option
{ + const PAGE_SIZE: u64 = 0x1000; + let (addr, len) = range; + // negation mod PAGE_SIZE + let distance_to_page = (PAGE_SIZE - (addr.value() % PAGE_SIZE)) % PAGE_SIZE; + // round up to the next multiple of PAGE_SIZE + let first_page = addr + distance_to_page; + for i in 0..((len - distance_to_page) / PAGE_SIZE) { + let a = first_page + (i * PAGE_SIZE); + match process.read::(a) { + Ok(MH_MAGIC_64 | MH_CIGAM_64 | MH_MAGIC_32 | MH_CIGAM_32) => { + return Some(a); + } + _ => (), + } + } + None +} + +// Constants for the cmd field of load commands, the type +// https://opensource.apple.com/source/xnu/xnu-4570.71.2/EXTERNAL_HEADERS/mach-o/loader.h.auto.html +/// link-edit stab symbol table info +#[cfg(feature = "alloc")] +const LC_SYMTAB: u32 = 0x2; +/// 64-bit segment of this file to be mapped +#[cfg(feature = "alloc")] +const LC_SEGMENT_64: u32 = 0x19; + +#[cfg(feature = "alloc")] +struct MachOFormatOffsets { + number_of_commands: u32, + load_commands: u32, + command_size: u32, + symtab_offset: u32, + number_of_symbols: u32, + strtab_offset: u32, + nlist_value: u32, + size_of_nlist_item: u32, + segcmd64_vmaddr: u32, + segcmd64_fileoff: u32, +} + +#[cfg(feature = "alloc")] +impl MachOFormatOffsets { + const fn new() -> Self { + // offsets taken from: + // - https://github.com/hackf5/unityspy/blob/master/src/HackF5.UnitySpy/Offsets/MachOFormatOffsets.cs + // - https://opensource.apple.com/source/xnu/xnu-4570.71.2/EXTERNAL_HEADERS/mach-o/loader.h.auto.html + MachOFormatOffsets { + number_of_commands: 0x10, + load_commands: 0x20, + command_size: 0x04, + symtab_offset: 0x08, + number_of_symbols: 0x0c, + strtab_offset: 0x10, + nlist_value: 0x08, + size_of_nlist_item: 0x10, + segcmd64_vmaddr: 0x18, + segcmd64_fileoff: 0x28, + } + } +} + +/// A symbol exported into the current module. +#[cfg(feature = "alloc")] +pub struct Symbol { + /// The address associated with the current function + pub address: Address, + /// The address storing the name of the current function + name_addr: Address, +} + +#[cfg(feature = "alloc")] +impl Symbol { + /// Tries to retrieve the name of the current function + pub fn get_name( + &self, + process: &Process, + ) -> Result, Error> { + process.read(self.name_addr) + } +} + +/// Iterates over the exported symbols for a given module. +/// Only 64-bit Mach-O format is supported +#[cfg(feature = "alloc")] +pub fn symbols( + process: &Process, + range: (Address, u64), +) -> Option + '_> { + let page = scan_macho_page(process, range)?; + let offsets = MachOFormatOffsets::new(); + let number_of_commands: u32 = process.read(page + offsets.number_of_commands).ok()?; + + let mut symtab_fileoff: u32 = 0; + let mut number_of_symbols: u32 = 0; + let mut strtab_fileoff: u32 = 0; + let mut map_fileoff_to_vmaddr: BTreeMap = BTreeMap::new(); + + let mut next: u32 = offsets.load_commands; + for _i in 0..number_of_commands { + let cmdtype: u32 = process.read(page + next).ok()?; + if cmdtype == LC_SYMTAB { + symtab_fileoff = process.read(page + next + offsets.symtab_offset).ok()?; + number_of_symbols = process.read(page + next + offsets.number_of_symbols).ok()?; + strtab_fileoff = process.read(page + next + offsets.strtab_offset).ok()?; + } else if cmdtype == LC_SEGMENT_64 { + let vmaddr: u64 = process.read(page + next + offsets.segcmd64_vmaddr).ok()?; + let fileoff: u64 = process.read(page + next + offsets.segcmd64_fileoff).ok()?; + map_fileoff_to_vmaddr.insert(fileoff, vmaddr); + } + let command_size: u32 = process.read(page + next + offsets.command_size).ok()?; + next += command_size; + } + + if symtab_fileoff == 0 || number_of_symbols == 0 || strtab_fileoff == 0 { + return None; + } + + let symtab_vmaddr = fileoff_to_vmaddr(&map_fileoff_to_vmaddr, symtab_fileoff as u64); + let strtab_vmaddr = fileoff_to_vmaddr(&map_fileoff_to_vmaddr, strtab_fileoff as u64); + + Some( + (0..number_of_symbols) + .filter_map(move |j| { + let nlist_item = page + symtab_vmaddr + (j * offsets.size_of_nlist_item); + let symname_offset: u32 = process.read(nlist_item).ok()?; + let string_address = page + strtab_vmaddr + symname_offset; + let symbol_fileoff = process.read(nlist_item + offsets.nlist_value).ok()?; + let symbol_vmaddr = fileoff_to_vmaddr(&map_fileoff_to_vmaddr, symbol_fileoff); + let symbol_address = page + symbol_vmaddr; + Some(Symbol { + address: symbol_address, + name_addr: string_address, + }) + }) + .fuse(), + ) +} + +#[cfg(feature = "alloc")] +fn fileoff_to_vmaddr(map: &BTreeMap, fileoff: u64) -> u64 { + map.iter() + .filter(|(&k, _)| k <= fileoff) + .max_by_key(|(&k, _)| k) + .map(|(&k, &v)| v + fileoff - k) + .unwrap_or(fileoff) +} diff --git a/src/file_format/mod.rs b/src/file_format/mod.rs index 14b8a83..8ca3122 100644 --- a/src/file_format/mod.rs +++ b/src/file_format/mod.rs @@ -1,4 +1,5 @@ //! Support for parsing various file formats. pub mod elf; +pub mod macho; pub mod pe; diff --git a/src/game_engine/unity/mono.rs b/src/game_engine/unity/mono.rs index 0e9857e..23e0326 100644 --- a/src/game_engine/unity/mono.rs +++ b/src/game_engine/unity/mono.rs @@ -1,8 +1,11 @@ //! Support for attaching to Unity games that are using the standard Mono //! backend. +#[cfg(feature = "alloc")] +use crate::file_format::macho; use crate::{ - file_format::pe, future::retry, signature::Signature, string::ArrayCString, Address, Address32, + file_format::{elf, pe}, + future::retry, signature::Signature, string::ArrayCString, Address, Address32, Address64, Error, PointerSize, Process, }; use core::{ @@ -40,34 +43,85 @@ impl Module { /// correct for this function to work. If you don't know the version in /// advance, use [`attach_auto_detect`](Self::attach_auto_detect) instead. pub fn attach(process: &Process, version: Version) -> Option { - let module = ["mono.dll", "mono-2.0-bdwgc.dll"] - .iter() - .find_map(|&name| process.get_module_address(name).ok())?; - - let pointer_size = match pe::MachineType::read(process, module)? { - pe::MachineType::X86_64 => PointerSize::Bit64, - _ => PointerSize::Bit32, + #[allow(unused)] + let (module_range, format) = [ + ("mono.dll", BinaryFormat::PE), + ("libmono.so", BinaryFormat::ELF), + #[cfg(feature = "alloc")] + ("libmono.0.dylib", BinaryFormat::MachO), + ("mono-2.0-bdwgc.dll", BinaryFormat::PE), + ("libmonobdwgc-2.0.so", BinaryFormat::ELF), + #[cfg(feature = "alloc")] + ("libmonobdwgc-2.0.dylib", BinaryFormat::MachO), + ] + .into_iter() + .find_map(|(name, format)| Some((process.get_module_range(name).ok()?, format)))?; + let module = module_range.0; + + let pointer_size = match format { + BinaryFormat::PE => pe::MachineType::read(process, module)?.pointer_size()?, + BinaryFormat::ELF => elf::pointer_size(process, module)?, + #[cfg(feature = "alloc")] + BinaryFormat::MachO => macho::pointer_size(process, module_range)?, }; - let offsets = Offsets::new(version, pointer_size)?; - - let root_domain_function_address = pe::symbols(process, module) - .find(|symbol| { - symbol - .get_name::<25>(process) - .is_ok_and(|name| name.matches("mono_assembly_foreach")) - })? - .address; + let offsets = Offsets::new(version, pointer_size, format)?; + + let root_domain_function_address = match format { + BinaryFormat::PE => { + pe::symbols(process, module) + .find(|symbol| { + symbol + .get_name::<25>(process) + .is_ok_and(|name| name.matches("mono_assembly_foreach")) + })? + .address + } + BinaryFormat::ELF => { + elf::symbols(process, module) + .find(|symbol| { + symbol + .get_name::<25>(process) + .is_ok_and(|name| name.matches("mono_assembly_foreach")) + })? + .address + } + #[cfg(feature = "alloc")] + BinaryFormat::MachO => { + macho::symbols(process, module_range)? + .find(|symbol| { + symbol + .get_name::<26>(process) + .is_ok_and(|name| name.matches("_mono_assembly_foreach")) + })? + .address + } + }; - let assemblies: Address = match pointer_size { - PointerSize::Bit64 => { + let assemblies: Address = match (pointer_size, format) { + (PointerSize::Bit64, BinaryFormat::PE) => { const SIG_MONO_64: Signature<3> = Signature::new("48 8B 0D"); let scan_address: Address = SIG_MONO_64 .scan_process_range(process, (root_domain_function_address, 0x100))? + 3; scan_address + 0x4 + process.read::(scan_address).ok()? } - PointerSize::Bit32 => { + (PointerSize::Bit64, BinaryFormat::ELF) => { + const SIG_MONO_64_ELF: Signature<3> = Signature::new("48 8B 3D"); + let scan_address: Address = SIG_MONO_64_ELF + .scan_process_range(process, (root_domain_function_address, 0x100))? + + 3; + scan_address + 0x4 + process.read::(scan_address).ok()? + } + #[cfg(feature = "alloc")] + (PointerSize::Bit64, BinaryFormat::MachO) => { + const SIG_MONO_64_MACHO: Signature<3> = Signature::new("48 8B 3D"); + let scan_address: Address = SIG_MONO_64_MACHO + .scan_process_range(process, (root_domain_function_address, 0x100))? + + 3; + scan_address + 0x4 + process.read::(scan_address).ok()? + } + (PointerSize::Bit32, BinaryFormat::PE) => { const SIG_32_1: Signature<2> = Signature::new("FF 35"); const SIG_32_2: Signature<2> = Signature::new("8B 0D"); @@ -754,9 +808,13 @@ struct Offsets { } impl Offsets { - const fn new(version: Version, pointer_size: PointerSize) -> Option<&'static Self> { - match pointer_size { - PointerSize::Bit64 => match version { + const fn new( + version: Version, + pointer_size: PointerSize, + format: BinaryFormat, + ) -> Option<&'static Self> { + match (pointer_size, format) { + (PointerSize::Bit64, BinaryFormat::PE) => match version { Version::V1 => Some(&Self { monoassembly_aname: 0x10, monoassembly_image: 0x58, @@ -799,6 +857,8 @@ impl Offsets { monovtable_vtable: 0x48, monoclassfieldalignment: 0x20, }), + // 64-bit PE V2 matches Unity2019_4_2020_3_x64_PE_Offsets from + // https://github.com/hackf5/unityspy/blob/master/src/HackF5.UnitySpy/Offsets/MonoLibraryOffsets.cs#L49 Version::V2 => Some(&Self { monoassembly_aname: 0x10, monoassembly_image: 0x60, @@ -842,7 +902,144 @@ impl Offsets { monoclassfieldalignment: 0x20, }), }, - PointerSize::Bit32 => match version { + (PointerSize::Bit64, BinaryFormat::ELF) => match version { + Version::V1 => Some(&Self { + monoassembly_aname: 0x10, + monoassembly_image: 0x58, + monoimage_class_cache: 0x3D0, + monointernalhashtable_table: 0x20, + monointernalhashtable_size: 0x18, + monoclassdef_next_class_cache: 0xF8, + monoclassdef_klass: 0x0, + monoclass_name: 0x40, + monoclass_name_space: 0x48, + monoclass_fields: 0xA0, + monoclassdef_field_count: 0x8C, + monoclass_runtime_info: 0xF0, + monoclass_vtable_size: 0x18, // MonoVtable.data + monoclass_parent: 0x28, + monoclassfield_name: 0x8, + monoclassfield_offset: 0x18, + monoclassruntimeinfo_domain_vtables: 0x8, + monovtable_vtable: 0x48, + monoclassfieldalignment: 0x20, + }), + Version::V1Cattrs => Some(&Self { + monoassembly_aname: 0x10, + monoassembly_image: 0x58, + monoimage_class_cache: 0x3D0, + monointernalhashtable_table: 0x20, + monointernalhashtable_size: 0x18, + monoclassdef_next_class_cache: 0x100, + monoclassdef_klass: 0x0, + monoclass_name: 0x48, + monoclass_name_space: 0x50, + monoclass_fields: 0xA8, + monoclassdef_field_count: 0x94, + monoclass_runtime_info: 0xF8, + monoclass_vtable_size: 0x18, // MonoVtable.data + monoclass_parent: 0x28, + monoclassfield_name: 0x8, + monoclassfield_offset: 0x18, + monoclassruntimeinfo_domain_vtables: 0x8, + monovtable_vtable: 0x48, + monoclassfieldalignment: 0x20, + }), + // 64-bit ELF V2 happens to match Unity2019_4_2020_3_x64_MachO_Offsets from + // https://github.com/hackf5/unityspy/blob/master/src/HackF5.UnitySpy/Offsets/MonoLibraryOffsets.cs#L86 + Version::V2 => Some(&Self { + monoassembly_aname: 0x10, + monoassembly_image: 0x60, + monoimage_class_cache: 0x4C0, + monointernalhashtable_table: 0x20, + monointernalhashtable_size: 0x18, + monoclassdef_next_class_cache: 0x100, + monoclassdef_klass: 0x0, + monoclass_name: 0x40, + monoclass_name_space: 0x48, + monoclass_fields: 0x90, + monoclassdef_field_count: 0xF8, + monoclass_runtime_info: 0xC8, + monoclass_vtable_size: 0x54, + monoclass_parent: 0x28, + monoclassfield_name: 0x8, + monoclassfield_offset: 0x18, + monoclassruntimeinfo_domain_vtables: 0x8, + monovtable_vtable: 0x40, + monoclassfieldalignment: 0x20, + }), + _ => None, + }, + #[cfg(feature = "alloc")] + (PointerSize::Bit64, BinaryFormat::MachO) => match version { + Version::V1 => Some(&Self { + monoassembly_aname: 0x10, + monoassembly_image: 0x58, + monoimage_class_cache: 0x3D0, + monointernalhashtable_table: 0x20, + monointernalhashtable_size: 0x18, + monoclassdef_next_class_cache: 0xF8, + monoclassdef_klass: 0x0, + monoclass_name: 0x40, + monoclass_name_space: 0x48, + monoclass_fields: 0xA0, + monoclassdef_field_count: 0x8C, + monoclass_runtime_info: 0xF0, + monoclass_vtable_size: 0x18, // MonoVtable.data + monoclass_parent: 0x28, + monoclassfield_name: 0x8, + monoclassfield_offset: 0x18, + monoclassruntimeinfo_domain_vtables: 0x8, + monovtable_vtable: 0x48, + monoclassfieldalignment: 0x20, + }), + Version::V1Cattrs => Some(&Self { + monoassembly_aname: 0x10, + monoassembly_image: 0x58, + monoimage_class_cache: 0x3D0, + monointernalhashtable_table: 0x20, + monointernalhashtable_size: 0x18, + monoclassdef_next_class_cache: 0x100, + monoclassdef_klass: 0x0, + monoclass_name: 0x48, + monoclass_name_space: 0x50, + monoclass_fields: 0xA8, + monoclassdef_field_count: 0x94, + monoclass_runtime_info: 0xF8, + monoclass_vtable_size: 0x18, // MonoVtable.data + monoclass_parent: 0x28, + monoclassfield_name: 0x8, + monoclassfield_offset: 0x18, + monoclassruntimeinfo_domain_vtables: 0x8, + monovtable_vtable: 0x48, + monoclassfieldalignment: 0x20, + }), + // 64-bit MachO V2 matches Unity2019_4_2020_3_x64_MachO_Offsets from + // https://github.com/hackf5/unityspy/blob/master/src/HackF5.UnitySpy/Offsets/MonoLibraryOffsets.cs#L86 + Version::V2 => Some(&Self { + monoassembly_aname: 0x10, + monoassembly_image: 0x60, + monoimage_class_cache: 0x4C0, + monointernalhashtable_table: 0x20, + monointernalhashtable_size: 0x18, + monoclassdef_next_class_cache: 0x100, + monoclassdef_klass: 0x0, + monoclass_name: 0x40, + monoclass_name_space: 0x48, + monoclass_fields: 0x90, + monoclassdef_field_count: 0xF8, + monoclass_runtime_info: 0xC8, + monoclass_vtable_size: 0x54, + monoclass_parent: 0x28, + monoclassfield_name: 0x8, + monoclassfield_offset: 0x18, + monoclassruntimeinfo_domain_vtables: 0x8, + monovtable_vtable: 0x40, + monoclassfieldalignment: 0x20, + }), + _ => None, + }, + (PointerSize::Bit32, BinaryFormat::PE) => match version { Version::V1 => Some(&Self { monoassembly_aname: 0x8, monoassembly_image: 0x40, @@ -885,6 +1082,8 @@ impl Offsets { monovtable_vtable: 0x28, monoclassfieldalignment: 0x10, }), + // 32-bit PE V2 matches Unity2018_4_10_x86_PE_Offsets from + // https://github.com/hackf5/unityspy/blob/master/src/HackF5.UnitySpy/Offsets/MonoLibraryOffsets.cs#L12 Version::V2 => Some(&Self { monoassembly_aname: 0x8, monoassembly_image: 0x44, @@ -933,6 +1132,14 @@ impl Offsets { } } +#[derive(Copy, Clone, PartialEq, Hash, Debug)] +enum BinaryFormat { + PE, + ELF, + #[cfg(feature = "alloc")] + MachO, +} + /// The version of Mono that was used for the game. These don't correlate to the /// Mono version numbers. #[derive(Copy, Clone, PartialEq, Hash, Debug)] @@ -948,7 +1155,10 @@ pub enum Version { } fn detect_version(process: &Process) -> Option { - if process.get_module_address("mono.dll").is_ok() { + if process.get_module_address("mono.dll").is_ok() + || process.get_module_address("libmono.so").is_ok() + || process.get_module_address("libmono.0.dylib").is_ok() + { // If the module mono.dll is present, then it's either V1 or V1Cattrs. // In order to distinguish between them, we check the first class listed in the // default Assembly-CSharp image and check for the pointer to its name, assuming it's using V1. @@ -972,11 +1182,23 @@ fn detect_version(process: &Process) -> Option { }); } - let unity_module = { - let address = process.get_module_address("UnityPlayer.dll").ok()?; - let range = pe::read_size_of_image(process, address)? as u64; - (address, range) - }; + let unity_module = [ + ("UnityPlayer.dll", BinaryFormat::PE), + ("UnityPlayer.so", BinaryFormat::ELF), + #[cfg(feature = "alloc")] + ("UnityPlayer.dylib", BinaryFormat::MachO), + ] + .into_iter() + .find_map(|(name, format)| match format { + BinaryFormat::PE => { + let address = process.get_module_address(name).ok()?; + let range = pe::read_size_of_image(process, address)? as u64; + Some((address, range)) + } + BinaryFormat::ELF => process.get_module_range(name).ok(), + #[cfg(feature = "alloc")] + BinaryFormat::MachO => process.get_module_range(name).ok(), + })?; const SIG_202X: Signature<6> = Signature::new("00 32 30 32 ?? 2E"); diff --git a/src/game_engine/unity/scene.rs b/src/game_engine/unity/scene.rs index 72625c2..31e2f12 100644 --- a/src/game_engine/unity/scene.rs +++ b/src/game_engine/unity/scene.rs @@ -13,8 +13,11 @@ use core::{ }; use crate::{ - file_format::pe, future::retry, signature::Signature, string::ArrayCString, Address, Address32, - Address64, Error, PointerSize, Process, + file_format::{elf, macho, pe}, + future::retry, + signature::Signature, + string::ArrayCString, + Address, Address32, Address64, Error, PointerSize, Process, }; const CSTR: usize = 128; @@ -34,38 +37,67 @@ pub struct SceneManager { impl SceneManager { /// Attaches to the scene manager in the given process. pub fn attach(process: &Process) -> Option { - const SIG_64_BIT: Signature<13> = Signature::new("48 83 EC 20 4C 8B ?5 ???????? 33 F6"); + const SIG_64_BIT_PE: Signature<13> = Signature::new("48 83 EC 20 4C 8B ?5 ???????? 33 F6"); + const SIG_64_BIT_ELF: Signature<13> = Signature::new("41 54 53 50 4C 8B ?5 ???????? 41 83"); + const SIG_64_BIT_MACHO: Signature<13> = + Signature::new("41 54 53 50 4C 8B ?5 ???????? 41 83"); const SIG_32_1: Signature<12> = Signature::new("55 8B EC 51 A1 ???????? 53 33 DB"); const SIG_32_2: Signature<6> = Signature::new("53 8D 41 ?? 33 DB"); const SIG_32_3: Signature<14> = Signature::new("55 8B EC 83 EC 18 A1 ???????? 33 C9 53"); - let unity_player = process - .get_module_address("UnityPlayer.dll") - .ok() - .and_then(|address| { - Some((address, pe::read_size_of_image(process, address)? as u64)) - })?; + let (unity_player, format) = [ + ("UnityPlayer.dll", BinaryFormat::PE), + ("UnityPlayer.so", BinaryFormat::ELF), + ("UnityPlayer.dylib", BinaryFormat::MachO), + ] + .into_iter() + .find_map(|(name, format)| { + match format { + BinaryFormat::PE => { + let address = process.get_module_address(name).ok()?; + Some(((address, pe::read_size_of_image(process, address)? as u64), format)) + } + _ => Some((process.get_module_range(name).ok()?, format)), + } + })?; - let pointer_size = match pe::MachineType::read(process, unity_player.0)? { - pe::MachineType::X86_64 => PointerSize::Bit64, - _ => PointerSize::Bit32, + let pointer_size = match format { + BinaryFormat::PE => pe::MachineType::read(process, unity_player.0)?.pointer_size()?, + BinaryFormat::ELF => elf::pointer_size(process, unity_player.0)?, + BinaryFormat::MachO => macho::pointer_size(process, unity_player)?, }; let is_il2cpp = process.get_module_address("GameAssembly.dll").is_ok(); // There are multiple signatures that can be used, depending on the version of Unity // used in the target game. - let base_address: Address = if pointer_size == PointerSize::Bit64 { - let addr = SIG_64_BIT.scan_process_range(process, unity_player)? + 7; - addr + 0x4 + process.read::(addr).ok()? - } else if let Some(addr) = SIG_32_1.scan_process_range(process, unity_player) { - process.read::(addr + 5).ok()?.into() - } else if let Some(addr) = SIG_32_2.scan_process_range(process, unity_player) { - process.read::(addr.add_signed(-4)).ok()?.into() - } else if let Some(addr) = SIG_32_3.scan_process_range(process, unity_player) { - process.read::(addr + 7).ok()?.into() - } else { - return None; + let base_address: Address = match (pointer_size, format) { + (PointerSize::Bit64, BinaryFormat::PE) => { + let addr = SIG_64_BIT_PE.scan_process_range(process, unity_player)? + 7; + addr + 0x4 + process.read::(addr).ok()? + } + (PointerSize::Bit64, BinaryFormat::ELF) => { + let addr = SIG_64_BIT_ELF.scan_process_range(process, unity_player)? + 7; + addr + 0x4 + process.read::(addr).ok()? + } + (PointerSize::Bit64, BinaryFormat::MachO) => { + let addr = SIG_64_BIT_MACHO.scan_process_range(process, unity_player)? + 7; + addr + 0x4 + process.read::(addr).ok()? + } + (PointerSize::Bit32, BinaryFormat::PE) => { + if let Some(addr) = SIG_32_1.scan_process_range(process, unity_player) { + process.read::(addr + 5).ok()?.into() + } else if let Some(addr) = SIG_32_2.scan_process_range(process, unity_player) { + process.read::(addr.add_signed(-4)).ok()?.into() + } else if let Some(addr) = SIG_32_3.scan_process_range(process, unity_player) { + process.read::(addr + 7).ok()?.into() + } else { + return None; + } + } + _ => { + return None; + } }; let offsets = Offsets::new(pointer_size); @@ -437,6 +469,13 @@ impl Transform { } } +#[derive(Copy, Clone, PartialEq, Hash, Debug)] +enum BinaryFormat { + PE, + ELF, + MachO, +} + struct Offsets { scene_count: u8, active_scene: u8,