diff --git a/src/game_engine/unity/il2cpp/class.rs b/src/game_engine/unity/il2cpp/class.rs index 4e1ba9c..92e9541 100644 --- a/src/game_engine/unity/il2cpp/class.rs +++ b/src/game_engine/unity/il2cpp/class.rs @@ -13,7 +13,20 @@ pub struct Class { } impl Class { - pub(super) fn get_name( + pub fn get_from_component( + process: &Process, + module: &Module, + component: Address, + ) -> Result { + process + .read_pointer(component, module.pointer_size) + .ok() + .filter(|val| !val.is_null()) + .map(|class| Class { class }) + .ok_or(Error {}) + } + + pub fn get_name( &self, process: &Process, module: &Module, diff --git a/src/game_engine/unity/mono/class.rs b/src/game_engine/unity/mono/class.rs index 2ffe93c..a8c7e4b 100644 --- a/src/game_engine/unity/mono/class.rs +++ b/src/game_engine/unity/mono/class.rs @@ -13,7 +13,22 @@ pub struct Class { } impl Class { - pub(super) fn get_name( + pub fn get_from_component( + process: &Process, + module: &Module, + component: Address, + ) -> Result { + process + .read_pointer(component, 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 {}) + } + + pub fn get_name( &self, process: &Process, module: &Module, diff --git a/src/game_engine/unity/scene_manager/game_object.rs b/src/game_engine/unity/scene_manager/game_object.rs new file mode 100644 index 0000000..22b3908 --- /dev/null +++ b/src/game_engine/unity/scene_manager/game_object.rs @@ -0,0 +1,164 @@ +use super::{SceneManager, CSTR}; +use crate::game_engine::unity::{il2cpp, mono}; +use crate::string::ArrayCString; +use crate::{Address, Address32, Address64, Error, PointerSize, Process}; +use core::array; +use core::mem::MaybeUninit; + +/// Representing a GameObject. From a GameObject, you can get the attached components (includes the +/// C# scripts). +#[derive(Clone, Debug)] +pub struct GameObject { + pub(super) address: Address, +} + +impl GameObject { + /// Get the name of the GameObject. + pub fn get_name( + &self, + process: &Process, + scene_manager: &SceneManager, + ) -> Result, Error> { + process.read_pointer_path( + self.address, + scene_manager.pointer_size, + &[scene_manager.offsets.game_object_name as u64, 0x0], + ) + } + + /// Traverse the classes associated with the Components attached to this game object. + pub fn classes<'a>( + &'a self, + process: &'a Process, + scene_manager: &'a SceneManager, + ) -> Result + 'a, Error> { + let (number_of_components, component_pair_array): (usize, Address) = + match scene_manager.pointer_size { + PointerSize::Bit64 => { + let array = process + .read::<[Address64; 3]>(self.address + scene_manager.offsets.game_object)?; + (array[2].value() as usize, array[0].into()) + } + _ => { + let array = process + .read::<[Address32; 3]>(self.address + scene_manager.offsets.game_object)?; + (array[2].value() as usize, array[0].into()) + } + }; + + if number_of_components == 0 { + return Err(Error {}); + } + + const ARRAY_SIZE: usize = 128; + + let components: [Address; ARRAY_SIZE] = match scene_manager.pointer_size { + PointerSize::Bit64 => { + let mut buf = [MaybeUninit::<[Address64; 2]>::uninit(); ARRAY_SIZE]; + let slice = process.read_into_uninit_slice( + component_pair_array, + &mut buf[..number_of_components], + )?; + + let mut iter = slice.iter_mut(); + array::from_fn(|_| { + iter.next() + .map(|&mut [_, second]| second.into()) + .unwrap_or_default() + }) + } + _ => { + let mut buf = [MaybeUninit::<[Address32; 2]>::uninit(); ARRAY_SIZE]; + let slice = process.read_into_uninit_slice( + component_pair_array, + &mut buf[..number_of_components], + )?; + + let mut iter = slice.iter_mut(); + array::from_fn(|_| { + iter.next() + .map(|&mut [_, second]| second.into()) + .unwrap_or_default() + }) + } + }; + + Ok((1..number_of_components).filter_map(move |m| { + process + .read_pointer( + components[m] + scene_manager.offsets.klass, + scene_manager.pointer_size, + ) + .ok() + .filter(|val| !val.is_null()) + })) + } + + /// Tries to find the base address of a class in the current `GameObject` by name. + /// + /// Mono only. + pub fn get_class_mono( + &self, + process: &Process, + scene_manager: &SceneManager, + module: &mono::Module, + name: &str, + ) -> Result { + if scene_manager.is_il2cpp { + return Err(Error {}); + } + + self.classes(process, scene_manager)? + .find(|&addr| { + let val = mono::Class::get_from_component(process, module, addr) + .and_then(|c| c.get_name::(process, module)); + + val.is_ok_and(|class_name| class_name.matches(name)) + }) + .ok_or(Error {}) + } + + /// Tries to find the base address of a class in the current `GameObject` by name. + /// + /// IL2CPP only. + pub fn get_class_il2cpp( + &self, + process: &Process, + scene_manager: &SceneManager, + module: &il2cpp::Module, + name: &str, + ) -> Result { + if !scene_manager.is_il2cpp { + return Err(Error {}); + } + + self.classes(process, scene_manager)? + .find(|&addr| { + let val = il2cpp::Class::get_from_component(process, module, addr) + .and_then(|c| c.get_name::(process, module)); + + val.is_ok_and(|class_name| class_name.matches(name)) + }) + .ok_or(Error {}) + } + + /// Returns whether the game object is considered "active" by the scene (if it or any of its + /// parents are inactive, then the game object is inactive) + pub fn is_active_in_hierarchy( + &self, + process: &Process, + scene_manager: &SceneManager, + ) -> Result { + process.read::(self.address + scene_manager.offsets.game_object_activeinhierarchy) + } + + /// Returns whether the game object is considered "active" by itself (irrespective of any of its + /// parents) + pub fn is_active_self( + &self, + process: &Process, + scene_manager: &SceneManager, + ) -> Result { + process.read::(self.address + scene_manager.offsets.game_object_activeself) + } +} diff --git a/src/game_engine/unity/scene_manager/game_objects.rs b/src/game_engine/unity/scene_manager/game_objects.rs deleted file mode 100644 index d5ed19b..0000000 --- a/src/game_engine/unity/scene_manager/game_objects.rs +++ /dev/null @@ -1,80 +0,0 @@ -use core::iter::{self, FusedIterator}; - -use super::{transform::Transform, Scene, SceneManager, CSTR}; -use crate::{Address, Address32, Address64, Error, PointerSize, Process}; - -impl SceneManager { - /// Iterates over all root [`Transform`]s declared for the - /// specified scene. - /// - /// Each Unity scene normally has a linked list of [`Transform`]s. - /// Each one can, recursively, have one or more children [`Transform`]s - /// (and so on), as well as a list of `Component`s, which are classes (eg. - /// `MonoBehaviour`) containing data we might want to retrieve for the auto - /// splitter logic. - fn root_game_objects<'a>( - &'a self, - process: &'a Process, - scene: &Scene, - ) -> impl FusedIterator + 'a { - let list_first = process - .read_pointer( - scene.address + self.offsets.root_storage_container, - self.pointer_size, - ) - .ok() - .filter(|val| !val.is_null()); - - let mut current_list = list_first; - - iter::from_fn(move || { - 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()), - }; - - 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 - /// active Unity scene. - pub fn get_root_game_object(&self, process: &Process, name: &str) -> Result { - self.root_game_objects(process, &self.get_current_scene(process)?) - .find(|obj| { - obj.get_name::(process, self) - .is_ok_and(|obj_name| obj_name.matches(name)) - }) - .ok_or(Error {}) - } - - /// Tries to find the specified root [`Transform`] from the - /// `DontDestroyOnLoad` Unity scene. - pub fn get_game_object_from_dont_destroy_on_load( - &self, - process: &Process, - name: &str, - ) -> Result { - self.root_game_objects(process, &self.get_dont_destroy_on_load_scene()) - .find(|obj| { - obj.get_name::(process, self) - .is_ok_and(|obj_name| obj_name.matches(name)) - }) - .ok_or(Error {}) - } -} diff --git a/src/game_engine/unity/scene_manager/mod.rs b/src/game_engine/unity/scene_manager/mod.rs index 126b991..a78f71a 100644 --- a/src/game_engine/unity/scene_manager/mod.rs +++ b/src/game_engine/unity/scene_manager/mod.rs @@ -14,8 +14,6 @@ use crate::{ Address, Address32, Error, PointerSize, Process, }; -mod game_objects; - mod offsets; mod transform; @@ -23,7 +21,11 @@ pub use transform::Transform; use offsets::Offsets; +mod game_object; +pub use game_object::GameObject; + mod scene; + pub use scene::Scene; use super::{BinaryFormat, CSTR}; diff --git a/src/game_engine/unity/scene_manager/offsets.rs b/src/game_engine/unity/scene_manager/offsets.rs index 25f485d..2d427be 100644 --- a/src/game_engine/unity/scene_manager/offsets.rs +++ b/src/game_engine/unity/scene_manager/offsets.rs @@ -9,8 +9,9 @@ pub(super) struct Offsets { pub(super) root_storage_container: u8, pub(super) game_object: u8, pub(super) game_object_name: u8, + pub(super) game_object_activeself: u8, + pub(super) game_object_activeinhierarchy: u8, pub(super) klass: u8, - pub(super) klass_name: u8, pub(super) children_pointer: u8, } @@ -26,8 +27,9 @@ impl Offsets { root_storage_container: 0xB0, game_object: 0x30, game_object_name: 0x60, + game_object_activeself: 0x5E, + game_object_activeinhierarchy: 0x5F, klass: 0x28, - klass_name: 0x48, children_pointer: 0x70, }), PointerSize::Bit32 => Some(&Self { @@ -39,8 +41,9 @@ impl Offsets { root_storage_container: 0x88, game_object: 0x1C, game_object_name: 0x3C, + game_object_activeself: 0x32, + game_object_activeinhierarchy: 0x33, klass: 0x18, - klass_name: 0x2C, children_pointer: 0x50, }), _ => None, diff --git a/src/game_engine/unity/scene_manager/scene.rs b/src/game_engine/unity/scene_manager/scene.rs index c081ead..94c5af8 100644 --- a/src/game_engine/unity/scene_manager/scene.rs +++ b/src/game_engine/unity/scene_manager/scene.rs @@ -1,5 +1,7 @@ -use super::SceneManager; -use crate::{string::ArrayCString, Address, Error, Process}; +use super::{SceneManager, Transform, CSTR}; +use crate::{string::ArrayCString, Address, Address32, Address64, Error, PointerSize, Process}; +use core::iter; +use core::iter::FusedIterator; /// A scene loaded in the attached game. pub struct Scene { @@ -37,4 +39,85 @@ impl Scene { ) .and_then(|addr| process.read(addr)) } + + /// Iterates over all root [`Transform`]s declared for the + /// specified scene. + /// + /// Each Unity scene normally has a linked list of [`Transform`]s. + /// Each one can, recursively, have one or more children [`Transform`]s + /// (and so on), as well as a list of `Component`s, which are classes (eg. + /// `MonoBehaviour`) containing data we might want to retrieve for the auto + /// splitter logic. + fn root_game_objects<'a>( + &'a self, + process: &'a Process, + scene_manager: &'a SceneManager, + ) -> impl FusedIterator + 'a { + let list_first = process + .read_pointer( + self.address + scene_manager.offsets.root_storage_container, + scene_manager.pointer_size, + ) + .ok() + .filter(|val| !val.is_null()); + + let mut current_list = list_first; + + iter::from_fn(move || { + let [_prev, next, current]: [Address; 3] = match scene_manager.pointer_size { + PointerSize::Bit64 => process + .read::<[Address64; 3]>(current_list?) + .ok() + .filter(|[_prev, next, current]| !next.is_null() && !current.is_null())? + .map(|a| a.into()), + _ => process + .read::<[Address32; 3]>(current_list?) + .ok() + .filter(|[_prev, next, current]| !next.is_null() && !current.is_null())? + .map(|a| a.into()), + }; + + if next == list_first? { + current_list = None; + } else { + current_list = Some(next); + } + + Some(Transform { address: current }) + }) + .fuse() + } + + /// Tries to find the specified root [`Transform`] from the currently + /// active Unity scene. + pub fn get_root_game_object( + &self, + process: &Process, + scene_manager: &SceneManager, + name: &str, + ) -> Result { + self.root_game_objects(process, scene_manager) + .find(|obj| { + obj.get_name::(process, scene_manager) + .is_ok_and(|obj_name| obj_name.matches(name)) + }) + .ok_or(Error {}) + } + + pub fn find_transform( + &self, + process: &Process, + scene_manager: &SceneManager, + root_object_name: &str, + child_path: &[&str], + ) -> Result { + let mut current_transform = + self.get_root_game_object(process, scene_manager, root_object_name)?; + + for object_name in child_path { + current_transform = current_transform.get_child(process, scene_manager, object_name)?; + } + + Ok(current_transform) + } } diff --git a/src/game_engine/unity/scene_manager/transform.rs b/src/game_engine/unity/scene_manager/transform.rs index 857a07e..2798013 100644 --- a/src/game_engine/unity/scene_manager/transform.rs +++ b/src/game_engine/unity/scene_manager/transform.rs @@ -1,4 +1,4 @@ -use super::{SceneManager, CSTR}; +use super::{GameObject, SceneManager, CSTR}; use crate::{string::ArrayCString, Address, Address32, Address64, Error, PointerSize, Process}; use core::{array, mem::MaybeUninit}; @@ -10,117 +10,34 @@ pub struct Transform { } impl Transform { - /// Tries to return the name of the current `Transform`. + /// Get the name of the [`GameObject`] associated with this `Transform`. pub fn get_name( &self, process: &Process, scene_manager: &SceneManager, ) -> Result, Error> { - process.read_pointer_path( - self.address, - scene_manager.pointer_size, - &[ - scene_manager.offsets.game_object as u64, - scene_manager.offsets.game_object_name as u64, - 0x0, - ], - ) + self.get_game_object(process, scene_manager) + .and_then(|obj| obj.get_name(process, scene_manager)) } - /// Iterates over the classes referred to in the current `Transform`. - pub fn classes<'a>( - &'a self, - process: &'a Process, - scene_manager: &'a SceneManager, - ) -> Result + 'a, Error> { + /// Get the game object attached to this `Transform`, if any + pub fn get_game_object( + &self, + process: &Process, + scene_manager: &SceneManager, + ) -> Result { let game_object = process.read_pointer( self.address + scene_manager.offsets.game_object, scene_manager.pointer_size, )?; - let (number_of_components, main_object): (usize, Address) = match scene_manager.pointer_size - { - PointerSize::Bit64 => { - let array = process - .read::<[Address64; 3]>(game_object + scene_manager.offsets.game_object)?; - (array[2].value() as usize, array[0].into()) - } - _ => { - let array = process - .read::<[Address32; 3]>(game_object + scene_manager.offsets.game_object)?; - (array[2].value() as usize, array[0].into()) - } - }; - - if number_of_components == 0 { + if game_object.is_null() { return Err(Error {}); } - const ARRAY_SIZE: usize = 128; - - let components: [Address; ARRAY_SIZE] = match scene_manager.pointer_size { - PointerSize::Bit64 => { - let mut buf = [MaybeUninit::<[Address64; 2]>::uninit(); ARRAY_SIZE]; - let slice = process - .read_into_uninit_slice(main_object, &mut buf[..number_of_components])?; - - let mut iter = slice.iter_mut(); - array::from_fn(|_| { - iter.next() - .map(|&mut [_, second]| second.into()) - .unwrap_or_default() - }) - } - _ => { - let mut buf = [MaybeUninit::<[Address32; 2]>::uninit(); ARRAY_SIZE]; - let slice = process - .read_into_uninit_slice(main_object, &mut buf[..number_of_components])?; - - let mut iter = slice.iter_mut(); - array::from_fn(|_| { - iter.next() - .map(|&mut [_, second]| second.into()) - .unwrap_or_default() - }) - } - }; - - Ok((1..number_of_components).filter_map(move |m| { - process - .read_pointer( - components[m] + scene_manager.offsets.klass, - scene_manager.pointer_size, - ) - .ok() - .filter(|val| !val.is_null()) - })) - } - - /// Tries to find the base address of a class in the current `GameObject`. - pub fn get_class( - &self, - process: &Process, - scene_manager: &SceneManager, - name: &str, - ) -> Result { - self.classes(process, scene_manager)? - .find(|&addr| { - let val: Result, Error> = match scene_manager.is_il2cpp { - true => process.read_pointer_path( - addr, - scene_manager.pointer_size, - &[0x0, scene_manager.size_of_ptr().wrapping_mul(2), 0x0], - ), - false => process.read_pointer_path( - addr, - scene_manager.pointer_size, - &[0x0, 0x0, scene_manager.offsets.klass_name as u64, 0x0], - ), - }; - - val.is_ok_and(|class_name| class_name.matches(name)) - }) - .ok_or(Error {}) + Ok(GameObject { + address: game_object, + }) } /// Iterates over children `Transform`s referred by the current one