From fe8d78b4ce8f95ad7502cab86e40873266924b00 Mon Sep 17 00:00:00 2001 From: Jujstme Date: Sat, 5 Aug 2023 12:51:30 +0200 Subject: [PATCH 1/3] Added the ability to look for clssses inside the linked list of Transforms inside each unity scene --- src/game_engine/unity/scene.rs | 261 +++++++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+) diff --git a/src/game_engine/unity/scene.rs b/src/game_engine/unity/scene.rs index c1a7e8f..7f9856c 100644 --- a/src/game_engine/unity/scene.rs +++ b/src/game_engine/unity/scene.rs @@ -2,6 +2,7 @@ // References: // https://gist.githubusercontent.com/just-ero/92457b51baf85bd1e5b8c87de8c9835e/raw/8aa3e6b8da01fd03ff2ff0c03cbd018e522ef988/UnityScene.hpp +// Offsets and logic for the GameObject functions taken from https://github.com/Micrologist/UnityInstanceDumper use crate::{ file_format::pe, future::retry, signature::Signature, string::ArrayCString, Address, Address32, @@ -15,8 +16,10 @@ use crate::{ /// the traditional class lookup in games with no useful static references. pub struct SceneManager { is_64_bit: bool, + is_il2cpp: bool, address: Address, offsets: &'static Offsets, + pointer_size: u8, } impl SceneManager { @@ -30,6 +33,7 @@ impl SceneManager { let unity_player = process.get_module_range("UnityPlayer.dll").ok()?; let is_64_bit = pe::MachineType::read(process, unity_player.0)? == pe::MachineType::X86_64; + let is_il2cpp = process.get_module_address("GameAssembly.dll").is_ok(); let address = if is_64_bit { let addr = SIG_64_BIT.scan_process_range(process, unity_player)? + 7; @@ -45,11 +49,14 @@ impl SceneManager { }; let offsets = Offsets::new(is_64_bit); + let pointer_size = if is_64_bit { 0x8 } else { 0x4 }; Some(Self { is_64_bit, + is_il2cpp, address, offsets, + pointer_size, }) } @@ -121,6 +128,236 @@ impl SceneManager { }) .filter(move |p| !fptr.is_null() && p.is_valid(process)) } + + /// Iterates over all root `Transform`s / `GameObject`s declared for the current scene. + /// + /// Each Unity scene normally has a linked list of `Transform`s (each one is a `GameObject`). + /// Each one can, recursively, have a child `Transform` (and so on), and has a list of `Component`s, which are + /// classes (eg. `MonoBehaviour`) containing data we might want to retreieve for the autosplitter logic. + fn root_game_objects<'a>( + &'a self, + process: &'a Process, + ) -> Result + 'a, Error> { + let current_scene_address = self.get_current_scene_address(process)?; + let first_game_object = self.read_pointer( + process, + current_scene_address + self.offsets.root_storage_container, + )?; + + let number_of_root_game_objects = { + let mut index: usize = 0; + let mut temp_tr = first_game_object; + + while temp_tr != current_scene_address + self.offsets.root_storage_container { + index += 1; + temp_tr = self.read_pointer(process, temp_tr)?; + } + + index + }; + + let mut current_game_object = first_game_object; + + Ok((0..number_of_root_game_objects).filter_map(move |n| { + let buf: [Address; 3] = match self.is_64_bit { + true => process + .read::<[Address64; 3]>(current_game_object) + .ok()? + .map(|a| a.into()), + false => process + .read::<[Address32; 3]>(current_game_object) + .ok()? + .map(|a| a.into()), + }; + + let game_object = self + .read_pointer(process, buf[2] + self.offsets.game_object) + .ok()?; + + // Load the next game object before looping, except at the last iteration of the loop + if n + 1 != number_of_root_game_objects { + current_game_object = buf[0]; + } + + Some(GameObject { + address: game_object, + }) + })) + } + + /// Tries to find the specified root `GameObject` in the current Unity scene. + pub fn get_root_game_object(&self, process: &Process, name: &str) -> Result { + self.root_game_objects(process)? + .find(|obj| { + obj.get_name::<128>(process, self) + .unwrap_or_default() + .as_bytes() + == name.as_bytes() + }) + .ok_or(Error {}) + } +} + +/// A `GameObject` is a base class for all entities used in a Unity scene. +/// All classes of interest useful for an autosplitter can be found starting from the addresses of the root `GameObject`s linked in each scene. +pub struct GameObject { + address: Address, +} + +impl GameObject { + /// Tries to return the name of the current `GameObject` + pub fn get_name( + &self, + process: &Process, + scene_manager: &SceneManager, + ) -> Result, Error> { + let name_ptr = scene_manager.read_pointer( + process, + self.address + scene_manager.offsets.game_object_name, + )?; + process.read(name_ptr) + } + + /// Iterates over the classes referred to in the current `GameObject` + pub fn classes<'a>( + &'a self, + process: &'a Process, + scene_manager: &'a SceneManager, + ) -> Result + 'a, Error> { + let number_of_components = process + .read::(self.address + scene_manager.offsets.number_of_object_components)? + as usize; + + if number_of_components == 0 { + return Err(Error {}); + } + + let main_object = scene_manager + .read_pointer(process, self.address + scene_manager.offsets.game_object)?; + + const ARRAY_SIZE: usize = 128; + let mut components = [Address::NULL; ARRAY_SIZE]; + + if scene_manager.is_64_bit { + let slice = &mut [Address64::NULL; ARRAY_SIZE * 2][0..number_of_components * 2]; + process.read_into_slice(main_object, slice)?; + + for val in 0..number_of_components { + components[val] = slice[val * 2 + 1].into(); + } + } else { + let slice = &mut [Address32::NULL; ARRAY_SIZE * 2][0..number_of_components * 2]; + process.read_into_slice(main_object, slice)?; + + for val in 0..number_of_components { + components[val] = slice[val * 2 + 1].into(); + } + } + + Ok((0..number_of_components).filter_map(move |m| { + scene_manager + .read_pointer(process, components[m] + scene_manager.offsets.klass) + .ok() + })) + } + + /// 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(|&c| { + let Ok(vtable) = scene_manager.read_pointer(process, c) else { return false }; + + let name_ptr = { + match scene_manager.is_il2cpp { + true => { + let Ok(name_ptr) = scene_manager.read_pointer(process, vtable + scene_manager.pointer_size as u32 * 2) else { return false }; + name_ptr + }, + false => { + let Ok(vtable) = scene_manager.read_pointer(process, vtable) else { return false }; + let Ok(name_ptr) = scene_manager.read_pointer(process, vtable + scene_manager.offsets.klass_name) else { return false }; + name_ptr + } + } + }; + + let Ok(class_name) = process.read::>(name_ptr) else { return false }; + class_name.as_bytes() == name.as_bytes() + }).ok_or(Error {}) + } + + /// Iterates over children `GameObject`s referred by the current one + pub fn children<'a>( + &'a self, + process: &'a Process, + scene_manager: &'a SceneManager, + ) -> Result + 'a, Error> { + let main_object = scene_manager + .read_pointer(process, self.address + scene_manager.offsets.game_object)?; + + let transform = + scene_manager.read_pointer(process, main_object + scene_manager.pointer_size)?; + + let child_count = + process.read::(transform + scene_manager.offsets.children_count)? as usize; + + if child_count == 0 { + return Err(Error {}); + } + + let child_pointer = scene_manager + .read_pointer(process, transform + scene_manager.offsets.children_pointer)?; + + // Define an empty array and fill it later with the addresses of all child classes found for the current GameObject. + // Reading the whole array of pointers is (slightly) faster than reading each address in a loop + const ARRAY_SIZE: usize = 128; + let mut children = [Address::NULL; ARRAY_SIZE]; + + if scene_manager.is_64_bit { + let slice = &mut [Address64::NULL; ARRAY_SIZE][0..child_count]; + process.read_into_slice(child_pointer, slice)?; + + for val in 0..child_count { + children[val] = slice[val].into(); + } + } else { + let slice = &mut [Address32::NULL; ARRAY_SIZE][0..child_count]; + process.read_into_slice(child_pointer, slice)?; + + for val in 0..child_count { + children[val] = slice[val].into(); + } + } + + Ok((0..child_count).filter_map(move |f| { + let game_object = scene_manager + .read_pointer(process, children[f] + scene_manager.offsets.game_object) + .ok()?; + + Some(Self { + address: game_object, + }) + })) + } + + /// Tries to find a child `GameObject` from the current one. + pub fn get_child( + &self, + process: &Process, + scene_manager: &SceneManager, + name: &str, + ) -> Result { + self.children(process, scene_manager)? + .find(|p| { + let Ok(obj_name) = p.get_name::<128>(process, scene_manager) else { return false }; + obj_name.as_bytes() == name.as_bytes() + }) + .ok_or(Error {}) + } } struct Offsets { @@ -129,6 +366,14 @@ struct Offsets { active_scene: u8, asset_path: u8, build_index: u8, + root_storage_container: u8, + game_object: u8, + game_object_name: u8, + number_of_object_components: u8, + klass: u8, + klass_name: u8, + children_count: u8, + children_pointer: u8, } impl Offsets { @@ -140,6 +385,14 @@ impl Offsets { active_scene: 0x48, asset_path: 0x10, build_index: 0x98, + root_storage_container: 0xB0, + game_object: 0x30, + game_object_name: 0x60, + number_of_object_components: 0x40, + klass: 0x28, + klass_name: 0x48, + children_count: 0x80, + children_pointer: 0x70, }, false => &Self { scene_count: 0xC, @@ -147,6 +400,14 @@ impl Offsets { active_scene: 0x28, asset_path: 0xC, build_index: 0x70, + root_storage_container: 0x88, + game_object: 0x1C, + game_object_name: 0x3C, + number_of_object_components: 0x24, + klass: 0x18, + klass_name: 0x2C, + children_count: 0x58, + children_pointer: 0x50, }, } } From 1e25708a456d6d37e986b971269ba0ffe4217b58 Mon Sep 17 00:00:00 2001 From: Jujstme Date: Sun, 6 Aug 2023 00:20:12 +0200 Subject: [PATCH 2/3] Added functions for the DontDestroyOnLoad scene Probably useless in the majority of cases, but still useful in very select scenarios. --- src/game_engine/unity/scene.rs | 37 +++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/src/game_engine/unity/scene.rs b/src/game_engine/unity/scene.rs index 7f9856c..704659f 100644 --- a/src/game_engine/unity/scene.rs +++ b/src/game_engine/unity/scene.rs @@ -80,6 +80,14 @@ impl SceneManager { self.read_pointer(process, addr + self.offsets.active_scene) } + /// `DontDestroyOnLoad` is a special Unity scene containing game objects that must be preserved + /// when switching between different scenes (eg. a `scene1` starting some background music that + /// continues when `scene2` loads) + fn get_dont_destroy_on_load_scene_address(&self, process: &Process) -> Result { + let addr = self.read_pointer(process, self.address)?; + Ok(addr + self.offsets.dont_destroy_on_load_scene) + } + /// Returns the current scene index. /// /// The value returned is a [`i32`] because some games will show `-1` as their @@ -129,7 +137,7 @@ impl SceneManager { .filter(move |p| !fptr.is_null() && p.is_valid(process)) } - /// Iterates over all root `Transform`s / `GameObject`s declared for the current scene. + /// Iterates over all root `Transform`s / `GameObject`s declared for the specified scene. /// /// Each Unity scene normally has a linked list of `Transform`s (each one is a `GameObject`). /// Each one can, recursively, have a child `Transform` (and so on), and has a list of `Component`s, which are @@ -137,18 +145,18 @@ impl SceneManager { fn root_game_objects<'a>( &'a self, process: &'a Process, + scene_address: Address, ) -> Result + 'a, Error> { - let current_scene_address = self.get_current_scene_address(process)?; let first_game_object = self.read_pointer( process, - current_scene_address + self.offsets.root_storage_container, + scene_address + self.offsets.root_storage_container, )?; let number_of_root_game_objects = { let mut index: usize = 0; let mut temp_tr = first_game_object; - while temp_tr != current_scene_address + self.offsets.root_storage_container { + while temp_tr != scene_address + self.offsets.root_storage_container { index += 1; temp_tr = self.read_pointer(process, temp_tr)?; } @@ -185,9 +193,21 @@ impl SceneManager { })) } - /// Tries to find the specified root `GameObject` in the current Unity scene. + /// Tries to find the specified root `GameObject` from the currently active Unity scene. pub fn get_root_game_object(&self, process: &Process, name: &str) -> Result { - self.root_game_objects(process)? + self.root_game_objects(process, self.get_current_scene_address(process)?)? + .find(|obj| { + obj.get_name::<128>(process, self) + .unwrap_or_default() + .as_bytes() + == name.as_bytes() + }) + .ok_or(Error {}) + } + + /// Tries to find the specified root `GameObject` 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_address(process)?)? .find(|obj| { obj.get_name::<128>(process, self) .unwrap_or_default() @@ -364,6 +384,7 @@ struct Offsets { scene_count: u8, loaded_scenes: u8, active_scene: u8, + dont_destroy_on_load_scene: u8, asset_path: u8, build_index: u8, root_storage_container: u8, @@ -383,6 +404,7 @@ impl Offsets { scene_count: 0x18, loaded_scenes: 0x28, active_scene: 0x48, + dont_destroy_on_load_scene: 0x70, asset_path: 0x10, build_index: 0x98, root_storage_container: 0xB0, @@ -398,6 +420,7 @@ impl Offsets { scene_count: 0xC, loaded_scenes: 0x18, active_scene: 0x28, + dont_destroy_on_load_scene: 0x40, asset_path: 0xC, build_index: 0x70, root_storage_container: 0x88, @@ -458,4 +481,4 @@ pub fn get_scene_name(scene_path: &[u8]) -> &[u8] { .split(|&b| b == b'.') .next() .unwrap_or_default() -} +} \ No newline at end of file From 02981ed0c13ed1c1c0db01900f99e35101a232d8 Mon Sep 17 00:00:00 2001 From: Christopher Serr Date: Wed, 9 Aug 2023 22:00:00 +0200 Subject: [PATCH 3/3] Some cleanup --- src/game_engine/unity/scene.rs | 206 +++++++++++++++++++-------------- src/runtime/process.rs | 31 +++++ 2 files changed, 151 insertions(+), 86 deletions(-) diff --git a/src/game_engine/unity/scene.rs b/src/game_engine/unity/scene.rs index 704659f..392f14d 100644 --- a/src/game_engine/unity/scene.rs +++ b/src/game_engine/unity/scene.rs @@ -4,6 +4,8 @@ // https://gist.githubusercontent.com/just-ero/92457b51baf85bd1e5b8c87de8c9835e/raw/8aa3e6b8da01fd03ff2ff0c03cbd018e522ef988/UnityScene.hpp // Offsets and logic for the GameObject functions taken from https://github.com/Micrologist/UnityInstanceDumper +use core::{array, mem::MaybeUninit}; + use crate::{ file_format::pe, future::retry, signature::Signature, string::ArrayCString, Address, Address32, Address64, Error, Process, @@ -19,7 +21,6 @@ pub struct SceneManager { is_il2cpp: bool, address: Address, offsets: &'static Offsets, - pointer_size: u8, } impl SceneManager { @@ -49,14 +50,12 @@ impl SceneManager { }; let offsets = Offsets::new(is_64_bit); - let pointer_size = if is_64_bit { 0x8 } else { 0x4 }; Some(Self { is_64_bit, is_il2cpp, address, offsets, - pointer_size, }) } @@ -80,9 +79,10 @@ impl SceneManager { self.read_pointer(process, addr + self.offsets.active_scene) } - /// `DontDestroyOnLoad` is a special Unity scene containing game objects that must be preserved - /// when switching between different scenes (eg. a `scene1` starting some background music that - /// continues when `scene2` loads) + /// `DontDestroyOnLoad` is a special Unity scene containing game objects + /// that must be preserved when switching between different scenes (eg. a + /// `scene1` starting some background music that continues when `scene2` + /// loads). fn get_dont_destroy_on_load_scene_address(&self, process: &Process) -> Result { let addr = self.read_pointer(process, self.address)?; Ok(addr + self.offsets.dont_destroy_on_load_scene) @@ -137,20 +137,21 @@ impl SceneManager { .filter(move |p| !fptr.is_null() && p.is_valid(process)) } - /// Iterates over all root `Transform`s / `GameObject`s declared for the specified scene. + /// Iterates over all root `Transform`s / [`GameObject`]s declared for the + /// specified scene. /// - /// Each Unity scene normally has a linked list of `Transform`s (each one is a `GameObject`). - /// Each one can, recursively, have a child `Transform` (and so on), and has a list of `Component`s, which are - /// classes (eg. `MonoBehaviour`) containing data we might want to retreieve for the autosplitter logic. + /// Each Unity scene normally has a linked list of `Transform`s (each one is + /// a [`GameObject`]). Each one can, recursively, have a child `Transform` + /// (and so on), and has 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_address: Address, ) -> Result + 'a, Error> { - let first_game_object = self.read_pointer( - process, - scene_address + self.offsets.root_storage_container, - )?; + let first_game_object = + self.read_pointer(process, scene_address + self.offsets.root_storage_container)?; let number_of_root_game_objects = { let mut index: usize = 0; @@ -167,7 +168,7 @@ impl SceneManager { let mut current_game_object = first_game_object; Ok((0..number_of_root_game_objects).filter_map(move |n| { - let buf: [Address; 3] = match self.is_64_bit { + let [first, _, third]: [Address; 3] = match self.is_64_bit { true => process .read::<[Address64; 3]>(current_game_object) .ok()? @@ -179,12 +180,12 @@ impl SceneManager { }; let game_object = self - .read_pointer(process, buf[2] + self.offsets.game_object) + .read_pointer(process, third + self.offsets.game_object) .ok()?; // Load the next game object before looping, except at the last iteration of the loop if n + 1 != number_of_root_game_objects { - current_game_object = buf[0]; + current_game_object = first; } Some(GameObject { @@ -193,39 +194,47 @@ impl SceneManager { })) } - /// Tries to find the specified root `GameObject` from the currently active Unity scene. + /// Tries to find the specified root [`GameObject`] 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_address(process)?)? .find(|obj| { obj.get_name::<128>(process, self) .unwrap_or_default() - .as_bytes() - == name.as_bytes() + .matches(name) }) .ok_or(Error {}) } - /// Tries to find the specified root `GameObject` 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_address(process)?)? - .find(|obj| { - obj.get_name::<128>(process, self) - .unwrap_or_default() - .as_bytes() - == name.as_bytes() - }) - .ok_or(Error {}) + /// Tries to find the specified root [`GameObject`] 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_address(process)?, + )? + .find(|obj| { + obj.get_name::<128>(process, self) + .unwrap_or_default() + .matches(name) + }) + .ok_or(Error {}) } } -/// A `GameObject` is a base class for all entities used in a Unity scene. -/// All classes of interest useful for an autosplitter can be found starting from the addresses of the root `GameObject`s linked in each scene. +/// A `GameObject` 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 `GameObject`s linked in each scene. pub struct GameObject { address: Address, } impl GameObject { - /// Tries to return the name of the current `GameObject` + /// Tries to return the name of the current `GameObject`. pub fn get_name( &self, process: &Process, @@ -238,7 +247,7 @@ impl GameObject { process.read(name_ptr) } - /// Iterates over the classes referred to in the current `GameObject` + /// Iterates over the classes referred to in the current `GameObject`. pub fn classes<'a>( &'a self, process: &'a Process, @@ -251,28 +260,35 @@ impl GameObject { if number_of_components == 0 { return Err(Error {}); } - + let main_object = scene_manager .read_pointer(process, self.address + scene_manager.offsets.game_object)?; const ARRAY_SIZE: usize = 128; - let mut components = [Address::NULL; ARRAY_SIZE]; - if scene_manager.is_64_bit { - let slice = &mut [Address64::NULL; ARRAY_SIZE * 2][0..number_of_components * 2]; - process.read_into_slice(main_object, slice)?; + let components: [Address; ARRAY_SIZE] = if scene_manager.is_64_bit { + let mut buf = [MaybeUninit::<[Address64; 2]>::uninit(); ARRAY_SIZE]; + let slice = + process.read_into_uninit_slice(main_object, &mut buf[..number_of_components])?; - for val in 0..number_of_components { - components[val] = slice[val * 2 + 1].into(); - } + let mut iter = slice.iter_mut(); + array::from_fn(|_| { + iter.next() + .map(|&mut [_, second]| second.into()) + .unwrap_or_default() + }) } else { - let slice = &mut [Address32::NULL; ARRAY_SIZE * 2][0..number_of_components * 2]; - process.read_into_slice(main_object, slice)?; - - for val in 0..number_of_components { - components[val] = slice[val * 2 + 1].into(); - } - } + 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((0..number_of_components).filter_map(move |m| { scene_manager @@ -281,33 +297,52 @@ impl GameObject { })) } - /// Tries to find the base address of a class in the current `GameObject` + /// 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(|&c| { - let Ok(vtable) = scene_manager.read_pointer(process, c) else { return false }; - - let name_ptr = { - match scene_manager.is_il2cpp { - true => { - let Ok(name_ptr) = scene_manager.read_pointer(process, vtable + scene_manager.pointer_size as u32 * 2) else { return false }; - name_ptr - }, - false => { - let Ok(vtable) = scene_manager.read_pointer(process, vtable) else { return false }; - let Ok(name_ptr) = scene_manager.read_pointer(process, vtable + scene_manager.offsets.klass_name) else { return false }; - name_ptr + self.classes(process, scene_manager)? + .find(|&c| { + let Ok(vtable) = scene_manager.read_pointer(process, c) else { + return false; + }; + + let name_ptr = { + match scene_manager.is_il2cpp { + true => { + let Ok(name_ptr) = scene_manager.read_pointer( + process, + vtable + 2 * if scene_manager.is_64_bit { 8 } else { 4 }, + ) else { + return false; + }; + + name_ptr + } + false => { + let Ok(vtable) = scene_manager.read_pointer(process, vtable) else { + return false; + }; + + let Ok(name_ptr) = scene_manager + .read_pointer(process, vtable + scene_manager.offsets.klass_name) + else { + return false; + }; + + name_ptr + } } - } - }; + }; - let Ok(class_name) = process.read::>(name_ptr) else { return false }; - class_name.as_bytes() == name.as_bytes() - }).ok_or(Error {}) + process + .read::>(name_ptr) + .is_ok_and(|class_name| class_name.matches(name)) + }) + .ok_or(Error {}) } /// Iterates over children `GameObject`s referred by the current one @@ -319,8 +354,10 @@ impl GameObject { let main_object = scene_manager .read_pointer(process, self.address + scene_manager.offsets.game_object)?; - let transform = - scene_manager.read_pointer(process, main_object + scene_manager.pointer_size)?; + let transform = scene_manager.read_pointer( + process, + main_object + if scene_manager.is_64_bit { 8 } else { 4 }, + )?; let child_count = process.read::(transform + scene_manager.offsets.children_count)? as usize; @@ -335,23 +372,20 @@ impl GameObject { // Define an empty array and fill it later with the addresses of all child classes found for the current GameObject. // Reading the whole array of pointers is (slightly) faster than reading each address in a loop const ARRAY_SIZE: usize = 128; - let mut children = [Address::NULL; ARRAY_SIZE]; - if scene_manager.is_64_bit { - let slice = &mut [Address64::NULL; ARRAY_SIZE][0..child_count]; - process.read_into_slice(child_pointer, slice)?; + let children: [Address; ARRAY_SIZE] = if scene_manager.is_64_bit { + let mut buf = [MaybeUninit::::uninit(); ARRAY_SIZE]; + let slice = process.read_into_uninit_slice(child_pointer, &mut buf[..child_count])?; - for val in 0..child_count { - children[val] = slice[val].into(); - } + let mut iter = slice.iter_mut(); + array::from_fn(|_| iter.next().copied().map(Into::into).unwrap_or_default()) } else { - let slice = &mut [Address32::NULL; ARRAY_SIZE][0..child_count]; - process.read_into_slice(child_pointer, slice)?; + let mut buf = [MaybeUninit::::uninit(); ARRAY_SIZE]; + let slice = process.read_into_uninit_slice(child_pointer, &mut buf[..child_count])?; - for val in 0..child_count { - children[val] = slice[val].into(); - } - } + let mut iter = slice.iter_mut(); + array::from_fn(|_| iter.next().copied().map(Into::into).unwrap_or_default()) + }; Ok((0..child_count).filter_map(move |f| { let game_object = scene_manager @@ -373,8 +407,8 @@ impl GameObject { ) -> Result { self.children(process, scene_manager)? .find(|p| { - let Ok(obj_name) = p.get_name::<128>(process, scene_manager) else { return false }; - obj_name.as_bytes() == name.as_bytes() + p.get_name::<128>(process, scene_manager) + .is_ok_and(|obj_name| obj_name.matches(name)) }) .ok_or(Error {}) } @@ -481,4 +515,4 @@ pub fn get_scene_name(scene_path: &[u8]) -> &[u8] { .split(|&b| b == b'.') .next() .unwrap_or_default() -} \ No newline at end of file +} diff --git a/src/runtime/process.rs b/src/runtime/process.rs index a71e44e..86b0816 100644 --- a/src/runtime/process.rs +++ b/src/runtime/process.rs @@ -206,6 +206,37 @@ impl Process { } } + /// Reads a range of bytes from the process at the address given into the + /// buffer provided. This is a convenience method for reading into a slice + /// of a specific type. The buffer does not need to be initialized. After + /// the slice successfully got filled, the initialized slice is returned. + #[inline] + pub fn read_into_uninit_slice( + &self, + address: impl Into
, + slice: &mut [MaybeUninit], + ) -> Result<&mut [T], Error> { + // SAFETY: The process handle is guaranteed to be valid. We provide a + // valid pointer and length to the buffer. We also do proper error + // handling afterwards. The buffer is guaranteed to be initialized + // afterwards, we just need to check if the values are valid bit + // patterns. We can then safely return a slice of it. + unsafe { + let byte_len = mem::size_of_val(slice); + self.read_into_uninit_buf( + address, + slice::from_raw_parts_mut(slice.as_mut_ptr().cast(), byte_len), + )?; + for element in &*slice { + if !T::is_valid_bit_pattern(&*element.as_ptr().cast::()) { + return Err(Error {}); + } + } + let len = slice.len(); + Ok(slice::from_raw_parts_mut(slice.as_mut_ptr().cast(), len)) + } + } + /// Follows a path of pointers from the address given and reads a value of /// the type specified from the process at the end of the pointer path. This /// method is specifically for dealing with processes that use 64-bit