mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-31 04:43:40 +01:00
Implement ReShade addon interface
This commit is contained in:
parent
42c728ec6c
commit
1f315be94e
7 changed files with 2140 additions and 244 deletions
75
Dalamud/Interface/Internal/InterfaceManager.AsHook.cs
Normal file
75
Dalamud/Interface/Internal/InterfaceManager.AsHook.cs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
using System.Diagnostics;
|
||||
|
||||
using Dalamud.Utility;
|
||||
|
||||
namespace Dalamud.Interface.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// This class manages interaction with the ImGui interface.
|
||||
/// </summary>
|
||||
internal partial class InterfaceManager
|
||||
{
|
||||
private IntPtr PresentDetour(IntPtr swapChain, uint syncInterval, uint presentFlags)
|
||||
{
|
||||
if (!SwapChainHelper.IsGameDeviceSwapChain(swapChain))
|
||||
return this.dxgiPresentHook!.Original(swapChain, syncInterval, presentFlags);
|
||||
|
||||
Debug.Assert(this.dxgiPresentHook is not null, "How did PresentDetour get called when presentHook is null?");
|
||||
Debug.Assert(this.dalamudAtlas is not null, "dalamudAtlas should have been set already");
|
||||
|
||||
if (this.scene == null)
|
||||
this.InitScene(swapChain);
|
||||
|
||||
Debug.Assert(this.scene is not null, "InitScene did not set the scene field, but did not throw an exception.");
|
||||
|
||||
if (!this.dalamudAtlas!.HasBuiltAtlas)
|
||||
{
|
||||
if (this.dalamudAtlas.BuildTask.Exception != null)
|
||||
{
|
||||
// TODO: Can we do something more user-friendly here? Unload instead?
|
||||
Log.Error(this.dalamudAtlas.BuildTask.Exception, "Failed to initialize Dalamud base fonts");
|
||||
Util.Fatal("Failed to initialize Dalamud base fonts.\nPlease report this error.", "Dalamud");
|
||||
}
|
||||
|
||||
return this.dxgiPresentHook!.Original(swapChain, syncInterval, presentFlags);
|
||||
}
|
||||
|
||||
this.CumulativePresentCalls++;
|
||||
this.IsMainThreadInPresent = true;
|
||||
|
||||
while (this.runBeforeImGuiRender.TryDequeue(out var action))
|
||||
action.InvokeSafely();
|
||||
|
||||
RenderImGui(this.scene!);
|
||||
this.PostImGuiRender();
|
||||
this.IsMainThreadInPresent = false;
|
||||
|
||||
return this.dxgiPresentHook!.Original(swapChain, syncInterval, presentFlags);
|
||||
}
|
||||
|
||||
private IntPtr AsHookResizeBuffersDetour(
|
||||
IntPtr swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags)
|
||||
{
|
||||
if (!SwapChainHelper.IsGameDeviceSwapChain(swapChain))
|
||||
return this.resizeBuffersHook!.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags);
|
||||
|
||||
#if DEBUG
|
||||
Log.Verbose(
|
||||
$"Calling resizebuffers swap@{swapChain.ToInt64():X}{bufferCount} {width} {height} {newFormat} {swapChainFlags}");
|
||||
#endif
|
||||
|
||||
this.ResizeBuffers?.InvokeSafely();
|
||||
|
||||
this.scene?.OnPreResize();
|
||||
|
||||
var ret = this.resizeBuffersHook!.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags);
|
||||
if (ret.ToInt64() == 0x887A0001)
|
||||
{
|
||||
Log.Error("invalid call to resizeBuffers");
|
||||
}
|
||||
|
||||
this.scene?.OnPostResize((int)width, (int)height);
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
using System.Diagnostics;
|
||||
|
||||
using Dalamud.Utility;
|
||||
|
||||
using TerraFX.Interop.DirectX;
|
||||
|
||||
namespace Dalamud.Interface.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// This class manages interaction with the ImGui interface.
|
||||
/// </summary>
|
||||
internal partial class InterfaceManager
|
||||
{
|
||||
private unsafe void ReShadeAddonInterfaceOnDestroySwapChain(ref ReShadeAddonInterface.ApiObject swapchain)
|
||||
{
|
||||
var swapChain = swapchain.GetNative<IDXGISwapChain>();
|
||||
if (this.scene?.SwapChain.NativePointer != (nint)swapChain)
|
||||
return;
|
||||
|
||||
this.scene?.OnPreResize();
|
||||
}
|
||||
|
||||
private unsafe void ReShadeAddonInterfaceOnInitSwapChain(ref ReShadeAddonInterface.ApiObject swapchain)
|
||||
{
|
||||
var swapChain = swapchain.GetNative<IDXGISwapChain>();
|
||||
if (this.scene?.SwapChain.NativePointer != (nint)swapChain)
|
||||
return;
|
||||
|
||||
DXGI_SWAP_CHAIN_DESC desc;
|
||||
if (swapChain->GetDesc(&desc).FAILED)
|
||||
return;
|
||||
|
||||
this.scene?.OnPostResize((int)desc.BufferDesc.Width, (int)desc.BufferDesc.Height);
|
||||
}
|
||||
|
||||
private void ReShadeAddonInterfaceOnReShadeOverlay(ref ReShadeAddonInterface.ApiObject runtime)
|
||||
{
|
||||
var swapChain = runtime.GetNative();
|
||||
|
||||
if (this.scene == null)
|
||||
this.InitScene(swapChain);
|
||||
|
||||
if (this.scene?.SwapChain.NativePointer != swapChain)
|
||||
return;
|
||||
|
||||
Debug.Assert(this.dalamudAtlas is not null, "this.dalamudAtlas is not null");
|
||||
|
||||
if (!this.dalamudAtlas!.HasBuiltAtlas)
|
||||
{
|
||||
if (this.dalamudAtlas.BuildTask.Exception != null)
|
||||
{
|
||||
// TODO: Can we do something more user-friendly here? Unload instead?
|
||||
Log.Error(this.dalamudAtlas.BuildTask.Exception, "Failed to initialize Dalamud base fonts");
|
||||
Util.Fatal("Failed to initialize Dalamud base fonts.\nPlease report this error.", "Dalamud");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.CumulativePresentCalls++;
|
||||
this.IsMainThreadInPresent = true;
|
||||
|
||||
while (this.runBeforeImGuiRender.TryDequeue(out var action))
|
||||
action.InvokeSafely();
|
||||
|
||||
RenderImGui(this.scene!);
|
||||
this.PostImGuiRender();
|
||||
this.IsMainThreadInPresent = false;
|
||||
}
|
||||
|
||||
private nint AsReShadeAddonResizeBuffersDetour(
|
||||
nint swapChain,
|
||||
uint bufferCount,
|
||||
uint width,
|
||||
uint height,
|
||||
uint newFormat,
|
||||
uint swapChainFlags)
|
||||
{
|
||||
// Hooked vtbl instead of registering ReShade event. This check is correct.
|
||||
if (!SwapChainHelper.IsGameDeviceSwapChain(swapChain))
|
||||
return this.resizeBuffersHook!.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags);
|
||||
|
||||
this.ResizeBuffers?.InvokeSafely();
|
||||
return this.resizeBuffersHook!.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags);
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ using Dalamud.Game;
|
|||
using Dalamud.Game.ClientState.GamePad;
|
||||
using Dalamud.Game.ClientState.Keys;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Hooking.Internal;
|
||||
using Dalamud.Hooking.WndProcHook;
|
||||
using Dalamud.Interface.ImGuiNotification.Internal;
|
||||
using Dalamud.Interface.Internal.ManagedAsserts;
|
||||
|
|
@ -29,10 +30,11 @@ using ImGuiNET;
|
|||
|
||||
using ImGuiScene;
|
||||
|
||||
using JetBrains.Annotations;
|
||||
|
||||
using PInvoke;
|
||||
|
||||
using SharpDX;
|
||||
using SharpDX.DXGI;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
// general dev notes, here because it's easiest
|
||||
|
||||
|
|
@ -52,7 +54,7 @@ namespace Dalamud.Interface.Internal;
|
|||
/// This class manages interaction with the ImGui interface.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal class InterfaceManager : IInternalDisposableService
|
||||
internal partial class InterfaceManager : IInternalDisposableService
|
||||
{
|
||||
/// <summary>
|
||||
/// The default font size, in points.
|
||||
|
|
@ -75,6 +77,11 @@ internal class InterfaceManager : IInternalDisposableService
|
|||
[ServiceManager.ServiceDependency]
|
||||
private readonly Framework framework = Service<Framework>.Get();
|
||||
|
||||
// ReShadeAddonInterface requires hooks to be alive to unregister itself.
|
||||
[ServiceManager.ServiceDependency]
|
||||
[UsedImplicitly]
|
||||
private readonly HookManager hookManager = Service<HookManager>.Get();
|
||||
|
||||
private readonly ConcurrentQueue<Action> runBeforeImGuiRender = new();
|
||||
private readonly ConcurrentQueue<Action> runAfterImGuiRender = new();
|
||||
|
||||
|
|
@ -82,8 +89,8 @@ internal class InterfaceManager : IInternalDisposableService
|
|||
|
||||
private Hook<SetCursorDelegate>? setCursorHook;
|
||||
private Hook<DxgiPresentDelegate>? dxgiPresentHook;
|
||||
private Hook<ReshadeOnPresentDelegate>? reshadeOnPresentHook;
|
||||
private Hook<ResizeBuffersDelegate>? resizeBuffersHook;
|
||||
private ReShadeAddonInterface? reShadeAddonInterface;
|
||||
|
||||
private IFontAtlas? dalamudAtlas;
|
||||
private ILockedImFont? defaultFontResourceLock;
|
||||
|
|
@ -101,9 +108,6 @@ internal class InterfaceManager : IInternalDisposableService
|
|||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr DxgiPresentDelegate(IntPtr swapChain, uint syncInterval, uint presentFlags);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate void ReshadeOnPresentDelegate(nint swapChain, uint flags, nint presentParams);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr ResizeBuffersDelegate(IntPtr swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags);
|
||||
|
||||
|
|
@ -299,8 +303,8 @@ internal class InterfaceManager : IInternalDisposableService
|
|||
this.wndProcHookManager.PreWndProc -= this.WndProcHookManagerOnPreWndProc;
|
||||
Interlocked.Exchange(ref this.setCursorHook, null)?.Dispose();
|
||||
Interlocked.Exchange(ref this.dxgiPresentHook, null)?.Dispose();
|
||||
Interlocked.Exchange(ref this.reshadeOnPresentHook, null)?.Dispose();
|
||||
Interlocked.Exchange(ref this.resizeBuffersHook, null)?.Dispose();
|
||||
Interlocked.Exchange(ref this.reShadeAddonInterface, null)?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -431,11 +435,11 @@ internal class InterfaceManager : IInternalDisposableService
|
|||
try
|
||||
{
|
||||
var dxgiDev = this.Device.QueryInterfaceOrNull<SharpDX.DXGI.Device>();
|
||||
var dxgiAdapter = dxgiDev?.Adapter.QueryInterfaceOrNull<Adapter4>();
|
||||
var dxgiAdapter = dxgiDev?.Adapter.QueryInterfaceOrNull<SharpDX.DXGI.Adapter4>();
|
||||
if (dxgiAdapter == null)
|
||||
return null;
|
||||
|
||||
var memInfo = dxgiAdapter.QueryVideoMemoryInfo(0, MemorySegmentGroup.Local);
|
||||
var memInfo = dxgiAdapter.QueryVideoMemoryInfo(0, SharpDX.DXGI.MemorySegmentGroup.Local);
|
||||
return (memInfo.CurrentUsage, memInfo.CurrentReservation);
|
||||
}
|
||||
catch
|
||||
|
|
@ -464,11 +468,11 @@ internal class InterfaceManager : IInternalDisposableService
|
|||
if (this.GameWindowHandle == 0)
|
||||
throw new InvalidOperationException("Game window is not yet ready.");
|
||||
var value = enabled ? 1 : 0;
|
||||
((Result)NativeFunctions.DwmSetWindowAttribute(
|
||||
((HRESULT)NativeFunctions.DwmSetWindowAttribute(
|
||||
this.GameWindowHandle,
|
||||
NativeFunctions.DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE,
|
||||
ref value,
|
||||
sizeof(int))).CheckError();
|
||||
sizeof(int))).ThrowOnError();
|
||||
}
|
||||
|
||||
private static InterfaceManager WhenFontsReady()
|
||||
|
|
@ -632,86 +636,6 @@ internal class InterfaceManager : IInternalDisposableService
|
|||
args.SuppressWithValue(r.Value);
|
||||
}
|
||||
|
||||
private void ReshadeOnPresentDetour(nint swapChain, uint flags, nint presentParams)
|
||||
{
|
||||
if (!SwapChainHelper.IsGameDeviceSwapChain(swapChain))
|
||||
{
|
||||
this.reshadeOnPresentHook!.Original(swapChain, flags, presentParams);
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Assert(this.reshadeOnPresentHook is not null, "this.reshadeOnPresentHook is not null");
|
||||
Debug.Assert(this.dalamudAtlas is not null, "this.dalamudAtlas is not null");
|
||||
|
||||
if (this.scene == null)
|
||||
this.InitScene(swapChain);
|
||||
|
||||
Debug.Assert(this.scene is not null, "InitScene did not set the scene field, but did not throw an exception.");
|
||||
|
||||
if (!this.dalamudAtlas!.HasBuiltAtlas)
|
||||
{
|
||||
if (this.dalamudAtlas.BuildTask.Exception != null)
|
||||
{
|
||||
// TODO: Can we do something more user-friendly here? Unload instead?
|
||||
Log.Error(this.dalamudAtlas.BuildTask.Exception, "Failed to initialize Dalamud base fonts");
|
||||
Util.Fatal("Failed to initialize Dalamud base fonts.\nPlease report this error.", "Dalamud");
|
||||
}
|
||||
|
||||
this.reshadeOnPresentHook!.Original(swapChain, flags, presentParams);
|
||||
return;
|
||||
}
|
||||
|
||||
this.CumulativePresentCalls++;
|
||||
this.IsMainThreadInPresent = true;
|
||||
|
||||
while (this.runBeforeImGuiRender.TryDequeue(out var action))
|
||||
action.InvokeSafely();
|
||||
|
||||
this.reshadeOnPresentHook!.Original(swapChain, flags, presentParams);
|
||||
|
||||
RenderImGui(this.scene!);
|
||||
this.PostImGuiRender();
|
||||
this.IsMainThreadInPresent = false;
|
||||
}
|
||||
|
||||
private IntPtr PresentDetour(IntPtr swapChain, uint syncInterval, uint presentFlags)
|
||||
{
|
||||
if (!SwapChainHelper.IsGameDeviceSwapChain(swapChain))
|
||||
return this.dxgiPresentHook!.Original(swapChain, syncInterval, presentFlags);
|
||||
|
||||
Debug.Assert(this.dxgiPresentHook is not null, "How did PresentDetour get called when presentHook is null?");
|
||||
Debug.Assert(this.dalamudAtlas is not null, "dalamudAtlas should have been set already");
|
||||
|
||||
if (this.scene == null)
|
||||
this.InitScene(swapChain);
|
||||
|
||||
Debug.Assert(this.scene is not null, "InitScene did not set the scene field, but did not throw an exception.");
|
||||
|
||||
if (!this.dalamudAtlas!.HasBuiltAtlas)
|
||||
{
|
||||
if (this.dalamudAtlas.BuildTask.Exception != null)
|
||||
{
|
||||
// TODO: Can we do something more user-friendly here? Unload instead?
|
||||
Log.Error(this.dalamudAtlas.BuildTask.Exception, "Failed to initialize Dalamud base fonts");
|
||||
Util.Fatal("Failed to initialize Dalamud base fonts.\nPlease report this error.", "Dalamud");
|
||||
}
|
||||
|
||||
return this.dxgiPresentHook!.Original(swapChain, syncInterval, presentFlags);
|
||||
}
|
||||
|
||||
this.CumulativePresentCalls++;
|
||||
this.IsMainThreadInPresent = true;
|
||||
|
||||
while (this.runBeforeImGuiRender.TryDequeue(out var action))
|
||||
action.InvokeSafely();
|
||||
|
||||
RenderImGui(this.scene!);
|
||||
this.PostImGuiRender();
|
||||
this.IsMainThreadInPresent = false;
|
||||
|
||||
return this.dxgiPresentHook!.Original(swapChain, syncInterval, presentFlags);
|
||||
}
|
||||
|
||||
private void PostImGuiRender()
|
||||
{
|
||||
while (this.runAfterImGuiRender.TryDequeue(out var action))
|
||||
|
|
@ -799,10 +723,7 @@ internal class InterfaceManager : IInternalDisposableService
|
|||
() =>
|
||||
{
|
||||
// Update the ImGui default font.
|
||||
unsafe
|
||||
{
|
||||
ImGui.GetIO().NativePtr->FontDefault = fontLocked.ImFont;
|
||||
}
|
||||
ImGui.GetIO().NativePtr->FontDefault = fontLocked.ImFont;
|
||||
|
||||
// Update the reference to the resources of the default font.
|
||||
this.defaultFontResourceLock?.Dispose();
|
||||
|
|
@ -818,7 +739,6 @@ internal class InterfaceManager : IInternalDisposableService
|
|||
_ = this.dalamudAtlas.BuildFontsAsync();
|
||||
|
||||
SwapChainHelper.BusyWaitForGameDeviceSwapChain();
|
||||
SwapChainHelper.DetectReShade();
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -839,52 +759,36 @@ internal class InterfaceManager : IInternalDisposableService
|
|||
this.SetCursorDetour);
|
||||
|
||||
Log.Verbose("===== S W A P C H A I N =====");
|
||||
this.resizeBuffersHook = Hook<ResizeBuffersDelegate>.FromAddress(
|
||||
(nint)SwapChainHelper.GameDeviceSwapChainVtbl->ResizeBuffers,
|
||||
this.ResizeBuffersDetour);
|
||||
Log.Verbose($"ResizeBuffers address {Util.DescribeAddress(this.resizeBuffersHook!.Address)}");
|
||||
|
||||
if (SwapChainHelper.ReshadeOnPresent is null)
|
||||
if (ReShadeAddonInterface.TryRegisterAddon(out this.reShadeAddonInterface))
|
||||
{
|
||||
var addr = (nint)SwapChainHelper.GameDeviceSwapChainVtbl->Present;
|
||||
this.dxgiPresentHook = Hook<DxgiPresentDelegate>.FromAddress(addr, this.PresentDetour);
|
||||
Log.Verbose($"ReShade::DXGISwapChain::on_present address {Util.DescribeAddress(addr)}");
|
||||
this.resizeBuffersHook = Hook<ResizeBuffersDelegate>.FromAddress(
|
||||
(nint)SwapChainHelper.GameDeviceSwapChainVtbl->ResizeBuffers,
|
||||
this.AsReShadeAddonResizeBuffersDetour);
|
||||
Log.Verbose($"ResizeBuffers address {Util.DescribeAddress(this.resizeBuffersHook!.Address)}");
|
||||
|
||||
Log.Verbose(
|
||||
"Registered as a ReShade({name}: 0x{addr:X}) addon.",
|
||||
ReShadeAddonInterface.ReShadeModule!.FileName,
|
||||
ReShadeAddonInterface.ReShadeModule!.BaseAddress);
|
||||
this.reShadeAddonInterface.InitSwapChain += this.ReShadeAddonInterfaceOnInitSwapChain;
|
||||
this.reShadeAddonInterface.DestroySwapChain += this.ReShadeAddonInterfaceOnDestroySwapChain;
|
||||
this.reShadeAddonInterface.ReShadeOverlay += this.ReShadeAddonInterfaceOnReShadeOverlay;
|
||||
}
|
||||
else
|
||||
{
|
||||
var addr = (nint)SwapChainHelper.ReshadeOnPresent;
|
||||
this.reshadeOnPresentHook = Hook<ReshadeOnPresentDelegate>.FromAddress(addr, this.ReshadeOnPresentDetour);
|
||||
this.resizeBuffersHook = Hook<ResizeBuffersDelegate>.FromAddress(
|
||||
(nint)SwapChainHelper.GameDeviceSwapChainVtbl->ResizeBuffers,
|
||||
this.AsHookResizeBuffersDetour);
|
||||
Log.Verbose($"ResizeBuffers address {Util.DescribeAddress(this.resizeBuffersHook!.Address)}");
|
||||
|
||||
var addr = (nint)SwapChainHelper.GameDeviceSwapChainVtbl->Present;
|
||||
this.dxgiPresentHook = Hook<DxgiPresentDelegate>.FromAddress(addr, this.PresentDetour);
|
||||
Log.Verbose($"IDXGISwapChain::Present address {Util.DescribeAddress(addr)}");
|
||||
}
|
||||
|
||||
this.setCursorHook.Enable();
|
||||
this.dxgiPresentHook?.Enable();
|
||||
this.reshadeOnPresentHook?.Enable();
|
||||
this.resizeBuffersHook.Enable();
|
||||
}
|
||||
|
||||
private IntPtr ResizeBuffersDetour(IntPtr swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags)
|
||||
{
|
||||
if (!SwapChainHelper.IsGameDeviceSwapChain(swapChain))
|
||||
return this.resizeBuffersHook!.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags);
|
||||
|
||||
#if DEBUG
|
||||
Log.Verbose($"Calling resizebuffers swap@{swapChain.ToInt64():X}{bufferCount} {width} {height} {newFormat} {swapChainFlags}");
|
||||
#endif
|
||||
|
||||
this.ResizeBuffers?.InvokeSafely();
|
||||
|
||||
this.scene?.OnPreResize();
|
||||
|
||||
var ret = this.resizeBuffersHook!.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags);
|
||||
if (ret.ToInt64() == 0x887A0001)
|
||||
{
|
||||
Log.Error("invalid call to resizeBuffers");
|
||||
}
|
||||
|
||||
this.scene?.OnPostResize((int)width, (int)height);
|
||||
|
||||
return ret;
|
||||
this.resizeBuffersHook?.Enable();
|
||||
}
|
||||
|
||||
private IntPtr SetCursorDetour(IntPtr hCursor)
|
||||
|
|
|
|||
1706
Dalamud/Interface/Internal/ReShadeAddonInterface.AddonEvent.cs
Normal file
1706
Dalamud/Interface/Internal/ReShadeAddonInterface.AddonEvent.cs
Normal file
File diff suppressed because it is too large
Load diff
59
Dalamud/Interface/Internal/ReShadeAddonInterface.Exports.cs
Normal file
59
Dalamud/Interface/Internal/ReShadeAddonInterface.Exports.cs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
using static TerraFX.Interop.Windows.Windows;
|
||||
|
||||
namespace Dalamud.Interface.Internal;
|
||||
|
||||
/// <summary>ReShade interface.</summary>
|
||||
[SuppressMessage(
|
||||
"StyleCop.CSharp.LayoutRules",
|
||||
"SA1519:Braces should not be omitted from multi-line child statement",
|
||||
Justification = "Multiple fixed blocks")]
|
||||
internal sealed unsafe partial class ReShadeAddonInterface
|
||||
{
|
||||
private static readonly ExportsStruct Exports;
|
||||
|
||||
static ReShadeAddonInterface()
|
||||
{
|
||||
foreach (var m in Process.GetCurrentProcess().Modules.Cast<ProcessModule>())
|
||||
{
|
||||
ExportsStruct e;
|
||||
if (!GetProcAddressInto(m, nameof(e.ReShadeRegisterAddon), &e.ReShadeRegisterAddon) ||
|
||||
!GetProcAddressInto(m, nameof(e.ReShadeUnregisterAddon), &e.ReShadeUnregisterAddon) ||
|
||||
!GetProcAddressInto(m, nameof(e.ReShadeRegisterEvent), &e.ReShadeRegisterEvent) ||
|
||||
!GetProcAddressInto(m, nameof(e.ReShadeUnregisterEvent), &e.ReShadeUnregisterEvent))
|
||||
continue;
|
||||
|
||||
ReShadeModule = m;
|
||||
Exports = e;
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
bool GetProcAddressInto(ProcessModule m, ReadOnlySpan<char> name, void* res)
|
||||
{
|
||||
Span<byte> name8 = stackalloc byte[Encoding.UTF8.GetByteCount(name) + 1];
|
||||
name8[Encoding.UTF8.GetBytes(name, name8)] = 0;
|
||||
*(nint*)res = GetProcAddress((HMODULE)m.BaseAddress, (sbyte*)Unsafe.AsPointer(ref name8[0]));
|
||||
return *(nint*)res != 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets the active ReShade module.</summary>
|
||||
public static ProcessModule? ReShadeModule { get; private set; }
|
||||
|
||||
private struct ExportsStruct
|
||||
{
|
||||
public delegate* unmanaged<HMODULE, uint, bool> ReShadeRegisterAddon;
|
||||
public delegate* unmanaged<HMODULE, void> ReShadeUnregisterAddon;
|
||||
public delegate* unmanaged<AddonEvent, void*, void> ReShadeRegisterEvent;
|
||||
public delegate* unmanaged<AddonEvent, void*, void> ReShadeUnregisterEvent;
|
||||
}
|
||||
}
|
||||
176
Dalamud/Interface/Internal/ReShadeAddonInterface.cs
Normal file
176
Dalamud/Interface/Internal/ReShadeAddonInterface.cs
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Hooking;
|
||||
|
||||
using JetBrains.Annotations;
|
||||
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
using static TerraFX.Interop.Windows.Windows;
|
||||
|
||||
namespace Dalamud.Interface.Internal;
|
||||
|
||||
/// <summary>ReShade interface.</summary>
|
||||
internal sealed unsafe partial class ReShadeAddonInterface : IDisposable
|
||||
{
|
||||
private const int ReShadeApiVersion = 12;
|
||||
|
||||
private readonly HMODULE hDalamudModule;
|
||||
|
||||
private readonly Hook<GetModuleHandleExWDelegate> addonModuleResolverHook;
|
||||
|
||||
private readonly DelegateStorage<ReShadeOverlayDelegate> reShadeOverlayDelegate;
|
||||
private readonly DelegateStorage<ReShadeInitSwapChain> initSwapChainDelegate;
|
||||
private readonly DelegateStorage<ReShadeDestroySwapChain> destroySwapChainDelegate;
|
||||
|
||||
private ReShadeAddonInterface()
|
||||
{
|
||||
this.hDalamudModule = (HMODULE)Marshal.GetHINSTANCE(typeof(ReShadeAddonInterface).Assembly.ManifestModule);
|
||||
if (!Exports.ReShadeRegisterAddon(this.hDalamudModule, ReShadeApiVersion))
|
||||
throw new InvalidOperationException("ReShadeRegisterAddon failure.");
|
||||
|
||||
this.addonModuleResolverHook = Hook<GetModuleHandleExWDelegate>.FromImport(
|
||||
ReShadeModule!,
|
||||
"kernel32.dll",
|
||||
nameof(GetModuleHandleExW),
|
||||
0,
|
||||
this.GetModuleHandleExWDetour);
|
||||
|
||||
this.addonModuleResolverHook.Enable();
|
||||
Exports.ReShadeRegisterEvent(
|
||||
AddonEvent.ReShadeOverlay,
|
||||
this.reShadeOverlayDelegate = new((ref ApiObject rt) => this.ReShadeOverlay?.Invoke(ref rt)));
|
||||
Exports.ReShadeRegisterEvent(
|
||||
AddonEvent.InitSwapChain,
|
||||
this.initSwapChainDelegate = new((ref ApiObject rt) => this.InitSwapChain?.Invoke(ref rt)));
|
||||
Exports.ReShadeRegisterEvent(
|
||||
AddonEvent.DestroySwapChain,
|
||||
this.destroySwapChainDelegate = new((ref ApiObject rt) => this.DestroySwapChain?.Invoke(ref rt)));
|
||||
}
|
||||
|
||||
/// <summary>Finalizes an instance of the <see cref="ReShadeAddonInterface"/> class.</summary>
|
||||
~ReShadeAddonInterface() => this.ReleaseUnmanagedResources();
|
||||
|
||||
/// <summary>Delegate for <see cref="AddonEvent.ReShadeOverlay"/>.</summary>
|
||||
/// <param name="effectRuntime">Reference to the ReShade runtime.</param>
|
||||
public delegate void ReShadeOverlayDelegate(ref ApiObject effectRuntime);
|
||||
|
||||
/// <summary>Delegate for <see cref="AddonEvent.InitSwapChain"/>.</summary>
|
||||
/// <param name="swapChain">Reference to the ReShade SwapChain wrapper.</param>
|
||||
public delegate void ReShadeInitSwapChain(ref ApiObject swapChain);
|
||||
|
||||
/// <summary>Delegate for <see cref="AddonEvent.DestroySwapChain"/>.</summary>
|
||||
/// <param name="swapChain">Reference to the ReShade SwapChain wrapper.</param>
|
||||
public delegate void ReShadeDestroySwapChain(ref ApiObject swapChain);
|
||||
|
||||
private delegate BOOL GetModuleHandleExWDelegate(uint dwFlags, ushort* lpModuleName, HMODULE* phModule);
|
||||
|
||||
/// <summary>Called on <see cref="AddonEvent.ReShadeOverlay"/>.</summary>
|
||||
public event ReShadeOverlayDelegate? ReShadeOverlay;
|
||||
|
||||
/// <summary>Called on <see cref="AddonEvent.InitSwapChain"/>.</summary>
|
||||
public event ReShadeInitSwapChain? InitSwapChain;
|
||||
|
||||
/// <summary>Called on <see cref="AddonEvent.DestroySwapChain"/>.</summary>
|
||||
public event ReShadeDestroySwapChain? DestroySwapChain;
|
||||
|
||||
/// <summary>Registers Dalamud as a ReShade addon.</summary>
|
||||
/// <param name="r">Initialized interface.</param>
|
||||
/// <returns><c>true</c> on success.</returns>
|
||||
public static bool TryRegisterAddon([NotNullWhen(true)] out ReShadeAddonInterface? r)
|
||||
{
|
||||
try
|
||||
{
|
||||
r = Exports.ReShadeRegisterAddon is null ? null : new();
|
||||
return r is not null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
r = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.ReleaseUnmanagedResources();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void ReleaseUnmanagedResources()
|
||||
{
|
||||
Exports.ReShadeUnregisterEvent(AddonEvent.InitSwapChain, this.initSwapChainDelegate);
|
||||
Exports.ReShadeUnregisterEvent(AddonEvent.DestroySwapChain, this.destroySwapChainDelegate);
|
||||
Exports.ReShadeUnregisterEvent(AddonEvent.ReShadeOverlay, this.reShadeOverlayDelegate);
|
||||
Exports.ReShadeUnregisterAddon(this.hDalamudModule);
|
||||
this.addonModuleResolverHook.Disable();
|
||||
this.addonModuleResolverHook.Dispose();
|
||||
}
|
||||
|
||||
private BOOL GetModuleHandleExWDetour(uint dwFlags, ushort* lpModuleName, HMODULE* phModule)
|
||||
{
|
||||
if ((dwFlags & GET.GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS) == 0)
|
||||
return this.addonModuleResolverHook.Original(dwFlags, lpModuleName, phModule);
|
||||
if ((dwFlags & GET.GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT) == 0)
|
||||
return this.addonModuleResolverHook.Original(dwFlags, lpModuleName, phModule);
|
||||
if (lpModuleName == this.initSwapChainDelegate ||
|
||||
lpModuleName == this.destroySwapChainDelegate ||
|
||||
lpModuleName == this.reShadeOverlayDelegate)
|
||||
{
|
||||
*phModule = this.hDalamudModule;
|
||||
return BOOL.TRUE;
|
||||
}
|
||||
|
||||
return this.addonModuleResolverHook.Original(dwFlags, lpModuleName, phModule);
|
||||
}
|
||||
|
||||
/// <summary>ReShade effect runtime object.</summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct ApiObject
|
||||
{
|
||||
/// <summary>The vtable.</summary>
|
||||
public VTable* Vtbl;
|
||||
|
||||
/// <summary>Gets this object as a typed pointer.</summary>
|
||||
/// <returns>Address of this instance.</returns>
|
||||
/// <remarks>This call is invalid if this object is not already fixed.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ApiObject* AsPointer() => (ApiObject*)Unsafe.AsPointer(ref this);
|
||||
|
||||
/// <summary>Gets the native object.</summary>
|
||||
/// <returns>The native object.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public nint GetNative() => this.Vtbl->GetNative(this.AsPointer());
|
||||
|
||||
/// <inheritdoc cref="GetNative"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public T* GetNative<T>() where T : unmanaged => (T*)this.GetNative();
|
||||
|
||||
/// <summary>VTable of <see cref="ApiObject"/>.</summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct VTable
|
||||
{
|
||||
/// <inheritdoc cref="ApiObject.GetNative"/>
|
||||
public delegate* unmanaged<ApiObject*, nint> GetNative;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct DelegateStorage<T> where T : Delegate
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public readonly T Delegate;
|
||||
|
||||
public readonly void* Address;
|
||||
|
||||
public DelegateStorage(T @delegate)
|
||||
{
|
||||
this.Delegate = @delegate;
|
||||
this.Address = (void*)Marshal.GetFunctionPointerForDelegate(@delegate);
|
||||
}
|
||||
|
||||
public static implicit operator void*(DelegateStorage<T> sto) => sto.Address;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,7 @@
|
|||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||
|
||||
using Serilog;
|
||||
|
||||
using TerraFX.Interop.DirectX;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
|
|
@ -16,12 +10,6 @@ namespace Dalamud.Interface.Internal;
|
|||
/// <summary>Helper for dealing with swap chains.</summary>
|
||||
internal static unsafe class SwapChainHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the function pointer for ReShade's DXGISwapChain::on_present.
|
||||
/// <a href="https://github.com/crosire/reshade/blob/59eeecd0c902129a168cd772a63c46c5254ff2c5/source/dxgi/dxgi_swapchain.hpp#L88">Source.</a>
|
||||
/// </summary>
|
||||
public static delegate* unmanaged<nint, uint, nint, void> ReshadeOnPresent { get; private set; }
|
||||
|
||||
/// <summary>Gets the game's active instance of IDXGISwapChain that is initialized.</summary>
|
||||
/// <value>Address of the game's instance of IDXGISwapChain, or <c>null</c> if not available (yet.)</value>
|
||||
public static IDXGISwapChain* GameDeviceSwapChain
|
||||
|
|
@ -92,102 +80,4 @@ internal static unsafe class SwapChainHelper
|
|||
while (GameDeviceSwapChain is null)
|
||||
Thread.Yield();
|
||||
}
|
||||
|
||||
/// <summary>Detects ReShade and populate <see cref="ReshadeOnPresent"/>.</summary>
|
||||
public static void DetectReShade()
|
||||
{
|
||||
var modules = Process.GetCurrentProcess().Modules;
|
||||
foreach (ProcessModule processModule in modules)
|
||||
{
|
||||
if (!processModule.FileName.EndsWith("game\\dxgi.dll", StringComparison.InvariantCultureIgnoreCase))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
var fileInfo = FileVersionInfo.GetVersionInfo(processModule.FileName);
|
||||
|
||||
if (fileInfo.FileDescription == null)
|
||||
break;
|
||||
|
||||
if (!fileInfo.FileDescription.Contains("GShade") && !fileInfo.FileDescription.Contains("ReShade"))
|
||||
break;
|
||||
|
||||
// warning: these comments may no longer be accurate.
|
||||
// reshade master@4232872 RVA
|
||||
// var p = processModule.BaseAddress + 0x82C7E0; // DXGISwapChain::Present
|
||||
// var p = processModule.BaseAddress + 0x82FAC0; // DXGISwapChain::runtime_present
|
||||
// DXGISwapChain::handle_device_loss =>df DXGISwapChain::Present => DXGISwapChain::runtime_present
|
||||
// 5.2+ - F6 C2 01 0F 85
|
||||
// 6.0+ - F6 C2 01 0F 85 88
|
||||
|
||||
var scanner = new SigScanner(processModule);
|
||||
var reShadeDxgiPresent = nint.Zero;
|
||||
|
||||
if (fileInfo.FileVersion?.StartsWith("6.") == true)
|
||||
{
|
||||
// No Addon
|
||||
if (scanner.TryScanText("F6 C2 01 0F 85 A8", out reShadeDxgiPresent))
|
||||
{
|
||||
Log.Information("Hooking present for ReShade 6 No-Addon");
|
||||
}
|
||||
|
||||
// Addon
|
||||
else if (scanner.TryScanText("F6 C2 01 0F 85 88", out reShadeDxgiPresent))
|
||||
{
|
||||
Log.Information("Hooking present for ReShade 6 Addon");
|
||||
}
|
||||
|
||||
// Fallback
|
||||
else
|
||||
{
|
||||
Log.Error("Failed to get ReShade 6 DXGISwapChain::on_present offset!");
|
||||
}
|
||||
}
|
||||
|
||||
// Looks like this sig only works for GShade 4
|
||||
if (reShadeDxgiPresent == nint.Zero && fileInfo.FileDescription?.Contains("GShade 4.") == true)
|
||||
{
|
||||
if (scanner.TryScanText("E8 ?? ?? ?? ?? 45 0F B6 5E ??", out reShadeDxgiPresent))
|
||||
{
|
||||
Log.Information("Hooking present for GShade 4");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("Failed to find GShade 4 DXGISwapChain::on_present offset!");
|
||||
}
|
||||
}
|
||||
|
||||
if (reShadeDxgiPresent == nint.Zero)
|
||||
{
|
||||
if (scanner.TryScanText("F6 C2 01 0F 85", out reShadeDxgiPresent))
|
||||
{
|
||||
Log.Information("Hooking present for ReShade with fallback 5.X sig");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("Failed to find ReShade DXGISwapChain::on_present offset with fallback sig!");
|
||||
}
|
||||
}
|
||||
|
||||
Log.Information(
|
||||
"ReShade DLL: {FileName} ({Info} - {Version}) with DXGISwapChain::on_present at {Address}",
|
||||
processModule.FileName,
|
||||
fileInfo.FileDescription ?? "Unknown",
|
||||
fileInfo.FileVersion ?? "Unknown",
|
||||
Util.DescribeAddress(reShadeDxgiPresent));
|
||||
|
||||
if (reShadeDxgiPresent != nint.Zero)
|
||||
{
|
||||
ReshadeOnPresent = (delegate* unmanaged<nint, uint, nint, void>)reShadeDxgiPresent;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Failed to get ReShade version info");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue