mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-01-01 05:13:40 +01:00
Use vtable replacement as swapchain hook method
This commit is contained in:
parent
509e33bf27
commit
18d9d136fb
6 changed files with 310 additions and 90 deletions
|
|
@ -46,6 +46,7 @@
|
||||||
<SDLCheck>true</SDLCheck>
|
<SDLCheck>true</SDLCheck>
|
||||||
<ConformanceMode>true</ConformanceMode>
|
<ConformanceMode>true</ConformanceMode>
|
||||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||||
|
<LanguageStandard_C>stdc17</LanguageStandard_C>
|
||||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||||
|
|
@ -66,6 +67,7 @@
|
||||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Use</PrecompiledHeader>
|
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Use</PrecompiledHeader>
|
||||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">26812</DisableSpecificWarnings>
|
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">26812</DisableSpecificWarnings>
|
||||||
|
<BuildStlModules Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</BuildStlModules>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<Link>
|
<Link>
|
||||||
<EnableCOMDATFolding>false</EnableCOMDATFolding>
|
<EnableCOMDATFolding>false</EnableCOMDATFolding>
|
||||||
|
|
@ -185,4 +187,4 @@
|
||||||
<Delete Files="$(OutDir)$(TargetName).lib" />
|
<Delete Files="$(OutDir)$(TargetName).lib" />
|
||||||
<Delete Files="$(OutDir)$(TargetName).exp" />
|
<Delete Files="$(OutDir)$(TargetName).exp" />
|
||||||
</Target>
|
</Target>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ public abstract class BaseAddressResolver
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether the resolver has successfully run <see cref="Setup32Bit(SigScanner)"/> or <see cref="Setup64Bit(SigScanner)"/>.
|
/// Gets or sets a value indicating whether the resolver has successfully run <see cref="Setup32Bit(SigScanner)"/> or <see cref="Setup64Bit(SigScanner)"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected bool IsResolved { get; set; }
|
public bool IsResolved { get; protected set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Setup the resolver, calling the appropriate method based on the process architecture,
|
/// Setup the resolver, calling the appropriate method based on the process architecture,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using System.Diagnostics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
using Dalamud.Game.Internal.DXGI.Definitions;
|
using Dalamud.Game.Internal.DXGI.Definitions;
|
||||||
|
using Dalamud.Utility;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
|
|
@ -17,12 +18,19 @@ namespace Dalamud.Game.Internal.DXGI;
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public class SwapChainVtableResolver : BaseAddressResolver, ISwapChainAddressResolver
|
public class SwapChainVtableResolver : BaseAddressResolver, ISwapChainAddressResolver
|
||||||
{
|
{
|
||||||
|
public static readonly int NumDxgiSwapChainMethods = Enum.GetValues(typeof(IDXGISwapChainVtbl)).Length;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IntPtr Present { get; set; }
|
public IntPtr Present { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IntPtr ResizeBuffers { get; set; }
|
public IntPtr ResizeBuffers { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the pointer to DxgiSwapChain.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr DxgiSwapChain { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether or not ReShade is loaded/used.
|
/// Gets a value indicating whether or not ReShade is loaded/used.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -31,28 +39,12 @@ public class SwapChainVtableResolver : BaseAddressResolver, ISwapChainAddressRes
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override unsafe void Setup64Bit(SigScanner sig)
|
protected override unsafe void Setup64Bit(SigScanner sig)
|
||||||
{
|
{
|
||||||
Device* kernelDev;
|
var kernelDev = Util.NotNull(Device.Instance(), "Device.Instance()");
|
||||||
SwapChain* swapChain;
|
var swapChain = Util.NotNull(kernelDev->SwapChain, "KernelDevice->SwapChain");
|
||||||
void* dxgiSwapChain;
|
var dxgiSwapChain = Util.NotNull(swapChain->DXGISwapChain, "SwapChain->DXGISwapChain");
|
||||||
|
|
||||||
while (true)
|
this.DxgiSwapChain = (nint)dxgiSwapChain;
|
||||||
{
|
var scVtbl = GetVTblAddresses(this.DxgiSwapChain, NumDxgiSwapChainMethods);
|
||||||
kernelDev = Device.Instance();
|
|
||||||
if (kernelDev == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
swapChain = kernelDev->SwapChain;
|
|
||||||
if (swapChain == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
dxgiSwapChain = swapChain->DXGISwapChain;
|
|
||||||
if (dxgiSwapChain == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var scVtbl = GetVTblAddresses(new IntPtr(dxgiSwapChain), Enum.GetValues(typeof(IDXGISwapChainVtbl)).Length);
|
|
||||||
|
|
||||||
this.Present = scVtbl[(int)IDXGISwapChainVtbl.Present];
|
this.Present = scVtbl[(int)IDXGISwapChainVtbl.Present];
|
||||||
|
|
||||||
|
|
|
||||||
143
Dalamud/Hooking/ObjectVTableHook.cs
Normal file
143
Dalamud/Hooking/ObjectVTableHook.cs
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Dalamud.Hooking;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Manages a hook that works by replacing the vtable of target object.
|
||||||
|
/// </summary>
|
||||||
|
public sealed unsafe class ObjectVTableHook : IDisposable
|
||||||
|
{
|
||||||
|
private readonly nint** ppVtbl;
|
||||||
|
private readonly int numMethods;
|
||||||
|
|
||||||
|
private readonly nint* pVtblOriginal;
|
||||||
|
private readonly nint* pVtblOverriden;
|
||||||
|
|
||||||
|
private readonly object?[] detourDelegates;
|
||||||
|
|
||||||
|
private bool released;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ObjectVTableHook"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ppVtbl">Address to vtable. Usually the address of the object itself.</param>
|
||||||
|
/// <param name="numMethods">Number of methods in this vtable.</param>
|
||||||
|
public ObjectVTableHook(nint ppVtbl, int numMethods)
|
||||||
|
{
|
||||||
|
this.ppVtbl = (nint**)ppVtbl;
|
||||||
|
this.numMethods = numMethods;
|
||||||
|
this.detourDelegates = new object?[numMethods];
|
||||||
|
this.pVtblOriginal = *this.ppVtbl;
|
||||||
|
this.pVtblOverriden = (nint*)Marshal.AllocHGlobal(sizeof(void*) * numMethods);
|
||||||
|
this.VtblOriginal.CopyTo(this.VtblOverriden);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finalizes an instance of the <see cref="ObjectVTableHook"/> class.
|
||||||
|
/// </summary>
|
||||||
|
~ObjectVTableHook() => this.ReleaseUnmanagedResources();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the span view of original vtable.
|
||||||
|
/// </summary>
|
||||||
|
private Span<nint> VtblOriginal => new(this.pVtblOriginal, this.numMethods);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the span view of overriden vtable.
|
||||||
|
/// </summary>
|
||||||
|
private Span<nint> VtblOverriden => new(this.pVtblOverriden, this.numMethods);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disables the hook.
|
||||||
|
/// </summary>
|
||||||
|
public void Disable() => *this.ppVtbl = this.pVtblOriginal;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
this.ReleaseUnmanagedResources();
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables the hook.
|
||||||
|
/// </summary>
|
||||||
|
public void Enable() => *this.ppVtbl = this.pVtblOverriden;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the original method address of the given method index.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="methodIndex">The method index.</param>
|
||||||
|
/// <returns>Address of the original method.</returns>
|
||||||
|
public nint GetOriginalMethodAddress(int methodIndex)
|
||||||
|
{
|
||||||
|
this.EnsureMethodIndex(methodIndex);
|
||||||
|
return this.pVtblOriginal[methodIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the original method of the given method index, as a delegate of given type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="methodIndex">The method index.</param>
|
||||||
|
/// <typeparam name="T">Type of delegate.</typeparam>
|
||||||
|
/// <returns>Delegate to the original method.</returns>
|
||||||
|
public T GetOriginalMethodDelegate<T>(int methodIndex)
|
||||||
|
where T : Delegate
|
||||||
|
{
|
||||||
|
this.EnsureMethodIndex(methodIndex);
|
||||||
|
return Marshal.GetDelegateForFunctionPointer<T>(this.pVtblOriginal[methodIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets a method to the original function.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="methodIndex">The method index.</param>
|
||||||
|
public void ResetVtableEntry(int methodIndex)
|
||||||
|
{
|
||||||
|
this.EnsureMethodIndex(methodIndex);
|
||||||
|
this.VtblOverriden[methodIndex] = this.pVtblOriginal[methodIndex];
|
||||||
|
this.detourDelegates[methodIndex] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets a method in vtable to the given address of function.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="methodIndex">The method index.</param>
|
||||||
|
/// <param name="pfn">Address of the detour function.</param>
|
||||||
|
/// <param name="refkeep">Additional reference to keep in memory.</param>
|
||||||
|
public void SetVtableEntry(int methodIndex, nint pfn, object? refkeep)
|
||||||
|
{
|
||||||
|
this.EnsureMethodIndex(methodIndex);
|
||||||
|
this.VtblOverriden[methodIndex] = pfn;
|
||||||
|
this.detourDelegates[methodIndex] = refkeep;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets a method in vtable to the given delegate.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="methodIndex">The method index.</param>
|
||||||
|
/// <param name="detourDelegate">Detour delegate.</param>
|
||||||
|
/// <typeparam name="T">Type of delegate.</typeparam>
|
||||||
|
public void SetVtableEntry<T>(int methodIndex, T detourDelegate)
|
||||||
|
where T : Delegate =>
|
||||||
|
this.SetVtableEntry(methodIndex, Marshal.GetFunctionPointerForDelegate(detourDelegate), detourDelegate);
|
||||||
|
|
||||||
|
private void EnsureMethodIndex(int methodIndex)
|
||||||
|
{
|
||||||
|
if (methodIndex < 0 || methodIndex >= this.numMethods)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(methodIndex), methodIndex, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReleaseUnmanagedResources()
|
||||||
|
{
|
||||||
|
if (!this.released)
|
||||||
|
{
|
||||||
|
this.Disable();
|
||||||
|
Marshal.FreeHGlobal((nint)this.pVtblOverriden);
|
||||||
|
this.released = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,7 @@ using Dalamud.Game.ClientState.GamePad;
|
||||||
using Dalamud.Game.ClientState.Keys;
|
using Dalamud.Game.ClientState.Keys;
|
||||||
using Dalamud.Game.Gui.Internal;
|
using Dalamud.Game.Gui.Internal;
|
||||||
using Dalamud.Game.Internal.DXGI;
|
using Dalamud.Game.Internal.DXGI;
|
||||||
|
using Dalamud.Game.Internal.DXGI.Definitions;
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Dalamud.Interface.GameFonts;
|
using Dalamud.Interface.GameFonts;
|
||||||
using Dalamud.Interface.Internal.ManagedAsserts;
|
using Dalamud.Interface.Internal.ManagedAsserts;
|
||||||
|
|
@ -60,6 +61,9 @@ internal class InterfaceManager : IDisposable, IServiceType
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly Framework framework = Service<Framework>.Get();
|
private readonly Framework framework = Service<Framework>.Get();
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly SigScanner sigScanner = Service<SigScanner>.Get();
|
||||||
|
|
||||||
private readonly ManualResetEvent fontBuildSignal;
|
private readonly ManualResetEvent fontBuildSignal;
|
||||||
private readonly SwapChainVtableResolver address;
|
private readonly SwapChainVtableResolver address;
|
||||||
private readonly Hook<DispatchMessageWDelegate> dispatchMessageWHook;
|
private readonly Hook<DispatchMessageWDelegate> dispatchMessageWHook;
|
||||||
|
|
@ -67,8 +71,12 @@ internal class InterfaceManager : IDisposable, IServiceType
|
||||||
private Hook<ProcessMessageDelegate> processMessageHook;
|
private Hook<ProcessMessageDelegate> processMessageHook;
|
||||||
private RawDX11Scene? scene;
|
private RawDX11Scene? scene;
|
||||||
|
|
||||||
private Hook<PresentDelegate>? presentHook;
|
private ObjectVTableHook? swapChainHook;
|
||||||
private Hook<ResizeBuffersDelegate>? resizeBuffersHook;
|
|
||||||
|
// Use these instead of querying for functions inside the above,
|
||||||
|
// since we behave differently if ReShade or stuff are detected.
|
||||||
|
private PresentDelegate presentOriginal;
|
||||||
|
private ResizeBuffersDelegate resizeBuffersOriginal;
|
||||||
|
|
||||||
// can't access imgui IO before first present call
|
// can't access imgui IO before first present call
|
||||||
private bool lastWantCapture = false;
|
private bool lastWantCapture = false;
|
||||||
|
|
@ -84,8 +92,9 @@ internal class InterfaceManager : IDisposable, IServiceType
|
||||||
null, "user32.dll", "SetCursor", 0, this.SetCursorDetour);
|
null, "user32.dll", "SetCursor", 0, this.SetCursorDetour);
|
||||||
|
|
||||||
this.fontBuildSignal = new ManualResetEvent(false);
|
this.fontBuildSignal = new ManualResetEvent(false);
|
||||||
|
|
||||||
this.address = new SwapChainVtableResolver();
|
this.address = new SwapChainVtableResolver();
|
||||||
|
|
||||||
|
this.QueueHookResolution();
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
|
|
@ -220,13 +229,13 @@ internal class InterfaceManager : IDisposable, IServiceType
|
||||||
this.framework.RunOnFrameworkThread(() =>
|
this.framework.RunOnFrameworkThread(() =>
|
||||||
{
|
{
|
||||||
this.setCursorHook.Dispose();
|
this.setCursorHook.Dispose();
|
||||||
this.presentHook?.Dispose();
|
|
||||||
this.resizeBuffersHook?.Dispose();
|
|
||||||
this.dispatchMessageWHook.Dispose();
|
this.dispatchMessageWHook.Dispose();
|
||||||
this.processMessageHook?.Dispose();
|
this.processMessageHook?.Dispose();
|
||||||
}).Wait();
|
}).Wait();
|
||||||
|
|
||||||
this.scene?.Dispose();
|
this.scene?.Dispose();
|
||||||
|
|
||||||
|
this.swapChainHook?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
@ -598,22 +607,22 @@ internal class InterfaceManager : IDisposable, IServiceType
|
||||||
*/
|
*/
|
||||||
private IntPtr PresentDetour(IntPtr swapChain, uint syncInterval, uint presentFlags)
|
private IntPtr PresentDetour(IntPtr swapChain, uint syncInterval, uint presentFlags)
|
||||||
{
|
{
|
||||||
if (this.scene != null && swapChain != this.scene.SwapChain.NativePointer)
|
|
||||||
return this.presentHook!.Original(swapChain, syncInterval, presentFlags);
|
|
||||||
|
|
||||||
if (this.scene == null)
|
if (this.scene == null)
|
||||||
this.InitScene(swapChain);
|
|
||||||
|
|
||||||
if (this.address.IsReshade)
|
|
||||||
{
|
{
|
||||||
var pRes = this.presentHook.Original(swapChain, syncInterval, presentFlags);
|
this.InitScene(swapChain);
|
||||||
|
|
||||||
this.RenderImGui();
|
|
||||||
|
|
||||||
return pRes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.RenderImGui();
|
nint res;
|
||||||
|
if (this.address.IsReshade)
|
||||||
|
{
|
||||||
|
res = this.presentOriginal(swapChain, syncInterval, presentFlags);
|
||||||
|
this.RenderImGui();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.RenderImGui();
|
||||||
|
res = this.presentOriginal(swapChain, syncInterval, presentFlags);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.deferredDisposeTextures.Count > 0)
|
if (this.deferredDisposeTextures.Count > 0)
|
||||||
{
|
{
|
||||||
|
|
@ -626,7 +635,95 @@ internal class InterfaceManager : IDisposable, IServiceType
|
||||||
this.deferredDisposeTextures.Clear();
|
this.deferredDisposeTextures.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.presentHook.Original(swapChain, syncInterval, presentFlags);
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void QueueHookResolution()
|
||||||
|
{
|
||||||
|
if (this.GameWindowHandle != 0 && this.address.IsResolved)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.framework.RunOnFrameworkThread(() =>
|
||||||
|
{
|
||||||
|
if (this.GameWindowHandle == 0)
|
||||||
|
{
|
||||||
|
while ((this.GameWindowHandle = NativeFunctions.FindWindowEx(IntPtr.Zero, this.GameWindowHandle, "FFXIVGAME", IntPtr.Zero)) != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
_ = User32.GetWindowThreadProcessId(this.GameWindowHandle, out var pid);
|
||||||
|
|
||||||
|
if (pid == Environment.ProcessId && User32.IsWindowVisible(this.GameWindowHandle))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.GameWindowHandle == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Service<DalamudConfiguration>.Get().WindowIsImmersive)
|
||||||
|
{
|
||||||
|
this.SetImmersiveMode(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Could not enable immersive mode");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.address.IsResolved)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.address.Setup();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Could not resolve addresses and set up hooks; trying again later");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Information("Resolver setup complete");
|
||||||
|
|
||||||
|
Log.Information("===== S W A P C H A I N =====");
|
||||||
|
Log.Information($"Is ReShade: {this.address.IsReshade}");
|
||||||
|
Log.Information($"Present address 0x{this.address.Present.ToInt64():X}");
|
||||||
|
Log.Information($"ResizeBuffers address 0x{this.address.ResizeBuffers.ToInt64():X}");
|
||||||
|
|
||||||
|
this.presentOriginal =
|
||||||
|
Marshal.GetDelegateForFunctionPointer<PresentDelegate>(this.address.Present);
|
||||||
|
this.resizeBuffersOriginal =
|
||||||
|
Marshal.GetDelegateForFunctionPointer<ResizeBuffersDelegate>(this.address.ResizeBuffers);
|
||||||
|
|
||||||
|
this.swapChainHook = new ObjectVTableHook(
|
||||||
|
this.address.DxgiSwapChain,
|
||||||
|
SwapChainVtableResolver.NumDxgiSwapChainMethods);
|
||||||
|
this.swapChainHook.SetVtableEntry<PresentDelegate>(
|
||||||
|
(int)IDXGISwapChainVtbl.Present,
|
||||||
|
this.PresentDetour);
|
||||||
|
this.swapChainHook.SetVtableEntry<ResizeBuffersDelegate>(
|
||||||
|
(int)IDXGISwapChainVtbl.ResizeBuffers,
|
||||||
|
this.ResizeBuffersDetour);
|
||||||
|
this.swapChainHook.Enable();
|
||||||
|
Log.Information("Present and ResizeBuffers hooked");
|
||||||
|
|
||||||
|
var wndProcAddress = this.sigScanner.ScanText("E8 ?? ?? ?? ?? 80 7C 24 ?? ?? 74 ?? B8");
|
||||||
|
Log.Information($"WndProc address 0x{wndProcAddress.ToInt64():X}");
|
||||||
|
this.processMessageHook =
|
||||||
|
Hook<ProcessMessageDelegate>.FromAddress(wndProcAddress, this.ProcessMessageDetour);
|
||||||
|
|
||||||
|
this.setCursorHook.Enable();
|
||||||
|
this.dispatchMessageWHook.Enable();
|
||||||
|
this.processMessageHook.Enable();
|
||||||
|
Log.Information("Hooks enabled");
|
||||||
|
}
|
||||||
|
}).ContinueWith(_ => this.QueueHookResolution());
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|
@ -991,49 +1088,6 @@ internal class InterfaceManager : IDisposable, IServiceType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[ServiceManager.CallWhenServicesReady]
|
|
||||||
private void ContinueConstruction(SigScanner sigScanner, Framework framework)
|
|
||||||
{
|
|
||||||
this.address.Setup(sigScanner);
|
|
||||||
framework.RunOnFrameworkThread(() =>
|
|
||||||
{
|
|
||||||
while ((this.GameWindowHandle = NativeFunctions.FindWindowEx(IntPtr.Zero, this.GameWindowHandle, "FFXIVGAME", IntPtr.Zero)) != IntPtr.Zero)
|
|
||||||
{
|
|
||||||
_ = User32.GetWindowThreadProcessId(this.GameWindowHandle, out var pid);
|
|
||||||
|
|
||||||
if (pid == Environment.ProcessId && User32.IsWindowVisible(this.GameWindowHandle))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (Service<DalamudConfiguration>.Get().WindowIsImmersive)
|
|
||||||
this.SetImmersiveMode(true);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "Could not enable immersive mode");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.presentHook = Hook<PresentDelegate>.FromAddress(this.address.Present, this.PresentDetour);
|
|
||||||
this.resizeBuffersHook = Hook<ResizeBuffersDelegate>.FromAddress(this.address.ResizeBuffers, this.ResizeBuffersDetour);
|
|
||||||
|
|
||||||
Log.Verbose("===== S W A P C H A I N =====");
|
|
||||||
Log.Verbose($"Present address 0x{this.presentHook!.Address.ToInt64():X}");
|
|
||||||
Log.Verbose($"ResizeBuffers address 0x{this.resizeBuffersHook!.Address.ToInt64():X}");
|
|
||||||
|
|
||||||
var wndProcAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? 80 7C 24 ?? ?? 74 ?? B8");
|
|
||||||
Log.Verbose($"WndProc address 0x{wndProcAddress.ToInt64():X}");
|
|
||||||
this.processMessageHook = Hook<ProcessMessageDelegate>.FromAddress(wndProcAddress, this.ProcessMessageDetour);
|
|
||||||
|
|
||||||
this.setCursorHook.Enable();
|
|
||||||
this.presentHook.Enable();
|
|
||||||
this.resizeBuffersHook.Enable();
|
|
||||||
this.dispatchMessageWHook.Enable();
|
|
||||||
this.processMessageHook.Enable();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is intended to only be called as a handler attached to scene.OnNewRenderFrame
|
// This is intended to only be called as a handler attached to scene.OnNewRenderFrame
|
||||||
private void RebuildFontsInternal()
|
private void RebuildFontsInternal()
|
||||||
{
|
{
|
||||||
|
|
@ -1078,14 +1132,9 @@ internal class InterfaceManager : IDisposable, IServiceType
|
||||||
|
|
||||||
this.ResizeBuffers?.InvokeSafely();
|
this.ResizeBuffers?.InvokeSafely();
|
||||||
|
|
||||||
// We have to ensure we're working with the main swapchain,
|
|
||||||
// as viewports might be resizing as well
|
|
||||||
if (this.scene == null || swapChain != this.scene.SwapChain.NativePointer)
|
|
||||||
return this.resizeBuffersHook!.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags);
|
|
||||||
|
|
||||||
this.scene?.OnPreResize();
|
this.scene?.OnPreResize();
|
||||||
|
|
||||||
var ret = this.resizeBuffersHook!.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags);
|
var ret = this.resizeBuffersOriginal(swapChain, bufferCount, width, height, newFormat, swapChainFlags);
|
||||||
if (ret.ToInt64() == 0x887A0001)
|
if (ret.ToInt64() == 0x887A0001)
|
||||||
{
|
{
|
||||||
Log.Error("invalid call to resizeBuffers");
|
Log.Error("invalid call to resizeBuffers");
|
||||||
|
|
|
||||||
|
|
@ -546,6 +546,40 @@ public static class Util
|
||||||
/// <returns>If Windows 11 has been detected.</returns>
|
/// <returns>If Windows 11 has been detected.</returns>
|
||||||
public static bool IsWindows11() => Environment.OSVersion.Version.Build >= 22000;
|
public static bool IsWindows11() => Environment.OSVersion.Version.Build >= 22000;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures that a pointer is not null, or throw a <see cref="NullReferenceException" />
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">Pointer value.</param>
|
||||||
|
/// <param name="what">Help text for exception.</param>
|
||||||
|
/// <typeparam name="T">Backing data type of the pointer.</typeparam>
|
||||||
|
/// <returns>The value, ensured to be not null.</returns>
|
||||||
|
public static unsafe T* NotNull<T>(T* value, string what)
|
||||||
|
where T : unmanaged
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
throw new NullReferenceException($"{what} is null.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures that a pointer is not null, or throw a <see cref="NullReferenceException" />
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">Pointer value.</param>
|
||||||
|
/// <param name="what">Help text for exception.</param>
|
||||||
|
/// <returns>The value, ensured to be not null.</returns>
|
||||||
|
public static unsafe void* NotNull(void* value, string what)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
throw new NullReferenceException($"{what} is null.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Open a link in the default browser.
|
/// Open a link in the default browser.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue