From 80ca36b0a8728a77f16de5f3e99bfefb4066ad38 Mon Sep 17 00:00:00 2001 From: Jade Macho Date: Sun, 9 Mar 2025 22:40:46 +0100 Subject: [PATCH] Move mostly away from D3D resize to fake window size (fix ReShade) --- CustomResolution/Hooks/CursorPosHooks.cs | 1 + CustomResolution/Hooks/WindowRectHooks.cs | 81 ++++++++++++++++++++ CustomResolution/Hooks/WndProcHook.cs | 23 ++++++ CustomResolution/Plugin.cs | 90 ++++++++++++----------- CustomResolution/Service.cs | 1 + 5 files changed, 153 insertions(+), 43 deletions(-) create mode 100644 CustomResolution/Hooks/WindowRectHooks.cs diff --git a/CustomResolution/Hooks/CursorPosHooks.cs b/CustomResolution/Hooks/CursorPosHooks.cs index 5c1c6d6..fae64be 100644 --- a/CustomResolution/Hooks/CursorPosHooks.cs +++ b/CustomResolution/Hooks/CursorPosHooks.cs @@ -73,4 +73,5 @@ public sealed unsafe class CursorPosHooks : IDisposable return _setCursorPosHook.Original(x, y); } + } diff --git a/CustomResolution/Hooks/WindowRectHooks.cs b/CustomResolution/Hooks/WindowRectHooks.cs new file mode 100644 index 0000000..f1f1e8b --- /dev/null +++ b/CustomResolution/Hooks/WindowRectHooks.cs @@ -0,0 +1,81 @@ +using CustomResolution.WndProcHookManagerProxyApi; +using Dalamud.Hooking; +using Serilog.Events; +using System; +using System.Linq; +using System.Reflection.Emit; +using System.Runtime.InteropServices; +using TerraFX.Interop.Windows; + + +namespace CustomResolution.Hooks; + +// THIS. IS. UGLY. +public sealed unsafe class WindowRectHooks : IDisposable +{ + private readonly Hook _getClientRectHook; + private readonly Hook _setWindowPosHook; + + public WindowRectHooks() + { + _getClientRectHook = Service.GameInteropProvider.HookFromSymbol("user32.dll", "GetClientRect", GetClientRectDetour); + _getClientRectHook.Enable(); + + _setWindowPosHook = Service.GameInteropProvider.HookFromSymbol("user32.dll", "SetWindowPos", SetWindowPosDetour); + _setWindowPosHook.Enable(); + } + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private unsafe delegate bool GetClientRectDelegate(HWND hWnd, RECT* lpRect); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private unsafe delegate bool SetWindowPosDelegate(HWND hWnd, HWND hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); + + public void Dispose() + { + _getClientRectHook.Dispose(); + } + + public bool GetClientRectOrig(HWND hWnd, RECT* lpRect) + { + return _getClientRectHook.Original(hWnd, lpRect); + } + + private bool GetClientRectDetour(HWND hWnd, RECT* lpRect) + { + var rv = _getClientRectHook.Original(hWnd, lpRect); + + if (!rv) + { + return false; + } + +#if false + Service.PluginLog.Debug($"GetClientRectDetour A @ {lpRect->left} {lpRect->top} {lpRect->right} {lpRect->bottom}"); +#endif + + Service.Plugin.ConvertCoordsWinToGame(ref lpRect->right, ref lpRect->bottom); + +#if false + Service.PluginLog.Debug($"GetClientRectDetour B @ {lpRect->left} {lpRect->top} {lpRect->right} {lpRect->bottom}"); +#endif + + return rv; + } + + private bool SetWindowPosDetour(HWND hWnd, HWND hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags) + { +#if false + Service.PluginLog.Debug($"SetWindowPosDetour A @ {X} {Y} {cx} {cy}"); +#endif + + Service.Plugin.ConvertCoordsGameToWin(ref cx, ref cy); + +#if false + Service.PluginLog.Debug($"SetWindowPosDetour B @ {X} {Y} {cx} {cy}"); +#endif + + return _setWindowPosHook.Original(hWnd, hWndInsertAfter, X, Y, cx, cy, uFlags); + } + +} diff --git a/CustomResolution/Hooks/WndProcHook.cs b/CustomResolution/Hooks/WndProcHook.cs index 9eb0a00..40be0cb 100644 --- a/CustomResolution/Hooks/WndProcHook.cs +++ b/CustomResolution/Hooks/WndProcHook.cs @@ -71,6 +71,21 @@ public sealed unsafe class WndProcHook : IDisposable return value; } + private static void ParamToSize(LPARAM param, out uint x, out uint y) + { + x = (ushort) (param.Value & 0xFFFF); + y = (ushort) (param.Value >> 16 & 0xFFFF); + } + + private static LPARAM SizeToParam(uint x, uint y) + { + nint value = 0; + value |= ((ushort) x) & 0xFFFF; + value |= ((ushort) y) >> 16 & 0xFFFF; + + return value; + } + private void Invoke(WndProcEventArgs args) { if (Service.Plugin is not { } plugin) @@ -78,6 +93,14 @@ public sealed unsafe class WndProcHook : IDisposable return; } + if (args.Message == WM.WM_SIZE) + { + ParamToCoords(args.LParam, out int x, out int y); + Service.PluginLog.Debug($"WM_SIZE {x} x {y}"); + Service.Plugin.Update(); + args.LParam = SizeToParam(Service.Plugin.CurrentWidth, Service.Plugin.CurrentHeight); + } + if (WM.WM_MOUSEFIRST <= args.Message && args.Message <= WM.WM_MOUSELAST) { ParamToCoords(args.LParam, out int x, out int y); diff --git a/CustomResolution/Plugin.cs b/CustomResolution/Plugin.cs index 279b408..053e4e6 100644 --- a/CustomResolution/Plugin.cs +++ b/CustomResolution/Plugin.cs @@ -1,8 +1,10 @@ -using Dalamud.Game.ClientState.Keys; +using CustomResolution.Hooks; +using Dalamud.Game.ClientState.Keys; using Dalamud.IoC; using Dalamud.Plugin; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; +using FFXIVClientStructs.FFXIV.Client.System.Framework; using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Common.Lua; using FFXIVClientStructs.Interop; @@ -11,6 +13,7 @@ using System; using System.Collections.Generic; using System.Linq; using TerraFX.Interop.Windows; +using static Dalamud.Interface.Utility.Raii.ImRaii; using static TerraFX.Interop.Windows.Windows; namespace CustomResolution; @@ -23,10 +26,9 @@ public sealed unsafe class Plugin : IDalamudPlugin private readonly List _cmds; private bool _unloading = false; - private int _tickCount = 0; + private bool _wasEnabled = false; private HWND _currentHwnd; private RECT _currentClientRect; - private RECT _currentWindowRect; private DXVKDWMHackMode _currentDXVKDWMHackMode = DXVKDWMHackMode.Off; public Plugin(IDalamudPluginInterface pluginInterface) @@ -44,6 +46,7 @@ public sealed unsafe class Plugin : IDalamudPlugin Service.WndProcHook = new(); Service.CursorPosHooks = new(); + Service.WindowRectHooks = new(); _cmds = typeof(Plugin).Assembly.GetTypes() .Where(t => !t.IsAbstract && typeof(Cmd).IsAssignableFrom(t)) @@ -77,8 +80,6 @@ public sealed unsafe class Plugin : IDalamudPlugin lock (_disposeLock) { - _tickCount = 0; - Service.Framework.Update -= OnFrameworkUpdate; Service.Framework.RunOnFrameworkThread(Update); @@ -89,6 +90,7 @@ public sealed unsafe class Plugin : IDalamudPlugin cmd.Dispose(); } + Service.WindowRectHooks.Dispose(); Service.CursorPosHooks.Dispose(); Service.WndProcHook.Dispose(); @@ -99,7 +101,7 @@ public sealed unsafe class Plugin : IDalamudPlugin public void ConvertCoordsWinToGame(ref int x, ref int y) { - if (CurrentWidth == CurrentWindowWidth && CurrentHeight == CurrentWindowHeight) + if (CurrentWidth == CurrentWindowWidth && CurrentHeight == CurrentWindowHeight) { return; } @@ -174,6 +176,15 @@ public sealed unsafe class Plugin : IDalamudPlugin public void Update() { var dev = Device.Instance(); + var framework = Framework.Instance(); + var win = framework->GameWindow; + + _currentHwnd = (HWND) (IntPtr) dev->hWnd; + + fixed (RECT* currentClientRectPtr = &_currentClientRect) + { + Service.WindowRectHooks.GetClientRectOrig(_currentHwnd, currentClientRectPtr); + } int rectWidth = _currentClientRect.right - _currentClientRect.left; int rectHeight = _currentClientRect.bottom - _currentClientRect.top; @@ -185,11 +196,16 @@ public sealed unsafe class Plugin : IDalamudPlugin uint width, height; - bool disabled = _unloading || !Service.Config.IsEnabled; - - if (Service.Config.IsScale || disabled) + bool enabled = !_unloading && Service.Config.IsEnabled; + if (_wasEnabled != enabled) { - var scale = disabled ? 1f : Service.Config.Scale; + Service.PluginLog.Info($"Changing state to: {enabled}"); + _wasEnabled = enabled; + } + + if (Service.Config.IsScale || !enabled) + { + var scale = enabled ? Service.Config.Scale : 1f; width = (uint) Math.Round(rectWidth * scale); height = (uint) Math.Round(rectHeight * scale); @@ -200,27 +216,33 @@ public sealed unsafe class Plugin : IDalamudPlugin height = Service.Config.Height; } + if (width < 256) + { + width = 256; + } + + if (height < 256) + { + height = 256; + } + if (width != dev->Width || height != dev->Height) { - Service.PluginLog.Info($"Changing resolution to {width} x {height}"); - - if (width < 256) - { - width = 256; - } - - if (height < 256) - { - height = 256; - } - + Service.PluginLog.Info($"Changing graphics resolution from {dev->Width} x {dev->Height} to {width} x {height}"); dev->NewWidth = width; dev->NewHeight = height; dev->RequestResolutionChange = 1; + Service.PluginLog.Info($"Changing game window from {win->WindowWidth} x {win->WindowHeight} to {width} x {height}"); + win->WindowWidth = (int) width; + win->WindowHeight = (int) height; } - // TODO: This isn't accurate! Figure out how to read the game's settings instead. - CurrentBorderlessFullscreen = (GetWindowLong(_currentHwnd, GWL.GWL_STYLE) & WS.WS_SYSMENU) == 0; + //Service.PluginLog.Debug($"NewWidth 0x{(long) (IntPtr) (&dev->NewWidth):X16}"); + //Service.PluginLog.Debug($"GameWindow->Width 0x{(long) (IntPtr) (&win->WindowWidth):X16}"); + + //Service.PluginLog.Info($"Game window at {win->WindowWidth} x {win->WindowHeight}"); + + CurrentBorderlessFullscreen = win->Borderless; if (Service.Config.DXVKDWMHackMode != DXVKDWMHackMode.Off && !_unloading) { @@ -338,25 +360,7 @@ public sealed unsafe class Plugin : IDalamudPlugin Service.Config.Save(); } - var dev = Device.Instance(); - - _currentHwnd = (HWND) (IntPtr) dev->hWnd; - - fixed (RECT* currentClientRectPtr = &_currentClientRect) - fixed (RECT* currentWindowRectPtr = &_currentWindowRect) - { - GetClientRect(_currentHwnd, currentClientRectPtr); - GetWindowRect(_currentHwnd, currentWindowRectPtr); - } - - if (_tickCount++ >= 2) - { - _tickCount = 0; - - Update(); - } - - _tickCount++; + Update(); } } } diff --git a/CustomResolution/Service.cs b/CustomResolution/Service.cs index 5ad09bd..81f946d 100644 --- a/CustomResolution/Service.cs +++ b/CustomResolution/Service.cs @@ -16,6 +16,7 @@ public sealed class Service public static WndProcHook WndProcHook { get; internal set; } = null!; public static CursorPosHooks CursorPosHooks { get; internal set; } = null!; + public static WindowRectHooks WindowRectHooks { get; internal set; } = null!; [PluginService] public static IDalamudPluginInterface PluginInterface { get; private set; } = null!;