diff --git a/Dalamud/Hooking/Internal/ObjectVTableHook.cs b/Dalamud/Hooking/Internal/ObjectVTableHook.cs
index b4500bb5f..8b2f24de2 100644
--- a/Dalamud/Hooking/Internal/ObjectVTableHook.cs
+++ b/Dalamud/Hooking/Internal/ObjectVTableHook.cs
@@ -54,6 +54,15 @@ internal unsafe class ObjectVTableHook : IDisposable
/// Gets the span view of overriden vtable.
public ReadOnlySpan OverridenVTableSpan => this.vtblOverriden.AsSpan();
+ /// Gets the address of the pointer to the vtable.
+ public nint Address => (nint)this.ppVtbl;
+
+ /// Gets the address of the original vtable.
+ public nint OriginalVTableAddress => (nint)this.pVtblOriginal;
+
+ /// Gets the address of the overriden vtable.
+ public nint OverridenVTableAddress => (nint)Unsafe.AsPointer(ref this.vtblOverriden[0]);
+
/// Disables the hook.
public void Disable()
{
diff --git a/Dalamud/Interface/Internal/InterfaceManager.AsHook.cs b/Dalamud/Interface/Internal/InterfaceManager.AsHook.cs
index b2afb970f..3dc8b30cd 100644
--- a/Dalamud/Interface/Internal/InterfaceManager.AsHook.cs
+++ b/Dalamud/Interface/Internal/InterfaceManager.AsHook.cs
@@ -1,75 +1,142 @@
using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using Dalamud.Utility;
+using TerraFX.Interop.DirectX;
+using TerraFX.Interop.Windows;
+
namespace Dalamud.Interface.Internal;
///
/// This class manages interaction with the ImGui interface.
///
-internal partial class InterfaceManager
+internal unsafe partial class InterfaceManager
{
- private IntPtr PresentDetour(IntPtr swapChain, uint syncInterval, uint presentFlags)
+ // NOTE: Do not use HRESULT as return value type. It appears that .NET marshaller thinks HRESULT needs to be still
+ // treated as a type that does not fit into RAX.
+
+ /// Delegate for DXGISwapChain::on_present(UINT flags, const DXGI_PRESENT_PARAMETERS *params) in
+ /// dxgi_swapchain.cpp.
+ /// Pointer to an instance of DXGISwapChain, which happens to be an
+ /// .
+ /// An integer value that contains swap-chain presentation options. These options are defined by
+ /// the DXGI_PRESENT constants.
+ /// Optional; DXGI present parameters.
+ [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
+ private delegate void ReShadeDxgiSwapChainPresentDelegate(
+ ReShadeDxgiSwapChain* swapChain,
+ uint flags,
+ DXGI_PRESENT_PARAMETERS* presentParams);
+
+ /// Delegate for .
+ /// Microsoft
+ /// Learn.
+ /// Pointer to an instance of .
+ /// An integer that specifies how to synchronize presentation of a frame with the
+ /// vertical blank.
+ /// An integer value that contains swap-chain presentation options. These options are defined by
+ /// the DXGI_PRESENT constants.
+ /// A representing the result of the operation.
+ [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
+ private delegate int DxgiSwapChainPresentDelegate(IDXGISwapChain* swapChain, uint syncInterval, uint flags);
+
+ /// Detour function for .
+ ///
+ /// Microsoft Learn.
+ /// Pointer to an instance of .
+ /// The number of buffers in the swap chain (including all back and front buffers).
+ /// This number can be different from the number of buffers with which you created the swap chain. This number
+ /// can't be greater than . Set this number to zero to preserve the
+ /// existing number of buffers in the swap chain. You can't specify less than two buffers for the flip presentation
+ /// model.
+ /// The new width of the back buffer. If you specify zero, DXGI will use the width of the client
+ /// area of the target window. You can't specify the width as zero if you called the
+ /// method to create the swap chain for a composition
+ /// surface.
+ /// The new height of the back buffer. If you specify zero, DXGI will use the height of the
+ /// client area of the target window. You can't specify the height as zero if you called the
+ /// method to create the swap chain for a composition
+ /// surface.
+ /// A DXGI_FORMAT-typed value for the new format of the back buffer. Set this value to
+ /// to preserve the existing format of the back buffer. The flip
+ /// presentation model supports a more restricted set of formats than the bit-block transfer (bitblt) model.
+ /// A combination of -typed values that are combined
+ /// by using a bitwise OR operation. The resulting value specifies options for swap-chain behavior.
+ /// A representing the result of the operation.
+ [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
+ private delegate int ResizeBuffersDelegate(
+ IDXGISwapChain* swapChain,
+ uint bufferCount,
+ uint width,
+ uint height,
+ DXGI_FORMAT newFormat,
+ uint swapChainFlags);
+
+ private void ReShadeDxgiSwapChainOnPresentDetour(
+ ReShadeDxgiSwapChain* swapChain,
+ uint flags,
+ DXGI_PRESENT_PARAMETERS* presentParams)
{
- if (!SwapChainHelper.IsGameDeviceSwapChain(swapChain))
- return this.dxgiPresentHook!.Original(swapChain, syncInterval, presentFlags);
+ Debug.Assert(
+ this.reShadeDxgiSwapChainPresentHook is not null,
+ "this.reShadeDxgiSwapChainPresentHook is not null");
- 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");
+ // Call this first to draw Dalamud over ReShade.
+ this.reShadeDxgiSwapChainPresentHook!.Original(swapChain, flags, presentParams);
- if (this.scene == null)
- this.InitScene(swapChain);
+ if (this.RenderDalamudCheckAndInitialize(swapChain->AsIDxgiSwapChain()) is { } activeScene)
+ this.RenderDalamudDraw(activeScene);
- 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);
+ // Upstream call to system IDXGISwapChain::Present will be called by ReShade.
}
- private IntPtr AsHookResizeBuffersDetour(
- IntPtr swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags)
+ private int DxgiSwapChainPresentDetour(IDXGISwapChain* swapChain, uint syncInterval, uint flags)
+ {
+ Debug.Assert(this.dxgiSwapChainPresentHook is not null, "this.dxgiSwapChainPresentHook is not null");
+
+ if (this.RenderDalamudCheckAndInitialize(swapChain) is { } activeScene)
+ this.RenderDalamudDraw(activeScene);
+
+ return this.dxgiSwapChainPresentHook!.Original(swapChain, syncInterval, flags);
+ }
+
+ private int AsHookDxgiSwapChainResizeBuffersDetour(
+ IDXGISwapChain* swapChain,
+ uint bufferCount,
+ uint width,
+ uint height,
+ DXGI_FORMAT newFormat,
+ uint swapChainFlags)
{
if (!SwapChainHelper.IsGameDeviceSwapChain(swapChain))
- return this.resizeBuffersHook!.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags);
+ return this.dxgiSwapChainResizeBuffersHook!.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags);
#if DEBUG
Log.Verbose(
- $"Calling resizebuffers swap@{swapChain.ToInt64():X}{bufferCount} {width} {height} {newFormat} {swapChainFlags}");
+ $"Calling resizebuffers swap@{(nint)swapChain: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)
- {
+ var ret = this.dxgiSwapChainResizeBuffersHook!.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags);
+ if (ret == DXGI.DXGI_ERROR_INVALID_CALL)
Log.Error("invalid call to resizeBuffers");
- }
this.scene?.OnPostResize((int)width, (int)height);
return ret;
}
+
+ /// Represents DXGISwapChain in ReShade.
+ [StructLayout(LayoutKind.Sequential)]
+ private struct ReShadeDxgiSwapChain
+ {
+ // DXGISwapChain only implements IDXGISwapChain4. The only vtable should be that.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public IDXGISwapChain* AsIDxgiSwapChain() => (IDXGISwapChain*)Unsafe.AsPointer(ref this);
+ }
}
diff --git a/Dalamud/Interface/Internal/InterfaceManager.AsReShadeAddon.cs b/Dalamud/Interface/Internal/InterfaceManager.AsReShadeAddon.cs
index 486a3874b..817a935ee 100644
--- a/Dalamud/Interface/Internal/InterfaceManager.AsReShadeAddon.cs
+++ b/Dalamud/Interface/Internal/InterfaceManager.AsReShadeAddon.cs
@@ -11,9 +11,9 @@ namespace Dalamud.Interface.Internal;
///
/// This class manages interaction with the ImGui interface.
///
-internal partial class InterfaceManager
+internal unsafe partial class InterfaceManager
{
- private unsafe void ReShadeAddonInterfaceOnDestroySwapChain(ref ReShadeAddonInterface.ApiObject swapChain)
+ private void ReShadeAddonInterfaceOnDestroySwapChain(ref ReShadeAddonInterface.ApiObject swapChain)
{
var swapChainNative = swapChain.GetNative();
if (this.scene?.SwapChain.NativePointer != (nint)swapChainNative)
@@ -22,7 +22,7 @@ internal partial class InterfaceManager
this.scene?.OnPreResize();
}
- private unsafe void ReShadeAddonInterfaceOnInitSwapChain(ref ReShadeAddonInterface.ApiObject swapChain)
+ private void ReShadeAddonInterfaceOnInitSwapChain(ref ReShadeAddonInterface.ApiObject swapChain)
{
var swapChainNative = swapChain.GetNative();
if (this.scene?.SwapChain.NativePointer != (nint)swapChainNative)
@@ -42,52 +42,25 @@ internal partial class InterfaceManager
ReadOnlySpan destRect,
ReadOnlySpan dirtyRects)
{
- var swapChainNative = swapChain.GetNative();
+ var swapChainNative = swapChain.GetNative();
- if (this.scene == null)
- this.InitScene(swapChainNative);
-
- if (this.scene?.SwapChain.NativePointer != swapChainNative)
- 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;
+ if (this.RenderDalamudCheckAndInitialize(swapChainNative) is { } activeScene)
+ this.RenderDalamudDraw(activeScene);
}
- private nint AsReShadeAddonResizeBuffersDetour(
- nint swapChain,
+ private int AsReShadeAddonDxgiSwapChainResizeBuffersDetour(
+ IDXGISwapChain* swapChain,
uint bufferCount,
uint width,
uint height,
- uint newFormat,
+ DXGI_FORMAT 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);
+ return this.dxgiSwapChainResizeBuffersHook!.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags);
this.ResizeBuffers?.InvokeSafely();
- return this.resizeBuffersHook!.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags);
+ return this.dxgiSwapChainResizeBuffersHook!.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags);
}
}
diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs
index 0491fed1e..45cba5c76 100644
--- a/Dalamud/Interface/Internal/InterfaceManager.cs
+++ b/Dalamud/Interface/Internal/InterfaceManager.cs
@@ -3,8 +3,8 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
-using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -97,9 +97,10 @@ internal partial class InterfaceManager : IInternalDisposableService
private RawDX11Scene? scene;
private Hook? setCursorHook;
- private Hook? dxgiPresentHook;
- private Hook? resizeBuffersHook;
- private ObjectVTableHook>? swapChainHook;
+ private Hook? reShadeDxgiSwapChainPresentHook;
+ private Hook? dxgiSwapChainPresentHook;
+ private Hook? dxgiSwapChainResizeBuffersHook;
+ private ObjectVTableHook>? dxgiSwapChainHook;
private ReShadeAddonInterface? reShadeAddonInterface;
private IFontAtlas? dalamudAtlas;
@@ -115,12 +116,6 @@ internal partial class InterfaceManager : IInternalDisposableService
{
}
- [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
- private delegate IntPtr DxgiPresentDelegate(IntPtr swapChain, uint syncInterval, uint presentFlags);
-
- [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
- private delegate IntPtr ResizeBuffersDelegate(IntPtr swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags);
-
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate IntPtr SetCursorDelegate(IntPtr hCursor);
@@ -231,7 +226,7 @@ internal partial class InterfaceManager : IInternalDisposableService
///
public bool IsDispatchingEvents { get; set; } = true;
- /// Gets a value indicating whether the main thread is executing .
+ /// Gets a value indicating whether the main thread is executing .
/// This still will be true even when queried off the main thread.
public bool IsMainThreadInPresent { get; private set; }
@@ -265,7 +260,7 @@ internal partial class InterfaceManager : IInternalDisposableService
///
public Task FontBuildTask => WhenFontsReady().dalamudAtlas!.BuildTask;
- /// Gets the number of calls to so far.
+ /// Gets the number of calls to so far.
///
/// The value increases even when Dalamud is hidden via "/xlui hide".
/// does not.
@@ -312,9 +307,10 @@ internal partial 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.resizeBuffersHook, null)?.Dispose();
- Interlocked.Exchange(ref this.swapChainHook, null)?.Dispose();
+ Interlocked.Exchange(ref this.dxgiSwapChainPresentHook, null)?.Dispose();
+ Interlocked.Exchange(ref this.reShadeDxgiSwapChainPresentHook, null)?.Dispose();
+ Interlocked.Exchange(ref this.dxgiSwapChainResizeBuffersHook, null)?.Dispose();
+ Interlocked.Exchange(ref this.dxgiSwapChainHook, null)?.Dispose();
Interlocked.Exchange(ref this.reShadeAddonInterface, null)?.Dispose();
}
}
@@ -497,31 +493,72 @@ internal partial class InterfaceManager : IInternalDisposableService
return im;
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void RenderImGui(RawDX11Scene scene)
+ /// Checks if the provided swap chain is the target that Dalamud should draw its interface onto,
+ /// and initializes ImGui for drawing.
+ /// The swap chain to test and initialize ImGui with if conditions are met.
+ /// An initialized instance of , or null if
+ /// is not the main swap chain.
+ private unsafe RawDX11Scene? RenderDalamudCheckAndInitialize(IDXGISwapChain* swapChain)
{
- var conf = Service.Get();
+ if (!SwapChainHelper.IsGameDeviceSwapChain(swapChain))
+ return null;
+
+ Debug.Assert(this.dalamudAtlas is not null, "dalamudAtlas should have been set already");
+
+ var activeScene = this.scene ?? this.InitScene(swapChain);
+
+ 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 null;
+ }
+
+ return activeScene;
+ }
+
+ /// Draws Dalamud to the given scene representing the ImGui context.
+ /// The scene to draw to.
+ private void RenderDalamudDraw(RawDX11Scene activeScene)
+ {
+ this.CumulativePresentCalls++;
+ this.IsMainThreadInPresent = true;
+
+ while (this.runBeforeImGuiRender.TryDequeue(out var action))
+ action.InvokeSafely();
// Process information needed by ImGuiHelpers each frame.
ImGuiHelpers.NewFrame();
// Enable viewports if there are no issues.
- if (conf.IsDisableViewport || scene.SwapChain.IsFullScreen || ImGui.GetPlatformIO().Monitors.Size == 1)
+ var viewportsEnable = this.dalamudConfiguration.IsDisableViewport ||
+ activeScene.SwapChain.IsFullScreen ||
+ ImGui.GetPlatformIO().Monitors.Size == 1;
+ if (viewportsEnable)
ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.ViewportsEnable;
else
ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.ViewportsEnable;
- scene.Render();
+ // Call drawing functions, which in turn will call Draw event.
+ activeScene.Render();
+
+ this.PostImGuiRender();
+ this.IsMainThreadInPresent = false;
}
- private void InitScene(IntPtr swapChain)
+ private unsafe RawDX11Scene InitScene(IDXGISwapChain* swapChain)
{
RawDX11Scene newScene;
using (Timings.Start("IM Scene Init"))
{
try
{
- newScene = new RawDX11Scene(swapChain);
+ newScene = new RawDX11Scene((nint)swapChain);
}
catch (DllNotFoundException ex)
{
@@ -547,7 +584,7 @@ internal partial class InterfaceManager : IInternalDisposableService
Environment.Exit(-1);
// Doesn't reach here, but to make the compiler not complain
- return;
+ throw new InvalidOperationException();
}
var startInfo = Service.Get().StartInfo;
@@ -638,6 +675,7 @@ internal partial class InterfaceManager : IInternalDisposableService
Service.Provide(new(this));
this.wndProcHookManager.PreWndProc += this.WndProcHookManagerOnPreWndProc;
+ return newScene;
}
private unsafe void WndProcHookManagerOnPreWndProc(WndProcEventArgs args)
@@ -805,39 +843,84 @@ internal partial class InterfaceManager : IInternalDisposableService
});
}
- Log.Verbose("===== S W A P C H A I N =====");
- if (this.dalamudConfiguration.ReShadeHandlingMode == ReShadeHandlingMode.UnwrapReShade)
+ Log.Information("===== S W A P C H A I N =====");
+ var sb = new StringBuilder();
+ foreach (var m in ReShadeAddonInterface.AllReShadeModules)
{
- if (SwapChainHelper.UnwrapReShade())
- Log.Information("Unwrapped ReShade");
+ sb.Clear();
+ sb.Append("ReShade detected: ");
+ sb.Append(m.FileName).Append('(');
+ sb.Append(m.FileVersionInfo.OriginalFilename);
+ sb.Append("; ").Append(m.FileVersionInfo.ProductName);
+ sb.Append("; ").Append(m.FileVersionInfo.ProductVersion);
+ sb.Append("; ").Append(m.FileVersionInfo.FileDescription);
+ sb.Append("; ").Append(m.FileVersionInfo.FileVersion);
+ sb.Append($"@ 0x{m.BaseAddress:X}");
+ if (!ReferenceEquals(m, ReShadeAddonInterface.ReShadeModule))
+ sb.Append(" [ignored by Dalamud]");
+ Log.Information(sb.ToString());
}
- ResizeBuffersDelegate? resizeBuffersDelegate = null;
- DxgiPresentDelegate? dxgiPresentDelegate = null;
- if (this.dalamudConfiguration.ReShadeHandlingMode == ReShadeHandlingMode.ReShadeAddon)
- {
- if (ReShadeAddonInterface.TryRegisterAddon(out this.reShadeAddonInterface))
- {
- resizeBuffersDelegate = this.AsReShadeAddonResizeBuffersDetour;
+ if (ReShadeAddonInterface.AllReShadeModules.Length > 1)
+ Log.Warning("Multiple ReShade dlls are detected.");
- Log.Information(
- "Registered as a ReShade({Name}: 0x{Addr:X}) addon",
- ReShadeAddonInterface.ReShadeModule!.FileName,
- ReShadeAddonInterface.ReShadeModule!.BaseAddress);
+ ResizeBuffersDelegate dxgiSwapChainResizeBuffersDelegate;
+ ReShadeDxgiSwapChainPresentDelegate? reShadeDxgiSwapChainPresentDelegate = null;
+ DxgiSwapChainPresentDelegate? dxgiSwapChainPresentDelegate = null;
+ nint pfnReShadeDxgiSwapChainPresent = 0;
+ switch (this.dalamudConfiguration.ReShadeHandlingMode)
+ {
+ // This is the only mode honored when SwapChainHookMode is set to VTable.
+ case ReShadeHandlingMode.UnwrapReShade when ReShadeAddonInterface.ReShadeModule is not null:
+ if (SwapChainHelper.UnwrapReShade())
+ Log.Information("Unwrapped ReShade");
+ else
+ Log.Warning("Could not unwrap ReShade");
+ goto default;
+
+ // Do no special ReShade handling.
+ // If ReShade is not found or SwapChainHookMode is set to VTable, also do nothing special.
+ case ReShadeHandlingMode.None:
+ case var _ when ReShadeAddonInterface.ReShadeModule is null:
+ case var _ when this.dalamudConfiguration.SwapChainHookMode == SwapChainHelper.HookMode.VTable:
+ default:
+ dxgiSwapChainResizeBuffersDelegate = this.AsHookDxgiSwapChainResizeBuffersDetour;
+ dxgiSwapChainPresentDelegate = this.DxgiSwapChainPresentDetour;
+ break;
+
+ // Register Dalamud as a ReShade addon.
+ case ReShadeHandlingMode.ReShadeAddon:
+ if (!ReShadeAddonInterface.TryRegisterAddon(out this.reShadeAddonInterface))
+ {
+ Log.Warning("Could not register as ReShade addon");
+ goto default;
+ }
+
+ Log.Information("Registered as a ReShade addon");
this.reShadeAddonInterface.InitSwapChain += this.ReShadeAddonInterfaceOnInitSwapChain;
this.reShadeAddonInterface.DestroySwapChain += this.ReShadeAddonInterfaceOnDestroySwapChain;
this.reShadeAddonInterface.Present += this.ReShadeAddonInterfaceOnPresent;
- }
- else
- {
- Log.Information("Could not register as ReShade addon");
- }
- }
- if (resizeBuffersDelegate is null)
- {
- resizeBuffersDelegate = this.AsHookResizeBuffersDetour;
- dxgiPresentDelegate = this.PresentDetour;
+ dxgiSwapChainResizeBuffersDelegate = this.AsReShadeAddonDxgiSwapChainResizeBuffersDetour;
+ break;
+
+ // Hook ReShade's DXGISwapChain::on_present. This is the legacy and the default option.
+ case ReShadeHandlingMode.Default:
+ case ReShadeHandlingMode.HookReShadeDxgiSwapChainOnPresent:
+ pfnReShadeDxgiSwapChainPresent = ReShadeAddonInterface.FindReShadeDxgiSwapChainOnPresent();
+
+ if (pfnReShadeDxgiSwapChainPresent == 0)
+ {
+ Log.Warning("ReShade::DXGISwapChain::on_present could not be found");
+ goto default;
+ }
+
+ Log.Information(
+ "Found ReShade::DXGISwapChain::on_present at {addr}",
+ Util.DescribeAddress(pfnReShadeDxgiSwapChainPresent));
+ reShadeDxgiSwapChainPresentDelegate = this.ReShadeDxgiSwapChainOnPresentDetour;
+ dxgiSwapChainResizeBuffersDelegate = this.AsHookDxgiSwapChainResizeBuffersDetour;
+ break;
}
switch (this.dalamudConfiguration.SwapChainHookMode)
@@ -846,16 +929,31 @@ internal partial class InterfaceManager : IInternalDisposableService
default:
{
Log.Information("Hooking using bytecode...");
- this.resizeBuffersHook = Hook.FromAddress(
+ this.dxgiSwapChainResizeBuffersHook = Hook.FromAddress(
(nint)SwapChainHelper.GameDeviceSwapChainVtbl->ResizeBuffers,
- resizeBuffersDelegate);
+ dxgiSwapChainResizeBuffersDelegate);
+ Log.Information(
+ "Hooked IDXGISwapChain::ResizeBuffers using bytecode: {addr}",
+ Util.DescribeAddress(this.dxgiSwapChainResizeBuffersHook.Address));
- if (dxgiPresentDelegate is not null)
+ if (dxgiSwapChainPresentDelegate is not null)
{
- this.dxgiPresentHook = Hook.FromAddress(
+ this.dxgiSwapChainPresentHook = Hook.FromAddress(
(nint)SwapChainHelper.GameDeviceSwapChainVtbl->Present,
- dxgiPresentDelegate);
- Log.Information("Hooked present using bytecode");
+ dxgiSwapChainPresentDelegate);
+ Log.Information(
+ "Hooked IDXGISwapChain::Present using bytecode: {addr}",
+ Util.DescribeAddress(this.dxgiSwapChainPresentHook.Address));
+ }
+
+ if (reShadeDxgiSwapChainPresentDelegate is not null && pfnReShadeDxgiSwapChainPresent != 0)
+ {
+ this.reShadeDxgiSwapChainPresentHook = Hook.FromAddress(
+ pfnReShadeDxgiSwapChainPresent,
+ reShadeDxgiSwapChainPresentDelegate);
+ Log.Information(
+ "Hooked ReShade::DXGISwapChain::on_present using bytecode: {addr}",
+ Util.DescribeAddress(this.reShadeDxgiSwapChainPresentHook.Address));
}
break;
@@ -864,30 +962,38 @@ internal partial class InterfaceManager : IInternalDisposableService
case SwapChainHelper.HookMode.VTable:
{
Log.Information("Hooking using VTable...");
- this.swapChainHook = new(SwapChainHelper.GameDeviceSwapChain);
- this.resizeBuffersHook = this.swapChainHook.CreateHook(
+ this.dxgiSwapChainHook = new(SwapChainHelper.GameDeviceSwapChain);
+ this.dxgiSwapChainResizeBuffersHook = this.dxgiSwapChainHook.CreateHook(
nameof(IDXGISwapChain.ResizeBuffers),
- resizeBuffersDelegate);
+ dxgiSwapChainResizeBuffersDelegate);
+ Log.Information(
+ "Hooked IDXGISwapChain::ResizeBuffers using VTable: {addr}",
+ Util.DescribeAddress(this.dxgiSwapChainResizeBuffersHook.Address));
- if (dxgiPresentDelegate is not null)
+ if (dxgiSwapChainPresentDelegate is not null)
{
- this.dxgiPresentHook = this.swapChainHook.CreateHook(
+ this.dxgiSwapChainPresentHook = this.dxgiSwapChainHook.CreateHook(
nameof(IDXGISwapChain.Present),
- dxgiPresentDelegate);
- Log.Information("Hooked present using VTable");
+ dxgiSwapChainPresentDelegate);
+ Log.Information(
+ "Hooked IDXGISwapChain::Present using VTable: {addr}",
+ Util.DescribeAddress(this.dxgiSwapChainPresentHook.Address));
}
+ Log.Information(
+ "Detouring vtable at {addr}: {prev} to {new}",
+ Util.DescribeAddress(this.dxgiSwapChainHook.Address),
+ Util.DescribeAddress(this.dxgiSwapChainHook.OriginalVTableAddress),
+ Util.DescribeAddress(this.dxgiSwapChainHook.OverridenVTableAddress));
break;
}
}
- Log.Information($"IDXGISwapChain::ResizeBuffers address: {Util.DescribeAddress(this.resizeBuffersHook.Address)}");
- Log.Information($"IDXGISwapChain::Present address: {Util.DescribeAddress(this.dxgiPresentHook?.Address ?? 0)}");
-
this.setCursorHook.Enable();
- this.resizeBuffersHook.Enable();
- this.dxgiPresentHook?.Enable();
- this.swapChainHook?.Enable();
+ this.reShadeDxgiSwapChainPresentHook?.Enable();
+ this.dxgiSwapChainResizeBuffersHook.Enable();
+ this.dxgiSwapChainPresentHook?.Enable();
+ this.dxgiSwapChainHook?.Enable();
}
private IntPtr SetCursorDetour(IntPtr hCursor)
diff --git a/Dalamud/Interface/Internal/ReShadeHandling/ReShadeAddonInterface.Exports.cs b/Dalamud/Interface/Internal/ReShadeHandling/ReShadeAddonInterface.Exports.cs
index b3add07e7..d8d210076 100644
--- a/Dalamud/Interface/Internal/ReShadeHandling/ReShadeAddonInterface.Exports.cs
+++ b/Dalamud/Interface/Internal/ReShadeHandling/ReShadeAddonInterface.Exports.cs
@@ -1,3 +1,5 @@
+using System.Collections.Generic;
+using System.Collections.Immutable;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
@@ -24,6 +26,7 @@ internal sealed unsafe partial class ReShadeAddonInterface
static ReShadeAddonInterface()
{
+ var modules = new List();
foreach (var m in Process.GetCurrentProcess().Modules.Cast())
{
ExportsStruct e;
@@ -33,27 +36,31 @@ internal sealed unsafe partial class ReShadeAddonInterface
!GetProcAddressInto(m, nameof(e.ReShadeUnregisterEvent), &e.ReShadeUnregisterEvent))
continue;
- try
+ modules.Add(m);
+ if (modules.Count == 1)
{
- var signerName = GetSignatureSignerNameWithoutVerification(m.FileName);
- ReShadeIsSignedByReShade = signerName == "ReShade";
- Log.Information(
- "ReShade DLL is signed by {signerName}. {vn}={v}",
- signerName,
- nameof(ReShadeIsSignedByReShade),
- ReShadeIsSignedByReShade);
- }
- catch (Exception ex)
- {
- Log.Information(ex, "ReShade DLL did not had a valid signature.");
- }
+ try
+ {
+ var signerName = GetSignatureSignerNameWithoutVerification(m.FileName);
+ ReShadeIsSignedByReShade = signerName == "ReShade";
+ Log.Information(
+ "ReShade DLL is signed by {signerName}. {vn}={v}",
+ signerName,
+ nameof(ReShadeIsSignedByReShade),
+ ReShadeIsSignedByReShade);
+ }
+ catch (Exception ex)
+ {
+ Log.Information(ex, "ReShade DLL did not had a valid signature.");
+ }
- ReShadeModule = m;
- Exports = e;
-
- return;
+ ReShadeModule = m;
+ Exports = e;
+ }
}
+ AllReShadeModules = [..modules];
+
return;
bool GetProcAddressInto(ProcessModule m, ReadOnlySpan name, void* res)
@@ -68,10 +75,30 @@ internal sealed unsafe partial class ReShadeAddonInterface
/// Gets the active ReShade module.
public static ProcessModule? ReShadeModule { get; private set; }
+ /// Gets all the detected ReShade modules.
+ public static ImmutableArray AllReShadeModules { get; private set; }
+
/// Gets a value indicating whether the loaded ReShade has signatures.
/// ReShade without addon support is signed, but may not pass signature verification.
public static bool ReShadeIsSignedByReShade { get; private set; }
+ /// Finds the address of DXGISwapChain::on_present in .
+ /// Address of the function, or 0 if not found.
+ public static nint FindReShadeDxgiSwapChainOnPresent()
+ {
+ if (ReShadeModule is not { } rsm)
+ return 0;
+
+ var m = new ReadOnlySpan((void*)rsm.BaseAddress, rsm.ModuleMemorySize);
+
+ // Signature validated against 5.0.0 to 6.2.0
+ var i = m.IndexOf(new byte[] { 0xCC, 0xF6, 0xC2, 0x01, 0x0F, 0x85 });
+ if (i == -1)
+ return 0;
+
+ return rsm.BaseAddress + i + 1;
+ }
+
/// Gets the name of the signer of a file that has a certificate embedded within, without verifying if the
/// file has a valid signature.
/// Path to the file.
diff --git a/Dalamud/Interface/Internal/ReShadeHandling/ReShadeHandlingMode.cs b/Dalamud/Interface/Internal/ReShadeHandling/ReShadeHandlingMode.cs
index e4561ca46..6ffba3878 100644
--- a/Dalamud/Interface/Internal/ReShadeHandling/ReShadeHandlingMode.cs
+++ b/Dalamud/Interface/Internal/ReShadeHandling/ReShadeHandlingMode.cs
@@ -3,11 +3,18 @@ namespace Dalamud.Interface.Internal.ReShadeHandling;
/// Available handling modes for working with ReShade.
internal enum ReShadeHandlingMode
{
+ /// Use the default method, whatever it is for the current Dalamud version.
+ Default = 0,
+
+ /// Unwrap ReShade from the swap chain obtained from the game.
+ UnwrapReShade,
+
/// Register as a ReShade addon, and draw on reshade_overlay event.
ReShadeAddon,
- /// Unwraps ReShade from the swap chain obtained from the game.
- UnwrapReShade,
+ /// Hook DXGISwapChain::on_present(UINT flags, const DXGI_PRESENT_PARAMETERS *params) in
+ /// dxgi_swapchain.cpp.
+ HookReShadeDxgiSwapChainOnPresent,
/// Do not do anything special about it. ReShade will process Dalamud rendered stuff.
None = -1,
diff --git a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabExperimental.cs b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabExperimental.cs
index 3f6ec783e..6da89004b 100644
--- a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabExperimental.cs
+++ b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabExperimental.cs
@@ -79,16 +79,28 @@ public class SettingsTabExperimental : SettingsTab
"You may try different options to work around problems you may encounter.\nRestart is required for changes to take effect."),
c => c.ReShadeHandlingMode,
(v, c) => c.ReShadeHandlingMode = v,
- fallbackValue: ReShadeHandlingMode.ReShadeAddon)
+ fallbackValue: ReShadeHandlingMode.ReShadeAddon,
+ warning: static rshm =>
+ rshm is ReShadeHandlingMode.UnwrapReShade or ReShadeHandlingMode.None
+ ? null
+ : Loc.Localize(
+ "DalamudSettingsReShadeHandlingModeIgnoredVTableHookMode",
+ "Current option will be ignored and no special ReShade handling will be done, because SwapChain vtable hook mode is set."))
{
FriendlyEnumNameGetter = x => x switch
{
- ReShadeHandlingMode.ReShadeAddon => Loc.Localize(
- "DalamudSettingsReShadeHandlingModeReShadeAddon",
- "ReShade addon"),
+ ReShadeHandlingMode.Default => Loc.Localize(
+ "DalamudSettingsReShadeHandlingModeDefault",
+ "Default"),
ReShadeHandlingMode.UnwrapReShade => Loc.Localize(
"DalamudSettingsReShadeHandlingModeUnwrapReShade",
"Unwrap ReShade"),
+ ReShadeHandlingMode.ReShadeAddon => Loc.Localize(
+ "DalamudSettingsReShadeHandlingModeReShadeAddon",
+ "ReShade addon"),
+ ReShadeHandlingMode.HookReShadeDxgiSwapChainOnPresent => Loc.Localize(
+ "DalamudSettingsReShadeHandlingModeHookReShadeDxgiSwapChainOnPresent",
+ "Hook ReShade DXGISwapChain::OnPresent"),
ReShadeHandlingMode.None => Loc.Localize(
"DalamudSettingsReShadeHandlingModeNone",
"Do not handle"),
@@ -96,12 +108,18 @@ public class SettingsTabExperimental : SettingsTab
},
FriendlyEnumDescriptionGetter = x => x switch
{
- ReShadeHandlingMode.ReShadeAddon => Loc.Localize(
- "DalamudSettingsReShadeHandlingModeReShadeAddonDescription",
- "Dalamud will register itself as a ReShade addon. Most compatibility is expected, but multi-monitor window option will require reloading ReShade every time a new window is opened, or even may not work at all."),
+ ReShadeHandlingMode.Default => Loc.Localize(
+ "DalamudSettingsReShadeHandlingModeDefaultDescription",
+ "Dalamud will use the developer-recommend settings. If nothing's wrong, keeping this option is recommended."),
ReShadeHandlingMode.UnwrapReShade => Loc.Localize(
"DalamudSettingsReShadeHandlingModeUnwrapReShadeDescription",
"Dalamud will exclude itself from all ReShade handling. Multi-monitor windows should work fine with this mode, but it may not be supported and crash in future ReShade versions."),
+ ReShadeHandlingMode.ReShadeAddon => Loc.Localize(
+ "DalamudSettingsReShadeHandlingModeReShadeAddonDescription",
+ "Dalamud will register itself as a ReShade addon. Multi-monitor window option will require reloading ReShade every time a new window is opened, or even may not work at all."),
+ ReShadeHandlingMode.HookReShadeDxgiSwapChainOnPresent => Loc.Localize(
+ "DalamudSettingsReShadeHandlingModeHookReShadeDxgiSwapChainOnPresentDescription",
+ "Dalamud will use an unsupported method of detouring an internal ReShade function. Multi-monitor window option will require reloading ReShade every time a new window is opened, or even may not work at all."),
ReShadeHandlingMode.None => Loc.Localize(
"DalamudSettingsReShadeHandlingModeNoneDescription",
"No special handling will be done for ReShade. Dalamud will be under the effect of ReShade postprocessing."),