diff --git a/src/lib.rs b/src/lib.rs index c09a73a..5674a64 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,8 +18,9 @@ use tweaks::editor_camera_speed::EditorCameraSpeedTweak; use tweaks::editor_placement::EditorPlacementTweak; use tweaks::editor_show_hidden::ShowHiddenComponents; use tweaks::fullscreen::FullscreenTweak; -use tweaks::loading::LoadingTweak; +use tweaks::fast_loading_animations::FastLoadingAnimationsTweak; use tweaks::map_lag::MapLagTweak; +use tweaks::multithreaded_loading::MultithreadedLoadingTweak; use tweaks::{Tweak, TweakWrapper}; use windows::Win32::Foundation::HINSTANCE; use windows::Win32::System::SystemServices::DLL_PROCESS_ATTACH; @@ -81,7 +82,8 @@ impl MainHud { this.add_tweak::(&process.region); this.add_tweak::(&process.region); this.add_tweak::(&process.region); - this.add_tweak::(&process.region); + this.add_tweak::(&process.region); + this.add_tweak::(&process.region); this.add_tweak::(&process.region); this.add_tweak::(&process.region); }, diff --git a/src/tweaks/loading.rs b/src/tweaks/fast_loading_animations.rs similarity index 97% rename from src/tweaks/loading.rs rename to src/tweaks/fast_loading_animations.rs index 1658d27..9f5e0a0 100644 --- a/src/tweaks/loading.rs +++ b/src/tweaks/fast_loading_animations.rs @@ -8,9 +8,9 @@ use super::{Defaults, InjectAt, Tweak}; const FAST_MENU_FADE_DEFAULTS: Defaults = Defaults::new(true, false); const SKIP_LOAD_FINISH_DEFAULTS: Defaults = Defaults::new(true, false); -pub struct LoadingTweak; +pub struct FastLoadingAnimationsTweak; -impl Tweak for LoadingTweak { +impl Tweak for FastLoadingAnimationsTweak { #[allow(clippy::too_many_lines)] fn new(builder: &mut super::TweakBuilder) -> anyhow::Result where diff --git a/src/tweaks/mod.rs b/src/tweaks/mod.rs index b817de7..4e3b7f0 100644 --- a/src/tweaks/mod.rs +++ b/src/tweaks/mod.rs @@ -7,6 +7,7 @@ use memory_rs::internal::{ memory_region::MemoryRegion, }; use num_traits::ToBytes; +use retour::GenericDetour; use settings::{slider::SliderBuilder, toggle::ToggleBuilder, SettingUntyped}; pub mod dev_mode; @@ -14,9 +15,10 @@ pub mod editor_camera_speed; pub mod editor_placement; pub mod editor_show_hidden; pub mod fullscreen; -pub mod loading; +pub mod fast_loading_animations; pub mod map_lag; pub mod settings; +pub mod multithreaded_loading; pub trait Tweak { fn new(builder: &mut TweakBuilder) -> anyhow::Result @@ -257,3 +259,18 @@ impl NumberInjection { self.injection.remove_injection(); } } + +pub trait DetourUntyped { + fn enable(&mut self) -> anyhow::Result<()>; + fn disable(&mut self) -> anyhow::Result<()>; +} + +impl DetourUntyped for GenericDetour { + fn enable(&mut self) -> anyhow::Result<()> { + unsafe { Ok(GenericDetour::enable(self)?) } + } + + fn disable(&mut self) -> anyhow::Result<()> { + unsafe { Ok(GenericDetour::disable(self)?) } + } +} diff --git a/src/tweaks/multithreaded_loading.rs b/src/tweaks/multithreaded_loading.rs new file mode 100644 index 0000000..176d14c --- /dev/null +++ b/src/tweaks/multithreaded_loading.rs @@ -0,0 +1,147 @@ +use std::thread::JoinHandle; + +use anyhow::Context; +use memory_rs::generate_aob_pattern; +use retour::GenericDetour; + +use crate::tweaks::MemoryRegionExt; + +use super::{Defaults, Tweak}; + +type LoadRomFn = extern "fastcall" fn(*mut (), *mut (), usize, *mut ()); +type LoadSaveFn = extern "fastcall" fn(*mut (), *mut (), *mut (), *mut (), *mut ()); + +const MULTITHREADED_LOADING_DEFAULTS: Defaults = Defaults::new(false, false); + +static mut LOAD_ROM_FN: Option = None; +static mut LOAD_SAVE_FN: Option = None; +static mut LOAD_ROM_THREAD: Option> = None; + +pub struct MultithreadedLoadingTweak; + +impl Tweak for MultithreadedLoadingTweak { + #[allow(clippy::too_many_lines)] + fn new(builder: &mut super::TweakBuilder) -> anyhow::Result + where + Self: Sized, + { + builder.set_category(Some("Performance")); + + // --- multithreaded loading + + // move load_rom call to another thread + // kind of a wildly unsafe change but I haven't had any issues with it so far + // note that subdividing this further (ie. load_audio on another thread) DID lead to issues when exiting a world + let load_rom_detour = unsafe { + extern "fastcall" fn hook( + param_1: *mut (), + param_2: *mut (), + param_3: usize, + param_4: *mut (), + ) { + unsafe { + let param_1_ptr = param_1 as usize; + let param_2_ptr = param_2 as usize; + let param_3_ptr = param_3; + let param_4_ptr = param_4 as usize; + let thread = std::thread::Builder::new() + .name("load_rom".to_owned()) + .spawn(move || { + let load_rom: LoadRomFn = LOAD_ROM_FN.unwrap_unchecked(); + load_rom( + param_1_ptr as _, + param_2_ptr as _, + param_3_ptr as _, + param_4_ptr as _, + ); + }) + .unwrap(); + + LOAD_ROM_THREAD = Some(thread); + } + } + + #[rustfmt::skip] + let load_rom_fn_addr = builder.region.scan_aob_single(&generate_aob_pattern![ + _, 0x89, 0x5c, _, 0x10, // MOV qword ptr [RSP + 0x10],RBX + _, 0x89, 0x4c, _, 0x20, // MOV qword ptr [RSP + 0x20],R9 + _, // PUSH _ + _, // PUSH _ + _, // PUSH _ + _, _, // PUSH _ + _, _, // PUSH _ + _, _, // PUSH _ + _, _, // PUSH _ + _, 0x8d, 0x6c, _, 0xd9, // LEA RBP,[RSP + -0x27] + 0x48, 0x81, 0xec, 0xa0, 0x00, 0x00, 0x00 // SUB RSP,0xa0 + ]).context("Error finding load_rom fn addr")?; + + let det = GenericDetour::new( + std::mem::transmute::(load_rom_fn_addr), + hook, + ) + .context("Failed to detour load_rom fn")?; + + LOAD_ROM_FN = Some(std::mem::transmute::<&(), LoadRomFn>(det.trampoline())); + + det.enable().context("Failed to enable load_rom detour")?; + + det + }; + + // join load_rom thread later on in loading to make sure we don't finish out of order + let load_save_detour = unsafe { + extern "fastcall" fn hook( + param_1: *mut (), + param_2: *mut (), + param_3: *mut (), + param_4: *mut (), + param_5: *mut (), + ) { + unsafe { + let load_save: LoadSaveFn = LOAD_SAVE_FN.unwrap_unchecked(); + load_save(param_1, param_2, param_3, param_4, param_5); + if let Some(handle) = LOAD_ROM_THREAD.take() { + handle.join().unwrap(); + } + } + } + + #[rustfmt::skip] + let load_save_fn_addr = builder.region.scan_aob_single(&generate_aob_pattern![ + _, 0x89, 0x5c, _, 0x18, // MOV qword ptr [RSP + 0x18],RBX + _, 0x89, 0x54, _, 0x10, // MOV qword ptr [RSP + 0x10],RDX + _, // PUSH _ + _, // PUSH _ + _, // PUSH _ + _, _, // PUSH _ + _, _, // PUSH _ + _, _, // PUSH _ + _, _, // PUSH _ + _, 0x8d, 0x6c, _, 0xe1, // LEA RBP,[RSP + -0x1f] + 0x48, 0x81, 0xec, 0xd0, 0x00, 0x00, 0x00 // SUB RSP,0xd0 + ]).context("Error finding load_save fn addr")?; + + let det = GenericDetour::new( + std::mem::transmute::(load_save_fn_addr), + hook, + ) + .context("Failed to detour load_save fn")?; + + LOAD_SAVE_FN = Some(std::mem::transmute::<&(), LoadSaveFn>(det.trampoline())); + + det.enable().context("Failed to enable load_save detour")?; + + det + }; + + builder + .toggle("Multithreaded Loading (experimental)", MULTITHREADED_LOADING_DEFAULTS) + .tooltip("EXPERIMENTAL!\nSplits asset loading into a separate thread, reducing world load time by ~40%.\nI haven't had any issues using this but I wouldn't be suprised if there are unknown edge cases.") + .detour(load_rom_detour, false) + .detour(load_save_detour, false) + .build()?; + + Ok(Self) + } +} diff --git a/src/tweaks/settings/toggle.rs b/src/tweaks/settings/toggle.rs index 220eab4..d15a1c7 100644 --- a/src/tweaks/settings/toggle.rs +++ b/src/tweaks/settings/toggle.rs @@ -1,6 +1,6 @@ use memory_rs::internal::injections::{Inject, Injection}; -use crate::tweaks::{Defaults, TweakBuilder}; +use crate::tweaks::{Defaults, DetourUntyped, TweakBuilder}; use super::{Setting, SettingImpl}; @@ -25,6 +25,7 @@ impl<'b, 'r> ToggleBuilder<'b, 'r> { tooltip: String::new(), label: label.into(), injections: vec![], + detours: vec![], }, } } @@ -45,6 +46,12 @@ impl<'b, 'r> ToggleBuilder<'b, 'r> { self } + #[must_use] + pub fn detour(mut self, detour: impl DetourUntyped + Send + Sync + 'static, invert: bool) -> Self { + self.toggle.detours.push((Box::new(detour), invert)); + self + } + pub fn build(self) -> anyhow::Result<()> { self.tweak_builder .add_setting(Setting::new(self.toggle, self.defaults)) @@ -55,6 +62,7 @@ pub struct Toggle { label: String, tooltip: String, injections: Vec<(Injection, bool)>, + detours: Vec<(Box, bool)>, } impl SettingImpl for Toggle { @@ -76,6 +84,23 @@ impl SettingImpl for Toggle { } } + for (detour, invert) in &mut self.detours { + #[allow(clippy::collapsible_else_if)] + if value { + if *invert { + detour.disable()?; + } else { + detour.enable()?; + } + } else { + if *invert { + detour.enable()?; + } else { + detour.disable()?; + } + } + } + Ok(()) }