diff --git a/Ausar/App.xaml.cs b/Ausar/App.xaml.cs index cb74a88..d0f34ce 100644 --- a/Ausar/App.xaml.cs +++ b/Ausar/App.xaml.cs @@ -1,4 +1,5 @@ -using System.Diagnostics; +using Ausar.Extensions; +using System.Diagnostics; using System.IO; using System.Windows; diff --git a/Ausar/Ausar.csproj b/Ausar/Ausar.csproj index 8c85802..f32a014 100644 --- a/Ausar/Ausar.csproj +++ b/Ausar/Ausar.csproj @@ -28,7 +28,7 @@ - + diff --git a/Ausar/Configuration.cs b/Ausar/Configuration.cs index 05f3b6b..507c0ea 100644 --- a/Ausar/Configuration.cs +++ b/Ausar/Configuration.cs @@ -25,6 +25,8 @@ public class Configuration : INotifyPropertyChanged public bool IsApplyCustomFOVToVehicles { get; set; } = false; + public byte CrosshairScaleMode { get; set; } = (byte)ECrosshairScaleMode.Off; + public bool IsDynamicAspectRatio { get; set; } = false; public int ResolutionScale { get; set; } = 100; @@ -120,6 +122,9 @@ public int PerformancePreset #region Frontend Config + [JsonIgnore] + public bool IsCrosshairScaleModeAvailable { get; set; } = true; + [JsonIgnore] public bool IsDynamicAspectRatioAvailable { get; set; } = true; diff --git a/Ausar/Enums/ECrosshairScaleMode.cs b/Ausar/Enums/ECrosshairScaleMode.cs new file mode 100644 index 0000000..694e9b4 --- /dev/null +++ b/Ausar/Enums/ECrosshairScaleMode.cs @@ -0,0 +1,9 @@ +namespace Ausar.Enums +{ + public enum ECrosshairScaleMode : byte + { + Off, + Scale, + ScaleSmartLink + } +} diff --git a/Ausar/Extensions/StringExtensions.cs b/Ausar/Extensions/StringExtensions.cs new file mode 100644 index 0000000..6e8351d --- /dev/null +++ b/Ausar/Extensions/StringExtensions.cs @@ -0,0 +1,28 @@ +namespace Ausar.Extensions +{ + public static class StringExtensions + { + public static string Truncate(this string in_str, int in_maxLength, bool in_isEllipsis = false) + { + if (string.IsNullOrEmpty(in_str)) + return in_str; + + if (in_str.Length <= in_maxLength) + return in_str; + + if (in_isEllipsis) + { + if (in_maxLength <= 3) + { + return in_str[..in_maxLength]; + } + else + { + return in_str[..(in_maxLength - 3)] + "..."; + } + } + + return in_str[..in_maxLength]; + } + } +} diff --git a/Ausar/Game/Memory.cs b/Ausar/Game/Memory.cs index 80fd0bb..b9a637e 100644 --- a/Ausar/Game/Memory.cs +++ b/Ausar/Game/Memory.cs @@ -12,10 +12,15 @@ public class Memory : IDisposable { private const float _defaultAspectRatio = 1.777777777777778f; private const float _defaultFOV = 78.0f; + private const float _maxCrosshairScale = 150.0f; + private const float _minCrosshairScale = 50.0f; + private const float _maxFOV = 150.0f; + private const float _minFOV = 60.0f; - private CancellationTokenSource _cancellationTokenSource = new(); + private static bool _isScaleCrosshairToFOVInitialised = false; + private static bool _isDynamicAspectRatioInitialised = false; - private bool _isDynamicAspectRatioInitialised = false; + private bool _isUpdating = true; private int[] _simPresentIntervals = [60, 30, 120]; @@ -72,6 +77,11 @@ public float FOV } } + public float FOVZoomScalar + { + get => Process.Read(Process.ToASLR(0x144857858)); + } + // Research from H5Tweak, ported from game version 1.114.4592.2 public float AspectRatio { @@ -187,7 +197,7 @@ public Memory(Process in_process) private void Update() { - while (!_cancellationTokenSource.IsCancellationRequested) + while (_isUpdating) { InstallPatches(); @@ -263,6 +273,107 @@ private void PatchApplyCustomFOVToVehicles(bool in_isEnabled) } } + private unsafe void PatchCrosshairScaleMode(ECrosshairScaleMode in_mode) + { + /* HACK: The mid-ASM hook is written in a function that is run + so frequently in-game that it can cause a crash if the + code is jumped to whilst still being written. */ + if (Map.Contains("mainmenu")) + { + App.Settings.IsCrosshairScaleModeAvailable = true; + } + else + { + App.Settings.IsCrosshairScaleModeAvailable = false; + return; + } + + var crosshairMatrixHookAddr = Process.ToASLR(0x1417147C7); + var crosshairScaleModeAddr = Process.Alloc("CrosshairScaleMode", 1); + + if (in_mode != ECrosshairScaleMode.Off) + { + Process.Write(crosshairScaleModeAddr, App.Settings.CrosshairScaleMode); + + if (!_isScaleCrosshairToFOVInitialised) + { + // Hook matrix code for crosshair. + _isScaleCrosshairToFOVInitialised = Process.TryWriteAsmHook + ( + $@" + mov r11d, 0x42700000 ; Store minimum FOV value (60.0f) in R11D. + movd xmm0, r11d ; Copy R11D to XMM0. + mov r11d, 0x43160000 ; Store maximum FOV value (150.0f) in R11D. + movd xmm1, r11d ; Copy R11D to XMM1. + mov r11, {(long)Process.ToASLR(0x14590E210)} ; Store address to FOV value in R11. + movss xmm2, dword ptr [r11] ; Copy FOV value to XMM2. + mov r11, {(long)Process.ToASLR(0x144857858)} ; Store address to FOV zoom scalar value in R11. + movss xmm3, dword ptr [r11] ; Copy FOV zoom scalar value to XMM3. + + mov r11, {(long)crosshairScaleModeAddr} ; Store address to crosshair scale mode in R11. + cmp byte ptr [r11], 2 ; Compare crosshair scale mode with 2 (ECrosshairScaleMode.ScaleSmartLink). + jne ignoreZoom ; Jump if not 2; otherwise, apply FOV zoom scalar value. + divss xmm2, xmm3 ; XMM2 = FOV / FOVZoomScalar + + ignoreZoom: + subss xmm2, xmm0 ; XMM2 -= _minFOV + subss xmm1, xmm0 ; XMM1 = _maxFOV - _minFOV + divss xmm2, xmm1 ; XMM2 /= XMM1 + + mov r11d, 0x42480000 ; Store minimum crosshair scale value (50.0f) in R11D. + movd xmm3, r11d ; Copy R11D to XMM3. + mov r11d, 0x43160000 ; Store maximum crosshair scale value (150.0f) in R11D. + movd xmm4, r11d ; Copy R11D to XMM4. + + ; Linear interpolation routine. + subss xmm4, xmm3 ; XMM4 = _maxCrosshairScale - _minCrosshairScale + mulss xmm2, xmm4 ; XMM2 *= XMM4 + addss xmm2, xmm3 ; XMM2 += _minCrosshairScale + + mov r11d, 0x41F00000 ; Store crosshair scale remainder value (30.0f) in R11D. + movd xmm5, r11d ; Copy R11D to XMM5. + addss xmm2, xmm5 ; XMM2 += 30.0f + + ; Clamp routine. + comiss xmm2, xmm3 ; Compare result with _minCrosshairScale. + jae checkMax ; Jump if result is greater than minimum. + movaps xmm2, xmm3 ; XMM2 = _minCrosshairScale + + checkMax: + mov r11d, 0x43160000 ; Store maximum crosshair scale value (150.0f) in R11D. + movd xmm4, r11d ; Copy R11D to XMM4. + comiss xmm2, xmm4 ; Compare result with _maxCrosshairScale. + jbe end ; Jump if result is less than maximum. + movaps xmm2, xmm4 ; XMM2 = _maxCrosshairScale + + end: + mov r11d, 0x42C80000 ; Store percentage max value (100.0f) in R11D. + movd xmm3, r11d ; Copy R11D to XMM3. + divss xmm2, xmm3 ; XMM2 /= 100.0f + mov r11d, 0x43B40000 ; Store radius max value (360.0f) in R11D. + movd xmm4, r11d ; Copy R11D to XMM4. + mulss xmm2, xmm4 ; XMM2 *= 360.0f + movss dword ptr [rax + 0x30], xmm2 ; Copy XMM2 to matrix at scale offset. + + ; Restore original code. + movups xmm0, xmmword ptr [rax] + lea r11, qword ptr [rsp + 0xC0] + movups xmm1, xmmword ptr [rax + 0x10] + ", + + crosshairMatrixHookAddr + ); + } + } + else + { + Process.RemoveAsmHook(crosshairMatrixHookAddr); + Process.Free(crosshairScaleModeAddr); + + _isScaleCrosshairToFOVInitialised = false; + } + } + private void PatchDynamicAspectRatio(bool in_isEnabled) { var smartLinkAspectRatioHookAddr = Process.ToASLR(0x14162D4BC); @@ -272,12 +383,12 @@ private void PatchDynamicAspectRatio(bool in_isEnabled) if (!_isDynamicAspectRatioInitialised) { // Hook aspect ratio code for Smart Link. - Process.WriteAsmHook + _isDynamicAspectRatioInitialised = Process.TryWriteAsmHook ( $@" mov r8, {Process.ToASLR(0x14333E458)} ; Store pointer to real aspect ratio in R8. movss xmm6, dword ptr [r8] ; Store real aspect ratio in XMM6. - mov r9d, 0x3FE38E39 ; Store 1.777777777777778f in R9D. + mov r9d, 0x3FE38E39 ; Store default aspect ratio (1.777777777777778f) in R9D. movd xmm7, r9d ; Copy R9D to XMM7. divss xmm6, xmm7 ; Divide real aspect ratio (XMM6) by default aspect ratio (XMM7). mulss xmm0, xmm6 ; Multiply Smart Link aspect ratio (XMM0) by our divided value (XMM6). @@ -291,13 +402,11 @@ private void PatchDynamicAspectRatio(bool in_isEnabled) smartLinkAspectRatioHookAddr ); - - _isDynamicAspectRatioInitialised = true; } } else { - Process.RestoreMemory(smartLinkAspectRatioHookAddr); + Process.RemoveAsmHook(smartLinkAspectRatioHookAddr); _isDynamicAspectRatioInitialised = false; } @@ -458,6 +567,7 @@ public void InstallPatches() return; PatchApplyCustomFOVToVehicles(App.Settings.IsApplyCustomFOVToVehicles); + PatchCrosshairScaleMode((ECrosshairScaleMode)App.Settings.CrosshairScaleMode); PatchDynamicAspectRatio(App.Settings.IsDynamicAspectRatio); PatchNetworkIntegrity(FPS > 60); PatchToggleFrontend(App.Settings.IsToggleFrontend); @@ -491,9 +601,10 @@ public void UninstallPatches() { /* FIXME: this will only work once, not a big deal for its current use case though. */ - _cancellationTokenSource.Cancel(); + _isUpdating = false; PatchApplyCustomFOVToVehicles(false); + PatchCrosshairScaleMode(ECrosshairScaleMode.Off); PatchDynamicAspectRatio(false); PatchNetworkIntegrity(false); PatchToggleFrontend(true); @@ -523,7 +634,7 @@ public void UninstallPatches() public void Dispose() { - _cancellationTokenSource.Cancel(); + _isUpdating = false; } } } diff --git a/Ausar/Helpers/MathHelper.cs b/Ausar/Helpers/MathHelper.cs new file mode 100644 index 0000000..1cdf860 --- /dev/null +++ b/Ausar/Helpers/MathHelper.cs @@ -0,0 +1,10 @@ +namespace Ausar.Helpers +{ + internal class MathHelper + { + public static float Lerp(float in_start, float in_end, float in_time) + { + return in_start + (in_end - in_start) * in_time; + } + } +} diff --git a/Ausar/UI/Trainer.xaml b/Ausar/UI/Trainer.xaml index c011f92..674268c 100644 --- a/Ausar/UI/Trainer.xaml +++ b/Ausar/UI/Trainer.xaml @@ -58,10 +58,23 @@ - - + + + + + + + + + + + - + + @@ -136,7 +149,7 @@ Minimum="0" Maximum="300" SpinButtonPlacementMode="Inline" SmallChange="1"/> - + diff --git a/Ausar/UI/Trainer.xaml.cs b/Ausar/UI/Trainer.xaml.cs index 4d9c4a8..fa30403 100644 --- a/Ausar/UI/Trainer.xaml.cs +++ b/Ausar/UI/Trainer.xaml.cs @@ -3,6 +3,7 @@ using Ausar.Helpers; using Ausar.Logger.Handlers; using ModernWpf.Controls; +using System.ComponentModel; using System.Diagnostics; using System.Windows; using System.Windows.Controls; @@ -47,6 +48,28 @@ protected override void OnSourceInitialized(EventArgs e) Task.Run(() => WaitForProcess(_cancellationTokenSource.Token)); } + protected override void OnClosing(CancelEventArgs e) + { + if (!App.Settings.IsCrosshairScaleModeAvailable || + !App.Settings.IsDynamicAspectRatioAvailable) + { + var result = MessageBox.Show + ( + "There are still patches installed that will not be uninstalled unless you return to the main menu first.\n\n" + + "Exiting now may lead to unexpected behaviour if Ausar is launched again with this same instance of Halo 5: Forge.\n\n" + + "Are you sure you want to quit?", + "Ausar", + MessageBoxButton.YesNo, + MessageBoxImage.Warning + ); + + if (result == MessageBoxResult.No) + e.Cancel = true; + } + + base.OnClosing(e); + } + private void Status(string in_text, Brush in_colour = null) { StatusText.Text = in_text;