diff --git a/src/SoulMemory/EldenRing/EldenRing.cs b/src/SoulMemory/EldenRing/EldenRing.cs index 98741d08..b5181a58 100644 --- a/src/SoulMemory/EldenRing/EldenRing.cs +++ b/src/SoulMemory/EldenRing/EldenRing.cs @@ -40,7 +40,10 @@ public class EldenRing : IGame private long _positionOffset; private long _mapIdOffset; private long _playerInsOffset; - + private Dictionary _exportedFunctions; + + private IntPtr _fpsPatch = IntPtr.Zero; + private IntPtr _fpsLimit = IntPtr.Zero; #region Refresh/init/reset ================================================================================================ public Process GetProcess() => _process; @@ -157,7 +160,8 @@ private ResultErr InitPointers() _igt.Clear(); return Result.Err(new RefreshError(RefreshErrorReason.UnknownException, "soulmods injection failed")); } - + + _exportedFunctions = _process.GetModuleExportedFunctions("soulmods.dll"); return Result.Ok(); } catch (Exception e) @@ -182,6 +186,8 @@ private void ResetPointers() _menuManImp.Clear(); _virtualMemoryFlag.Clear(); _noLogo.Clear(); + _fpsPatch = IntPtr.Zero; + _fpsLimit = IntPtr.Zero; } #endregion @@ -577,5 +583,55 @@ public void WriteInGameTimeMilliseconds(int milliseconds) } #endregion + + #region soulmods + public bool FpsPatchGet() + { + var address = _exportedFunctions["fps_patch_get"]; + if (_fpsPatch == IntPtr.Zero) + { + _fpsPatch = _process.Allocate(1); + } + _process.Execute((IntPtr)address, _fpsPatch); + var b = _process.ReadMemory(_fpsPatch.ToInt64()).Unwrap(); + return b; + } + + public void FpsPatchSet(bool b) + { + var address = _exportedFunctions["fps_patch_set"]; + if (_fpsPatch == IntPtr.Zero) + { + _fpsPatch = _process.Allocate(1); + } + _process.WriteProcessMemory(_fpsPatch.ToInt64(), BitConverter.GetBytes(b)); + _process.Execute((IntPtr)address, _fpsPatch); + } + + + public float FpsLimitGet() + { + var address = _exportedFunctions["fps_limit_get"]; + if (_fpsLimit == IntPtr.Zero) + { + _fpsLimit = _process.Allocate(4); + } + _process.Execute((IntPtr)address, _fpsLimit); + var f = _process.ReadMemory(_fpsLimit.ToInt64()).Unwrap(); + return f; + } + + public void FpsLimitSet(float f) + { + var address = _exportedFunctions["fps_limit_set"]; + if (_fpsLimit == IntPtr.Zero) + { + _fpsLimit = _process.Allocate(4); + } + _process.WriteProcessMemory(_fpsLimit.ToInt64(), BitConverter.GetBytes(f)); + _process.Execute((IntPtr)address, _fpsLimit); + } + + #endregion } } diff --git a/src/SoulMemory/Native/Kernel32.cs b/src/SoulMemory/Native/Kernel32.cs index 64d96e32..40424752 100644 --- a/src/SoulMemory/Native/Kernel32.cs +++ b/src/SoulMemory/Native/Kernel32.cs @@ -332,7 +332,7 @@ void CleanupSnapshot() - public static List<(string name, long address)> GetModuleExportedFunctions(this Process process, string hModuleName) + public static Dictionary GetModuleExportedFunctions(this Process process, string hModuleName) { var modules = process.GetModulesViaSnapshot(); var module = modules.First(i => i.szModule.ToLowerInvariant() == hModuleName.ToLowerInvariant()); @@ -362,12 +362,12 @@ void CleanupSnapshot() var ordinalTable = module.modBaseAddr.ToInt64() + exportTable.AddressOfNameOrdinals; var functionOffsetTable = module.modBaseAddr.ToInt64() + exportTable.AddressOfFunctions; - var functions = new List<(string name, long address)>(); + var functions = new Dictionary(); for (var i = 0; i < exportTable.NumberOfNames; i++) { - var EAT = module.modBaseAddr.ToInt64() + exportTable.AddressOfFunctions; + var eat = module.modBaseAddr.ToInt64() + exportTable.AddressOfFunctions; - var EATPtr = (IntPtr)EAT; + var eatPtr = (IntPtr)eat; //Function name offset is an array of 4byte numbers var functionNameOffset = process.ReadMemory(nameOffsetTable + i * sizeof(uint)).Unwrap(); @@ -380,7 +380,7 @@ void CleanupSnapshot() var functionOffset = process.ReadMemory(functionOffsetTable + i * sizeof(uint)).Unwrap(); var functionAddress = module.modBaseAddr.ToInt64() + functionOffset; - functions.Add((functionName, functionAddress)); + functions.Add(functionName, functionAddress); } return functions; diff --git a/src/SoulMemory/soulmods/Soulmods.cs b/src/SoulMemory/soulmods/Soulmods.cs index b88b1d7d..58a2be3a 100644 --- a/src/SoulMemory/soulmods/Soulmods.cs +++ b/src/SoulMemory/soulmods/Soulmods.cs @@ -75,14 +75,15 @@ public static bool Inject(Process process) - private static List<(string name, long address)> _soulmodsMethods; + private static Dictionary _soulmodsMethods; public static TSized RustCall(this Process process, string function, TSized? parameter = null) where TSized : struct { if (_soulmodsMethods == null) { _soulmodsMethods = process.GetModuleExportedFunctions("soulmods.dll"); } - var functionPtr = _soulmodsMethods.First(i => i.name == function).address; + + var functionPtr = _soulmodsMethods[function]; var buffer = process.Allocate(Marshal.SizeOf()); if (parameter.HasValue) @@ -100,7 +101,7 @@ public static void GetMorphemeMessages(Process process) { //Get function address var soulmods = process.GetModuleExportedFunctions("soulmods.dll"); - var func = soulmods.First(i => i.name == "GetQueuedDarkSouls2MorphemeMessages").address; + var func = soulmods["GetQueuedDarkSouls2MorphemeMessages"]; //Get buffer size var buffer = process.Allocate(4); diff --git a/src/SoulSplitter/SoulComponent.cs b/src/SoulSplitter/SoulComponent.cs index 36329eb4..1beda6b8 100644 --- a/src/SoulSplitter/SoulComponent.cs +++ b/src/SoulSplitter/SoulComponent.cs @@ -32,6 +32,7 @@ using System.Reflection; using System.Windows.Forms; using SoulSplitter.Migrations; +using SoulMemory.Native; namespace SoulSplitter { diff --git a/src/SoulSplitter/Splitters/EldenRingSplitter.cs b/src/SoulSplitter/Splitters/EldenRingSplitter.cs index ce0524b1..27d9215a 100644 --- a/src/SoulSplitter/Splitters/EldenRingSplitter.cs +++ b/src/SoulSplitter/Splitters/EldenRingSplitter.cs @@ -24,6 +24,9 @@ using SoulSplitter.UI; using SoulSplitter.UI.EldenRing; using SoulSplitter.UI.Generic; +using SoulSplitter.Hotkeys; +using System.Windows.Input; +using System.Windows.Automation.Provider; namespace SoulSplitter.Splitters { @@ -170,9 +173,139 @@ private void ResetTimer() } + bool hotkeysCreated = false; // TODO: Remove when UI hotkeys are implemented + bool lastFpsPatchState = false; // TODO: Replace with proper get/set public void UpdateTimer(bool startAutomatically) { - //Allow updates from the UI only when a run isn't in progress + // Hardcoded hotkeys. TODO: Remove when UI hotkeys are implemented + if (!hotkeysCreated) + { + /* + // Danflesh + GlobalHotKey.RegisterHotKey(ModifierKeys.None, Key.Divide, () => + { + _eldenRingViewModel.FpsPatch = false; + }); + GlobalHotKey.RegisterHotKey(ModifierKeys.None, Key.Add, () => + { + if (_eldenRing.FpsLimitGet() == 60.0f && _eldenRingViewModel.FpsPatch) + { + _eldenRingViewModel.FpsPatch = false; + } + else + { + _eldenRing.FpsLimitSet(60.0f); + _eldenRingViewModel.FpsPatch = true; + } + }); + GlobalHotKey.RegisterHotKey(ModifierKeys.None, Key.Subtract, () => + { + if (_eldenRing.FpsLimitGet() == 36.1f && _eldenRingViewModel.FpsPatch) + { + _eldenRingViewModel.FpsPatch = false; + } + else + { + _eldenRing.FpsLimitSet(36.1f); + _eldenRingViewModel.FpsPatch = true; + } + }); + GlobalHotKey.RegisterHotKey(ModifierKeys.None, Key.Multiply, () => + { + if (_eldenRing.FpsLimitGet() == 34.0f && _eldenRingViewModel.FpsPatch) + { + _eldenRingViewModel.FpsPatch = false; + } + else + { + _eldenRing.FpsLimitSet(34.0f); + _eldenRingViewModel.FpsPatch = true; + } + }); + */ + + /* + // Regole + GlobalHotKey.RegisterHotKey(ModifierKeys.None, Key.OemBackslash, () => + { + if (_eldenRing.FpsLimitGet() == 36.1f && _eldenRingViewModel.FpsPatch) + { + _eldenRingViewModel.FpsPatch = false; + } + else + { + _eldenRing.FpsLimitSet(36.1f); + _eldenRingViewModel.FpsPatch = true; + } + }); + */ + + /* + // Pennek + GlobalHotKey.RegisterHotKey(ModifierKeys.None, Key.OemOpenBrackets, () => + { + if (_eldenRing.FpsLimitGet() == 57.0f && _eldenRingViewModel.FpsPatch) + { + _eldenRingViewModel.FpsPatch = false; + } + else + { + _eldenRing.FpsLimitSet(57.0f); + _eldenRingViewModel.FpsPatch = true; + } + }); + GlobalHotKey.RegisterHotKey(ModifierKeys.None, Key.OemCloseBrackets, () => + { + if (_eldenRing.FpsLimitGet() == 0.0f && _eldenRingViewModel.FpsPatch) + { + _eldenRingViewModel.FpsPatch = false; + } + else + { + _eldenRing.FpsLimitSet(0.0f); + _eldenRingViewModel.FpsPatch = true; + } + }); + GlobalHotKey.RegisterHotKey(ModifierKeys.None, Key.OemBackslash, () => + { + _eldenRingViewModel.FpsPatch = false; + }); + */ + + // Testing + GlobalHotKey.RegisterHotKey(ModifierKeys.None, Key.F2, () => + { + _eldenRing.FpsPatchSet(true); + }); + GlobalHotKey.RegisterHotKey(ModifierKeys.None, Key.F3, () => + { + _eldenRing.FpsPatchSet(false); + }); + GlobalHotKey.RegisterHotKey(ModifierKeys.None, Key.F5, () => + { + _eldenRing.FpsLimitSet(0.0f); + }); + GlobalHotKey.RegisterHotKey(ModifierKeys.None, Key.F6, () => + { + _eldenRing.FpsLimitSet(40.0f); + }); + GlobalHotKey.RegisterHotKey(ModifierKeys.None, Key.F7, () => + { + _eldenRing.FpsLimitSet(25.0f); + }); + + hotkeysCreated = true; + } + + if (lastFpsPatchState != _eldenRingViewModel.FpsPatch) + { + lastFpsPatchState = _eldenRingViewModel.FpsPatch; + _eldenRing.FpsPatchSet(_eldenRingViewModel.FpsPatch); + } + + // _eldenRing.FpsPatchSet(!_eldenRing.FpsPatchGet()); // TEMP SPAM TOGGLE + + // Allow updates from the UI only when a run isn't in progress if (_timerState == TimerState.WaitForStart) { _startAutomatically = startAutomatically; diff --git a/src/SoulSplitter/UI/EldenRing/EldenRingControl.xaml b/src/SoulSplitter/UI/EldenRing/EldenRingControl.xaml index a1e16d55..de64aeba 100644 --- a/src/SoulSplitter/UI/EldenRing/EldenRingControl.xaml +++ b/src/SoulSplitter/UI/EldenRing/EldenRingControl.xaml @@ -80,12 +80,13 @@ 0 - 1 - 2 - 3 - 4 - 5 - 6 + 1 + 2 + 3 + 4 + 5 + 6 + 7 @@ -100,6 +101,7 @@ + @@ -116,6 +118,14 @@ + + + + _fpsPatch; + set => SetField(ref _fpsPatch, value); + } + private bool _fpsPatch = false; + public bool LockIgtToZero { get => _lockIgtToZero; diff --git a/src/cli/Program.cs b/src/cli/Program.cs index 5e9271e0..15e853d0 100644 --- a/src/cli/Program.cs +++ b/src/cli/Program.cs @@ -41,13 +41,18 @@ internal class Program [STAThread] static void Main(string[] args) { - GlobalHotKey.RegisterHotKey(ModifierKeys.Alt, Key.A, () =>{ Debug.WriteLine("A"); }); - GlobalHotKey.RegisterHotKey(ModifierKeys.Alt, Key.S, () =>{ Debug.WriteLine("S"); }); - GlobalHotKey.RegisterHotKey(ModifierKeys.Alt, Key.D, () =>{ Debug.WriteLine("D"); }); + //GlobalHotKey.RegisterHotKey(ModifierKeys.Alt, Key.A, () =>{ Debug.WriteLine("A"); }); + //GlobalHotKey.RegisterHotKey(ModifierKeys.Alt, Key.S, () =>{ Debug.WriteLine("S"); }); + //GlobalHotKey.RegisterHotKey(ModifierKeys.Alt, Key.D, () =>{ Debug.WriteLine("D"); }); //TestUi(); + + bool init = true; GameLoop((e) => { + bool currentStatus = e.FpsPatchGet(); + e.FpsPatchSet(!currentStatus); + var igtElapsed = TimeSpan.FromMilliseconds(e.GetInGameTimeMilliseconds()); Console.WriteLine($"IGT: {igtElapsed}"); }); diff --git a/src/soulmods/src/games/x64/eldenring.rs b/src/soulmods/src/games/x64/eldenring.rs index b30a8901..9f94f228 100644 --- a/src/soulmods/src/games/x64/eldenring.rs +++ b/src/soulmods/src/games/x64/eldenring.rs @@ -18,44 +18,248 @@ use ilhook::x64::{Hooker, HookType, Registers, CallbackOption, HookFlags, HookPo use mem_rs::prelude::*; use log::info; use crate::util::GLOBAL_VERSION; +use crate::util::Version; + +struct FpsOffsets +{ + target_frame_delta: isize, + frame_delta: isize, + timestamp_previous: isize, + timestamp_current: isize, + timestamp_history: isize, +} static mut IGT_BUFFER: f32 = 0.0f32; static mut IGT_HOOK: Option = None; +static mut FPS_HOOK: Option = None; +static mut FPS_HISTORY_HOOK: Option = None; +static mut FPS_CUSTOM_LIMIT_HOOK: Option = None; + +static mut FPS_HOOK_ENABLED: bool = false; +static mut FPS_CUSTOM_LIMIT: f32 = 0.0f32; + +static mut FPS_OFFSETS: FpsOffsets = FpsOffsets { + target_frame_delta: 0x0, + frame_delta: 0x0, + timestamp_previous: 0x0, + timestamp_current: 0x0, + timestamp_history: 0x0, +}; + + #[allow(unused_assignments)] pub fn init_eldenring() { unsafe { info!("version: {}", GLOBAL_VERSION); - + + // Get ER process let mut process = Process::new("eldenring.exe"); process.refresh().unwrap(); - let fn_increment_igt_address = process.scan_abs("increment igt", "48 c7 44 24 20 fe ff ff ff 0f 29 74 24 40 0f 28 f0 48 8b 0d ? ? ? ? 0f 28 c8 f3 0f 59 0d ? ? ? ?", 35, Vec::new()).unwrap().get_base_address(); + // AoB scan for timer patch + let fn_increment_igt_address = process.scan_abs("increment igt", "48 c7 44 24 20 fe ff ff ff 0f 29 74 24 40 0f 28 f0 48 8b 0d ? ? ? ? 0f 28 c8 f3 0f 59 0d ? ? ? ?", 35, Vec::new()).unwrap().get_base_address(); info!("increment IGT at 0x{:x}", fn_increment_igt_address); - unsafe extern "win64" fn increment_igt(registers: *mut Registers, _:usize) + // Enable timer patch + IGT_HOOK = Some(Hooker::new(fn_increment_igt_address, HookType::JmpBack(increment_igt), CallbackOption::None, 0, HookFlags::empty()).hook().unwrap()); + + // AoBs for FPS patches + let mut fps_aob = ""; + let mut fps_history_aob = ""; + let mut fps_custom_limit_aob = ""; + + // Adjust AoBs and offsets for FPS patch based on version + // The relevant function was adjusted slightly with the release of the DLC + if (GLOBAL_VERSION > Version { major: 2, minor: 0, build: 1, revision: 0 }) { - let mut frame_delta = std::mem::transmute::((*registers).xmm0 as u32); - //convert to milliseconds - frame_delta = frame_delta * 1000f32; - frame_delta = frame_delta * 0.96f32; //scale to IGT - - //Rather than casting, like the game does, make the behavior explicit by flooring - let mut floored_frame_delta = frame_delta.floor(); - let remainder = frame_delta - floored_frame_delta; - IGT_BUFFER = IGT_BUFFER + remainder; - - if IGT_BUFFER > 1.0f32 - { - IGT_BUFFER = IGT_BUFFER - 1f32; - floored_frame_delta += 1f32; - } - - (*registers).xmm1 = std::mem::transmute::(floored_frame_delta) as u128; + info!("Post-DLC version detected."); + + FPS_OFFSETS = FpsOffsets { + target_frame_delta: 0x1c, + frame_delta: 0x264, + timestamp_previous: 0x20, + timestamp_current: 0x28, + timestamp_history: 0x0, + }; + + fps_aob = "8b 83 64 02 00 00 89 83 b4 02 00 00"; + fps_history_aob = "0f b6 83 74 02 00 00 89 44 cb 08"; + fps_custom_limit_aob = "0F 57 F6 44 38 BB D0 02 00 00"; } + else + { + info!("Pre-DLC version detected."); - IGT_HOOK = Some(Hooker::new(fn_increment_igt_address, HookType::JmpBack(increment_igt), CallbackOption::None, 0, HookFlags::empty()).hook().unwrap()); + FPS_OFFSETS = FpsOffsets { + target_frame_delta: 0x20, + frame_delta: 0x26c, + timestamp_previous: 0x28, + timestamp_current: 0x30, + timestamp_history: 0x68, + }; + + fps_aob = "8b 83 6c 02 00 00 89 83 bc 02 00 00"; + fps_history_aob = "0f b6 83 7c 02 00 00 89 44 cb 70"; + fps_custom_limit_aob = "0F 57 F6 44 38 BB D8 02 00 00"; + } + + // AoB scan for FPS patch + let fn_fps_address = process.scan_abs("fps", fps_aob, 0, Vec::new()).unwrap().get_base_address(); + info!("FPS at 0x{:x}", fn_fps_address); + + // Enable FPS patch + FPS_HOOK = Some(Hooker::new(fn_fps_address, HookType::JmpBack(fps), CallbackOption::None, 0, HookFlags::empty()).hook().unwrap()); + + // AoB scan for FPS history patch + let fn_fps_history_address = process.scan_abs("fps history", fps_history_aob, 0, Vec::new()).unwrap().get_base_address(); + info!("FPS history at 0x{:x}", fn_fps_history_address); + + // Enable FPS history patch + FPS_HISTORY_HOOK = Some(Hooker::new(fn_fps_history_address, HookType::JmpBack(fps_history), CallbackOption::None, 0, HookFlags::empty()).hook().unwrap()); + + // AoB scan for FPS custom limit patch + let fn_fps_custom_limit_address = process.scan_abs("fps custom limit", fps_custom_limit_aob, 0, Vec::new()).unwrap().get_base_address(); + info!("FPS custom limit at 0x{:x}", fn_fps_custom_limit_address); + + // Enable FPS custom limit patch + FPS_CUSTOM_LIMIT_HOOK = Some(Hooker::new(fn_fps_custom_limit_address, HookType::JmpBack(fps_custom_limit), CallbackOption::None, 0, HookFlags::empty()).hook().unwrap()); + } +} + +#[no_mangle] +pub extern "C" fn fps_patch_get(b: *mut bool) // Get FPS patch status +{ + unsafe + { + *b = FPS_HOOK_ENABLED; + } +} + +#[no_mangle] +pub extern "C" fn fps_patch_set(b: *mut bool) // Set FPS patch status +{ + unsafe + { + FPS_HOOK_ENABLED = *b; + } +} + +#[no_mangle] +pub extern "C" fn fps_limit_get(f: *mut f32) // Get FPS custom limit +{ + unsafe + { + *f = FPS_CUSTOM_LIMIT; + } +} + +#[no_mangle] +pub extern "C" fn fps_limit_set(f: *mut f32) // Set FPS custom limit +{ + unsafe + { + FPS_CUSTOM_LIMIT = *f; + } +} + + +unsafe extern "win64" fn increment_igt(registers: *mut Registers, _:usize) +{ + let mut frame_delta = std::mem::transmute::((*registers).xmm0 as u32); + //convert to milliseconds + frame_delta = frame_delta * 1000f32; + frame_delta = frame_delta * 0.96f32; //scale to IGT + + //Rather than casting, like the game does, make the behavior explicit by flooring + let mut floored_frame_delta = frame_delta.floor(); + let remainder = frame_delta - floored_frame_delta; + IGT_BUFFER = IGT_BUFFER + remainder; + + if IGT_BUFFER > 1.0f32 + { + IGT_BUFFER = IGT_BUFFER - 1f32; + floored_frame_delta += 1f32; + } + + (*registers).xmm1 = std::mem::transmute::(floored_frame_delta) as u128; +} + +// FPS patch +// Sets the calculated frame delta to always be the target frame delta. +// It also sets the previous frames timestamp to be the current one minus the target frame delta. +// This makes it so the game always behaves as if it's running at the FPS limit, with slowdowns if the PC can't keep up. +// A second patch, "FPS history" below, is required in addition to this one to ensure accuracy. +unsafe extern "win64" fn fps(registers: *mut Registers, _:usize) +{ + if FPS_HOOK_ENABLED + { + let ptr_flipper = (*registers).rbx as *const u8; // Flipper struct - Contains all the stuff we need + + let ptr_target_frame_delta = ptr_flipper.offset(FPS_OFFSETS.target_frame_delta) as *mut f32; // Target frame delta - Set in a switch/case at the start + let ptr_timestamp_previous = ptr_flipper.offset(FPS_OFFSETS.timestamp_previous) as *mut u64; // Previous frames timestamp + let ptr_timestamp_current = ptr_flipper.offset(FPS_OFFSETS.timestamp_current) as *mut u64; // Current frames timestamp + let ptr_frame_delta = ptr_flipper.offset(FPS_OFFSETS.frame_delta) as *mut f32; // Current frames frame delta + + // Read target frame data, the current timestamp and then calculate the timestamp diff at stable FPS + let target_frame_delta = std::ptr::read_volatile(ptr_target_frame_delta); + let timestamp_current = std::ptr::read_volatile(ptr_timestamp_current); + let timestamp_diff = (target_frame_delta * 10000000.0) as i32; + + // Calculate the previous timestamp, as well as the frame delta + let timestamp_previous = timestamp_current - (timestamp_diff as u64); + let frame_delta = (timestamp_diff as f32) / 10000000.0; + + // Write values back + std::ptr::write_volatile(ptr_timestamp_previous, timestamp_previous); + std::ptr::write_volatile(ptr_frame_delta, frame_delta); + } +} + +// FPS history patch +// Similar to the FPS patch, this sets the difference between the previous and current frames timestamps to the target frame delta. +// This gets stored in an array with 32 elements, possibly for calculating FPS averages. +unsafe extern "win64" fn fps_history(registers: *mut Registers, _:usize) +{ + if FPS_HOOK_ENABLED + { + let ptr_flipper = (*registers).rbx as *const u8; // Flipper struct - Contains all the stuff we need + + let ptr_target_frame_delta = ptr_flipper.offset(FPS_OFFSETS.target_frame_delta) as *mut f32; // Target frame delta - Set in a switch/case at the start + let ptr_frame_delta_history = ptr_flipper.offset((*registers).rcx as isize * 0x8 + FPS_OFFSETS.timestamp_history) as *mut u64; // Frame delta history - In an array of 32, with the index incrementing each frame + + // Read the target frame delta and calculate the frame delta timestamp + let target_frame_delta = std::ptr::read_volatile(ptr_target_frame_delta); + let target_frame_delta_history = (target_frame_delta * 10000000.0) as u64; + + // Write values back + std::ptr::write_volatile(ptr_frame_delta_history, target_frame_delta_history); + } +} + +// FPS custom limit patch +// This patch adjusts the target frame delta based on a custom set FPS limit. +// It is only active while the FPS patch is enabled too, and uses the default value if it's set to 0 or less. +// This does not allow you to go above the stock FPS limit. It is purely a QoL patch to improve glitch consistency, not an FPS unlocker. +unsafe extern "win64" fn fps_custom_limit(registers: *mut Registers, _:usize) +{ + if FPS_HOOK_ENABLED && FPS_CUSTOM_LIMIT > 0.0f32 + { + let ptr_flipper = (*registers).rbx as *const u8; // Flipper struct - Contains all the stuff we need + + let ptr_target_frame_delta = ptr_flipper.offset(FPS_OFFSETS.target_frame_delta) as *mut f32; // Target frame delta - Set in a switch/case at the start + + // Read the stock target frame delta and calculate the custom target frame delta + let target_frame_delta = std::ptr::read_volatile(ptr_target_frame_delta); + let custom_target_frame_delta = 1.0f32 / FPS_CUSTOM_LIMIT; + + // Make sure the custom target frame delta is higher than the stock one, in order to avoid going above the stock FPS limit + if custom_target_frame_delta > target_frame_delta + { + // Write values back + std::ptr::write_volatile(ptr_target_frame_delta, custom_target_frame_delta); + } } } \ No newline at end of file diff --git a/src/soulmods/src/util/version.rs b/src/soulmods/src/util/version.rs index 14073c2e..3ddf01fb 100644 --- a/src/soulmods/src/util/version.rs +++ b/src/soulmods/src/util/version.rs @@ -2,9 +2,11 @@ use std::ffi::c_void; use std::fmt::{Display, Formatter}; use std::mem::MaybeUninit; use std::path::PathBuf; +use std::cmp::Ordering; use windows::core::PCWSTR; use windows::Win32::Storage::FileSystem::{GET_FILE_VERSION_INFO_FLAGS, GetFileVersionInfoExW, GetFileVersionInfoSizeW, VerQueryValueW, VS_FIXEDFILEINFO}; + pub struct Version { pub major: u16, @@ -33,55 +35,114 @@ impl Version pub fn from_file_version_info(path: PathBuf) -> Self { unsafe + { + let mut os_str = path.into_os_string(); + os_str.push("\0"); + + let str = os_str.to_string_lossy(); + + let mut thing: Vec = str.encode_utf16().collect(); + let pcwstr = PCWSTR::from_raw(thing.as_mut_ptr()); + + let mut dwlen: u32 = 0; + let file_version_info_size = GetFileVersionInfoSizeW(pcwstr, Some(&mut dwlen)); + let mut buffer: Vec = vec![0; file_version_info_size as usize]; + + let get_file_version_info_result = GetFileVersionInfoExW( + GET_FILE_VERSION_INFO_FLAGS(0x02), + pcwstr, + 0, + file_version_info_size, + buffer.as_mut_ptr() as *mut c_void + ); + + if get_file_version_info_result.is_err() + { + return Version::default(); + } + + let mut pu_len = 0; + let mut info_ptr = MaybeUninit::<*const VS_FIXEDFILEINFO>::uninit(); + + let ver_query_value_w_result = VerQueryValueW( + buffer.as_mut_ptr() as *const c_void, + windows::core::w!("\\"), + info_ptr.as_mut_ptr().cast(), + &mut pu_len + ); + + if ver_query_value_w_result.0 == 0 { - let mut os_str = path.into_os_string(); - os_str.push("\0"); - - let str = os_str.to_string_lossy(); - - let mut thing: Vec = str.encode_utf16().collect(); - let pcwstr = PCWSTR::from_raw(thing.as_mut_ptr()); - - let mut dwlen: u32 = 0; - let file_version_info_size = GetFileVersionInfoSizeW(pcwstr, Some(&mut dwlen)); - let mut buffer: Vec = vec![0; file_version_info_size as usize]; - - let get_file_version_info_result = GetFileVersionInfoExW( - GET_FILE_VERSION_INFO_FLAGS(0x02), - pcwstr, - 0, - file_version_info_size, - buffer.as_mut_ptr() as *mut c_void - ); - - if get_file_version_info_result.is_err() - { - return Version::default(); - } - - let mut pu_len = 0; - let mut info_ptr = MaybeUninit::<*const VS_FIXEDFILEINFO>::uninit(); - - let ver_query_value_w_result = VerQueryValueW( - buffer.as_mut_ptr() as *const c_void, - windows::core::w!("\\"), - info_ptr.as_mut_ptr().cast(), - &mut pu_len - ); - - if ver_query_value_w_result.0 == 0 - { - return Version::default(); - } - - let fixed_file_info = *(info_ptr.assume_init()); - - Version{ - major: (fixed_file_info.dwFileVersionMS >> 16) as u16, - minor: fixed_file_info.dwFileVersionMS as u16, - build: (fixed_file_info.dwFileVersionLS >> 16) as u16, - revision: fixed_file_info.dwFileVersionLS as u16 - } + return Version::default(); + } + + let fixed_file_info = *(info_ptr.assume_init()); + + Version{ + major: (fixed_file_info.dwFileVersionMS >> 16) as u16, + minor: fixed_file_info.dwFileVersionMS as u16, + build: (fixed_file_info.dwFileVersionLS >> 16) as u16, + revision: fixed_file_info.dwFileVersionLS as u16 } + } } } + +impl PartialEq for Version +{ + fn eq(&self, other: &Self) -> bool + { + if self.major == other.major && self.minor == other.minor && self.build == other.build && self.revision == other.revision + { + return true; + } + else + { + return false; + } + } +} + +impl PartialOrd for Version +{ + fn partial_cmp(&self, other: &Self) -> Option + { + if self.major > other.major + { + return Some(Ordering::Greater); + } + else if self.major < other.major + { + return Some(Ordering::Less); + } + + if self.minor > other.minor + { + return Some(Ordering::Greater); + } + else if self.minor < other.minor + { + return Some(Ordering::Less); + } + + if self.build > other.build + { + return Some(Ordering::Greater); + } + else if self.build < other.build + { + return Some(Ordering::Less); + } + + if self.revision > other.revision + { + return Some(Ordering::Greater); + } + else if self.revision < other.revision + { + return Some(Ordering::Less); + } + + return Some(Ordering::Equal); + } +} \ No newline at end of file