From 6c56fedbe288c0f13565a952dd13c4088fb00649 Mon Sep 17 00:00:00 2001 From: Mitchell Merry Date: Mon, 11 May 2026 20:56:02 +1000 Subject: [PATCH 1/7] Separate the concept of Transform and GameObject; separate the concept of SceneManager and Scene --- .../unity/scene_manager/game_object.rs | 115 +++++++++++++++++ .../unity/scene_manager/game_objects.rs | 80 ------------ src/game_engine/unity/scene_manager/mod.rs | 6 +- .../unity/scene_manager/offsets.rs | 6 + src/game_engine/unity/scene_manager/scene.rs | 118 +++++++++++++++++- .../unity/scene_manager/transform.rs | 98 ++------------- 6 files changed, 251 insertions(+), 172 deletions(-) create mode 100644 src/game_engine/unity/scene_manager/game_object.rs delete mode 100644 src/game_engine/unity/scene_manager/game_objects.rs 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..c883a2d --- /dev/null +++ b/src/game_engine/unity/scene_manager/game_object.rs @@ -0,0 +1,115 @@ +use super::{SceneManager, CSTR}; +use crate::{string::ArrayCString, Address, Address32, Address64, Error, PointerSize, Process}; +use core::array; +use core::mem::MaybeUninit; + +/// Representing a GameObject. +/// +/// This contains the information about attached Components and other internals like activeSelf. +/// +/// If you have an instance of a C# game object (which you might get via following a path from a +/// static field), the C++ game object is at + pointer_size * 2 (0x8 on 32 bit, 0x10 on 64 bit). +#[derive(Clone, Debug)] +pub struct GameObject { + pub(super) address: Address, +} + +impl GameObject { + pub fn components<'a>( + &'a self, + process: &'a Process, + scene_manager: &'a SceneManager, + ) -> Result + 'a, Error> { + let (number_of_components, main_object): (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(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.components(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 {}) + } + + pub fn is_active_in_hierarchy( + &self, + process: &Process, + scene_manager: &SceneManager, + ) -> Result { + process.read::(self.address + scene_manager.offsets.game_object_activeinhierarchy) + } +} 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..9e51c61 100644 --- a/src/game_engine/unity/scene_manager/offsets.rs +++ b/src/game_engine/unity/scene_manager/offsets.rs @@ -9,6 +9,8 @@ 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,6 +28,8 @@ 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, @@ -39,6 +43,8 @@ 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, diff --git a/src/game_engine/unity/scene_manager/scene.rs b/src/game_engine/unity/scene_manager/scene.rs index c081ead..ea94453 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 { @@ -24,7 +26,9 @@ impl Scene { process.read(self.address + scene_manager.offsets.build_index) } - /// Returns the full path to the scene. + /// Returns the full path to the [scene](Scene). + /// + /// Usually looks something like `Assets/..path../scene.unity`. pub fn path( &self, process: &Process, @@ -37,4 +41,112 @@ impl Scene { ) .and_then(|addr| process.read(addr)) } + + /// Returns the full path to the [scene](Scene), as a [String](alloc::string::String). + pub fn path_as_string( + &self, + process: &Process, + scene_manager: &SceneManager, + ) -> Result { + let path = self.path::(process, scene_manager)?; + let str = path.validate_utf8().map_err(|_| Error {})?; + + Ok(str.into()) + } + + /// Returns the name of the [scene](Scene), as a [String](alloc::string::String). + pub fn name( + &self, + process: &Process, + scene_manager: &SceneManager, + ) -> Result { + // The name is also stored in memory, but it's just easier to interpret on the path + let path = self.path_as_string(process, scene_manager)?; + let cs = path.rsplit_once('/').unwrap_or(("", &path)).1; + Ok(cs.split_once('.').unwrap_or((cs, "")).0.into()) + } + + /// 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 || { + // TODO check if this is correct on other games + 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( + crate::game_engine::unity::scene_manager::transform::Transform { address: current }, + ) + }) + .fuse() + } + + /// Tries to find the specified root [`crate::game_engine::unity::scene_manager::transform::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..5f83231 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}; @@ -27,100 +27,24 @@ impl Transform { ) } - /// 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 From 958fbd8b2c36fd4782892b6c5b2c620c2ab62f15 Mon Sep 17 00:00:00 2001 From: Mitchell Merry Date: Mon, 11 May 2026 21:06:41 +1000 Subject: [PATCH 2/7] Add isSelf function, add / update more comments --- .../unity/scene_manager/game_object.rs | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/game_engine/unity/scene_manager/game_object.rs b/src/game_engine/unity/scene_manager/game_object.rs index c883a2d..fc9dc45 100644 --- a/src/game_engine/unity/scene_manager/game_object.rs +++ b/src/game_engine/unity/scene_manager/game_object.rs @@ -3,18 +3,15 @@ use crate::{string::ArrayCString, Address, Address32, Address64, Error, PointerS use core::array; use core::mem::MaybeUninit; -/// Representing a GameObject. -/// -/// This contains the information about attached Components and other internals like activeSelf. -/// -/// If you have an instance of a C# game object (which you might get via following a path from a -/// static field), the C++ game object is at + pointer_size * 2 (0x8 on 32 bit, 0x10 on 64 bit). +/// 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 { + /// Traverse the Components attached to this game object. pub fn components<'a>( &'a self, process: &'a Process, @@ -78,8 +75,9 @@ impl GameObject { })) } - /// Tries to find the base address of a class in the current `GameObject`. - pub fn get_class( + /// Tries to find the base address of a component in the current `GameObject` by the name of it's + /// class. + pub fn get_component( &self, process: &Process, scene_manager: &SceneManager, @@ -105,6 +103,8 @@ impl GameObject { .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, @@ -112,4 +112,14 @@ impl GameObject { ) -> 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_in_self( + &self, + process: &Process, + scene_manager: &SceneManager, + ) -> Result { + process.read::(self.address + scene_manager.offsets.game_object_activeself) + } } From f575ae4d1b4590630aac357547f0cd7f5639cabe Mon Sep 17 00:00:00 2001 From: Mitchell Merry Date: Mon, 11 May 2026 22:47:51 +1000 Subject: [PATCH 3/7] Create get_class_mono/il2cpp which should fix the klass_name discrepancy - by just shelling out to the Mono/il2cpp class code --- src/game_engine/unity/il2cpp/class.rs | 15 ++- src/game_engine/unity/mono/class.rs | 17 ++- .../unity/scene_manager/game_object.rs | 113 ++++++++++++------ .../unity/scene_manager/offsets.rs | 3 - .../unity/scene_manager/transform.rs | 13 +- 5 files changed, 110 insertions(+), 51 deletions(-) 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 index fc9dc45..397e7e9 100644 --- a/src/game_engine/unity/scene_manager/game_object.rs +++ b/src/game_engine/unity/scene_manager/game_object.rs @@ -1,5 +1,7 @@ use super::{SceneManager, CSTR}; -use crate::{string::ArrayCString, Address, Address32, Address64, Error, PointerSize, Process}; +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; @@ -11,25 +13,38 @@ pub struct GameObject { } impl GameObject { - /// Traverse the Components attached to this game object. - pub fn components<'a>( + /// 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, main_object): (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()) - } - }; + 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 {}); @@ -40,8 +55,10 @@ impl GameObject { 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 slice = process.read_into_uninit_slice( + component_pair_array, + &mut buf[..number_of_components], + )?; let mut iter = slice.iter_mut(); array::from_fn(|_| { @@ -52,8 +69,10 @@ impl GameObject { } _ => { let mut buf = [MaybeUninit::<[Address32; 2]>::uninit(); ARRAY_SIZE]; - let slice = process - .read_into_uninit_slice(main_object, &mut buf[..number_of_components])?; + let slice = process.read_into_uninit_slice( + component_pair_array, + &mut buf[..number_of_components], + )?; let mut iter = slice.iter_mut(); array::from_fn(|_| { @@ -75,28 +94,50 @@ impl GameObject { })) } - /// Tries to find the base address of a component in the current `GameObject` by the name of it's - /// class. - pub fn get_component( + // TODO it's really dumb i have to split this by mono/il2cpp + + /// 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 { - self.components(process, scene_manager)? + 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: 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], - ), - }; + 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)) }) diff --git a/src/game_engine/unity/scene_manager/offsets.rs b/src/game_engine/unity/scene_manager/offsets.rs index 9e51c61..2d427be 100644 --- a/src/game_engine/unity/scene_manager/offsets.rs +++ b/src/game_engine/unity/scene_manager/offsets.rs @@ -12,7 +12,6 @@ pub(super) struct Offsets { 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, } @@ -31,7 +30,6 @@ impl Offsets { game_object_activeself: 0x5E, game_object_activeinhierarchy: 0x5F, klass: 0x28, - klass_name: 0x48, children_pointer: 0x70, }), PointerSize::Bit32 => Some(&Self { @@ -46,7 +44,6 @@ impl Offsets { 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/transform.rs b/src/game_engine/unity/scene_manager/transform.rs index 5f83231..2798013 100644 --- a/src/game_engine/unity/scene_manager/transform.rs +++ b/src/game_engine/unity/scene_manager/transform.rs @@ -10,21 +10,14 @@ 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)) } /// Get the game object attached to this `Transform`, if any From 01527845c26771dcf17b13db0dd82544bca2a085 Mon Sep 17 00:00:00 2001 From: Mitchell Merry Date: Mon, 11 May 2026 23:10:05 +1000 Subject: [PATCH 4/7] come on now --- src/game_engine/unity/scene_manager/scene.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/game_engine/unity/scene_manager/scene.rs b/src/game_engine/unity/scene_manager/scene.rs index ea94453..786e68c 100644 --- a/src/game_engine/unity/scene_manager/scene.rs +++ b/src/game_engine/unity/scene_manager/scene.rs @@ -110,14 +110,12 @@ impl Scene { current_list = Some(next); } - Some( - crate::game_engine::unity::scene_manager::transform::Transform { address: current }, - ) + Some(Transform { address: current }) }) .fuse() } - /// Tries to find the specified root [`crate::game_engine::unity::scene_manager::transform::Transform`] from the currently + /// Tries to find the specified root [`Transform`] from the currently /// active Unity scene. pub fn get_root_game_object( &self, From 25d397b514acb53274f33ffbd68c456802a9241e Mon Sep 17 00:00:00 2001 From: Mitchell Merry Date: Mon, 11 May 2026 23:18:45 +1000 Subject: [PATCH 5/7] remove todo comments --- src/game_engine/unity/scene_manager/game_object.rs | 2 -- src/game_engine/unity/scene_manager/scene.rs | 1 - 2 files changed, 3 deletions(-) diff --git a/src/game_engine/unity/scene_manager/game_object.rs b/src/game_engine/unity/scene_manager/game_object.rs index 397e7e9..b2c0eb8 100644 --- a/src/game_engine/unity/scene_manager/game_object.rs +++ b/src/game_engine/unity/scene_manager/game_object.rs @@ -94,8 +94,6 @@ impl GameObject { })) } - // TODO it's really dumb i have to split this by mono/il2cpp - /// Tries to find the base address of a class in the current `GameObject` by name. /// /// Mono only. diff --git a/src/game_engine/unity/scene_manager/scene.rs b/src/game_engine/unity/scene_manager/scene.rs index 786e68c..a57ecd5 100644 --- a/src/game_engine/unity/scene_manager/scene.rs +++ b/src/game_engine/unity/scene_manager/scene.rs @@ -90,7 +90,6 @@ impl Scene { let mut current_list = list_first; iter::from_fn(move || { - // TODO check if this is correct on other games let [_prev, next, current]: [Address; 3] = match scene_manager.pointer_size { PointerSize::Bit64 => process .read::<[Address64; 3]>(current_list?) From d0787236b7fa50d6702a1efafddf79fe837d6ac5 Mon Sep 17 00:00:00 2001 From: Mitchell Merry Date: Mon, 11 May 2026 23:37:16 +1000 Subject: [PATCH 6/7] tweak --- src/game_engine/unity/scene_manager/game_object.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game_engine/unity/scene_manager/game_object.rs b/src/game_engine/unity/scene_manager/game_object.rs index b2c0eb8..22b3908 100644 --- a/src/game_engine/unity/scene_manager/game_object.rs +++ b/src/game_engine/unity/scene_manager/game_object.rs @@ -154,7 +154,7 @@ impl GameObject { /// Returns whether the game object is considered "active" by itself (irrespective of any of its /// parents) - pub fn is_active_in_self( + pub fn is_active_self( &self, process: &Process, scene_manager: &SceneManager, From c974ef27abfb00523be758cee82e777754871051 Mon Sep 17 00:00:00 2001 From: Mitchell Merry Date: Wed, 13 May 2026 21:41:34 +1000 Subject: [PATCH 7/7] Revert changes for scene name and path as string --- src/game_engine/unity/scene_manager/scene.rs | 28 +------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/src/game_engine/unity/scene_manager/scene.rs b/src/game_engine/unity/scene_manager/scene.rs index a57ecd5..94c5af8 100644 --- a/src/game_engine/unity/scene_manager/scene.rs +++ b/src/game_engine/unity/scene_manager/scene.rs @@ -26,9 +26,7 @@ impl Scene { process.read(self.address + scene_manager.offsets.build_index) } - /// Returns the full path to the [scene](Scene). - /// - /// Usually looks something like `Assets/..path../scene.unity`. + /// Returns the full path to the scene. pub fn path( &self, process: &Process, @@ -42,30 +40,6 @@ impl Scene { .and_then(|addr| process.read(addr)) } - /// Returns the full path to the [scene](Scene), as a [String](alloc::string::String). - pub fn path_as_string( - &self, - process: &Process, - scene_manager: &SceneManager, - ) -> Result { - let path = self.path::(process, scene_manager)?; - let str = path.validate_utf8().map_err(|_| Error {})?; - - Ok(str.into()) - } - - /// Returns the name of the [scene](Scene), as a [String](alloc::string::String). - pub fn name( - &self, - process: &Process, - scene_manager: &SceneManager, - ) -> Result { - // The name is also stored in memory, but it's just easier to interpret on the path - let path = self.path_as_string(process, scene_manager)?; - let cs = path.rsplit_once('/').unwrap_or(("", &path)).1; - Ok(cs.split_once('.').unwrap_or((cs, "")).0.into()) - } - /// Iterates over all root [`Transform`]s declared for the /// specified scene. ///