diff --git a/src/game_engine/unity/il2cpp.rs b/src/game_engine/unity/il2cpp.rs index f6e55b8..a570d73 100644 --- a/src/game_engine/unity/il2cpp.rs +++ b/src/game_engine/unity/il2cpp.rs @@ -1,10 +1,14 @@ //! Support for attaching to Unity games that are using the IL2CPP backend. -use core::{array, cell::RefCell, iter}; +use core::{ + array, + cell::RefCell, + iter::{self, FusedIterator}, +}; use crate::{ - deep_pointer::DeepPointer, file_format::pe, future::retry, signature::Signature, - string::ArrayCString, Address, Address64, Error, PointerSize, Process, + file_format::pe, future::retry, signature::Signature, string::ArrayCString, Address, Address64, + Error, PointerSize, Process, }; #[cfg(feature = "derive")] @@ -124,14 +128,13 @@ impl Module { }; (0..nr_of_assemblies).filter_map(move |i| { - Some(Assembly { - assembly: process - .read_pointer( - assemblies + i.wrapping_mul(self.size_of_ptr()), - self.pointer_size, - ) - .ok()?, - }) + process + .read_pointer( + assemblies + i.wrapping_mul(self.size_of_ptr()), + self.pointer_size, + ) + .ok() + .map(|assembly| Assembly { assembly }) }) } @@ -147,8 +150,8 @@ impl Module { assembly .get_name::(process, self) .is_ok_and(|name| name.matches(assembly_name)) - })? - .get_image(process, self) + }) + .and_then(|assembly| assembly.get_image(process, self)) } /// Looks for the `Assembly-CSharp` binary [image](Image) inside the target @@ -229,23 +232,24 @@ impl Assembly { process: &Process, module: &Module, ) -> Result, Error> { - process.read(process.read_pointer( - self.assembly - + module.offsets.monoassembly_aname - + module.offsets.monoassemblyname_name, - module.pointer_size, - )?) + process + .read_pointer( + self.assembly + + module.offsets.monoassembly_aname + + module.offsets.monoassemblyname_name, + module.pointer_size, + ) + .and_then(|addr| process.read(addr)) } fn get_image(&self, process: &Process, module: &Module) -> Option { - Some(Image { - image: process - .read_pointer( - self.assembly + module.offsets.monoassembly_image, - module.pointer_size, - ) - .ok()?, - }) + process + .read_pointer( + self.assembly + module.offsets.monoassembly_image, + module.pointer_size, + ) + .ok() + .map(|image| Image { image }) } } @@ -263,41 +267,40 @@ impl Image { process: &'a Process, module: &'a Module, ) -> impl DoubleEndedIterator + 'a { - let type_count = process.read::(self.image + module.offsets.monoimage_typecount); + let type_count = process + .read::(self.image + module.offsets.monoimage_typecount) + .ok() + .filter(|val| !val.eq(&0)); - let metadata_ptr = match type_count { - Ok(_) => match module.version { - Version::V2020 => process.read_pointer( + let metadata_ptr = type_count.and_then(|_| match module.version { + Version::V2020 => process + .read_pointer( self.image + module.offsets.monoimage_metadatahandle, module.pointer_size, - ), - _ => Ok(self.image + module.offsets.monoimage_metadatahandle), - }, - _ => Err(Error {}), - }; + ) + .ok(), + _ => Some(self.image + module.offsets.monoimage_metadatahandle), + }); - let metadata_handle = match type_count { - Ok(0) => None, - Ok(_) => match metadata_ptr { - Ok(x) => process.read::(x).ok(), - _ => None, - }, - _ => None, - }; + let metadata_handle = type_count + .and_then(|_| metadata_ptr) + .and_then(|x| process.read::(x).ok()); let ptr = metadata_handle.map(|val| { module.type_info_definition_table + (val as u64).wrapping_mul(module.size_of_ptr()) }); (0..type_count.unwrap_or_default() as u64).filter_map(move |i| { - let class = process - .read_pointer( - ptr? + i.wrapping_mul(module.size_of_ptr()), - module.pointer_size, - ) - .ok() - .filter(|val| !val.is_null())?; - Some(Class { class }) + ptr.and_then(|ptr| { + process + .read_pointer( + ptr + i.wrapping_mul(module.size_of_ptr()), + module.pointer_size, + ) + .ok() + }) + .filter(|val| !val.is_null()) + .map(|class| Class { class }) }) } @@ -335,12 +338,12 @@ impl Class { process: &Process, module: &Module, ) -> Result, Error> { - process.read_pointer_path( - self.class, - module.pointer_size, - &[module.offsets.monoclass_name.into(), 0x0], - ) - //process.read(module.read_pointer(process, self.class + module.offsets.monoclass_name)?) + process + .read_pointer( + self.class + module.offsets.monoclass_name, + module.pointer_size, + ) + .and_then(|addr| process.read(addr)) } fn get_name_space( @@ -348,65 +351,56 @@ impl Class { process: &Process, module: &Module, ) -> Result, Error> { - process.read_pointer_path( - self.class, - module.pointer_size, - &[module.offsets.monoclass_name_space.into(), 0x0], - ) + process + .read_pointer( + self.class + module.offsets.monoclass_name_space, + module.pointer_size, + ) + .and_then(|addr| process.read(addr)) } fn fields<'a>( &'a self, process: &'a Process, module: &'a Module, - ) -> impl Iterator + '_ { - let mut this_class = Class { class: self.class }; - let mut iter_break = this_class.class.is_null(); + ) -> impl FusedIterator + '_ { + let mut this_class = Some(*self); iter::from_fn(move || { - if iter_break { - None - } else if !this_class.class.is_null() - && this_class - .get_name::(process, module) - .is_ok_and(|name| !name.matches("Object")) - && this_class + if this_class? + .get_name::(process, module) + .is_ok_and(|name| name.matches("Object")) + || this_class? .get_name_space::(process, module) - .is_ok_and(|name| !name.matches("UnityEngine")) + .is_ok_and(|name| name.matches("UnityEngine")) { + None + } else { let field_count = - process.read::(this_class.class + module.offsets.monoclass_field_count); + process.read::(this_class?.class + module.offsets.monoclass_field_count); - let fields = match field_count { - Ok(_) => process + let fields = field_count.as_ref().ok().and_then(|_| { + process .read_pointer( - this_class.class + module.offsets.monoclass_fields, + this_class?.class + module.offsets.monoclass_fields, module.pointer_size, ) - .ok(), - _ => None, - }; + .ok() + }); - let monoclassfield_structsize = module.offsets.monoclassfield_structsize as u64; - - if let Some(x) = this_class.get_parent(process, module) { - this_class = x; - } else { - iter_break = true; - } + this_class = this_class?.get_parent(process, module); Some( (0..field_count.unwrap_or_default() as u64).filter_map(move |i| { - Some(Field { - field: fields? + i.wrapping_mul(monoclassfield_structsize), + fields.map(|fields| Field { + field: fields + + i.wrapping_mul(module.offsets.monoclassfield_structsize as u64), }) }), ) - } else { - iter_break = true; - None } }) + .fuse() .flatten() } @@ -425,8 +419,8 @@ impl Class { field .get_name::(process, module) .is_ok_and(|name| name.matches(field_name)) - })? - .get_offset(process, module) + }) + .and_then(|field| field.get_offset(process, module)) } /// Tries to find the address of a static instance of the class based on its @@ -467,14 +461,14 @@ impl Class { /// Tries to find the parent class. pub fn get_parent(&self, process: &Process, module: &Module) -> Option { - let parent = process + process .read_pointer( self.class + module.offsets.monoclass_parent, module.pointer_size, ) .ok() - .filter(|val| !val.is_null())?; - Some(Class { class: parent }) + .filter(|val| !val.is_null()) + .map(|class| Class { class }) } /// Tries to find a field with the specified name in the class. This returns @@ -516,11 +510,12 @@ impl Field { process: &Process, module: &Module, ) -> Result, Error> { - process.read_pointer_path( - self.field, - module.pointer_size, - &[module.offsets.monoclassfield_name.into(), 0x0], - ) + process + .read_pointer( + self.field + module.offsets.monoclassfield_name, + module.pointer_size, + ) + .and_then(|addr| process.read(addr)) } fn get_offset(&self, process: &Process, module: &Module) -> Option { @@ -545,7 +540,6 @@ struct UnityPointerCache { base_address: Address, offsets: [u64; CAP], resolved_offsets: usize, - current_instance_pointer: Option
, starting_class: Option, } @@ -565,9 +559,8 @@ impl UnityPointer { let cache = RefCell::new(UnityPointerCache { base_address: Address::default(), offsets: [u64::default(); CAP], - current_instance_pointer: None, - starting_class: None, resolved_offsets: usize::default(), + starting_class: None, }); Self { @@ -613,45 +606,40 @@ impl UnityPointer { // Recovering the address of the static table is not very CPU intensive, // but it might be worth caching it as well if cache.base_address.is_null() { - let s_table = starting_class + cache.base_address = starting_class .get_static_table(process, module) .ok_or(Error {})?; - cache.base_address = s_table; }; - // As we need to be able to find instances in a more reliable way, - // instead of the Class itself, we need the address pointing to an - // instance of that Class. If the cache is empty, we start from the - // pointer to the static table of the first class. - let mut current_instance_pointer = match cache.current_instance_pointer { - Some(val) => val, - _ => starting_class.get_static_table_pointer(module), + // If we already resolved some offsets, we need to traverse them again starting from the base address + // of the static table in order to recalculate the address of the farthest object we can reach. + // If no offsets have been resolved yet, we just need to read the base address instead. + let mut current_object = { + let mut addr = cache.base_address; + for &i in &cache.offsets[..cache.resolved_offsets] { + addr = process.read_pointer(addr + i, module.pointer_size)?; + } + addr }; // We keep track of the already resolved offsets in order to skip resolving them again for i in cache.resolved_offsets..self.depth { - let class_instance = process - .read_pointer(current_instance_pointer, module.pointer_size) - .ok() - .filter(|val| !val.is_null()) - .ok_or(Error {})?; - - // Try to parse the offset, passed as a string, as an actual hex or decimal value - let offset_from_string = super::value_from_string(self.fields[i]); + let offset_from_string = match self.fields[i].strip_prefix("0x") { + Some(rem) => u32::from_str_radix(rem, 16).ok(), + _ => self.fields[i].parse().ok(), + }; let current_offset = match offset_from_string { Some(offset) => offset as u64, _ => { let current_class = match i { 0 => starting_class, - _ => { - let class = process - .read_pointer(class_instance, module.pointer_size) - .ok() - .filter(|val| !val.is_null()) - .ok_or(Error {})?; - Class { class } - } + _ => process + .read_pointer(current_object, module.pointer_size) + .ok() + .filter(|val| !val.is_null()) + .map(|class| Class { class }) + .ok_or(Error {})?, }; let val = current_class @@ -661,8 +649,7 @@ impl UnityPointer { .get_name::(process, module) .is_ok_and(|name| name.matches(self.fields[i])) }) - .ok_or(Error {})? - .get_offset(process, module) + .and_then(|val| val.get_offset(process, module)) .ok_or(Error {})? as u64; // Explicitly allowing this clippy because of borrowing rules shenanigans @@ -672,10 +659,10 @@ impl UnityPointer { }; cache.offsets[i] = current_offset; - - current_instance_pointer = class_instance + current_offset; - cache.current_instance_pointer = Some(current_instance_pointer); cache.resolved_offsets += 1; + + current_object = + process.read_pointer(current_object + current_offset, module.pointer_size)?; } Ok(()) @@ -705,30 +692,7 @@ impl UnityPointer { module: &Module, image: &Image, ) -> Result { - self.find_offsets(process, module, image)?; - let cache = self.cache.borrow(); - process.read_pointer_path( - cache.base_address, - module.pointer_size, - &cache.offsets[..self.depth], - ) - } - - /// Generates a `DeepPointer` struct based on the offsets - /// recovered from this `UnityPointer`. - pub fn get_deep_pointer( - &self, - process: &Process, - module: &Module, - image: &Image, - ) -> Option> { - self.find_offsets(process, module, image).ok()?; - let cache = self.cache.borrow(); - Some(DeepPointer::::new( - cache.base_address, - module.pointer_size, - &cache.offsets[..self.depth], - )) + process.read(self.deref_offsets(process, module, image)?) } } diff --git a/src/game_engine/unity/mod.rs b/src/game_engine/unity/mod.rs index 277f97f..1d4b970 100644 --- a/src/game_engine/unity/mod.rs +++ b/src/game_engine/unity/mod.rs @@ -88,11 +88,3 @@ pub mod mono; mod scene; pub use self::scene::*; - -fn value_from_string(value: &str) -> Option { - if let Some(rem) = value.strip_prefix("0x") { - u32::from_str_radix(rem, 16).ok() - } else { - value.parse().ok() - } -} diff --git a/src/game_engine/unity/mono.rs b/src/game_engine/unity/mono.rs index 3e63dc8..170c27f 100644 --- a/src/game_engine/unity/mono.rs +++ b/src/game_engine/unity/mono.rs @@ -2,10 +2,14 @@ //! backend. use crate::{ - deep_pointer::DeepPointer, file_format::pe, future::retry, signature::Signature, - string::ArrayCString, Address, Address32, Address64, Error, PointerSize, Process, + file_format::pe, future::retry, signature::Signature, string::ArrayCString, Address, Address32, + Address64, Error, PointerSize, Process, +}; +use core::{ + array, + cell::RefCell, + iter::{self, FusedIterator}, }; -use core::{array, cell::RefCell, iter}; #[cfg(feature = "derive")] pub use asr_derive::MonoClass as Class; @@ -55,7 +59,7 @@ impl Module { })? .address; - let assemblies_pointer: Address = match pointer_size { + let assemblies: Address = match pointer_size { PointerSize::Bit64 => { const SIG_MONO_64: Signature<3> = Signature::new("48 8B 0D"); let scan_address: Address = SIG_MONO_64 @@ -76,11 +80,6 @@ impl Module { _ => return None, }; - let assemblies = process - .read_pointer(assemblies_pointer, pointer_size) - .ok() - .filter(|val| !val.is_null())?; - Some(Self { pointer_size, version, @@ -89,33 +88,29 @@ impl Module { }) } - fn assemblies<'a>(&'a self, process: &'a Process) -> impl Iterator + 'a { - let mut assembly = self.assemblies; - let mut iter_break = assembly.is_null(); + fn assemblies<'a>(&'a self, process: &'a Process) -> impl FusedIterator + 'a { + let mut assembly = process + .read_pointer(self.assemblies, self.pointer_size) + .ok() + .filter(|val| !val.is_null()); + iter::from_fn(move || { - if iter_break { - None - } else { - let [data, next_assembly]: [Address; 2] = match self.pointer_size { - PointerSize::Bit64 => process - .read::<[Address64; 2]>(assembly) - .ok()? - .map(|item| item.into()), - _ => process - .read::<[Address32; 2]>(assembly) - .ok()? - .map(|item| item.into()), - }; - - if next_assembly.is_null() { - iter_break = true; - } else { - assembly = next_assembly; - } + let [data, next_assembly]: [Address; 2] = match self.pointer_size { + PointerSize::Bit64 => process + .read::<[Address64; 2]>(assembly?) + .ok()? + .map(|item| item.into()), + _ => process + .read::<[Address32; 2]>(assembly?) + .ok()? + .map(|item| item.into()), + }; - Some(Assembly { assembly: data }) - } + assembly = Some(next_assembly); + + Some(Assembly { assembly: data }) }) + .fuse() } /// Looks for the specified binary [image](Image) inside the target process. @@ -130,8 +125,8 @@ impl Module { assembly .get_name::(process, self) .is_ok_and(|name| name.matches(assembly_name)) - })? - .get_image(process, self) + }) + .and_then(|assembly| assembly.get_image(process, self)) } /// Looks for the `Assembly-CSharp` binary [image](Image) inside the target @@ -212,23 +207,23 @@ impl Assembly { process: &Process, module: &Module, ) -> Result, Error> { - process.read_pointer_path( - self.assembly, - module.pointer_size, - &[module.offsets.monoassembly_aname.into(), 0x0], - ) + process + .read_pointer( + self.assembly + module.offsets.monoassembly_aname, + module.pointer_size, + ) + .and_then(|addr| process.read(addr)) } fn get_image(&self, process: &Process, module: &Module) -> Option { - Some(Image { - image: process - .read_pointer( - self.assembly + module.offsets.monoassembly_image, - module.pointer_size, - ) - .ok() - .filter(|val| !val.is_null())?, - }) + process + .read_pointer( + self.assembly + module.offsets.monoassembly_image, + module.pointer_size, + ) + .ok() + .filter(|val| !val.is_null()) + .map(|image| Image { image }) } } @@ -245,7 +240,7 @@ impl Image { &self, process: &'a Process, module: &'a Module, - ) -> impl Iterator + 'a { + ) -> impl FusedIterator + 'a { let class_cache_size = process .read::( self.image @@ -255,26 +250,26 @@ impl Image { .ok() .filter(|&val| val != 0); - let table_addr = match class_cache_size { - Some(_) => process.read_pointer( - self.image - + module.offsets.monoimage_class_cache - + module.offsets.monointernalhashtable_table, - module.pointer_size, - ), - _ => Err(Error {}), - }; + let table_addr = class_cache_size.and_then(|_| { + process + .read_pointer( + self.image + + module.offsets.monoimage_class_cache + + module.offsets.monointernalhashtable_table, + module.pointer_size, + ) + .ok() + }); (0..class_cache_size.unwrap_or_default()).flat_map(move |i| { - let mut table = match table_addr { - Ok(table_addr) => process + let mut table = table_addr.and_then(|addr| { + process .read_pointer( - table_addr + (i as u64).wrapping_mul(module.size_of_ptr()), + addr + (i as u64).wrapping_mul(module.size_of_ptr()), module.pointer_size, ) - .ok(), - _ => None, - }; + .ok() + }); iter::from_fn(move || { let class = process.read_pointer(table?, module.pointer_size).ok()?; @@ -289,6 +284,7 @@ impl Image { Some(Class { class }) }) + .fuse() }) } @@ -326,14 +322,12 @@ impl Class { process: &Process, module: &Module, ) -> Result, Error> { - process.read_pointer_path( - self.class, - module.pointer_size, - &[ - module.offsets.monoclassdef_klass as u64 + module.offsets.monoclass_name as u64, - 0x0, - ], - ) + process + .read_pointer( + self.class + module.offsets.monoclassdef_klass + module.offsets.monoclass_name, + module.pointer_size, + ) + .and_then(|addr| process.read(addr)) } fn get_name_space( @@ -341,73 +335,64 @@ impl Class { process: &Process, module: &Module, ) -> Result, Error> { - process.read_pointer_path( - self.class, - module.pointer_size, - &[ - module.offsets.monoclassdef_klass as u64 - + module.offsets.monoclass_name_space as u64, - 0x0, - ], - ) + process + .read_pointer( + self.class + + module.offsets.monoclassdef_klass + + module.offsets.monoclass_name_space, + module.pointer_size, + ) + .and_then(|addr| process.read(addr)) } fn fields<'a>( &'a self, process: &'a Process, module: &'a Module, - ) -> impl Iterator + 'a { - let mut this_class = Class { class: self.class }; - let mut iter_break = this_class.class.is_null(); + ) -> impl FusedIterator + 'a { + let mut this_class = Some(*self); iter::from_fn(move || { - if iter_break { - None - } else if !this_class.class.is_null() - && this_class - .get_name::(process, module) - .is_ok_and(|name| !name.matches("Object")) - && this_class + if this_class? + .get_name::(process, module) + .ok()? + .matches("Object") + || this_class? .get_name_space::(process, module) - .is_ok_and(|name| !name.matches("UnityEngine")) + .ok()? + .matches("UnityEngine") { + None + } else { let field_count = process - .read::(this_class.class + module.offsets.monoclassdef_field_count) + .read::(this_class?.class + module.offsets.monoclassdef_field_count) .ok() - .filter(|&val| val != 0); + .filter(|val| !val.eq(&0)); - let fields = match field_count { - Some(_) => process + let fields = field_count.and_then(|_| { + process .read_pointer( - this_class.class + this_class?.class + module.offsets.monoclassdef_klass + module.offsets.monoclass_fields, module.pointer_size, ) - .ok(), - _ => None, - }; - - let monoclassfieldalignment = module.offsets.monoclassfieldalignment as u64; + .ok() + }); - if let Some(x) = this_class.get_parent(process, module) { - this_class = x; - } else { - iter_break = true; - } + this_class = this_class?.get_parent(process, module); Some( (0..field_count.unwrap_or_default() as u64).filter_map(move |i| { - Some(Field { - field: fields? + i.wrapping_mul(monoclassfieldalignment), + fields.map(|fields| Field { + field: fields + + i.wrapping_mul(module.offsets.monoclassfieldalignment as u64), }) }), ) - } else { - iter_break = true; - None } }) + .fuse() .flatten() } @@ -425,8 +410,8 @@ impl Class { field .get_name::(process, module) .is_ok_and(|name| name.matches(field_name)) - })? - .get_offset(process, module) + }) + .and_then(|field| field.get_offset(process, module)) } /// Tries to find the address of a static instance of the class based on its @@ -507,20 +492,16 @@ impl Class { /// Tries to find the parent class. pub fn get_parent(&self, process: &Process, module: &Module) -> Option { - let parent_addr = process + process .read_pointer( self.class + module.offsets.monoclassdef_klass + module.offsets.monoclass_parent, module.pointer_size, ) .ok() - .filter(|val| !val.is_null())?; - - Some(Class { - class: process - .read_pointer(parent_addr, module.pointer_size) - .ok() - .filter(|val| !val.is_null())?, - }) + .filter(|val| !val.is_null()) + .and_then(|parent_addr| process.read_pointer(parent_addr, module.pointer_size).ok()) + .filter(|val| !val.is_null()) + .map(|class| Class { class }) } /// Tries to find a field with the specified name in the class. This returns @@ -562,11 +543,12 @@ impl Field { process: &Process, module: &Module, ) -> Result, Error> { - process.read_pointer_path( - self.field, - module.pointer_size, - &[module.offsets.monoclassfield_name.into(), 0x0], - ) + process + .read_pointer( + self.field + module.offsets.monoclassfield_name, + module.pointer_size, + ) + .and_then(|addr| process.read(addr)) } fn get_offset(&self, process: &Process, module: &Module) -> Option { @@ -591,7 +573,6 @@ struct UnityPointerCache { base_address: Address, offsets: [u64; CAP], resolved_offsets: usize, - current_instance_pointer: Option
, starting_class: Option, } @@ -611,9 +592,8 @@ impl UnityPointer { let cache = RefCell::new(UnityPointerCache { base_address: Address::default(), offsets: [u64::default(); CAP], - current_instance_pointer: None, - starting_class: None, resolved_offsets: usize::default(), + starting_class: None, }); Self { @@ -659,55 +639,42 @@ impl UnityPointer { // Recovering the address of the static table is not very CPU intensive, // but it might be worth caching it as well if cache.base_address.is_null() { - let s_table = starting_class + cache.base_address = starting_class .get_static_table(process, module) .ok_or(Error {})?; - cache.base_address = s_table; }; - // As we need to be able to find instances in a more reliable way, - // instead of the Class itself, we need the address pointing to an - // instance of that Class. If the cache is empty, we start from the - // pointer to the static table of the first class. - let mut current_instance_pointer = match cache.current_instance_pointer { - Some(val) => val, - _ => starting_class - .get_static_table_pointer(process, module) - .ok_or(Error {})?, + // If we already resolved some offsets, we need to traverse them again starting from the base address + // of the static table in order to recalculate the address of the farthest object we can reach. + // If no offsets have been resolved yet, we just need to read the base address instead. + let mut current_object = { + let mut addr = cache.base_address; + for &i in &cache.offsets[..cache.resolved_offsets] { + addr = process.read_pointer(addr + i, module.pointer_size)?; + } + addr }; // We keep track of the already resolved offsets in order to skip resolving them again for i in cache.resolved_offsets..self.depth { - let class_instance = process - .read_pointer(current_instance_pointer, module.pointer_size) - .ok() - .filter(|val| !val.is_null()) - .ok_or(Error {})?; - - // Try to parse the offset, passed as a string, as an actual hex or decimal value - let offset_from_string = super::value_from_string(self.fields[i]); + let offset_from_string = match self.fields[i].strip_prefix("0x") { + Some(rem) => u32::from_str_radix(rem, 16).ok(), + _ => self.fields[i].parse().ok(), + }; let current_offset = match offset_from_string { Some(offset) => offset as u64, _ => { let current_class = match i { 0 => starting_class, - _ => { - let class = process - .read_pointer( - process - .read_pointer(class_instance, module.pointer_size) - .ok() - .filter(|val| !val.is_null()) - .ok_or(Error {})?, - module.pointer_size, - ) - .ok() - .filter(|val| !val.is_null()) - .ok_or(Error {})?; - - Class { class } - } + _ => process + .read_pointer(current_object, module.pointer_size) + .ok() + .filter(|val| !val.is_null()) + .and_then(|addr| process.read_pointer(addr, module.pointer_size).ok()) + .filter(|val| !val.is_null()) + .map(|class| Class { class }) + .ok_or(Error {})?, }; let val = current_class @@ -717,8 +684,7 @@ impl UnityPointer { .get_name::(process, module) .is_ok_and(|name| name.matches(self.fields[i])) }) - .ok_or(Error {})? - .get_offset(process, module) + .and_then(|val| val.get_offset(process, module)) .ok_or(Error {})? as u64; // Explicitly allowing this clippy because of borrowing rules shenanigans @@ -728,10 +694,10 @@ impl UnityPointer { }; cache.offsets[i] = current_offset; - - current_instance_pointer = class_instance + current_offset; - cache.current_instance_pointer = Some(current_instance_pointer); cache.resolved_offsets += 1; + + current_object = + process.read_pointer(current_object + current_offset, module.pointer_size)?; } Ok(()) @@ -761,30 +727,7 @@ impl UnityPointer { module: &Module, image: &Image, ) -> Result { - self.find_offsets(process, module, image)?; - let cache = self.cache.borrow(); - process.read_pointer_path( - cache.base_address, - module.pointer_size, - &cache.offsets[..self.depth], - ) - } - - /// Generates a `DeepPointer` struct based on the offsets - /// recovered from this `UnityPointer`. - pub fn get_deep_pointer( - &self, - process: &Process, - module: &Module, - image: &Image, - ) -> Option> { - self.find_offsets(process, module, image).ok()?; - let cache = self.cache.borrow(); - Some(DeepPointer::::new( - cache.base_address, - module.pointer_size, - &cache.offsets[..self.depth], - )) + process.read(self.deref_offsets(process, module, image)?) } } diff --git a/src/game_engine/unity/scene.rs b/src/game_engine/unity/scene.rs index 51c3214..72625c2 100644 --- a/src/game_engine/unity/scene.rs +++ b/src/game_engine/unity/scene.rs @@ -6,7 +6,11 @@ // // Offsets and logic for Transforms and GameObjects taken from https://github.com/Micrologist/UnityInstanceDumper -use core::{array, iter, mem::MaybeUninit}; +use core::{ + array, + iter::{self, FusedIterator}, + mem::MaybeUninit, +}; use crate::{ file_format::pe, future::retry, signature::Signature, string::ArrayCString, Address, Address32, @@ -35,7 +39,12 @@ impl SceneManager { 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_range("UnityPlayer.dll").ok()?; + 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 pointer_size = match pe::MachineType::read(process, unity_player.0)? { pe::MachineType::X86_64 => PointerSize::Bit64, @@ -91,13 +100,12 @@ impl SceneManager { /// Tries to retrieve the current active scene. fn get_current_scene(&self, process: &Process) -> Result { - Ok(Scene { - address: process - .read_pointer(self.address + self.offsets.active_scene, self.pointer_size) - .ok() - .filter(|val| !val.is_null()) - .ok_or(Error {})?, - }) + process + .read_pointer(self.address + self.offsets.active_scene, self.pointer_size) + .ok() + .filter(|val| !val.is_null()) + .map(|address| Scene { address }) + .ok_or(Error {}) } /// `DontDestroyOnLoad` is a special Unity scene containing game objects @@ -115,7 +123,8 @@ impl SceneManager { /// The value returned is a [`i32`] because some games will show `-1` as their /// current scene until fully initialized. pub fn get_current_scene_index(&self, process: &Process) -> Result { - self.get_current_scene(process)?.index(process, self) + self.get_current_scene(process) + .and_then(|scene| scene.index(process, self)) } /// Returns the full path to the current scene. Use [`get_scene_name`] @@ -124,7 +133,8 @@ impl SceneManager { &self, process: &Process, ) -> Result, Error> { - self.get_current_scene(process)?.path(process, self) + self.get_current_scene(process) + .and_then(|scene| scene.path(process, self)) } /// Returns the number of currently loaded scenes in the attached game. @@ -153,15 +163,14 @@ impl SceneManager { }; (0..num_scenes).filter_map(move |index| { - Some(Scene { - address: process - .read_pointer( - addr + (index as u64).wrapping_mul(self.size_of_ptr()), - self.pointer_size, - ) - .ok() - .filter(|val| !val.is_null())?, - }) + process + .read_pointer( + addr + (index as u64).wrapping_mul(self.size_of_ptr()), + self.pointer_size, + ) + .ok() + .filter(|val| !val.is_null()) + .map(|address| Scene { address }) }) } @@ -177,41 +186,40 @@ impl SceneManager { &'a self, process: &'a Process, scene: &Scene, - ) -> impl Iterator + 'a { + ) -> impl FusedIterator + 'a { let list_first = process .read_pointer( scene.address + self.offsets.root_storage_container, self.pointer_size, ) - .unwrap_or_default(); + .ok() + .filter(|val| !val.is_null()); let mut current_list = list_first; - let mut iter_break = current_list.is_null(); iter::from_fn(move || { - if iter_break { - None - } else { - let [first, _, third]: [Address; 3] = match self.pointer_size { - PointerSize::Bit64 => process - .read::<[Address64; 3]>(current_list) - .ok()? - .map(|a| a.into()), - _ => process - .read::<[Address32; 3]>(current_list) - .ok()? - .map(|a| a.into()), - }; - - if first == list_first { - iter_break = true; - } else { - current_list = first; - } + let [first, _, third]: [Address; 3] = match self.pointer_size { + PointerSize::Bit64 => process + .read::<[Address64; 3]>(current_list?) + .ok() + .filter(|[first, _, third]| !first.is_null() && !third.is_null())? + .map(|a| a.into()), + _ => process + .read::<[Address32; 3]>(current_list?) + .ok() + .filter(|[first, _, third]| !first.is_null() && !third.is_null())? + .map(|a| a.into()), + }; - Some(Transform { address: third }) + if first == list_first? { + current_list = None; + } else { + current_list = Some(first); } + + Some(Transform { address: third }) }) + .fuse() } /// Tries to find the specified root [`Transform`] from the currently @@ -505,11 +513,12 @@ impl Scene { process: &Process, scene_manager: &SceneManager, ) -> Result, Error> { - process.read_pointer_path( - self.address, - scene_manager.pointer_size, - &[scene_manager.offsets.asset_path as u64, 0x0], - ) + process + .read_pointer( + self.address + scene_manager.offsets.asset_path, + scene_manager.pointer_size, + ) + .and_then(|addr| process.read(addr)) } }