From 6c56fedbe288c0f13565a952dd13c4088fb00649 Mon Sep 17 00:00:00 2001 From: Mitchell Merry Date: Mon, 11 May 2026 20:56:02 +1000 Subject: [PATCH 01/27] 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 02/27] 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 03/27] 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 04/27] 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 05/27] 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 06/27] 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 07/27] 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. /// From e964365ec7d72aa2b9055413c1e738e9a2441142 Mon Sep 17 00:00:00 2001 From: Mitchell Merry Date: Thu, 14 May 2026 14:42:12 +1000 Subject: [PATCH 08/27] tmp --- src/game_engine/unity/mono/mod.rs | 18 ++++++++++++++++++ src/game_engine/unity/scene_manager/scene.rs | 5 +++-- .../unity/scene_manager/transform.rs | 9 +++++++-- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/game_engine/unity/mono/mod.rs b/src/game_engine/unity/mono/mod.rs index 56b44f4..29f5c37 100644 --- a/src/game_engine/unity/mono/mod.rs +++ b/src/game_engine/unity/mono/mod.rs @@ -6,9 +6,11 @@ use crate::file_format::macho; use crate::{ file_format::{elf, pe}, future::retry, + print_message, signature::Signature, Address, Address32, Address64, PointerSize, Process, }; +use alloc::format; use core::iter::{self, FusedIterator}; mod assembly; @@ -43,6 +45,7 @@ impl Module { /// [`attach`](Self::attach) instead. pub fn attach_auto_detect(process: &Process) -> Option { let version = Version::detect(process)?; + print_message(&format!("found versin {version:?}")); Self::attach(process, version) } @@ -51,6 +54,7 @@ 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 { + print_message("ok!"); let (module_range, format) = [ ("mono.dll", BinaryFormat::PE), ("libmono.so", BinaryFormat::ELF), @@ -63,6 +67,7 @@ impl Module { ] .into_iter() .find_map(|(name, format)| Some((process.get_module_range(name).ok()?, format)))?; + print_message("not even"); let (mono_module, _) = module_range; @@ -98,8 +103,16 @@ impl Module { } #[cfg(feature = "alloc")] BinaryFormat::MachO => { + print_message("rootdonmainfge"); macho::symbols(process, module_range) .find(|symbol| { + let name = symbol.get_name::<26>(process); + + if let Ok(name) = name { + if let Ok(name) = name.validate_utf8() { + print_message(name); + } + } symbol .get_name::<26>(process) .is_ok_and(|name| name.matches("_mono_assembly_foreach")) @@ -109,6 +122,7 @@ impl Module { #[allow(unreachable_patterns)] _ => return None, }; + print_message("assemlyes"); let assemblies: Address = match (pointer_size, format) { (PointerSize::Bit64, BinaryFormat::PE) => { @@ -127,6 +141,7 @@ impl Module { } #[cfg(feature = "alloc")] (PointerSize::Bit64, BinaryFormat::MachO) => { + print_message("ok lets do this"); const SIG_MONO_X86_64_MACHO: Signature<3> = Signature::new("48 8B 3D"); // 57 0f 00 d0 adrp x23,(page + 0x1ea000) // e0 da 47 f9 ldr x0,[x23, #0xfb0]=>(page + 0x1eafb0) @@ -150,10 +165,12 @@ impl Module { .scan_process_range(process, (root_domain_function_address, 0x100)) .map(|a| a + 3) { + print_message("yay"); scan_address + 0x4 + process.read::(scan_address).ok()? } else if let Some(scan_address) = SIG_MONO_ARM_64_MACHO .scan_process_range(process, (root_domain_function_address, 0x100)) { + print_message("what"); let page = scan_address.value() & 0xfffffffffffff000; let bs = process.read::<[u8; 8]>(scan_address).ok()?; // adrp @@ -168,6 +185,7 @@ impl Module { let ldr = (i0 << 3) | (i1 << 9); (page + adrp + ldr).into() } else { + print_message("got hands."); return None; } } diff --git a/src/game_engine/unity/scene_manager/scene.rs b/src/game_engine/unity/scene_manager/scene.rs index 94c5af8..6710212 100644 --- a/src/game_engine/unity/scene_manager/scene.rs +++ b/src/game_engine/unity/scene_manager/scene.rs @@ -4,8 +4,9 @@ use core::iter; use core::iter::FusedIterator; /// A scene loaded in the attached game. +#[derive(Debug)] pub struct Scene { - pub(super) address: Address, + pub address: Address, } impl Scene { @@ -48,7 +49,7 @@ impl Scene { /// (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>( + pub fn root_game_objects<'a>( &'a self, process: &'a Process, scene_manager: &'a SceneManager, diff --git a/src/game_engine/unity/scene_manager/transform.rs b/src/game_engine/unity/scene_manager/transform.rs index 2798013..89c3b33 100644 --- a/src/game_engine/unity/scene_manager/transform.rs +++ b/src/game_engine/unity/scene_manager/transform.rs @@ -1,12 +1,16 @@ use super::{GameObject, SceneManager, CSTR}; -use crate::{string::ArrayCString, Address, Address32, Address64, Error, PointerSize, Process}; +use crate::{ + print_message, string::ArrayCString, Address, Address32, Address64, Error, PointerSize, Process, +}; +use alloc::format; use core::{array, mem::MaybeUninit}; /// A `Transform` is a base class for all entities used in a Unity scene. All /// classes of interest useful for an auto splitter can be found starting from /// the addresses of the root `Transform`s linked in each scene. +#[derive(Debug)] pub struct Transform { - pub(super) address: Address, + pub address: Address, } impl Transform { @@ -66,6 +70,7 @@ impl Transform { if child_count == 0 || child_count > ARRAY_SIZE { return Err(Error {}); } + print_message(&format!("cc {child_count}")); let children: [Address; ARRAY_SIZE] = match scene_manager.pointer_size { PointerSize::Bit64 => { From 5ab8c871cdf35cc371f14cfff90eabf6892b291b Mon Sep 17 00:00:00 2001 From: Mitchell Merry Date: Fri, 15 May 2026 15:02:37 +1000 Subject: [PATCH 09/27] add new sig, add offsets --- src/game_engine/unity/scene_manager/mod.rs | 25 ++- .../unity/scene_manager/offsets.md | 165 ++++++++++++++++++ .../unity/scene_manager/offsets.rs | 8 +- src/game_engine/unity/scene_manager/scene.rs | 2 +- 4 files changed, 192 insertions(+), 8 deletions(-) create mode 100644 src/game_engine/unity/scene_manager/offsets.md diff --git a/src/game_engine/unity/scene_manager/mod.rs b/src/game_engine/unity/scene_manager/mod.rs index a78f71a..43c58dc 100644 --- a/src/game_engine/unity/scene_manager/mod.rs +++ b/src/game_engine/unity/scene_manager/mod.rs @@ -9,10 +9,12 @@ use crate::{ file_format::{elf, macho, pe}, future::retry, + print_message, signature::Signature, string::ArrayCString, Address, Address32, Error, PointerSize, Process, }; +use alloc::format; mod offsets; @@ -45,8 +47,10 @@ pub struct SceneManager { impl SceneManager { /// Attaches to the scene manager in the given process. pub fn attach(process: &Process) -> Option { - const SIG_64_BIT_PE: Signature<13> = + const SIG_64_BIT_PE_1: Signature<13> = Signature::new("48 83 EC 20 4C 8B ?5 ?? ?? ?? ?? 33 F6"); + const SIG_64_BIT_PE_2: Signature<13> = + Signature::new("48 83 EC 20 48 8B 2D ?? ?? ?? ?? 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> = @@ -71,12 +75,14 @@ impl SceneManager { } _ => Some((process.get_module_range(name).ok()?, format)), })?; + print_message("b"); 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)?, }; + print_message("c"); let is_il2cpp = process.get_module_address("GameAssembly.dll").is_ok(); @@ -84,8 +90,19 @@ impl SceneManager { // used in the target game. 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()? + // let addr = SIG_64_BIT_PE.scan_process_range(process, unity_player)? + 7; + // addr + 0x4 + process.read::(addr).ok()? + print_message("dd"); + if let Some(addr) = SIG_64_BIT_PE_1.scan_process_range(process, unity_player) { + print_message(&format!("dd1 {addr}")); + addr + 0x4 + process.read::(addr + 7).ok()? + } else if let Some(addr) = SIG_64_BIT_PE_2.scan_process_range(process, unity_player) + { + print_message(&format!("dd2 {addr}")); + addr + 0x4 + process.read::(addr + 7).ok()? + 7 + } else { + return None; + } } (PointerSize::Bit64, BinaryFormat::ELF) => { let addr = SIG_64_BIT_ELF.scan_process_range(process, unity_player)? + 7; @@ -110,6 +127,7 @@ impl SceneManager { return None; } }; + print_message(&format!("e {base_address}")); let offsets = Offsets::new(pointer_size)?; @@ -119,6 +137,7 @@ impl SceneManager { .read_pointer(base_address, pointer_size) .ok() .filter(|val| !val.is_null())?; + print_message("g"); Some(Self { pointer_size, diff --git a/src/game_engine/unity/scene_manager/offsets.md b/src/game_engine/unity/scene_manager/offsets.md new file mode 100644 index 0000000..776c304 --- /dev/null +++ b/src/game_engine/unity/scene_manager/offsets.md @@ -0,0 +1,165 @@ +# project setup + +- scene 1 + - toplevelgame + - children: + - childgame1 + - childgame2 (inactive) + - classes: + - RestartScript + - other default GOs under +- scene 2 + - (empty) + +build profiles: + +- w/ dev on/off +- scene list has both +- player settings -> ScriptingBackend Mono/Il2Cpp + +# lib + +## restart script + +```cs +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.SceneManagement; + +public class RestartScript : MonoBehaviour +{ + // Start is called before the first frame update + void Start() + { + + } + + // Update is called once per frame + void Update() + { + if (Input.GetKeyDown(KeyCode.R)) + { + SceneManager.LoadScene(SceneManager.GetActiveScene().name); + } + + if (Input.GetKeyDown(KeyCode.F)) + { + SceneManager.LoadScene("scene 2", LoadSceneMode.Additive); + } + } +} +``` + +## lua sig script + +```lua +-- Cheat Engine Lua +-- Resolves the FINAL absolute address from an instruction matched by AOB. +-- +-- Example: +-- sig = "48 8B 05 ?? ?? ?? ?? 48 85 C0" +-- dispOff = 3 -- offset inside instruction where relative displacement starts +-- instrLen = 7 -- total instruction length +-- +-- This reproduces what CE shows in disassembler for RIP-relative instructions. + +function sig(sig, dispOffset) + local scan = AOBScan(sig) + if not scan or scan.Count == 0 then + print("sig not found") + return nil + end + + local instr = tonumber(scan[0], 16) + scan.destroy() + + -- read signed rel32 + local rel = readInteger(instr + dispOffset) + + if rel >= 0x80000000 then + rel = rel - 0x100000000 + end + + -- rel32 is ALWAYS relative to NEXT instruction + local final = instr + dispOffset + 4 + rel + + return final +end +``` + +# Unity 2023.1.22f1 + +### 64 bit, Windows, Mono + +``` +is_dev_build = false +go_dev = 0x10 -- size of EditorExtensions +co_dev = 0x8 + +pointer_size = 0x8 + +scene_manager = sig("48 83 EC 20 4C 8B ?5 ?? ?? ?? ?? 33 F6", 7, 7) + +loaded_scenes = 0x8 +scene_count = 0x18 +active_scene = 0x48 +dont_destroy_on_load_scene = 0x70 + +asset_path = 0x10 +build_index = 0x98 +root_storage_container = 0xF0 + +prev = 0x0 +next = 0x8 +current = 0x10 + +game_object = 0x30 + (is_dev_build and go_dev or 0) +game_object_name = 0x60 + (is_dev_build and go_dev or 0) +active_self = 0x56 + (is_dev_build and go_dev or 0) +active_in_hierarchy = 0x57 + (is_dev_build and go_dev or 0) +children = 0x70 + (is_dev_build and go_dev or 0) + +classes = game_object +class = 0x28 + (is_dev_build and co_dev or 0) +class_name = 0x48 +``` + +# Unity 6000.4.5f1 + +### 64 bit windows + +```lua +is_dev_build = false +go_dev = 0x10 -- size of EditorExtensions +co_dev = 0x8 + +pointer_size = 0x8 + +scene_manager = sig("48 83 EC 20 48 8B 2D ?? ?? ?? ?? 33 F6", 7, 7) + +loaded_scenes = 0x8 +scene_count = 0x18 +active_scene = 0x48 +dont_destroy_on_load_scene = 0x70 + +asset_path = 0x10 +build_index = 0x98 +root_storage_container = 0xF0 + +prev = 0x0 +next = 0x8 +current = 0x10 + +game_object = 0x20 + (is_dev_build and go_dev or 0) +game_object_name = 0x50 + (is_dev_build and go_dev or 0) +active_self = 0x46 + (is_dev_build and go_dev or 0) +active_in_hierarchy = 0x47 + (is_dev_build and go_dev or 0) +children = 0x48 + (is_dev_build and go_dev or 0) + +classes = game_object +class_mono = 0x28 + (is_dev_build and co_dev or 0) +class_name_mono = 0x48 +class_il2cpp = 0x18 +class_name_il2cpp = 0x10 +``` diff --git a/src/game_engine/unity/scene_manager/offsets.rs b/src/game_engine/unity/scene_manager/offsets.rs index 2d427be..621af83 100644 --- a/src/game_engine/unity/scene_manager/offsets.rs +++ b/src/game_engine/unity/scene_manager/offsets.rs @@ -24,13 +24,13 @@ impl Offsets { dont_destroy_on_load_scene: 0x70, asset_path: 0x10, build_index: 0x98, - root_storage_container: 0xB0, - game_object: 0x30, - game_object_name: 0x60, + root_storage_container: 0xF0, + game_object: 0x20, + game_object_name: 0x50, game_object_activeself: 0x5E, game_object_activeinhierarchy: 0x5F, klass: 0x28, - children_pointer: 0x70, + children_pointer: 0x48, }), PointerSize::Bit32 => Some(&Self { scene_count: 0x10, diff --git a/src/game_engine/unity/scene_manager/scene.rs b/src/game_engine/unity/scene_manager/scene.rs index 6710212..2dfe0dd 100644 --- a/src/game_engine/unity/scene_manager/scene.rs +++ b/src/game_engine/unity/scene_manager/scene.rs @@ -79,7 +79,7 @@ impl Scene { }; if next == list_first? { - current_list = None; + return None; } else { current_list = Some(next); } From 135f37111b0d114f07dd5bd5ce78aa6633504d40 Mon Sep 17 00:00:00 2001 From: Mitchell Merry Date: Fri, 15 May 2026 16:04:34 +1000 Subject: [PATCH 10/27] update project settings --- .../unity/scene_manager/offsets.md | 36 ++++++++++++++++--- .../unity/scene_manager/offsets.rs | 4 +-- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/game_engine/unity/scene_manager/offsets.md b/src/game_engine/unity/scene_manager/offsets.md index 776c304..86ca1fd 100644 --- a/src/game_engine/unity/scene_manager/offsets.md +++ b/src/game_engine/unity/scene_manager/offsets.md @@ -8,6 +8,7 @@ - classes: - RestartScript - other default GOs under + - disable MainCamera audio listener - scene 2 - (empty) @@ -15,7 +16,11 @@ build profiles: - w/ dev on/off - scene list has both -- player settings -> ScriptingBackend Mono/Il2Cpp + +player settings: + +- ScriptingBackend Mono/Il2Cpp +- Active Input Handling - Old # lib @@ -24,6 +29,7 @@ build profiles: ```cs using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using UnityEngine; using UnityEngine.SceneManagement; @@ -32,20 +38,42 @@ public class RestartScript : MonoBehaviour // Start is called before the first frame update void Start() { - + } // Update is called once per frame void Update() { + Time.timeScale = 1f; + if (Input.GetKeyDown(KeyCode.R)) { - SceneManager.LoadScene(SceneManager.GetActiveScene().name); + UnityEngine.Debug.Log("reloading scene"); + SceneManager.LoadSceneAsync(SceneManager.GetActiveScene().name); + } + + if (Input.GetKeyDown(KeyCode.D)) + { + UnityEngine.Debug.Log("loading scene 1 additively"); + SceneManager.LoadSceneAsync("scene 1", LoadSceneMode.Additive); } if (Input.GetKeyDown(KeyCode.F)) { - SceneManager.LoadScene("scene 2", LoadSceneMode.Additive); + UnityEngine.Debug.Log("loading scene 2 additively"); + SceneManager.LoadSceneAsync("scene 2", LoadSceneMode.Additive); + } + + if (Input.GetKeyDown(KeyCode.C)) + { + UnityEngine.Debug.Log("loading scene 1 singly"); + SceneManager.LoadSceneAsync("scene 1"); + } + + if (Input.GetKeyDown(KeyCode.V)) + { + UnityEngine.Debug.Log("loading scene 2 singly"); + SceneManager.LoadSceneAsync("scene 2"); } } } diff --git a/src/game_engine/unity/scene_manager/offsets.rs b/src/game_engine/unity/scene_manager/offsets.rs index 621af83..64ab409 100644 --- a/src/game_engine/unity/scene_manager/offsets.rs +++ b/src/game_engine/unity/scene_manager/offsets.rs @@ -27,8 +27,8 @@ impl Offsets { root_storage_container: 0xF0, game_object: 0x20, game_object_name: 0x50, - game_object_activeself: 0x5E, - game_object_activeinhierarchy: 0x5F, + game_object_activeself: 0x46, + game_object_activeinhierarchy: 0x47, klass: 0x28, children_pointer: 0x48, }), From 305c1c5c6f310e80121cf1da76bf871b52aaf002 Mon Sep 17 00:00:00 2001 From: Mitchell Merry Date: Fri, 15 May 2026 16:05:06 +1000 Subject: [PATCH 11/27] add cheat table --- .../scene_manager/platformer-playground.CT | 1238 +++++++++++++++++ 1 file changed, 1238 insertions(+) create mode 100644 src/game_engine/unity/scene_manager/platformer-playground.CT diff --git a/src/game_engine/unity/scene_manager/platformer-playground.CT b/src/game_engine/unity/scene_manager/platformer-playground.CT new file mode 100644 index 0000000..22791dc --- /dev/null +++ b/src/game_engine/unity/scene_manager/platformer-playground.CT @@ -0,0 +1,1238 @@ + + + + + 0 + "scene manager" + 1 + 0 + 8 Bytes +
scene_manager
+ + + 83 + "scene count" + 0 + 4 Bytes +
+0
+ + +scene_count + +
+ + 84 + "scenes" + + 1 + 0 + 8 Bytes +
+0
+ + loaded_scenes + + + + 85 + "[0]" + 1 + 0 + 8 Bytes +
+0
+ + 0 + + + + 87 + "path" + 0 + String + 105 + 0 + 0 + 1 +
+0
+ + asset_path + +
+ + 88 + "build index" + 1 + 4 Bytes +
+0
+ + build_index + +
+
+
+ + 89 + "[1]" + 1 + 0 + 8 Bytes +
+0
+ + 0x8 + + + + 90 + "path" + 0 + String + 105 + 0 + 0 + 1 +
+0
+ + asset_path + +
+ + 91 + "build index" + 1 + 4 Bytes +
+0
+ + build_index + +
+
+
+
+
+ + 101 + "dont destroy on load" + 1 + 0 + 8 Bytes +
+0
+ + dont_destroy_on_load_scene + + + + 102 + "path" + 0 + String + 105 + 0 + 0 + 1 +
+asset_path
+
+ + 103 + "build index" + 1 + 4 Bytes +
+build_index
+
+
+
+ + 1 + "active scene" + + 1 + 0 + 8 Bytes +
+0
+ + active_scene + + + + 82 + "path" + 0 + String + 105 + 0 + 0 + 1 +
+0
+ + asset_path + +
+ + 2 + "ROT STORAGE CONTAINER" + + 1 + 0 + 8 Bytes +
+0
+ + root_storage_container + + + + 79 + "current" + 1 + 0 + 8 Bytes +
+0
+ + current + + + + 80 + "name" + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + game_object_name + game_object + +
+ + 81 + "activeself" + 0 + Byte +
+0
+ + active_self + game_object + +
+ + 112 + "activeinhierarchy" + 0 + Byte +
+0
+ + active_in_hierarchy + game_object + +
+ + 104 + "children_size" + 0 + 4 Bytes +
+0
+ + children + pointer_size * 2 + +
+ + 68 + "children" + + 0 + 8 Bytes +
+0
+ + children + + + + 69 + "[0]" + 1 + 0 + 8 Bytes +
+0
+ + 0 + + + + 93 + "name" + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + game_object_name + game_object + +
+ + 94 + "activeself" + 0 + Byte +
+0
+ + active_self + game_object + +
+
+
+ + 98 + "[1]" + 1 + 0 + 8 Bytes +
+0
+ + 0x8 + + + + 99 + "name" + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + game_object_name + game_object + +
+ + 100 + "activeself" + 0 + Byte +
+0
+ + active_self + game_object + +
+
+
+ + 95 + "[2]" + 1 + 0 + 8 Bytes +
+0
+ + 0x10 + + + + 96 + "name" + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + game_object_name + game_object + +
+ + 97 + "activeself" + 0 + Byte +
+0
+ + active_self + game_object + +
+
+
+
+
+ + 105 + "classes size" + 0 + 4 Bytes +
+0
+ + classes + pointer_size * 2 + game_object + +
+ + 134 + "classes" + + 0 + 8 Bytes +
+0
+ + classes + game_object + + + + 135 + "[0]" + + 0 + 8 Bytes +
+0
+ + pointer_size + (pointer_size * 2) * 0 + + + + 136 + "class name (mono)" + + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + class_name_mono + 0 + 0 + class_mono + +
+ + 137 + "class name (il2cpp)" + + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + class_name_il2cpp + 0 + 0 + class_il2cpp + +
+
+
+ + 138 + "[1]" + + 0 + 8 Bytes +
+0
+ + pointer_size + (pointer_size * 2) * 1 + + + + 139 + "class name (mono)" + + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + class_name_mono + 0 + 0 + class_mono + +
+ + 140 + "class name (il2cpp)" + + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + class_name_il2cpp + 0 + 0 + class_il2cpp + +
+
+
+
+
+
+
+ + 51 + "next" + + 1 + 0 + 8 Bytes +
+0
+ + next + + + + 52 + "current" + 1 + 0 + 8 Bytes +
+0
+ + current + + + + 72 + "name" + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + game_object_name + game_object + +
+
+
+ + 53 + "next" + + 1 + 0 + 8 Bytes +
+0
+ + next + + + + 73 + "current" + 1 + 0 + 8 Bytes +
+0
+ + current + + + + 74 + "name" + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + game_object_name + game_object + +
+ + 64 + "children" + + 0 + 8 Bytes +
+0
+ + children + + + + 65 + "name" + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + game_object_name + game_object + 0 + +
+ + 67 + "name" + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + game_object_name + game_object + 0x8 + +
+ + 66 + "name" + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + game_object_name + game_object + 0x10 + +
+
+
+
+
+ + 10 + "next" + + 1 + 0 + 8 Bytes +
+0
+ + next + + + + 75 + "current" + 1 + 0 + 8 Bytes +
+0
+ + current + + + + 76 + "name" + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + game_object_name + game_object + +
+ + 113 + "children_size" + 0 + 4 Bytes +
+0
+ + children + pointer_size * 2 + +
+ + 114 + "children" + + 0 + 8 Bytes +
+0
+ + children + + + + 115 + "[0]" + 1 + 0 + 8 Bytes +
+0
+ + 0 + + + + 116 + "name" + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + game_object_name + game_object + +
+ + 117 + "activeself" + 0 + Byte +
+0
+ + active_self + game_object + +
+
+
+ + 118 + "[1]" + 1 + 0 + 8 Bytes +
+0
+ + 0x8 + + + + 119 + "name" + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + game_object_name + game_object + +
+ + 120 + "activeself" + 0 + Byte +
+0
+ + active_self + game_object + +
+
+
+ + 121 + "[2]" + 1 + 0 + 8 Bytes +
+0
+ + 0x10 + + + + 122 + "name" + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + game_object_name + game_object + +
+ + 123 + "activeself" + 0 + Byte +
+0
+ + active_self + game_object + +
+
+
+
+
+ + 124 + "classes size" + 0 + 4 Bytes +
+0
+ + classes + pointer_size * 2 + game_object + +
+ + 125 + "classes" + + 0 + 8 Bytes +
+0
+ + classes + game_object + + + + 126 + "[0]" + + 0 + 8 Bytes +
+0
+ + pointer_size + (pointer_size * 2) * 0 + + + + 132 + "class name (mono)" + + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + class_name_mono + 0 + 0 + class_mono + +
+ + 133 + "class name (il2cpp)" + + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + class_name_il2cpp + 0 + 0 + class_il2cpp + +
+
+
+ + 128 + "[1]" + + 0 + 8 Bytes +
+0
+ + pointer_size + (pointer_size * 2) * 1 + + + + 129 + "class name (mono)" + + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + class_name_mono + 0 + 0 + class_mono + +
+ + 131 + "class name (il2cpp)" + + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + class_name_il2cpp + 0 + 0 + class_il2cpp + +
+
+
+
+
+ + 49 + "next" + + 1 + 0 + 8 Bytes +
+0
+ + next + + + + 77 + "current" + 1 + 0 + 8 Bytes +
+0
+ + current + + + + 78 + "name" + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + game_object_name + game_object + +
+
+
+
+
+
+
+
+
+
+
+
+
+ + 38 + "prev" + + 1 + 0 + 8 Bytes +
+0
+ + prev + + + + 55 + "name" + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + game_object_name + game_object + current + +
+ + 56 + "prev" + + 1 + 0 + 8 Bytes +
+0
+ + prev + + + + 57 + "name" + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + game_object_name + game_object + current + +
+ + 58 + "prev" + + 1 + 0 + 8 Bytes +
+0
+ + prev + + + + 59 + "name" + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + game_object_name + game_object + current + +
+ + 60 + "prev" + + 1 + 0 + 8 Bytes +
+0
+ + prev + + + + 61 + "name" + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + game_object_name + game_object + current + +
+ + 62 + "prev" + + 1 + 0 + 8 Bytes +
+0
+ + prev + + + + 63 + "name" + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + game_object_name + game_object + current + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + Info about this table: + + -- Cheat Engine Lua +-- Resolves the FINAL absolute address from an instruction matched by AOB. +-- +-- Example: +-- sig = "48 8B 05 ?? ?? ?? ?? 48 85 C0" +-- dispOff = 3 -- offset inside instruction where relative displacement starts +-- instrLen = 7 -- total instruction length +-- +-- This reproduces what CE shows in disassembler for RIP-relative instructions. + +function sig(sig, dispOffset) + local scan = AOBScan(sig) + if not scan or scan.Count == 0 then + print("sig not found") + return nil + end + + local instr = tonumber(scan[0], 16) + scan.destroy() + + -- read signed rel32 + local rel = readInteger(instr + dispOffset) + + if rel >= 0x80000000 then + rel = rel - 0x100000000 + end + + -- rel32 is ALWAYS relative to NEXT instruction + local final = instr + dispOffset + 4 + rel + + return final +end + + +is_dev_build = false +go_dev = 0x10 -- size of EditorExtensions +co_dev = 0x8 + +pointer_size = 0x8 + +scene_manager = sig("48 83 EC 20 48 8B 2D ?? ?? ?? ?? 33 F6", 7, 7) + +loaded_scenes = 0x8 +scene_count = 0x18 +active_scene = 0x48 +dont_destroy_on_load_scene = 0x70 + +asset_path = 0x10 +build_index = 0x98 +root_storage_container = 0xF0 + +prev = 0x0 +next = 0x8 +current = 0x10 + +game_object = 0x20 + (is_dev_build and go_dev or 0) +game_object_name = 0x50 + (is_dev_build and go_dev or 0) +active_self = 0x46 + (is_dev_build and go_dev or 0) +active_in_hierarchy = 0x47 + (is_dev_build and go_dev or 0) +children = 0x48 + (is_dev_build and go_dev or 0) + +classes = game_object +class_mono = 0x28 + (is_dev_build and co_dev or 0) +class_name_mono = 0x48 +class_il2cpp = 0x18 +class_name_il2cpp = 0x10 + +
From 440ec3509ed478fd8fe10dde0fcb83e3c745c3a9 Mon Sep 17 00:00:00 2001 From: Mitchell Merry Date: Fri, 15 May 2026 16:06:00 +1000 Subject: [PATCH 12/27] add to todo --- src/game_engine/unity/scene_manager/offsets.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/game_engine/unity/scene_manager/offsets.md b/src/game_engine/unity/scene_manager/offsets.md index 86ca1fd..2db4186 100644 --- a/src/game_engine/unity/scene_manager/offsets.md +++ b/src/game_engine/unity/scene_manager/offsets.md @@ -1,3 +1,9 @@ +# todo + +- check the asr can load the classes (log 'em) +- check the other scenes loaded + - can we get "is loading"? + # project setup - scene 1 From 25f596a645f5f185ec59ea7a83161c22aece7127 Mon Sep 17 00:00:00 2001 From: Mitchell Merry Date: Sat, 16 May 2026 22:59:02 +1000 Subject: [PATCH 13/27] latest unity version etc --- .gitignore | 1 + src/game_engine/unity/mono/class.rs | 4 +- src/game_engine/unity/mono/mod.rs | 2 + .../unity/scene_manager/offsets.md | 49 +- .../unity/scene_manager/offsets.rs | 2 +- .../scene_manager/platformer-playground.CT | 651 +++++++++++++++++- 6 files changed, 667 insertions(+), 42 deletions(-) diff --git a/.gitignore b/.gitignore index 3ab5292..385ef42 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target /Cargo.lock .idea +**/.DS_Store diff --git a/src/game_engine/unity/mono/class.rs b/src/game_engine/unity/mono/class.rs index a8c7e4b..6938874 100644 --- a/src/game_engine/unity/mono/class.rs +++ b/src/game_engine/unity/mono/class.rs @@ -7,9 +7,9 @@ use crate::{future::retry, string::ArrayCString, Address, Error, Process}; pub use asr_derive::MonoClass as Class; /// A .NET class that is part of an [`Image`](Image). -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] pub struct Class { - pub(super) class: Address, + pub class: Address, } impl Class { diff --git a/src/game_engine/unity/mono/mod.rs b/src/game_engine/unity/mono/mod.rs index 29f5c37..053b582 100644 --- a/src/game_engine/unity/mono/mod.rs +++ b/src/game_engine/unity/mono/mod.rs @@ -202,6 +202,8 @@ impl Module { _ => return None, }; + print_message("attached"); + Some(Self { assemblies, version, diff --git a/src/game_engine/unity/scene_manager/offsets.md b/src/game_engine/unity/scene_manager/offsets.md index 2db4186..4840fe7 100644 --- a/src/game_engine/unity/scene_manager/offsets.md +++ b/src/game_engine/unity/scene_manager/offsets.md @@ -1,5 +1,7 @@ # todo +- fix mono (v4?) + - check if we can get special - check the asr can load the classes (log 'em) - check the other scenes loaded - can we get "is loading"? @@ -41,10 +43,16 @@ using UnityEngine.SceneManagement; public class RestartScript : MonoBehaviour { + static int myStatic = 0x73399007; + public int special = 0x191B1D1F; + public int FPS = 60; + // Start is called before the first frame update void Start() { + QualitySettings.vSyncCount = 1; + Application.targetFrameRate = FPS; } // Update is called once per frame @@ -161,7 +169,7 @@ class_name = 0x48 # Unity 6000.4.5f1 -### 64 bit windows +## 64 bit windows ```lua is_dev_build = false @@ -197,3 +205,42 @@ class_name_mono = 0x48 class_il2cpp = 0x18 class_name_il2cpp = 0x10 ``` + +## 64 bit mono + +```lua +is_dev_build = false +go_dev = 0x10 -- size of EditorExtensions +co_dev = 0x8 + +pointer_size = 0x8 + +scene_manager = sig("41 54 53 50 4C 8B ?5 ???????? 41 83", 7, 7) + +loaded_scenes = 0x8 +scene_count = 0x18 +active_scene = 0x48 +dont_destroy_on_load_scene = 0x70 + +asset_path = 0x10 +build_index = 0x98 +root_storage_container = 0xF0 + +prev_node = 0x0 +next_node = 0x8 +current_node = 0x10 + +game_object = 0x20 + (is_dev_build and go_dev or 0) +game_object_name = 0x50 + (is_dev_build and go_dev or 0) +active_self = 0x46 + (is_dev_build and go_dev or 0) +active_in_hierarchy = 0x47 + (is_dev_build and go_dev or 0) +children = 0x48 + (is_dev_build and go_dev or 0) + +classes = game_object +class_mono = 0x18 + (is_dev_build and co_dev or 0) +-- has extra dereference +-- component.m_MonoReference->raw->vtable->class->name +class_name_mono = 0x40 +class_il2cpp = 0x18 +class_name_il2cpp = 0x10 +``` \ No newline at end of file diff --git a/src/game_engine/unity/scene_manager/offsets.rs b/src/game_engine/unity/scene_manager/offsets.rs index 64ab409..51d043d 100644 --- a/src/game_engine/unity/scene_manager/offsets.rs +++ b/src/game_engine/unity/scene_manager/offsets.rs @@ -29,7 +29,7 @@ impl Offsets { game_object_name: 0x50, game_object_activeself: 0x46, game_object_activeinhierarchy: 0x47, - klass: 0x28, + klass: 0xE0, children_pointer: 0x48, }), PointerSize::Bit32 => Some(&Self { diff --git a/src/game_engine/unity/scene_manager/platformer-playground.CT b/src/game_engine/unity/scene_manager/platformer-playground.CT index 22791dc..a8297ef 100644 --- a/src/game_engine/unity/scene_manager/platformer-playground.CT +++ b/src/game_engine/unity/scene_manager/platformer-playground.CT @@ -1,5 +1,5 @@ - + 0 @@ -184,7 +184,7 @@ 8 Bytes
+0
- current + 0x010 @@ -232,7 +232,7 @@ 4 Bytes
+0
- children + pointer_size * 2 + children + 0x008 * 2
@@ -375,7 +375,7 @@ 4 Bytes
+0
- classes + pointer_size * 2 + classes + 0x008 * 2 game_object
@@ -387,7 +387,7 @@ 8 Bytes
+0
- classes + game_object game_object @@ -399,7 +399,7 @@ 8 Bytes
+0
- pointer_size + (pointer_size * 2) * 0 + 0x008 + (0x008 * 2) * 0 @@ -450,7 +450,7 @@ 8 Bytes
+0
- pointer_size + (pointer_size * 2) * 1 + 0x008 + (0x008 * 2) * 1 @@ -493,6 +493,108 @@
+ + 160 + "[2]" + + 0 + 8 Bytes +
+0
+ + 0x008 + (0x008 * 2) * 2 + + + + 161 + "class name (mono)" + + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + class_name_mono + 0 + 0 + class_mono + +
+ + 162 + "class name (il2cpp)" + + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + class_name_il2cpp + 0 + 0 + class_il2cpp + +
+
+
+ + 157 + "[3]" + + 0 + 8 Bytes +
+0
+ + 0x008 + (0x008 * 2) * 3 + + + + 158 + "class name (mono)" + + 0 + String + 50 + 0 + 0 + 1 +
+0
+ + 0 + class_name_mono + 0 + 0 + class_mono + +
+ + 159 + "class name (il2cpp)" + + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + class_name_il2cpp + 0 + 0 + class_il2cpp + +
+
+
@@ -506,7 +608,7 @@ 8 Bytes
+0
- next + 0x08 @@ -517,7 +619,7 @@ 8 Bytes
+0
- current + 0x010 @@ -536,6 +638,235 @@ game_object + + 163 + "classes size" + 0 + 4 Bytes +
+0
+ + classes + 0x008 * 2 + game_object + +
+ + 164 + "classes" + + 0 + 8 Bytes +
+0
+ + game_object + game_object + + + + 165 + "[0]" + + 0 + 8 Bytes +
+0
+ + 0x008 + (0x008 * 2) * 0 + + + + 166 + "class name (mono)" + + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + class_name_mono + 0 + 0 + class_mono + +
+ + 167 + "class name (il2cpp)" + + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + class_name_il2cpp + 0 + 0 + class_il2cpp + +
+
+
+ + 168 + "[1]" + + 0 + 8 Bytes +
+0
+ + 0x008 + (0x008 * 2) * 1 + + + + 169 + "class name (mono)" + + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + class_name_mono + 0 + 0 + class_mono + +
+ + 170 + "class name (il2cpp)" + + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + class_name_il2cpp + 0 + 0 + class_il2cpp + +
+
+
+ + 171 + "[2]" + + 0 + 8 Bytes +
+0
+ + 0x008 + (0x008 * 2) * 2 + + + + 172 + "class name (mono)" + + 0 + String + 100 + 0 + 0 + 1 +
+0
+ + 0 + class_name_mono + 0 + 0 + class_mono + +
+ + 173 + "class name (il2cpp)" + + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + class_name_il2cpp + 0 + 0 + class_il2cpp + +
+
+
+ + 174 + "[3]" + + 0 + 8 Bytes +
+0
+ + 0x008 + (0x008 * 2) * 3 + + + + 175 + "class name (mono)" + + 0 + String + 50 + 0 + 0 + 1 +
+0
+ + 0 + class_name_mono + 0 + 0 + class_mono + +
+ + 176 + "class name (il2cpp)" + + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + class_name_il2cpp + 0 + 0 + class_il2cpp + +
+
+
+
+
@@ -547,7 +878,7 @@ 8 Bytes
+0
- next + 0x08 @@ -558,7 +889,7 @@ 8 Bytes
+0
- current + 0x010 @@ -652,7 +983,7 @@ 8 Bytes
+0
- next + 0x08 @@ -663,7 +994,7 @@ 8 Bytes
+0
- current + 0x010 @@ -689,7 +1020,7 @@ 4 Bytes
+0
- children + pointer_size * 2 + children + 0x008 * 2
@@ -832,7 +1163,7 @@ 4 Bytes
+0
- classes + pointer_size * 2 + classes + 0x008 * 2 game_object
@@ -844,7 +1175,7 @@ 8 Bytes
+0
- classes + game_object game_object @@ -856,11 +1187,11 @@ 8 Bytes
+0
- pointer_size + (pointer_size * 2) * 0 + 0x008 + (0x008 * 2) * 0 - 132 + 156 "class name (mono)" 0 @@ -903,15 +1234,16 @@ 128 "[1]" + 1 0 8 Bytes
+0
- pointer_size + (pointer_size * 2) * 1 + 0x008 + (0x008 * 2) * 1 - 129 + 155 "class name (mono)" 0 @@ -929,6 +1261,110 @@ class_mono + + 177 + "class name (mono… 4?)" + + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + class_name_mono + 0 + 0 + 0 + 0x18 + +
+ + 178 + "monoobjecthandle (v4?)" + + 1 + 0 + 8 Bytes +
+0
+ + 0x18 + + + + 179 + "monoobject*" + + 1 + 0 + 8 Bytes +
+0
+ + 0x0 + + + + 180 + "special" + + 1 + 0 + 4 Bytes +
+0
+ + 0x20 + +
+ + 181 + "monovtable*" + + 1 + 0 + 8 Bytes +
+0
+ + 0x0 + + + + 182 + "mono class*" + + 1 + 0 + 4 Bytes +
+0
+ + 0 + + + + 183 + "name" + + 0 + String + 100 + 0 + 0 + 1 +
+0
+ + 0 + class_name_mono + +
+
+
+
+
+
+
+
+
131 "class name (il2cpp)" @@ -961,7 +1397,7 @@ 8 Bytes
+0
- next + 0x08 @@ -972,7 +1408,7 @@ 8 Bytes
+0
- current + 0x010 @@ -1012,7 +1448,7 @@ 8 Bytes
+0
- prev + 0x00 @@ -1029,7 +1465,7 @@ 0 game_object_name game_object - current + 0x010 @@ -1041,7 +1477,7 @@ 8 Bytes
+0
- prev + 0x00 @@ -1058,7 +1494,7 @@ 0 game_object_name game_object - current + 0x010 @@ -1070,7 +1506,7 @@ 8 Bytes
+0
- prev + 0x00 @@ -1087,7 +1523,7 @@ 0 game_object_name game_object - current + 0x010 @@ -1099,7 +1535,7 @@ 8 Bytes
+0
- prev + 0x00 @@ -1116,7 +1552,7 @@ 0 game_object_name game_object - current + 0x010 @@ -1128,7 +1564,7 @@ 8 Bytes
+0
- prev + 0x00 @@ -1145,7 +1581,7 @@ 0 game_object_name game_object - current + 0x010 @@ -1164,6 +1600,143 @@
+ + 141 + "No description" + String + 13 + 0 + 0 + 1 +
113730BF4
+
+ + 142 + "No description" + String + 13 + 0 + 0 + 1 +
113730BF4
+
+ + 143 + "No description" + String + 13 + 0 + 0 + 1 +
10B9ECC18
+
+ + 144 + "No description" + String + 13 + 0 + 0 + 1 +
113730C1C
+
+ + 145 + "No description" + String + 13 + 0 + 0 + 1 +
12FF117F0
+
+ + 146 + "No description" + 1 + 8 Bytes +
12FF11790
+
+ + 147 + "No description" + String + 13 + 0 + 0 + 1 +
7F84880A682C
+
+ + 148 + "No description" + String + 13 + 0 + 0 + 1 +
7F84880A6C04
+
+ + 149 + "No description" + String + 13 + 0 + 0 + 1 +
7F84880A6C04
+
+ + 150 + "No description" + String + 13 + 0 + 0 + 1 +
7F84880A6C04
+
+ + 151 + "No description" + String + 13 + 0 + 0 + 1 +
7F84880A6C04
+
+ + 152 + "No description" + String + 13 + 0 + 0 + 1 +
7F84880A6C5D
+
+ + 153 + "No description" + String + 13 + 0 + 0 + 1 +
7F84880A6C04
+
+ + 154 + "No description" + String + 13 + 0 + 0 + 1 +
7F84880A682C
+
Info about this table: @@ -1208,7 +1781,7 @@ co_dev = 0x8 pointer_size = 0x8 -scene_manager = sig("48 83 EC 20 48 8B 2D ?? ?? ?? ?? 33 F6", 7, 7) +scene_manager = sig("41 54 53 50 4C 8B ?5 ???????? 41 83", 7, 7) loaded_scenes = 0x8 scene_count = 0x18 @@ -1219,9 +1792,9 @@ asset_path = 0x10 build_index = 0x98 root_storage_container = 0xF0 -prev = 0x0 -next = 0x8 -current = 0x10 +prev_node = 0x0 +next_node = 0x8 +current_node = 0x10 game_object = 0x20 + (is_dev_build and go_dev or 0) game_object_name = 0x50 + (is_dev_build and go_dev or 0) @@ -1230,8 +1803,10 @@ active_in_hierarchy = 0x47 + (is_dev_build and go_dev or 0) children = 0x48 + (is_dev_build and go_dev or 0) classes = game_object -class_mono = 0x28 + (is_dev_build and co_dev or 0) -class_name_mono = 0x48 +class_mono = 0x18 + (is_dev_build and co_dev or 0) +-- has extra dereference +-- component.m_MonoReference->raw->vtable->class->name +class_name_mono = 0x40 class_il2cpp = 0x18 class_name_il2cpp = 0x10 From 5df5ac0434d1012dfa9504058e11c7e0325bc93a Mon Sep 17 00:00:00 2001 From: Mitchell Merry Date: Sun, 17 May 2026 00:48:10 +1000 Subject: [PATCH 14/27] create mono::Object and clarify everything --- src/game_engine/unity/mono/class.rs | 41 +- src/game_engine/unity/mono/mod.rs | 2 +- .../unity/scene_manager/component.rs | 40 ++ .../unity/scene_manager/game_object.rs | 42 +- src/game_engine/unity/scene_manager/mod.rs | 2 + .../unity/scene_manager/offsets.rs | 8 +- .../scene_manager/platformer-playground.CT | 408 ++++++++++++++++++ 7 files changed, 501 insertions(+), 42 deletions(-) create mode 100644 src/game_engine/unity/scene_manager/component.rs diff --git a/src/game_engine/unity/mono/class.rs b/src/game_engine/unity/mono/class.rs index 6938874..2909b62 100644 --- a/src/game_engine/unity/mono/class.rs +++ b/src/game_engine/unity/mono/class.rs @@ -13,21 +13,6 @@ pub struct Class { } impl Class { - 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, @@ -230,3 +215,29 @@ impl Class { retry(|| self.get_parent(process, module)).await } } + +/** Represents a MonoObject, which is an instantiation of a MonoVTable (and therefore MonoClass). It + * is where the actual fields of the class live. + * + * See https://github.com/mono/mono/blob/0f53e9e151d92944cacab3e24ac359410c606df6/mono/metadata/object.h#L31. + */ +pub struct Object { + pub address: Address, +} + +impl Object { + pub fn get_class(&self, process: &Process, module: &Module) -> Result { + process + // MonoVTable *vtable + // See https://github.com/mono/mono/blob/0f53e9e151d92944cacab3e24ac359410c606df6/mono/metadata/object.h#L32 + .read_pointer(self.address, module.pointer_size) + .ok() + .filter(|val| !val.is_null()) + // MonoClass *klass + // See https://github.com/mono/mono/blob/0f53e9e151d92944cacab3e24ac359410c606df6/mono/metadata/class-internals.h#L360 + .and_then(|addr| process.read_pointer(addr, module.pointer_size).ok()) + .filter(|val| !val.is_null()) + .map(|class| Class { class }) + .ok_or(Error {}) + } +} diff --git a/src/game_engine/unity/mono/mod.rs b/src/game_engine/unity/mono/mod.rs index 053b582..d0b8d19 100644 --- a/src/game_engine/unity/mono/mod.rs +++ b/src/game_engine/unity/mono/mod.rs @@ -18,7 +18,7 @@ use assembly::Assembly; mod image; pub use image::Image; mod class; -pub use class::Class; +pub use class::{Class, Object}; mod field; use field::Field; mod version; diff --git a/src/game_engine/unity/scene_manager/component.rs b/src/game_engine/unity/scene_manager/component.rs new file mode 100644 index 0000000..dd91e7d --- /dev/null +++ b/src/game_engine/unity/scene_manager/component.rs @@ -0,0 +1,40 @@ +use crate::game_engine::unity::mono; +use crate::game_engine::unity::scene_manager::SceneManager; +use crate::{Address, Error, Process}; + +/// Representing a Unity Component, usually attached to a GameObject +#[derive(Debug)] +pub struct Component { + /// the base address of the component + pub address: Address, +} + +impl Component { + /** Retrieves the MonoObject this Component holds */ + pub fn get_mono_object( + &self, + process: &Process, + scene_manager: &SceneManager, + ) -> Result { + // TODO is this only true on 6000? + // other versions, according to how ASR currently is, do not dereference twice here + // NOTE: I'm like 80% sure the following is true + process + // class ScriptingGCHandle m_MonoReference + // See https://gist.github.com/just-ero/92457b51baf85bd1e5b8c87de8c9835e#file-object-hpp-L18 + // In Mono this is a MonoObjectHandle, which is a MonoObject** + // MonoObject **__raw + // See https://github.com/mono/mono/blob/0f53e9e151d92944cacab3e24ac359410c606df6/mono/metadata/handle-decl.h#L72 + .read_pointer( + self.address + scene_manager.offsets.scripting_object_handle, + scene_manager.pointer_size, + ) + .ok() + .filter(|val| !val.is_null()) + // MonoObject *__raw + .and_then(|addr| process.read_pointer(addr, scene_manager.pointer_size).ok()) + .filter(|val| !val.is_null()) + .map(|address| mono::Object { address }) + .ok_or(Error {}) + } +} diff --git a/src/game_engine/unity/scene_manager/game_object.rs b/src/game_engine/unity/scene_manager/game_object.rs index 22b3908..fbdf034 100644 --- a/src/game_engine/unity/scene_manager/game_object.rs +++ b/src/game_engine/unity/scene_manager/game_object.rs @@ -1,4 +1,4 @@ -use super::{SceneManager, CSTR}; +use super::{Component, SceneManager, CSTR}; use crate::game_engine::unity::{il2cpp, mono}; use crate::string::ArrayCString; use crate::{Address, Address32, Address64, Error, PointerSize, Process}; @@ -26,12 +26,12 @@ impl GameObject { ) } - /// Traverse the classes associated with the Components attached to this game object. - pub fn classes<'a>( + /// Traverse the components attached to this game object. + pub fn components<'a>( &'a self, process: &'a Process, scene_manager: &'a SceneManager, - ) -> Result + 'a, Error> { + ) -> Result + 'a, Error> { let (number_of_components, component_pair_array): (usize, Address) = match scene_manager.pointer_size { PointerSize::Bit64 => { @@ -83,34 +83,30 @@ impl GameObject { } }; - 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()) + Ok((0..number_of_components).map(move |m| Component { + address: components[m], })) } - /// Tries to find the base address of a class in the current `GameObject` by name. + /// Get a Component attached to the current `GameObject` by name of it's class. /// - /// Mono only. - pub fn get_class_mono( + /// For Mono. + pub fn get_component_mono( &self, process: &Process, scene_manager: &SceneManager, module: &mono::Module, name: &str, - ) -> Result { + ) -> 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) + self.components(process, scene_manager)? + .find(|component| { + let val = component + .get_mono_object(process, scene_manager) + .and_then(|object| object.get_class(process, module)) .and_then(|c| c.get_name::(process, module)); val.is_ok_and(|class_name| class_name.matches(name)) @@ -127,14 +123,14 @@ impl GameObject { scene_manager: &SceneManager, module: &il2cpp::Module, name: &str, - ) -> Result { + ) -> 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) + self.components(process, scene_manager)? + .find(|comp| { + let val = il2cpp::Class::get_from_component(process, module, comp.address) .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/mod.rs b/src/game_engine/unity/scene_manager/mod.rs index 43c58dc..717dbea 100644 --- a/src/game_engine/unity/scene_manager/mod.rs +++ b/src/game_engine/unity/scene_manager/mod.rs @@ -26,6 +26,8 @@ use offsets::Offsets; mod game_object; pub use game_object::GameObject; +mod component; +pub use component::Component; mod scene; pub use scene::Scene; diff --git a/src/game_engine/unity/scene_manager/offsets.rs b/src/game_engine/unity/scene_manager/offsets.rs index 51d043d..b202dc8 100644 --- a/src/game_engine/unity/scene_manager/offsets.rs +++ b/src/game_engine/unity/scene_manager/offsets.rs @@ -11,7 +11,9 @@ pub(super) struct Offsets { pub(super) game_object_name: u8, pub(super) game_object_activeself: u8, pub(super) game_object_activeinhierarchy: u8, - pub(super) klass: u8, + /// a handle to the scripting object + /// MonoObjectHandle for Mono for e.g. + pub(super) scripting_object_handle: u8, pub(super) children_pointer: u8, } @@ -29,7 +31,7 @@ impl Offsets { game_object_name: 0x50, game_object_activeself: 0x46, game_object_activeinhierarchy: 0x47, - klass: 0xE0, + scripting_object_handle: 0x18, children_pointer: 0x48, }), PointerSize::Bit32 => Some(&Self { @@ -43,7 +45,7 @@ impl Offsets { game_object_name: 0x3C, game_object_activeself: 0x32, game_object_activeinhierarchy: 0x33, - klass: 0x18, + scripting_object_handle: 0x18, children_pointer: 0x50, }), _ => None, diff --git a/src/game_engine/unity/scene_manager/platformer-playground.CT b/src/game_engine/unity/scene_manager/platformer-playground.CT index a8297ef..de2e5f8 100644 --- a/src/game_engine/unity/scene_manager/platformer-playground.CT +++ b/src/game_engine/unity/scene_manager/platformer-playground.CT @@ -440,6 +440,90 @@ class_il2cpp
+ + 194 + "monoobjecthandle (v4?)" + + 1 + 0 + 8 Bytes +
+0
+ + 0x18 + + + + 195 + "monoobject*" + + 1 + 0 + 8 Bytes +
+0
+ + 0x0 + + + + 196 + "special" + + 1 + 0 + 4 Bytes +
+0
+ + 0x20 + +
+ + 197 + "monovtable*" + + 1 + 0 + 8 Bytes +
+0
+ + 0x0 + + + + 198 + "mono class*" + + 1 + 0 + 4 Bytes +
+0
+ + 0 + + + + 199 + "name" + + 0 + String + 100 + 0 + 0 + 1 +
+0
+ + 0 + class_name_mono + +
+
+
+
+
+
+
+
+
@@ -491,6 +575,90 @@ class_il2cpp + + 206 + "monoobjecthandle (v4?)" + + 1 + 0 + 8 Bytes +
+0
+ + 0x18 + + + + 207 + "monoobject*" + + 1 + 0 + 8 Bytes +
+0
+ + 0x0 + + + + 208 + "special" + + 1 + 0 + 4 Bytes +
+0
+ + 0x20 + +
+ + 209 + "monovtable*" + + 1 + 0 + 8 Bytes +
+0
+ + 0x0 + + + + 210 + "mono class*" + + 1 + 0 + 4 Bytes +
+0
+ + 0 + + + + 211 + "name" + + 0 + String + 100 + 0 + 0 + 1 +
+0
+ + 0 + class_name_mono + +
+
+
+
+
+
+
+
+
@@ -710,6 +878,90 @@ class_il2cpp + + 200 + "monoobjecthandle (v4?)" + + 1 + 0 + 8 Bytes +
+0
+ + 0x18 + + + + 201 + "monoobject*" + + 1 + 0 + 8 Bytes +
+0
+ + 0x0 + + + + 202 + "special" + + 1 + 0 + 4 Bytes +
+0
+ + 0x20 + +
+ + 203 + "monovtable*" + + 1 + 0 + 8 Bytes +
+0
+ + 0x0 + + + + 204 + "mono class*" + + 1 + 0 + 4 Bytes +
+0
+ + 0 + + + + 205 + "name" + + 0 + String + 100 + 0 + 0 + 1 +
+0
+ + 0 + class_name_mono + +
+
+
+
+
+
+
+
+
@@ -1386,6 +1638,162 @@
+ + 184 + "[2]" + + 1 + 0 + 8 Bytes +
+0
+ + 0x008 + (0x008 * 2) * 2 + + + + 185 + "class name (mono)" + + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + class_name_mono + 0 + 0 + class_mono + +
+ + 186 + "class name (mono… 4?)" + + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + class_name_mono + 0 + 0 + 0 + 0x18 + +
+ + 187 + "monoobjecthandle (v4?)" + + 1 + 0 + 8 Bytes +
+0
+ + 0x18 + + + + 188 + "monoobject*" + + 1 + 0 + 8 Bytes +
+0
+ + 0x0 + + + + 189 + "special" + + 1 + 0 + 4 Bytes +
+0
+ + 0x20 + +
+ + 190 + "monovtable*" + + 1 + 0 + 8 Bytes +
+0
+ + 0x0 + + + + 191 + "mono class*" + + 1 + 0 + 4 Bytes +
+0
+ + 0 + + + + 192 + "name" + + 0 + String + 100 + 0 + 0 + 1 +
+0
+ + 0 + class_name_mono + +
+
+
+
+
+
+
+
+
+ + 193 + "class name (il2cpp)" + + 0 + String + 10 + 0 + 0 + 1 +
+0
+ + 0 + class_name_il2cpp + 0 + 0 + class_il2cpp + +
+
+
From 822dac378aadde78febaf8390996f03e8f88e0aa Mon Sep 17 00:00:00 2001 From: Mitchell Merry Date: Sun, 17 May 2026 00:48:26 +1000 Subject: [PATCH 15/27] comments --- src/game_engine/unity/mono/class.rs | 1 + src/game_engine/unity/scene_manager/game_object.rs | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/game_engine/unity/mono/class.rs b/src/game_engine/unity/mono/class.rs index 2909b62..6e186aa 100644 --- a/src/game_engine/unity/mono/class.rs +++ b/src/game_engine/unity/mono/class.rs @@ -226,6 +226,7 @@ pub struct Object { } impl Object { + /** Get the class of this object */ pub fn get_class(&self, process: &Process, module: &Module) -> Result { process // MonoVTable *vtable diff --git a/src/game_engine/unity/scene_manager/game_object.rs b/src/game_engine/unity/scene_manager/game_object.rs index fbdf034..6c232c7 100644 --- a/src/game_engine/unity/scene_manager/game_object.rs +++ b/src/game_engine/unity/scene_manager/game_object.rs @@ -83,7 +83,9 @@ impl GameObject { } }; - Ok((0..number_of_components).map(move |m| Component { + // 0 is always Transform + // TODO how to read these and other default types? + Ok((1..number_of_components).map(move |m| Component { address: components[m], })) } @@ -148,7 +150,7 @@ impl GameObject { 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 + /// Returns whether the game object is considered "active" by itself (irrespective of its /// parents) pub fn is_active_self( &self, From 1b3ed11faf64df51fc8568b196f9636260908750 Mon Sep 17 00:00:00 2001 From: Mitchell Merry Date: Sun, 17 May 2026 00:49:28 +1000 Subject: [PATCH 16/27] update todo --- src/game_engine/unity/scene_manager/offsets.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/game_engine/unity/scene_manager/offsets.md b/src/game_engine/unity/scene_manager/offsets.md index 4840fe7..3cf34c3 100644 --- a/src/game_engine/unity/scene_manager/offsets.md +++ b/src/game_engine/unity/scene_manager/offsets.md @@ -1,7 +1,6 @@ # todo -- fix mono (v4?) - - check if we can get special +- do other unity versions NOT have the extra dereference for mono object? - check the asr can load the classes (log 'em) - check the other scenes loaded - can we get "is loading"? From 50be28586afd18db5238a6c618869730d9bf1fa0 Mon Sep 17 00:00:00 2001 From: Mitchell Merry Date: Sat, 27 Jun 2026 11:09:05 +1000 Subject: [PATCH 17/27] Start cleaning --- src/game_engine/unity/mono/mod.rs | 10 - src/game_engine/unity/scene_manager/mod.rs | 6 - .../unity/scene_manager/offsets.md | 245 -- .../scene_manager/platformer-playground.CT | 2221 ----------------- 4 files changed, 2482 deletions(-) delete mode 100644 src/game_engine/unity/scene_manager/offsets.md delete mode 100644 src/game_engine/unity/scene_manager/platformer-playground.CT diff --git a/src/game_engine/unity/mono/mod.rs b/src/game_engine/unity/mono/mod.rs index d0b8d19..3495db3 100644 --- a/src/game_engine/unity/mono/mod.rs +++ b/src/game_engine/unity/mono/mod.rs @@ -54,7 +54,6 @@ 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 { - print_message("ok!"); let (module_range, format) = [ ("mono.dll", BinaryFormat::PE), ("libmono.so", BinaryFormat::ELF), @@ -67,7 +66,6 @@ impl Module { ] .into_iter() .find_map(|(name, format)| Some((process.get_module_range(name).ok()?, format)))?; - print_message("not even"); let (mono_module, _) = module_range; @@ -103,7 +101,6 @@ impl Module { } #[cfg(feature = "alloc")] BinaryFormat::MachO => { - print_message("rootdonmainfge"); macho::symbols(process, module_range) .find(|symbol| { let name = symbol.get_name::<26>(process); @@ -122,7 +119,6 @@ impl Module { #[allow(unreachable_patterns)] _ => return None, }; - print_message("assemlyes"); let assemblies: Address = match (pointer_size, format) { (PointerSize::Bit64, BinaryFormat::PE) => { @@ -141,7 +137,6 @@ impl Module { } #[cfg(feature = "alloc")] (PointerSize::Bit64, BinaryFormat::MachO) => { - print_message("ok lets do this"); const SIG_MONO_X86_64_MACHO: Signature<3> = Signature::new("48 8B 3D"); // 57 0f 00 d0 adrp x23,(page + 0x1ea000) // e0 da 47 f9 ldr x0,[x23, #0xfb0]=>(page + 0x1eafb0) @@ -165,12 +160,10 @@ impl Module { .scan_process_range(process, (root_domain_function_address, 0x100)) .map(|a| a + 3) { - print_message("yay"); scan_address + 0x4 + process.read::(scan_address).ok()? } else if let Some(scan_address) = SIG_MONO_ARM_64_MACHO .scan_process_range(process, (root_domain_function_address, 0x100)) { - print_message("what"); let page = scan_address.value() & 0xfffffffffffff000; let bs = process.read::<[u8; 8]>(scan_address).ok()?; // adrp @@ -185,7 +178,6 @@ impl Module { let ldr = (i0 << 3) | (i1 << 9); (page + adrp + ldr).into() } else { - print_message("got hands."); return None; } } @@ -202,8 +194,6 @@ impl Module { _ => return None, }; - print_message("attached"); - Some(Self { assemblies, version, diff --git a/src/game_engine/unity/scene_manager/mod.rs b/src/game_engine/unity/scene_manager/mod.rs index 717dbea..8868911 100644 --- a/src/game_engine/unity/scene_manager/mod.rs +++ b/src/game_engine/unity/scene_manager/mod.rs @@ -77,14 +77,12 @@ impl SceneManager { } _ => Some((process.get_module_range(name).ok()?, format)), })?; - print_message("b"); 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)?, }; - print_message("c"); let is_il2cpp = process.get_module_address("GameAssembly.dll").is_ok(); @@ -92,9 +90,6 @@ impl SceneManager { // used in the target game. 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()? - print_message("dd"); if let Some(addr) = SIG_64_BIT_PE_1.scan_process_range(process, unity_player) { print_message(&format!("dd1 {addr}")); addr + 0x4 + process.read::(addr + 7).ok()? @@ -139,7 +134,6 @@ impl SceneManager { .read_pointer(base_address, pointer_size) .ok() .filter(|val| !val.is_null())?; - print_message("g"); Some(Self { pointer_size, diff --git a/src/game_engine/unity/scene_manager/offsets.md b/src/game_engine/unity/scene_manager/offsets.md deleted file mode 100644 index 3cf34c3..0000000 --- a/src/game_engine/unity/scene_manager/offsets.md +++ /dev/null @@ -1,245 +0,0 @@ -# todo - -- do other unity versions NOT have the extra dereference for mono object? -- check the asr can load the classes (log 'em) -- check the other scenes loaded - - can we get "is loading"? - -# project setup - -- scene 1 - - toplevelgame - - children: - - childgame1 - - childgame2 (inactive) - - classes: - - RestartScript - - other default GOs under - - disable MainCamera audio listener -- scene 2 - - (empty) - -build profiles: - -- w/ dev on/off -- scene list has both - -player settings: - -- ScriptingBackend Mono/Il2Cpp -- Active Input Handling - Old - -# lib - -## restart script - -```cs -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using UnityEngine; -using UnityEngine.SceneManagement; - -public class RestartScript : MonoBehaviour -{ - static int myStatic = 0x73399007; - public int special = 0x191B1D1F; - public int FPS = 60; - - // Start is called before the first frame update - void Start() - { - QualitySettings.vSyncCount = 1; - - Application.targetFrameRate = FPS; - } - - // Update is called once per frame - void Update() - { - Time.timeScale = 1f; - - if (Input.GetKeyDown(KeyCode.R)) - { - UnityEngine.Debug.Log("reloading scene"); - SceneManager.LoadSceneAsync(SceneManager.GetActiveScene().name); - } - - if (Input.GetKeyDown(KeyCode.D)) - { - UnityEngine.Debug.Log("loading scene 1 additively"); - SceneManager.LoadSceneAsync("scene 1", LoadSceneMode.Additive); - } - - if (Input.GetKeyDown(KeyCode.F)) - { - UnityEngine.Debug.Log("loading scene 2 additively"); - SceneManager.LoadSceneAsync("scene 2", LoadSceneMode.Additive); - } - - if (Input.GetKeyDown(KeyCode.C)) - { - UnityEngine.Debug.Log("loading scene 1 singly"); - SceneManager.LoadSceneAsync("scene 1"); - } - - if (Input.GetKeyDown(KeyCode.V)) - { - UnityEngine.Debug.Log("loading scene 2 singly"); - SceneManager.LoadSceneAsync("scene 2"); - } - } -} -``` - -## lua sig script - -```lua --- Cheat Engine Lua --- Resolves the FINAL absolute address from an instruction matched by AOB. --- --- Example: --- sig = "48 8B 05 ?? ?? ?? ?? 48 85 C0" --- dispOff = 3 -- offset inside instruction where relative displacement starts --- instrLen = 7 -- total instruction length --- --- This reproduces what CE shows in disassembler for RIP-relative instructions. - -function sig(sig, dispOffset) - local scan = AOBScan(sig) - if not scan or scan.Count == 0 then - print("sig not found") - return nil - end - - local instr = tonumber(scan[0], 16) - scan.destroy() - - -- read signed rel32 - local rel = readInteger(instr + dispOffset) - - if rel >= 0x80000000 then - rel = rel - 0x100000000 - end - - -- rel32 is ALWAYS relative to NEXT instruction - local final = instr + dispOffset + 4 + rel - - return final -end -``` - -# Unity 2023.1.22f1 - -### 64 bit, Windows, Mono - -``` -is_dev_build = false -go_dev = 0x10 -- size of EditorExtensions -co_dev = 0x8 - -pointer_size = 0x8 - -scene_manager = sig("48 83 EC 20 4C 8B ?5 ?? ?? ?? ?? 33 F6", 7, 7) - -loaded_scenes = 0x8 -scene_count = 0x18 -active_scene = 0x48 -dont_destroy_on_load_scene = 0x70 - -asset_path = 0x10 -build_index = 0x98 -root_storage_container = 0xF0 - -prev = 0x0 -next = 0x8 -current = 0x10 - -game_object = 0x30 + (is_dev_build and go_dev or 0) -game_object_name = 0x60 + (is_dev_build and go_dev or 0) -active_self = 0x56 + (is_dev_build and go_dev or 0) -active_in_hierarchy = 0x57 + (is_dev_build and go_dev or 0) -children = 0x70 + (is_dev_build and go_dev or 0) - -classes = game_object -class = 0x28 + (is_dev_build and co_dev or 0) -class_name = 0x48 -``` - -# Unity 6000.4.5f1 - -## 64 bit windows - -```lua -is_dev_build = false -go_dev = 0x10 -- size of EditorExtensions -co_dev = 0x8 - -pointer_size = 0x8 - -scene_manager = sig("48 83 EC 20 48 8B 2D ?? ?? ?? ?? 33 F6", 7, 7) - -loaded_scenes = 0x8 -scene_count = 0x18 -active_scene = 0x48 -dont_destroy_on_load_scene = 0x70 - -asset_path = 0x10 -build_index = 0x98 -root_storage_container = 0xF0 - -prev = 0x0 -next = 0x8 -current = 0x10 - -game_object = 0x20 + (is_dev_build and go_dev or 0) -game_object_name = 0x50 + (is_dev_build and go_dev or 0) -active_self = 0x46 + (is_dev_build and go_dev or 0) -active_in_hierarchy = 0x47 + (is_dev_build and go_dev or 0) -children = 0x48 + (is_dev_build and go_dev or 0) - -classes = game_object -class_mono = 0x28 + (is_dev_build and co_dev or 0) -class_name_mono = 0x48 -class_il2cpp = 0x18 -class_name_il2cpp = 0x10 -``` - -## 64 bit mono - -```lua -is_dev_build = false -go_dev = 0x10 -- size of EditorExtensions -co_dev = 0x8 - -pointer_size = 0x8 - -scene_manager = sig("41 54 53 50 4C 8B ?5 ???????? 41 83", 7, 7) - -loaded_scenes = 0x8 -scene_count = 0x18 -active_scene = 0x48 -dont_destroy_on_load_scene = 0x70 - -asset_path = 0x10 -build_index = 0x98 -root_storage_container = 0xF0 - -prev_node = 0x0 -next_node = 0x8 -current_node = 0x10 - -game_object = 0x20 + (is_dev_build and go_dev or 0) -game_object_name = 0x50 + (is_dev_build and go_dev or 0) -active_self = 0x46 + (is_dev_build and go_dev or 0) -active_in_hierarchy = 0x47 + (is_dev_build and go_dev or 0) -children = 0x48 + (is_dev_build and go_dev or 0) - -classes = game_object -class_mono = 0x18 + (is_dev_build and co_dev or 0) --- has extra dereference --- component.m_MonoReference->raw->vtable->class->name -class_name_mono = 0x40 -class_il2cpp = 0x18 -class_name_il2cpp = 0x10 -``` \ No newline at end of file diff --git a/src/game_engine/unity/scene_manager/platformer-playground.CT b/src/game_engine/unity/scene_manager/platformer-playground.CT deleted file mode 100644 index de2e5f8..0000000 --- a/src/game_engine/unity/scene_manager/platformer-playground.CT +++ /dev/null @@ -1,2221 +0,0 @@ - - - - - 0 - "scene manager" - 1 - 0 - 8 Bytes -
scene_manager
- - - 83 - "scene count" - 0 - 4 Bytes -
+0
- - +scene_count - -
- - 84 - "scenes" - - 1 - 0 - 8 Bytes -
+0
- - loaded_scenes - - - - 85 - "[0]" - 1 - 0 - 8 Bytes -
+0
- - 0 - - - - 87 - "path" - 0 - String - 105 - 0 - 0 - 1 -
+0
- - asset_path - -
- - 88 - "build index" - 1 - 4 Bytes -
+0
- - build_index - -
-
-
- - 89 - "[1]" - 1 - 0 - 8 Bytes -
+0
- - 0x8 - - - - 90 - "path" - 0 - String - 105 - 0 - 0 - 1 -
+0
- - asset_path - -
- - 91 - "build index" - 1 - 4 Bytes -
+0
- - build_index - -
-
-
-
-
- - 101 - "dont destroy on load" - 1 - 0 - 8 Bytes -
+0
- - dont_destroy_on_load_scene - - - - 102 - "path" - 0 - String - 105 - 0 - 0 - 1 -
+asset_path
-
- - 103 - "build index" - 1 - 4 Bytes -
+build_index
-
-
-
- - 1 - "active scene" - - 1 - 0 - 8 Bytes -
+0
- - active_scene - - - - 82 - "path" - 0 - String - 105 - 0 - 0 - 1 -
+0
- - asset_path - -
- - 2 - "ROT STORAGE CONTAINER" - - 1 - 0 - 8 Bytes -
+0
- - root_storage_container - - - - 79 - "current" - 1 - 0 - 8 Bytes -
+0
- - 0x010 - - - - 80 - "name" - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - game_object_name - game_object - -
- - 81 - "activeself" - 0 - Byte -
+0
- - active_self - game_object - -
- - 112 - "activeinhierarchy" - 0 - Byte -
+0
- - active_in_hierarchy - game_object - -
- - 104 - "children_size" - 0 - 4 Bytes -
+0
- - children + 0x008 * 2 - -
- - 68 - "children" - - 0 - 8 Bytes -
+0
- - children - - - - 69 - "[0]" - 1 - 0 - 8 Bytes -
+0
- - 0 - - - - 93 - "name" - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - game_object_name - game_object - -
- - 94 - "activeself" - 0 - Byte -
+0
- - active_self - game_object - -
-
-
- - 98 - "[1]" - 1 - 0 - 8 Bytes -
+0
- - 0x8 - - - - 99 - "name" - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - game_object_name - game_object - -
- - 100 - "activeself" - 0 - Byte -
+0
- - active_self - game_object - -
-
-
- - 95 - "[2]" - 1 - 0 - 8 Bytes -
+0
- - 0x10 - - - - 96 - "name" - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - game_object_name - game_object - -
- - 97 - "activeself" - 0 - Byte -
+0
- - active_self - game_object - -
-
-
-
-
- - 105 - "classes size" - 0 - 4 Bytes -
+0
- - classes + 0x008 * 2 - game_object - -
- - 134 - "classes" - - 0 - 8 Bytes -
+0
- - game_object - game_object - - - - 135 - "[0]" - - 0 - 8 Bytes -
+0
- - 0x008 + (0x008 * 2) * 0 - - - - 136 - "class name (mono)" - - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - class_name_mono - 0 - 0 - class_mono - -
- - 137 - "class name (il2cpp)" - - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - class_name_il2cpp - 0 - 0 - class_il2cpp - -
- - 194 - "monoobjecthandle (v4?)" - - 1 - 0 - 8 Bytes -
+0
- - 0x18 - - - - 195 - "monoobject*" - - 1 - 0 - 8 Bytes -
+0
- - 0x0 - - - - 196 - "special" - - 1 - 0 - 4 Bytes -
+0
- - 0x20 - -
- - 197 - "monovtable*" - - 1 - 0 - 8 Bytes -
+0
- - 0x0 - - - - 198 - "mono class*" - - 1 - 0 - 4 Bytes -
+0
- - 0 - - - - 199 - "name" - - 0 - String - 100 - 0 - 0 - 1 -
+0
- - 0 - class_name_mono - -
-
-
-
-
-
-
-
-
-
-
- - 138 - "[1]" - - 0 - 8 Bytes -
+0
- - 0x008 + (0x008 * 2) * 1 - - - - 139 - "class name (mono)" - - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - class_name_mono - 0 - 0 - class_mono - -
- - 140 - "class name (il2cpp)" - - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - class_name_il2cpp - 0 - 0 - class_il2cpp - -
- - 206 - "monoobjecthandle (v4?)" - - 1 - 0 - 8 Bytes -
+0
- - 0x18 - - - - 207 - "monoobject*" - - 1 - 0 - 8 Bytes -
+0
- - 0x0 - - - - 208 - "special" - - 1 - 0 - 4 Bytes -
+0
- - 0x20 - -
- - 209 - "monovtable*" - - 1 - 0 - 8 Bytes -
+0
- - 0x0 - - - - 210 - "mono class*" - - 1 - 0 - 4 Bytes -
+0
- - 0 - - - - 211 - "name" - - 0 - String - 100 - 0 - 0 - 1 -
+0
- - 0 - class_name_mono - -
-
-
-
-
-
-
-
-
-
-
- - 160 - "[2]" - - 0 - 8 Bytes -
+0
- - 0x008 + (0x008 * 2) * 2 - - - - 161 - "class name (mono)" - - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - class_name_mono - 0 - 0 - class_mono - -
- - 162 - "class name (il2cpp)" - - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - class_name_il2cpp - 0 - 0 - class_il2cpp - -
-
-
- - 157 - "[3]" - - 0 - 8 Bytes -
+0
- - 0x008 + (0x008 * 2) * 3 - - - - 158 - "class name (mono)" - - 0 - String - 50 - 0 - 0 - 1 -
+0
- - 0 - class_name_mono - 0 - 0 - class_mono - -
- - 159 - "class name (il2cpp)" - - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - class_name_il2cpp - 0 - 0 - class_il2cpp - -
-
-
-
-
-
-
- - 51 - "next" - - 1 - 0 - 8 Bytes -
+0
- - 0x08 - - - - 52 - "current" - 1 - 0 - 8 Bytes -
+0
- - 0x010 - - - - 72 - "name" - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - game_object_name - game_object - -
- - 163 - "classes size" - 0 - 4 Bytes -
+0
- - classes + 0x008 * 2 - game_object - -
- - 164 - "classes" - - 0 - 8 Bytes -
+0
- - game_object - game_object - - - - 165 - "[0]" - - 0 - 8 Bytes -
+0
- - 0x008 + (0x008 * 2) * 0 - - - - 166 - "class name (mono)" - - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - class_name_mono - 0 - 0 - class_mono - -
- - 167 - "class name (il2cpp)" - - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - class_name_il2cpp - 0 - 0 - class_il2cpp - -
- - 200 - "monoobjecthandle (v4?)" - - 1 - 0 - 8 Bytes -
+0
- - 0x18 - - - - 201 - "monoobject*" - - 1 - 0 - 8 Bytes -
+0
- - 0x0 - - - - 202 - "special" - - 1 - 0 - 4 Bytes -
+0
- - 0x20 - -
- - 203 - "monovtable*" - - 1 - 0 - 8 Bytes -
+0
- - 0x0 - - - - 204 - "mono class*" - - 1 - 0 - 4 Bytes -
+0
- - 0 - - - - 205 - "name" - - 0 - String - 100 - 0 - 0 - 1 -
+0
- - 0 - class_name_mono - -
-
-
-
-
-
-
-
-
-
-
- - 168 - "[1]" - - 0 - 8 Bytes -
+0
- - 0x008 + (0x008 * 2) * 1 - - - - 169 - "class name (mono)" - - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - class_name_mono - 0 - 0 - class_mono - -
- - 170 - "class name (il2cpp)" - - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - class_name_il2cpp - 0 - 0 - class_il2cpp - -
-
-
- - 171 - "[2]" - - 0 - 8 Bytes -
+0
- - 0x008 + (0x008 * 2) * 2 - - - - 172 - "class name (mono)" - - 0 - String - 100 - 0 - 0 - 1 -
+0
- - 0 - class_name_mono - 0 - 0 - class_mono - -
- - 173 - "class name (il2cpp)" - - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - class_name_il2cpp - 0 - 0 - class_il2cpp - -
-
-
- - 174 - "[3]" - - 0 - 8 Bytes -
+0
- - 0x008 + (0x008 * 2) * 3 - - - - 175 - "class name (mono)" - - 0 - String - 50 - 0 - 0 - 1 -
+0
- - 0 - class_name_mono - 0 - 0 - class_mono - -
- - 176 - "class name (il2cpp)" - - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - class_name_il2cpp - 0 - 0 - class_il2cpp - -
-
-
-
-
-
-
- - 53 - "next" - - 1 - 0 - 8 Bytes -
+0
- - 0x08 - - - - 73 - "current" - 1 - 0 - 8 Bytes -
+0
- - 0x010 - - - - 74 - "name" - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - game_object_name - game_object - -
- - 64 - "children" - - 0 - 8 Bytes -
+0
- - children - - - - 65 - "name" - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - game_object_name - game_object - 0 - -
- - 67 - "name" - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - game_object_name - game_object - 0x8 - -
- - 66 - "name" - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - game_object_name - game_object - 0x10 - -
-
-
-
-
- - 10 - "next" - - 1 - 0 - 8 Bytes -
+0
- - 0x08 - - - - 75 - "current" - 1 - 0 - 8 Bytes -
+0
- - 0x010 - - - - 76 - "name" - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - game_object_name - game_object - -
- - 113 - "children_size" - 0 - 4 Bytes -
+0
- - children + 0x008 * 2 - -
- - 114 - "children" - - 0 - 8 Bytes -
+0
- - children - - - - 115 - "[0]" - 1 - 0 - 8 Bytes -
+0
- - 0 - - - - 116 - "name" - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - game_object_name - game_object - -
- - 117 - "activeself" - 0 - Byte -
+0
- - active_self - game_object - -
-
-
- - 118 - "[1]" - 1 - 0 - 8 Bytes -
+0
- - 0x8 - - - - 119 - "name" - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - game_object_name - game_object - -
- - 120 - "activeself" - 0 - Byte -
+0
- - active_self - game_object - -
-
-
- - 121 - "[2]" - 1 - 0 - 8 Bytes -
+0
- - 0x10 - - - - 122 - "name" - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - game_object_name - game_object - -
- - 123 - "activeself" - 0 - Byte -
+0
- - active_self - game_object - -
-
-
-
-
- - 124 - "classes size" - 0 - 4 Bytes -
+0
- - classes + 0x008 * 2 - game_object - -
- - 125 - "classes" - - 0 - 8 Bytes -
+0
- - game_object - game_object - - - - 126 - "[0]" - - 0 - 8 Bytes -
+0
- - 0x008 + (0x008 * 2) * 0 - - - - 156 - "class name (mono)" - - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - class_name_mono - 0 - 0 - class_mono - -
- - 133 - "class name (il2cpp)" - - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - class_name_il2cpp - 0 - 0 - class_il2cpp - -
-
-
- - 128 - "[1]" - - 1 - 0 - 8 Bytes -
+0
- - 0x008 + (0x008 * 2) * 1 - - - - 155 - "class name (mono)" - - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - class_name_mono - 0 - 0 - class_mono - -
- - 177 - "class name (mono… 4?)" - - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - class_name_mono - 0 - 0 - 0 - 0x18 - -
- - 178 - "monoobjecthandle (v4?)" - - 1 - 0 - 8 Bytes -
+0
- - 0x18 - - - - 179 - "monoobject*" - - 1 - 0 - 8 Bytes -
+0
- - 0x0 - - - - 180 - "special" - - 1 - 0 - 4 Bytes -
+0
- - 0x20 - -
- - 181 - "monovtable*" - - 1 - 0 - 8 Bytes -
+0
- - 0x0 - - - - 182 - "mono class*" - - 1 - 0 - 4 Bytes -
+0
- - 0 - - - - 183 - "name" - - 0 - String - 100 - 0 - 0 - 1 -
+0
- - 0 - class_name_mono - -
-
-
-
-
-
-
-
-
- - 131 - "class name (il2cpp)" - - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - class_name_il2cpp - 0 - 0 - class_il2cpp - -
-
-
- - 184 - "[2]" - - 1 - 0 - 8 Bytes -
+0
- - 0x008 + (0x008 * 2) * 2 - - - - 185 - "class name (mono)" - - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - class_name_mono - 0 - 0 - class_mono - -
- - 186 - "class name (mono… 4?)" - - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - class_name_mono - 0 - 0 - 0 - 0x18 - -
- - 187 - "monoobjecthandle (v4?)" - - 1 - 0 - 8 Bytes -
+0
- - 0x18 - - - - 188 - "monoobject*" - - 1 - 0 - 8 Bytes -
+0
- - 0x0 - - - - 189 - "special" - - 1 - 0 - 4 Bytes -
+0
- - 0x20 - -
- - 190 - "monovtable*" - - 1 - 0 - 8 Bytes -
+0
- - 0x0 - - - - 191 - "mono class*" - - 1 - 0 - 4 Bytes -
+0
- - 0 - - - - 192 - "name" - - 0 - String - 100 - 0 - 0 - 1 -
+0
- - 0 - class_name_mono - -
-
-
-
-
-
-
-
-
- - 193 - "class name (il2cpp)" - - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - class_name_il2cpp - 0 - 0 - class_il2cpp - -
-
-
-
-
- - 49 - "next" - - 1 - 0 - 8 Bytes -
+0
- - 0x08 - - - - 77 - "current" - 1 - 0 - 8 Bytes -
+0
- - 0x010 - - - - 78 - "name" - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - game_object_name - game_object - -
-
-
-
-
-
-
-
-
-
-
-
-
- - 38 - "prev" - - 1 - 0 - 8 Bytes -
+0
- - 0x00 - - - - 55 - "name" - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - game_object_name - game_object - 0x010 - -
- - 56 - "prev" - - 1 - 0 - 8 Bytes -
+0
- - 0x00 - - - - 57 - "name" - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - game_object_name - game_object - 0x010 - -
- - 58 - "prev" - - 1 - 0 - 8 Bytes -
+0
- - 0x00 - - - - 59 - "name" - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - game_object_name - game_object - 0x010 - -
- - 60 - "prev" - - 1 - 0 - 8 Bytes -
+0
- - 0x00 - - - - 61 - "name" - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - game_object_name - game_object - 0x010 - -
- - 62 - "prev" - - 1 - 0 - 8 Bytes -
+0
- - 0x00 - - - - 63 - "name" - 0 - String - 10 - 0 - 0 - 1 -
+0
- - 0 - game_object_name - game_object - 0x010 - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - 141 - "No description" - String - 13 - 0 - 0 - 1 -
113730BF4
-
- - 142 - "No description" - String - 13 - 0 - 0 - 1 -
113730BF4
-
- - 143 - "No description" - String - 13 - 0 - 0 - 1 -
10B9ECC18
-
- - 144 - "No description" - String - 13 - 0 - 0 - 1 -
113730C1C
-
- - 145 - "No description" - String - 13 - 0 - 0 - 1 -
12FF117F0
-
- - 146 - "No description" - 1 - 8 Bytes -
12FF11790
-
- - 147 - "No description" - String - 13 - 0 - 0 - 1 -
7F84880A682C
-
- - 148 - "No description" - String - 13 - 0 - 0 - 1 -
7F84880A6C04
-
- - 149 - "No description" - String - 13 - 0 - 0 - 1 -
7F84880A6C04
-
- - 150 - "No description" - String - 13 - 0 - 0 - 1 -
7F84880A6C04
-
- - 151 - "No description" - String - 13 - 0 - 0 - 1 -
7F84880A6C04
-
- - 152 - "No description" - String - 13 - 0 - 0 - 1 -
7F84880A6C5D
-
- - 153 - "No description" - String - 13 - 0 - 0 - 1 -
7F84880A6C04
-
- - 154 - "No description" - String - 13 - 0 - 0 - 1 -
7F84880A682C
-
-
- - Info about this table: - - -- Cheat Engine Lua --- Resolves the FINAL absolute address from an instruction matched by AOB. --- --- Example: --- sig = "48 8B 05 ?? ?? ?? ?? 48 85 C0" --- dispOff = 3 -- offset inside instruction where relative displacement starts --- instrLen = 7 -- total instruction length --- --- This reproduces what CE shows in disassembler for RIP-relative instructions. - -function sig(sig, dispOffset) - local scan = AOBScan(sig) - if not scan or scan.Count == 0 then - print("sig not found") - return nil - end - - local instr = tonumber(scan[0], 16) - scan.destroy() - - -- read signed rel32 - local rel = readInteger(instr + dispOffset) - - if rel >= 0x80000000 then - rel = rel - 0x100000000 - end - - -- rel32 is ALWAYS relative to NEXT instruction - local final = instr + dispOffset + 4 + rel - - return final -end - - -is_dev_build = false -go_dev = 0x10 -- size of EditorExtensions -co_dev = 0x8 - -pointer_size = 0x8 - -scene_manager = sig("41 54 53 50 4C 8B ?5 ???????? 41 83", 7, 7) - -loaded_scenes = 0x8 -scene_count = 0x18 -active_scene = 0x48 -dont_destroy_on_load_scene = 0x70 - -asset_path = 0x10 -build_index = 0x98 -root_storage_container = 0xF0 - -prev_node = 0x0 -next_node = 0x8 -current_node = 0x10 - -game_object = 0x20 + (is_dev_build and go_dev or 0) -game_object_name = 0x50 + (is_dev_build and go_dev or 0) -active_self = 0x46 + (is_dev_build and go_dev or 0) -active_in_hierarchy = 0x47 + (is_dev_build and go_dev or 0) -children = 0x48 + (is_dev_build and go_dev or 0) - -classes = game_object -class_mono = 0x18 + (is_dev_build and co_dev or 0) --- has extra dereference --- component.m_MonoReference->raw->vtable->class->name -class_name_mono = 0x40 -class_il2cpp = 0x18 -class_name_il2cpp = 0x10 - -
From 1cb0786c9d4d4039800bb7dfca95d3401735f4a9 Mon Sep 17 00:00:00 2001 From: Mitchell Merry Date: Sat, 27 Jun 2026 11:10:01 +1000 Subject: [PATCH 18/27] remove more --- src/game_engine/unity/mono/mod.rs | 2 -- src/game_engine/unity/scene_manager/mod.rs | 8 ++------ src/game_engine/unity/scene_manager/transform.rs | 4 +--- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/game_engine/unity/mono/mod.rs b/src/game_engine/unity/mono/mod.rs index 3495db3..e9e22d2 100644 --- a/src/game_engine/unity/mono/mod.rs +++ b/src/game_engine/unity/mono/mod.rs @@ -10,7 +10,6 @@ use crate::{ signature::Signature, Address, Address32, Address64, PointerSize, Process, }; -use alloc::format; use core::iter::{self, FusedIterator}; mod assembly; @@ -45,7 +44,6 @@ impl Module { /// [`attach`](Self::attach) instead. pub fn attach_auto_detect(process: &Process) -> Option { let version = Version::detect(process)?; - print_message(&format!("found versin {version:?}")); Self::attach(process, version) } diff --git a/src/game_engine/unity/scene_manager/mod.rs b/src/game_engine/unity/scene_manager/mod.rs index 8868911..f00d19a 100644 --- a/src/game_engine/unity/scene_manager/mod.rs +++ b/src/game_engine/unity/scene_manager/mod.rs @@ -8,13 +8,12 @@ use crate::{ file_format::{elf, macho, pe}, - future::retry, - print_message, + future::retry + , signature::Signature, string::ArrayCString, Address, Address32, Error, PointerSize, Process, }; -use alloc::format; mod offsets; @@ -91,11 +90,9 @@ impl SceneManager { let base_address: Address = match (pointer_size, format) { (PointerSize::Bit64, BinaryFormat::PE) => { if let Some(addr) = SIG_64_BIT_PE_1.scan_process_range(process, unity_player) { - print_message(&format!("dd1 {addr}")); addr + 0x4 + process.read::(addr + 7).ok()? } else if let Some(addr) = SIG_64_BIT_PE_2.scan_process_range(process, unity_player) { - print_message(&format!("dd2 {addr}")); addr + 0x4 + process.read::(addr + 7).ok()? + 7 } else { return None; @@ -124,7 +121,6 @@ impl SceneManager { return None; } }; - print_message(&format!("e {base_address}")); let offsets = Offsets::new(pointer_size)?; diff --git a/src/game_engine/unity/scene_manager/transform.rs b/src/game_engine/unity/scene_manager/transform.rs index 89c3b33..3d0e7e3 100644 --- a/src/game_engine/unity/scene_manager/transform.rs +++ b/src/game_engine/unity/scene_manager/transform.rs @@ -1,8 +1,7 @@ use super::{GameObject, SceneManager, CSTR}; use crate::{ - print_message, string::ArrayCString, Address, Address32, Address64, Error, PointerSize, Process, + string::ArrayCString, Address, Address32, Address64, Error, PointerSize, Process, }; -use alloc::format; use core::{array, mem::MaybeUninit}; /// A `Transform` is a base class for all entities used in a Unity scene. All @@ -70,7 +69,6 @@ impl Transform { if child_count == 0 || child_count > ARRAY_SIZE { return Err(Error {}); } - print_message(&format!("cc {child_count}")); let children: [Address; ARRAY_SIZE] = match scene_manager.pointer_size { PointerSize::Bit64 => { From 03646e077d95f30f75633153e6dad5964264e820 Mon Sep 17 00:00:00 2001 From: Mitchell Merry Date: Sat, 27 Jun 2026 11:12:50 +1000 Subject: [PATCH 19/27] more cleaning + formatting --- src/game_engine/unity/mono/mod.rs | 8 - src/game_engine/unity/mono/offsets.rs | 232 +++++++++--------- .../unity/scene_manager/game_object.rs | 2 +- src/game_engine/unity/scene_manager/mod.rs | 3 +- .../unity/scene_manager/transform.rs | 4 +- 5 files changed, 123 insertions(+), 126 deletions(-) diff --git a/src/game_engine/unity/mono/mod.rs b/src/game_engine/unity/mono/mod.rs index e9e22d2..3aeff44 100644 --- a/src/game_engine/unity/mono/mod.rs +++ b/src/game_engine/unity/mono/mod.rs @@ -6,7 +6,6 @@ use crate::file_format::macho; use crate::{ file_format::{elf, pe}, future::retry, - print_message, signature::Signature, Address, Address32, Address64, PointerSize, Process, }; @@ -101,13 +100,6 @@ impl Module { BinaryFormat::MachO => { macho::symbols(process, module_range) .find(|symbol| { - let name = symbol.get_name::<26>(process); - - if let Ok(name) = name { - if let Ok(name) = name.validate_utf8() { - print_message(name); - } - } symbol .get_name::<26>(process) .is_ok_and(|name| name.matches("_mono_assembly_foreach")) diff --git a/src/game_engine/unity/mono/offsets.rs b/src/game_engine/unity/mono/offsets.rs index dbfb5e5..2a77ae4 100644 --- a/src/game_engine/unity/mono/offsets.rs +++ b/src/game_engine/unity/mono/offsets.rs @@ -241,118 +241,126 @@ impl MonoOffsets { }, v_table: MonoVTableOffsets { vtable: 0x28 }, }), - (BinaryFormat::ELF | BinaryFormat::MachO, Version::V3, PointerSize::Bit64) => Some(&Self { - assembly: AssemblyOffsets { - aname: 0x10, - image: 0x60, - }, - image: ImageOffsets { class_cache: 0x4D0 }, - hash_table: HashTableOffsets { - size: 0x18, - table: 0x20, - }, - class: ClassOffsets { - parent: 0x28, - image: 0x38, - name: 0x40, - namespace: 0x48, - vtable_size: 0x54, - fields: 0x90, - runtime_info: 0xC8, - field_count: 0xF8, - next_class_cache: 0x100, - }, - field: FieldInfoOffsets { - name: 0x8, - offset: 0x18, - alignment: 0x20, - }, - v_table: MonoVTableOffsets { vtable: 0x48 }, - }), - (BinaryFormat::ELF | BinaryFormat::MachO, Version::V2, PointerSize::Bit64) => Some(&Self { - assembly: AssemblyOffsets { - aname: 0x10, - image: 0x60, - }, - image: ImageOffsets { class_cache: 0x4C0 }, - hash_table: HashTableOffsets { - size: 0x18, - table: 0x20, - }, - class: ClassOffsets { - parent: 0x28, - image: 0x38, - name: 0x40, - namespace: 0x48, - vtable_size: 0x54, - fields: 0x90, - runtime_info: 0xC8, - field_count: 0xF8, - next_class_cache: 0x100, - }, - field: FieldInfoOffsets { - name: 0x8, - offset: 0x18, - alignment: 0x20, - }, - v_table: MonoVTableOffsets { vtable: 0x40 }, - }), - (BinaryFormat::ELF | BinaryFormat::MachO, Version::V1Cattrs, PointerSize::Bit64) => Some(&Self { - assembly: AssemblyOffsets { - aname: 0x10, - image: 0x58, - }, - image: ImageOffsets { class_cache: 0x3D0 }, - hash_table: HashTableOffsets { - size: 0x18, - table: 0x20, - }, - class: ClassOffsets { - parent: 0x28, - image: 0x40, - name: 0x48, - namespace: 0x50, - vtable_size: 0x18, - fields: 0xA8, - runtime_info: 0xF8, - field_count: 0x94, - next_class_cache: 0x100, - }, - field: FieldInfoOffsets { - name: 0x8, - offset: 0x18, - alignment: 0x20, - }, - v_table: MonoVTableOffsets { vtable: 0x48 }, - }), - (BinaryFormat::ELF | BinaryFormat::MachO, Version::V1, PointerSize::Bit64) => Some(&Self { - assembly: AssemblyOffsets { - aname: 0x10, - image: 0x58, - }, - image: ImageOffsets { class_cache: 0x3D0 }, - hash_table: HashTableOffsets { - size: 0x18, - table: 0x20, - }, - class: ClassOffsets { - parent: 0x28, - image: 0x38, - name: 0x40, - namespace: 0x48, - vtable_size: 0x18, - fields: 0xA0, - runtime_info: 0xF0, - field_count: 0x8C, - next_class_cache: 0xF8, - }, - field: FieldInfoOffsets { - name: 0x8, - offset: 0x18, - alignment: 0x20, - }, - v_table: MonoVTableOffsets { vtable: 0x48 }, - }), + (BinaryFormat::ELF | BinaryFormat::MachO, Version::V3, PointerSize::Bit64) => { + Some(&Self { + assembly: AssemblyOffsets { + aname: 0x10, + image: 0x60, + }, + image: ImageOffsets { class_cache: 0x4D0 }, + hash_table: HashTableOffsets { + size: 0x18, + table: 0x20, + }, + class: ClassOffsets { + parent: 0x28, + image: 0x38, + name: 0x40, + namespace: 0x48, + vtable_size: 0x54, + fields: 0x90, + runtime_info: 0xC8, + field_count: 0xF8, + next_class_cache: 0x100, + }, + field: FieldInfoOffsets { + name: 0x8, + offset: 0x18, + alignment: 0x20, + }, + v_table: MonoVTableOffsets { vtable: 0x48 }, + }) + } + (BinaryFormat::ELF | BinaryFormat::MachO, Version::V2, PointerSize::Bit64) => { + Some(&Self { + assembly: AssemblyOffsets { + aname: 0x10, + image: 0x60, + }, + image: ImageOffsets { class_cache: 0x4C0 }, + hash_table: HashTableOffsets { + size: 0x18, + table: 0x20, + }, + class: ClassOffsets { + parent: 0x28, + image: 0x38, + name: 0x40, + namespace: 0x48, + vtable_size: 0x54, + fields: 0x90, + runtime_info: 0xC8, + field_count: 0xF8, + next_class_cache: 0x100, + }, + field: FieldInfoOffsets { + name: 0x8, + offset: 0x18, + alignment: 0x20, + }, + v_table: MonoVTableOffsets { vtable: 0x40 }, + }) + } + (BinaryFormat::ELF | BinaryFormat::MachO, Version::V1Cattrs, PointerSize::Bit64) => { + Some(&Self { + assembly: AssemblyOffsets { + aname: 0x10, + image: 0x58, + }, + image: ImageOffsets { class_cache: 0x3D0 }, + hash_table: HashTableOffsets { + size: 0x18, + table: 0x20, + }, + class: ClassOffsets { + parent: 0x28, + image: 0x40, + name: 0x48, + namespace: 0x50, + vtable_size: 0x18, + fields: 0xA8, + runtime_info: 0xF8, + field_count: 0x94, + next_class_cache: 0x100, + }, + field: FieldInfoOffsets { + name: 0x8, + offset: 0x18, + alignment: 0x20, + }, + v_table: MonoVTableOffsets { vtable: 0x48 }, + }) + } + (BinaryFormat::ELF | BinaryFormat::MachO, Version::V1, PointerSize::Bit64) => { + Some(&Self { + assembly: AssemblyOffsets { + aname: 0x10, + image: 0x58, + }, + image: ImageOffsets { class_cache: 0x3D0 }, + hash_table: HashTableOffsets { + size: 0x18, + table: 0x20, + }, + class: ClassOffsets { + parent: 0x28, + image: 0x38, + name: 0x40, + namespace: 0x48, + vtable_size: 0x18, + fields: 0xA0, + runtime_info: 0xF0, + field_count: 0x8C, + next_class_cache: 0xF8, + }, + field: FieldInfoOffsets { + name: 0x8, + offset: 0x18, + alignment: 0x20, + }, + v_table: MonoVTableOffsets { vtable: 0x48 }, + }) + } _ => None, } } diff --git a/src/game_engine/unity/scene_manager/game_object.rs b/src/game_engine/unity/scene_manager/game_object.rs index 6c232c7..0b2cdf3 100644 --- a/src/game_engine/unity/scene_manager/game_object.rs +++ b/src/game_engine/unity/scene_manager/game_object.rs @@ -84,7 +84,7 @@ impl GameObject { }; // 0 is always Transform - // TODO how to read these and other default types? + // FIXME: It would be good to be able to read those components here too Ok((1..number_of_components).map(move |m| Component { address: components[m], })) diff --git a/src/game_engine/unity/scene_manager/mod.rs b/src/game_engine/unity/scene_manager/mod.rs index f00d19a..15ecea4 100644 --- a/src/game_engine/unity/scene_manager/mod.rs +++ b/src/game_engine/unity/scene_manager/mod.rs @@ -8,8 +8,7 @@ use crate::{ file_format::{elf, macho, pe}, - future::retry - , + future::retry, signature::Signature, string::ArrayCString, Address, Address32, Error, PointerSize, Process, diff --git a/src/game_engine/unity/scene_manager/transform.rs b/src/game_engine/unity/scene_manager/transform.rs index 3d0e7e3..4059d42 100644 --- a/src/game_engine/unity/scene_manager/transform.rs +++ b/src/game_engine/unity/scene_manager/transform.rs @@ -1,7 +1,5 @@ use super::{GameObject, SceneManager, CSTR}; -use crate::{ - string::ArrayCString, Address, Address32, Address64, Error, PointerSize, Process, -}; +use crate::{string::ArrayCString, Address, Address32, Address64, Error, PointerSize, Process}; use core::{array, mem::MaybeUninit}; /// A `Transform` is a base class for all entities used in a Unity scene. All From b54ef8ef80b3c9eb1a5f9c0807c0063b1b69454e Mon Sep 17 00:00:00 2001 From: Mitchell Merry Date: Sat, 27 Jun 2026 11:22:05 +1000 Subject: [PATCH 20/27] Allow manually overriding the scene manager offsets --- src/game_engine/unity/scene_manager/mod.rs | 15 ++++++++++- .../unity/scene_manager/offsets.rs | 26 +++++++++---------- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/game_engine/unity/scene_manager/mod.rs b/src/game_engine/unity/scene_manager/mod.rs index 15ecea4..b070866 100644 --- a/src/game_engine/unity/scene_manager/mod.rs +++ b/src/game_engine/unity/scene_manager/mod.rs @@ -47,6 +47,19 @@ pub struct SceneManager { impl SceneManager { /// Attaches to the scene manager in the given process. pub fn attach(process: &Process) -> Option { + SceneManager::attach_with_offsets(process, None) + } + + /// Attaches to the scene manager in the given process with known offsets. + /// Use this function if the default offsets are wrong. + /// + /// FIXME: This function is a bandaid while we don't have support for all + /// scene manager versions in asr. We should remove this by supporting + /// those versions without override. + pub fn attach_with_offsets( + process: &Process, + offsets: Option<&'static Offsets>, + ) -> Option { const SIG_64_BIT_PE_1: Signature<13> = Signature::new("48 83 EC 20 4C 8B ?5 ?? ?? ?? ?? 33 F6"); const SIG_64_BIT_PE_2: Signature<13> = @@ -121,7 +134,7 @@ impl SceneManager { } }; - let offsets = Offsets::new(pointer_size)?; + let offsets = offsets.or_else(|| Offsets::new(pointer_size))?; // Dereferencing one level because this pointer never changes as long as the game is open. // It might not seem a lot, but it helps make things a bit faster when querying for scene stuff. diff --git a/src/game_engine/unity/scene_manager/offsets.rs b/src/game_engine/unity/scene_manager/offsets.rs index b202dc8..1b37242 100644 --- a/src/game_engine/unity/scene_manager/offsets.rs +++ b/src/game_engine/unity/scene_manager/offsets.rs @@ -1,20 +1,20 @@ use crate::PointerSize; -pub(super) struct Offsets { - pub(super) scene_count: u8, - pub(super) active_scene: u8, - pub(super) dont_destroy_on_load_scene: u8, - pub(super) asset_path: u8, - pub(super) build_index: u8, - 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 struct Offsets { + pub scene_count: u8, + pub active_scene: u8, + pub dont_destroy_on_load_scene: u8, + pub asset_path: u8, + pub build_index: u8, + pub root_storage_container: u8, + pub game_object: u8, + pub game_object_name: u8, + pub game_object_activeself: u8, + pub game_object_activeinhierarchy: u8, /// a handle to the scripting object /// MonoObjectHandle for Mono for e.g. - pub(super) scripting_object_handle: u8, - pub(super) children_pointer: u8, + pub scripting_object_handle: u8, + pub children_pointer: u8, } impl Offsets { From a24f19ed4e4f7fe6775b1bc0e20af4457f535254 Mon Sep 17 00:00:00 2001 From: Mitchell Merry Date: Sat, 27 Jun 2026 11:36:06 +1000 Subject: [PATCH 21/27] Add another fixme --- src/game_engine/unity/scene_manager/offsets.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/game_engine/unity/scene_manager/offsets.rs b/src/game_engine/unity/scene_manager/offsets.rs index 1b37242..920f13c 100644 --- a/src/game_engine/unity/scene_manager/offsets.rs +++ b/src/game_engine/unity/scene_manager/offsets.rs @@ -18,6 +18,7 @@ pub struct Offsets { } impl Offsets { + // FIXME: This needs to support differentiation on unity version / binary format pub(super) const fn new(pointer_size: PointerSize) -> Option<&'static Self> { match pointer_size { PointerSize::Bit64 => Some(&Self { From 63776b0ab612658587806d16e68568bac0b38518 Mon Sep 17 00:00:00 2001 From: Mitchell Merry Date: Sat, 27 Jun 2026 11:46:33 +1000 Subject: [PATCH 22/27] Revert changes to offsets, leave note --- .../unity/scene_manager/offsets.rs | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/game_engine/unity/scene_manager/offsets.rs b/src/game_engine/unity/scene_manager/offsets.rs index 920f13c..a6c4f37 100644 --- a/src/game_engine/unity/scene_manager/offsets.rs +++ b/src/game_engine/unity/scene_manager/offsets.rs @@ -27,13 +27,16 @@ impl Offsets { dont_destroy_on_load_scene: 0x70, asset_path: 0x10, build_index: 0x98, - root_storage_container: 0xF0, - game_object: 0x20, - game_object_name: 0x50, - game_object_activeself: 0x46, - game_object_activeinhierarchy: 0x47, + root_storage_container: 0xB0, + game_object: 0x30, + game_object_name: 0x60, + // NOTE: I'm not sure what version of unity the above offsets were + // gotten with, so I can't get the appropriate offsets for the active + // fields + game_object_activeself: 0x0, + game_object_activeinhierarchy: 0x1, scripting_object_handle: 0x18, - children_pointer: 0x48, + children_pointer: 0x70, }), PointerSize::Bit32 => Some(&Self { scene_count: 0x10, @@ -44,8 +47,11 @@ impl Offsets { root_storage_container: 0x88, game_object: 0x1C, game_object_name: 0x3C, - game_object_activeself: 0x32, - game_object_activeinhierarchy: 0x33, + // NOTE: I'm not sure what version of unity the above offsets were + // gotten with, so I can't get the appropriate offsets for the active + // fields + game_object_activeself: 0x0, + game_object_activeinhierarchy: 0x1, scripting_object_handle: 0x18, children_pointer: 0x50, }), From 972bb5e6a77dd995a7751361622bb005d052f1c5 Mon Sep 17 00:00:00 2001 From: Mitchell Merry Date: Sat, 27 Jun 2026 17:55:30 +1000 Subject: [PATCH 23/27] Add get_component_mono_object to streamline --- src/game_engine/unity/mono/class.rs | 1 + .../unity/scene_manager/game_object.rs | 27 +++++++++++++++++++ src/game_engine/unity/scene_manager/mod.rs | 3 +-- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/game_engine/unity/mono/class.rs b/src/game_engine/unity/mono/class.rs index 6e186aa..6cab85f 100644 --- a/src/game_engine/unity/mono/class.rs +++ b/src/game_engine/unity/mono/class.rs @@ -221,6 +221,7 @@ impl Class { * * See https://github.com/mono/mono/blob/0f53e9e151d92944cacab3e24ac359410c606df6/mono/metadata/object.h#L31. */ +#[derive(Debug, Clone)] pub struct Object { pub address: Address, } diff --git a/src/game_engine/unity/scene_manager/game_object.rs b/src/game_engine/unity/scene_manager/game_object.rs index 0b2cdf3..e93d503 100644 --- a/src/game_engine/unity/scene_manager/game_object.rs +++ b/src/game_engine/unity/scene_manager/game_object.rs @@ -116,6 +116,33 @@ impl GameObject { .ok_or(Error {}) } + /// Get the Mono object of a Component attached to the current `GameObject` + /// by name of it's class. + /// + /// For Mono. + pub fn get_component_mono_object( + &self, + process: &Process, + scene_manager: &SceneManager, + module: &mono::Module, + name: &str, + ) -> Result { + if scene_manager.is_il2cpp { + return Err(Error {}); + } + + self.components(process, scene_manager)? + .filter_map(|component| component.get_mono_object(process, scene_manager).ok()) + .find(|object| { + let val = object + .get_class(process, module) + .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. diff --git a/src/game_engine/unity/scene_manager/mod.rs b/src/game_engine/unity/scene_manager/mod.rs index b070866..b8c7440 100644 --- a/src/game_engine/unity/scene_manager/mod.rs +++ b/src/game_engine/unity/scene_manager/mod.rs @@ -15,12 +15,11 @@ use crate::{ }; mod offsets; +pub use offsets::Offsets; mod transform; pub use transform::Transform; -use offsets::Offsets; - mod game_object; pub use game_object::GameObject; From 1a29b0029d721a1d1939806f42452590092caff0 Mon Sep 17 00:00:00 2001 From: Mitchell Merry Date: Sat, 27 Jun 2026 22:31:41 +1000 Subject: [PATCH 24/27] Support older Unity versions by splitting on Mono version when reading the mono object and add getting the scene manager when it's not in the UnityPlayer module --- src/game_engine/unity/mod.rs | 28 +++ src/game_engine/unity/mono/mod.rs | 4 +- .../unity/scene_manager/component.rs | 59 ++++--- .../unity/scene_manager/game_object.rs | 8 +- src/game_engine/unity/scene_manager/mod.rs | 165 ++++++++++++------ 5 files changed, 185 insertions(+), 79 deletions(-) diff --git a/src/game_engine/unity/mod.rs b/src/game_engine/unity/mod.rs index baa3bfa..ab48c48 100644 --- a/src/game_engine/unity/mod.rs +++ b/src/game_engine/unity/mod.rs @@ -83,6 +83,9 @@ // https://github.com/Unity-Technologies/mono // https://github.com/CryZe/lunistice-auto-splitter/blob/b8c01031991783f7b41044099ee69edd54514dba/asr-dotnet/src/lib.rs +use crate::file_format::{elf, macho, pe}; +use crate::{Error, PointerSize, Process}; + pub mod il2cpp; pub mod mono; pub mod scene_manager; @@ -91,9 +94,15 @@ const CSTR: usize = 128; #[derive(Copy, Clone, PartialEq, Hash, Debug)] #[non_exhaustive] +/// The file format of a given executable / binary - different per operating system. +/// See https://en.wikipedia.org/wiki/Comparison_of_executable_file_formats enum BinaryFormat { + /// [Portable Executable](https://en.wikipedia.org/wiki/Portable_Executable), for Windows. PE, + /// [Executable and Linkable Format](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format), + /// for Linux. ELF, + /// [Mach-O](https://en.wikipedia.org/wiki/Mach-O) (Mach object), for Mac. MachO, } @@ -103,3 +112,22 @@ fn get_backing_name(name: &str) -> Option<&str> { let end = name[start + 1..].find('>')?; Some(&name[start + 1..start + 1 + end]) } + +impl Process { + /// Get the [BinaryFormat](BinaryFormat) and pointer size of the main module of the Process + // FIXME: BinaryFormat and this function should probably be widely available (rather + // than gated by Unity) + fn get_format_and_pointer_size(&self) -> Result<(BinaryFormat, PointerSize), Error> { + let main_module = self.get_main_module_range()?; + + if let Some(x) = pe::MachineType::read(self, main_module.0) { + Ok((BinaryFormat::PE, x.pointer_size().ok_or(Error {})?)) + } else if let Some(pointer_size) = elf::pointer_size(self, main_module.0) { + Ok((BinaryFormat::ELF, pointer_size)) + } else if let Some(pointer_size) = macho::pointer_size(self, main_module) { + Ok((BinaryFormat::MachO, pointer_size)) + } else { + Err(Error {}) + } + } +} diff --git a/src/game_engine/unity/mono/mod.rs b/src/game_engine/unity/mono/mod.rs index 3aeff44..3308e39 100644 --- a/src/game_engine/unity/mono/mod.rs +++ b/src/game_engine/unity/mono/mod.rs @@ -193,12 +193,12 @@ impl Module { } /// Retrieve the [Mono version](Version) of the module. - pub fn get_version(&self) -> Version { + pub const fn get_version(&self) -> Version { self.version } /// Retrieve the [pointer size](PointerSize) of the process/module. - pub fn get_pointer_size(&self) -> PointerSize { + pub const fn get_pointer_size(&self) -> PointerSize { self.pointer_size } diff --git a/src/game_engine/unity/scene_manager/component.rs b/src/game_engine/unity/scene_manager/component.rs index dd91e7d..359a25e 100644 --- a/src/game_engine/unity/scene_manager/component.rs +++ b/src/game_engine/unity/scene_manager/component.rs @@ -15,26 +15,45 @@ impl Component { &self, process: &Process, scene_manager: &SceneManager, + module: &mono::Module, ) -> Result { - // TODO is this only true on 6000? - // other versions, according to how ASR currently is, do not dereference twice here - // NOTE: I'm like 80% sure the following is true - process - // class ScriptingGCHandle m_MonoReference - // See https://gist.github.com/just-ero/92457b51baf85bd1e5b8c87de8c9835e#file-object-hpp-L18 - // In Mono this is a MonoObjectHandle, which is a MonoObject** - // MonoObject **__raw - // See https://github.com/mono/mono/blob/0f53e9e151d92944cacab3e24ac359410c606df6/mono/metadata/handle-decl.h#L72 - .read_pointer( - self.address + scene_manager.offsets.scripting_object_handle, - scene_manager.pointer_size, - ) - .ok() - .filter(|val| !val.is_null()) - // MonoObject *__raw - .and_then(|addr| process.read_pointer(addr, scene_manager.pointer_size).ok()) - .filter(|val| !val.is_null()) - .map(|address| mono::Object { address }) - .ok_or(Error {}) + // FIXME: I'm using mono version as a proxy, this has not been well tested and may require changes + match module.get_version() { + mono::Version::V1 | mono::Version::V1Cattrs => { + // FIXME: Understand this better in the mono source code + // It's probably similar to the V3 logic + // e.g. not sure if "scripting object handle" is correct + process + .read_pointer( + self.address + scene_manager.offsets.scripting_object_handle, + scene_manager.pointer_size, + ) + .ok() + .filter(|val| !val.is_null()) + .map(|address| mono::Object { address }) + .ok_or(Error {}) + } + // FIXME: Untested on V2 + mono::Version::V2 | mono::Version::V3 => { + // NOTE: I'm like 80% sure the following is true + process + // class ScriptingGCHandle m_MonoReference + // See https://gist.github.com/just-ero/92457b51baf85bd1e5b8c87de8c9835e#file-object-hpp-L18 + // In Mono this is a MonoObjectHandle, which is a MonoObject** + // MonoObject **__raw + // See https://github.com/mono/mono/blob/0f53e9e151d92944cacab3e24ac359410c606df6/mono/metadata/handle-decl.h#L72 + .read_pointer( + self.address + scene_manager.offsets.scripting_object_handle, + scene_manager.pointer_size, + ) + .ok() + .filter(|val| !val.is_null()) + // MonoObject *__raw + .and_then(|addr| process.read_pointer(addr, scene_manager.pointer_size).ok()) + .filter(|val| !val.is_null()) + .map(|address| mono::Object { address }) + .ok_or(Error {}) + } + } } } diff --git a/src/game_engine/unity/scene_manager/game_object.rs b/src/game_engine/unity/scene_manager/game_object.rs index e93d503..4a4e491 100644 --- a/src/game_engine/unity/scene_manager/game_object.rs +++ b/src/game_engine/unity/scene_manager/game_object.rs @@ -107,7 +107,7 @@ impl GameObject { self.components(process, scene_manager)? .find(|component| { let val = component - .get_mono_object(process, scene_manager) + .get_mono_object(process, scene_manager, module) .and_then(|object| object.get_class(process, module)) .and_then(|c| c.get_name::(process, module)); @@ -132,7 +132,11 @@ impl GameObject { } self.components(process, scene_manager)? - .filter_map(|component| component.get_mono_object(process, scene_manager).ok()) + .filter_map(|component| { + component + .get_mono_object(process, scene_manager, module) + .ok() + }) .find(|object| { let val = object .get_class(process, module) diff --git a/src/game_engine/unity/scene_manager/mod.rs b/src/game_engine/unity/scene_manager/mod.rs index b8c7440..5049df3 100644 --- a/src/game_engine/unity/scene_manager/mod.rs +++ b/src/game_engine/unity/scene_manager/mod.rs @@ -31,6 +31,25 @@ pub use scene::Scene; use super::{BinaryFormat, CSTR}; +fn try_attach_unity_player(process: &Process) -> Option<((Address, u64), BinaryFormat)> { + [ + ("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)), + }) +} + /// The scene manager allows you to easily identify the current scene loaded in /// the attached Unity game. /// @@ -71,69 +90,105 @@ 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, 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)), - })?; + const SIG_32_MAIN_1: Signature<17> = + Signature::new("55 8B EC E8 ?? ?? ?? ?? 8B C8 E8 ?? ?? ?? ?? 85 C0"); + const SIG_64_BIT_MACHO_MAIN_1: Signature<18> = + Signature::new("48 89 FB E8 ?? ?? ?? ?? 48 89 C7 E8 ?? ?? ?? ?? 31 C9"); - 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(); + let (pointer_size, base_address) = if let Some((unity_player, format)) = + try_attach_unity_player(process) + { + 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)?, + }; - // There are multiple signatures that can be used, depending on the version of Unity - // used in the target game. - let base_address: Address = match (pointer_size, format) { - (PointerSize::Bit64, BinaryFormat::PE) => { - if let Some(addr) = SIG_64_BIT_PE_1.scan_process_range(process, unity_player) { - addr + 0x4 + process.read::(addr + 7).ok()? - } else if let Some(addr) = SIG_64_BIT_PE_2.scan_process_range(process, unity_player) - { - addr + 0x4 + process.read::(addr + 7).ok()? + 7 - } else { - return None; + // There are multiple signatures that can be used, depending on the version of Unity + // used in the target game. + let base_address: Address = match (pointer_size, format) { + (PointerSize::Bit64, BinaryFormat::PE) => { + if let Some(addr) = SIG_64_BIT_PE_1.scan_process_range(process, unity_player) { + addr + 0x4 + process.read::(addr + 7).ok()? + } else if let Some(addr) = + SIG_64_BIT_PE_2.scan_process_range(process, unity_player) + { + addr + 0x4 + process.read::(addr + 7).ok()? + 7 + } else { + return None; + } } - } - (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 { + (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; } - } - _ => { - return None; - } + }; + + (pointer_size, base_address) + } else { + // If it's not in UnityPlayer, then it's in the main module (old unity versions) + let main_module_range = process.get_main_module_range().ok()?; + let (format, pointer_size) = process.get_format_and_pointer_size().ok()?; + + // These signatures are for the GetActiveScene function, which calls GetSceneManager, + // which is the thing that actually references the scene manager address + + let base_address: Address = match (pointer_size, format) { + (PointerSize::Bit32, BinaryFormat::PE) => { + // pointer to GetSceneManager + let gsm_ptr = + SIG_32_MAIN_1.scan_process_range(process, main_module_range)? + 0x4; + + // pointer to g_RuntimeSceneManager + // mov [address] + let rsm_ptr = gsm_ptr + process.read::(gsm_ptr).ok()? + 0x5; + + Address::from(process.read::(rsm_ptr).ok()?) + } + (PointerSize::Bit64, BinaryFormat::MachO) => { + // pointing to the rip-relative address of GetSceneManager + let addr = SIG_64_BIT_MACHO_MAIN_1 + .scan_process_range(process, main_module_range)? + + 0x4; + // pointing to the beginning of GetSceneManager + let gsm_addr = addr + process.read::(addr).ok()? + 0x4; + + // Cuphead.GetSceneManager() - 55 - push rbp + // Cuphead.GetSceneManager()+1 - 48 89 E5 - mov rbp,rsp + // Cuphead.GetSceneManager()+4 - 48 8B 05 4D821101 - mov rax,[Cuphead.g_RuntimeSceneManager] + // pointing to the rip-relative address of g_RuntimeSceneManager + let addr = gsm_addr + 0x7; + addr + process.read::(addr).ok()? + 0x4 + } + // FIXME: support missing cases + _ => return None, + }; + + (pointer_size, base_address) }; let offsets = offsets.or_else(|| Offsets::new(pointer_size))?; + let is_il2cpp = process.get_module_address("GameAssembly.dll").is_ok(); // Dereferencing one level because this pointer never changes as long as the game is open. // It might not seem a lot, but it helps make things a bit faster when querying for scene stuff. From 6657b258e6bb57329966ebaccd9a69fdd691f644 Mon Sep 17 00:00:00 2001 From: Mitchell Merry Date: Sat, 27 Jun 2026 22:37:15 +1000 Subject: [PATCH 25/27] Couple doc improvements --- src/game_engine/unity/mono/class.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/game_engine/unity/mono/class.rs b/src/game_engine/unity/mono/class.rs index 6cab85f..8f62af7 100644 --- a/src/game_engine/unity/mono/class.rs +++ b/src/game_engine/unity/mono/class.rs @@ -13,7 +13,7 @@ pub struct Class { } impl Class { - pub fn get_name( + fn get_name( &self, process: &Process, module: &Module, @@ -223,11 +223,12 @@ impl Class { */ #[derive(Debug, Clone)] pub struct Object { + /// The address of the Mono object. pub address: Address, } impl Object { - /** Get the class of this object */ + /** Get the Mono [class](Class) of this object. */ pub fn get_class(&self, process: &Process, module: &Module) -> Result { process // MonoVTable *vtable From f3233168db060fbb0679c38689cd8085f633bcfb Mon Sep 17 00:00:00 2001 From: Mitchell Merry Date: Sun, 28 Jun 2026 19:58:08 +1000 Subject: [PATCH 26/27] Some more documentation --- src/game_engine/unity/mono/class.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/game_engine/unity/mono/class.rs b/src/game_engine/unity/mono/class.rs index 8f62af7..e63d991 100644 --- a/src/game_engine/unity/mono/class.rs +++ b/src/game_engine/unity/mono/class.rs @@ -9,11 +9,13 @@ pub use asr_derive::MonoClass as Class; /// A .NET class that is part of an [`Image`](Image). #[derive(Copy, Clone, Debug)] pub struct Class { + /// The address of the class pub class: Address, } impl Class { - fn get_name( + /// Get the name of the class + pub fn get_name( &self, process: &Process, module: &Module, From 7f2f828e06e60b77e3f098ace43e3b3deb247e70 Mon Sep 17 00:00:00 2001 From: Mitchell Merry Date: Thu, 2 Jul 2026 20:22:56 +1000 Subject: [PATCH 27/27] Fix the PE UnityPlayer.dll sig code --- src/game_engine/unity/scene_manager/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game_engine/unity/scene_manager/mod.rs b/src/game_engine/unity/scene_manager/mod.rs index 5049df3..cf2dcfb 100644 --- a/src/game_engine/unity/scene_manager/mod.rs +++ b/src/game_engine/unity/scene_manager/mod.rs @@ -111,7 +111,7 @@ impl SceneManager { let base_address: Address = match (pointer_size, format) { (PointerSize::Bit64, BinaryFormat::PE) => { if let Some(addr) = SIG_64_BIT_PE_1.scan_process_range(process, unity_player) { - addr + 0x4 + process.read::(addr + 7).ok()? + addr + 0x4 + process.read::(addr + 7).ok()? + 7 } else if let Some(addr) = SIG_64_BIT_PE_2.scan_process_range(process, unity_player) {