From 1f315be94e3c8df3e27b0dede77f25654529909f Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Mon, 22 Jul 2024 05:42:18 +0900 Subject: [PATCH 1/8] Implement ReShade addon interface --- .../Internal/InterfaceManager.AsHook.cs | 75 + .../InterfaceManager.AsReShadeAddon.cs | 86 + .../Interface/Internal/InterfaceManager.cs | 172 +- .../ReShadeAddonInterface.AddonEvent.cs | 1706 +++++++++++++++++ .../Internal/ReShadeAddonInterface.Exports.cs | 59 + .../Internal/ReShadeAddonInterface.cs | 176 ++ Dalamud/Interface/Internal/SwapChainHelper.cs | 110 -- 7 files changed, 2140 insertions(+), 244 deletions(-) create mode 100644 Dalamud/Interface/Internal/InterfaceManager.AsHook.cs create mode 100644 Dalamud/Interface/Internal/InterfaceManager.AsReShadeAddon.cs create mode 100644 Dalamud/Interface/Internal/ReShadeAddonInterface.AddonEvent.cs create mode 100644 Dalamud/Interface/Internal/ReShadeAddonInterface.Exports.cs create mode 100644 Dalamud/Interface/Internal/ReShadeAddonInterface.cs diff --git a/Dalamud/Interface/Internal/InterfaceManager.AsHook.cs b/Dalamud/Interface/Internal/InterfaceManager.AsHook.cs new file mode 100644 index 000000000..b2afb970f --- /dev/null +++ b/Dalamud/Interface/Internal/InterfaceManager.AsHook.cs @@ -0,0 +1,75 @@ +using System.Diagnostics; + +using Dalamud.Utility; + +namespace Dalamud.Interface.Internal; + +/// +/// This class manages interaction with the ImGui interface. +/// +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; + } +} diff --git a/Dalamud/Interface/Internal/InterfaceManager.AsReShadeAddon.cs b/Dalamud/Interface/Internal/InterfaceManager.AsReShadeAddon.cs new file mode 100644 index 000000000..0f1eeb707 --- /dev/null +++ b/Dalamud/Interface/Internal/InterfaceManager.AsReShadeAddon.cs @@ -0,0 +1,86 @@ +using System.Diagnostics; + +using Dalamud.Utility; + +using TerraFX.Interop.DirectX; + +namespace Dalamud.Interface.Internal; + +/// +/// This class manages interaction with the ImGui interface. +/// +internal partial class InterfaceManager +{ + private unsafe void ReShadeAddonInterfaceOnDestroySwapChain(ref ReShadeAddonInterface.ApiObject swapchain) + { + var swapChain = swapchain.GetNative(); + if (this.scene?.SwapChain.NativePointer != (nint)swapChain) + return; + + this.scene?.OnPreResize(); + } + + private unsafe void ReShadeAddonInterfaceOnInitSwapChain(ref ReShadeAddonInterface.ApiObject swapchain) + { + var swapChain = swapchain.GetNative(); + 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); + } +} diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs index cbbf63075..10d508d99 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.cs @@ -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. /// [ServiceManager.EarlyLoadedService] -internal class InterfaceManager : IInternalDisposableService +internal partial class InterfaceManager : IInternalDisposableService { /// /// The default font size, in points. @@ -75,6 +77,11 @@ internal class InterfaceManager : IInternalDisposableService [ServiceManager.ServiceDependency] private readonly Framework framework = Service.Get(); + // ReShadeAddonInterface requires hooks to be alive to unregister itself. + [ServiceManager.ServiceDependency] + [UsedImplicitly] + private readonly HookManager hookManager = Service.Get(); + private readonly ConcurrentQueue runBeforeImGuiRender = new(); private readonly ConcurrentQueue runAfterImGuiRender = new(); @@ -82,8 +89,8 @@ internal class InterfaceManager : IInternalDisposableService private Hook? setCursorHook; private Hook? dxgiPresentHook; - private Hook? reshadeOnPresentHook; private Hook? 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(); - var dxgiAdapter = dxgiDev?.Adapter.QueryInterfaceOrNull(); + var dxgiAdapter = dxgiDev?.Adapter.QueryInterfaceOrNull(); 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.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.FromAddress(addr, this.PresentDetour); - Log.Verbose($"ReShade::DXGISwapChain::on_present address {Util.DescribeAddress(addr)}"); + this.resizeBuffersHook = Hook.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.FromAddress(addr, this.ReshadeOnPresentDetour); + this.resizeBuffersHook = Hook.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.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) diff --git a/Dalamud/Interface/Internal/ReShadeAddonInterface.AddonEvent.cs b/Dalamud/Interface/Internal/ReShadeAddonInterface.AddonEvent.cs new file mode 100644 index 000000000..23f01875d --- /dev/null +++ b/Dalamud/Interface/Internal/ReShadeAddonInterface.AddonEvent.cs @@ -0,0 +1,1706 @@ +namespace Dalamud.Interface.Internal; + +/// ReShade interface. +internal sealed partial class ReShadeAddonInterface +{ + /// Supported events emitted by ReShade. + private enum AddonEvent : uint + { +#pragma warning disable + + /// + /// Called after successful device creation, from: + /// + /// IDirect3D9::CreateDevice + /// IDirect3D9Ex::CreateDeviceEx + /// IDirect3DDevice9::Reset + /// IDirect3DDevice9Ex::ResetEx + /// D3D10CreateDevice + /// D3D10CreateDevice1 + /// D3D10CreateDeviceAndSwapChain + /// D3D10CreateDeviceAndSwapChain1 + /// D3D11CreateDevice + /// D3D11CreateDeviceAndSwapChain + /// D3D12CreateDevice + /// glMakeCurrent + /// vkCreateDevice + /// + /// Callback function signature: void (api::device *device) + /// + InitDevice, + + /// + /// Called on device destruction, before: + /// + /// IDirect3DDevice9::Reset + /// IDirect3DDevice9Ex::ResetEx + /// IDirect3DDevice9::Release + /// ID3D10Device::Release + /// ID3D11Device::Release + /// ID3D12Device::Release + /// wglDeleteContext + /// vkDestroyDevice + /// + /// Callback function signature: void (api::device *device) + /// + DestroyDevice, + + /// + /// Called after successful command list creation, from: + /// + /// ID3D11Device::CreateDeferredContext + /// ID3D11Device1::CreateDeferredContext1 + /// ID3D11Device2::CreateDeferredContext2 + /// ID3D11Device3::CreateDeferredContext3 + /// ID3D12Device::CreateCommandList + /// ID3D12Device4::CreateCommandList1 + /// vkAllocateCommandBuffers + /// + /// Callback function signature: void (api::command_list *cmd_list) + /// + /// + /// In case of D3D9, D3D10, D3D11 and OpenGL this is called during device initialization as well and behaves as if an implicit immediate command list was created. + /// + InitCommandList, + + /// + /// Called on command list destruction, before: + /// + /// ID3D11CommandList::Release + /// ID3D12CommandList::Release + /// vkFreeCommandBuffers + /// + /// Callback function signature: void (api::command_list *cmd_list) + /// + DestroyCommandList, + + /// + /// Called after successful command queue creation, from: + /// + /// ID3D12Device::CreateCommandQueue + /// vkCreateDevice (for every queue associated with the device) + /// + /// Callback function signature: void (api::command_queue *queue) + /// + /// + /// In case of D3D9, D3D10, D3D11 and OpenGL this is called during device initialization as well and behaves as if an implicit command queue was created. + /// + InitCommandQueue, + + /// + /// Called on command queue destruction, before: + /// + /// ID3D12CommandQueue::Release + /// vkDestroyDevice (for every queue associated with the device) + /// + /// Callback function signature: void (api::command_queue *queue) + /// + DestroyCommandQueue, + + /// + /// Called after successful swap chain creation, from: + /// + /// IDirect3D9::CreateDevice (for the implicit swap chain) + /// IDirect3D9Ex::CreateDeviceEx (for the implicit swap chain) + /// IDirect3D9Device::CreateAdditionalSwapChain + /// IDXGIFactory::CreateSwapChain + /// IDXGIFactory2::CreateSwapChain(...) + /// wglMakeCurrent + /// wglSwapBuffers (after window was resized) + /// vkCreateSwapchainKHR + /// xrCreateSession + /// + /// In addition, called when swap chain is resized, after: + /// + /// IDirect3DDevice9::Reset (for the implicit swap chain) + /// IDirect3DDevice9Ex::ResetEx (for the implicit swap chain) + /// IDXGISwapChain::ResizeBuffers + /// IDXGISwapChain3::ResizeBuffers1 + /// + /// Callback function signature: void (api::swapchain *swapchain) + /// + InitSwapChain, + + /// + /// Called on swap chain creation, before: + /// + /// IDirect3D9::CreateDevice (for the implicit swap chain) + /// IDirect3D9Ex::CreateDeviceEx (for the implicit swap chain) + /// IDirect3D9Device::CreateAdditionalSwapChain + /// IDirect3D9Device::Reset (for the implicit swap chain) + /// IDirect3D9DeviceEx::ResetEx (for the implicit swap chain) + /// IDXGIFactory::CreateSwapChain + /// IDXGIFactory2::CreateSwapChain(...) + /// IDXGISwapChain::ResizeBuffers + /// IDXGISwapChain3::ResizeBuffers1 + /// wglSetPixelFormat + /// vkCreateSwapchainKHR + /// + /// Callback function signature: bool (api::swapchain_desc &desc, void *hwnd) + /// + /// + /// To overwrite the swap chain description, modify desc in the callback and return , otherwise return . + /// + CreateSwapChain, + + /// + /// Called on swap chain destruction, before: + /// + /// IDirect3DDevice9::Release (for the implicit swap chain) + /// IDirect3DSwapChain9::Release + /// IDXGISwapChain::Release + /// wglDeleteContext + /// wglSwapBuffers (after window was resized) + /// vkDestroySwapchainKHR + /// xrDestroySession + /// + /// In addition, called when swap chain is resized, before: + /// + /// IDirect3DDevice9::Reset (for the implicit swap chain) + /// IDirect3DDevice9Ex::ResetEx (for the implicit swap chain) + /// IDXGISwapChain::ResizeBuffers + /// IDXGISwapChain1::ResizeBuffers1 + /// + /// Callback function signature: void (api::swapchain *swapchain) + /// + DestroySwapChain, + + /// + /// Called after effect runtime initialization (which happens after swap chain creation or a swap chain buffer resize). + /// Callback function signature: void (api::effect_runtime *runtime) + /// + InitEffectRuntime, + + /// + /// Called when an effect runtime is reset or destroyed. + /// Callback function signature: void (api::effect_runtime *runtime) + /// + DestroyEffectRuntime, + + /// + /// Called after successful sampler creation from: + /// + /// ID3D10Device::CreateSamplerState + /// ID3D11Device::CreateSamplerState + /// ID3D12Device::CreateSampler + /// vkCreateSampler + /// + /// Callback function signature: void (api::device *device, const api::sampler_desc &desc, api::sampler sampler) + /// + /// + /// Is not called in D3D9 (since samplers are loose state there) or OpenGL. + /// + InitSampler, + + /// + /// Called on sampler creation, before: + /// + /// ID3D10Device::CreateSamplerState + /// ID3D11Device::CreateSamplerState + /// ID3D12Device::CreateSampler + /// ID3D12Device::CreateRootSignature + /// vkCreateSampler + /// + /// Callback function signature: bool (api::device *device, api::sampler_desc &desc) + /// + /// + /// To overwrite the sampler description, modify desc in the callback and return , otherwise return . + /// Is not called in D3D9 (since samplers are loose state there) or OpenGL. + /// + CreateSampler, + + /// + /// Called on sampler destruction, before: + /// + /// ID3D10SamplerState::Release + /// ID3D11SamplerState::Release + /// glDeleteSamplers + /// vkDestroySampler + /// + /// Callback function signature: void (api::device *device, api::sampler sampler) + /// + /// + /// Is not called in D3D9 (since samplers are loose state there), D3D12 (since samplers are descriptor handles instead of objects there) or OpenGL. + /// + DestroySampler, + + /// + /// Called after successful resource creation from: + /// + /// IDirect3DDevice9::CreateVertexBuffer + /// IDirect3DDevice9::CreateIndexBuffer + /// IDirect3DDevice9::CreateTexture + /// IDirect3DDevice9::CreateCubeTexture + /// IDirect3DDevice9::CreateVolumeTexture + /// IDirect3DDevice9::CreateRenderTargetSurface + /// IDirect3DDevice9::CreateDepthStencilSurface + /// IDirect3DDevice9::CreateOffscreenPlainSurface + /// IDirect3DDevice9Ex::CreateRenderTargetSurfaceEx + /// IDirect3DDevice9Ex::CreateDepthStencilSurfaceEx + /// IDirect3DDevice9Ex::CreateOffscreenPlainSurfaceEx + /// ID3D10Device::CreateBuffer + /// ID3D10Device::CreateTexture1D + /// ID3D10Device::CreateTexture2D + /// ID3D10Device::CreateTexture2D + /// ID3D11Device::CreateBuffer + /// ID3D11Device::CreateTexture1D + /// ID3D11Device::CreateTexture2D + /// ID3D11Device::CreateTexture3D + /// ID3D11Device3::CreateTexture2D + /// ID3D11Device3::CreateTexture3D + /// ID3D12Device::CreateCommittedResource + /// ID3D12Device::CreatePlacedResource + /// ID3D12Device::CreateReservedResource + /// ID3D12Device4::CreateCommittedResource1 + /// ID3D12Device4::CreateReservedResource1 + /// glBufferData + /// glBufferStorage + /// glNamedBufferData + /// glNamedBufferStorage + /// glTexImage1D + /// glTexImage2D + /// glTexImage2DMultisample + /// glTexImage3D + /// glTexImage3DMultisample + /// glCompressedTexImage1D + /// glCompressedTexImage2D + /// glCompressedTexImage3D + /// glTexStorage1D + /// glTexStorage2D + /// glTexStorage2DMultisample + /// glTexStorage3D + /// glTexStorage3DMultisample + /// glTextureStorage1D + /// glTextureStorage2D + /// glTextureStorage2DMultisample + /// glTextureStorage3D + /// glTextureStorage3DMultisample + /// glRenderbufferStorage + /// glRenderbufferStorageMultisample + /// glNamedRenderbufferStorage + /// glNamedRenderbufferStorageMultisample + /// vkBindBufferMemory + /// vkBindBufferMemory2 + /// vkBindImageMemory + /// vkBindImageMemory2 + /// + /// Callback function signature: void (api::device *device, const api::resource_desc &desc, const api::subresource_data *initial_data, api::resource_usage initial_state, api::resource resource) + /// + /// + /// May be called multiple times with the same resource handle (whenever the resource is updated or its reference count is incremented). + /// + InitResource, + + /// + /// Called on resource creation, before: + /// + /// IDirect3DDevice9::CreateVertexBuffer + /// IDirect3DDevice9::CreateIndexBuffer + /// IDirect3DDevice9::CreateTexture + /// IDirect3DDevice9::CreateCubeTexture + /// IDirect3DDevice9::CreateVolumeTexture + /// IDirect3DDevice9::CreateRenderTargetSurface + /// IDirect3DDevice9::CreateDepthStencilSurface + /// IDirect3DDevice9::CreateOffscreenPlainSurface + /// IDirect3DDevice9Ex::CreateRenderTargetSurfaceEx + /// IDirect3DDevice9Ex::CreateDepthStencilSurfaceEx + /// IDirect3DDevice9Ex::CreateOffscreenPlainSurfaceEx + /// ID3D10Device::CreateBuffer + /// ID3D10Device::CreateTexture1D + /// ID3D10Device::CreateTexture2D + /// ID3D10Device::CreateTexture2D + /// ID3D11Device::CreateBuffer + /// ID3D11Device::CreateTexture1D + /// ID3D11Device::CreateTexture2D + /// ID3D11Device::CreateTexture3D + /// ID3D11Device3::CreateTexture2D + /// ID3D11Device3::CreateTexture3D + /// ID3D12Device::CreateCommittedResource + /// ID3D12Device::CreatePlacedResource + /// ID3D12Device::CreateReservedResource + /// ID3D12Device4::CreateCommittedResource1 + /// ID3D12Device4::CreateReservedResource1 + /// glBufferData + /// glBufferStorage + /// glNamedBufferData + /// glNamedBufferStorage + /// glTexImage1D + /// glTexImage2D + /// glTexImage2DMultisample + /// glTexImage3D + /// glTexImage3DMultisample + /// glCompressedTexImage1D + /// glCompressedTexImage2D + /// glCompressedTexImage3D + /// glTexStorage1D + /// glTexStorage2D + /// glTexStorage2DMultisample + /// glTexStorage3D + /// glTexStorage3DMultisample + /// glTextureStorage1D + /// glTextureStorage2D + /// glTextureStorage2DMultisample + /// glTextureStorage3D + /// glTextureStorage3DMultisample + /// glRenderbufferStorage + /// glRenderbufferStorageMultisample + /// glNamedRenderbufferStorage + /// glNamedRenderbufferStorageMultisample + /// vkCreateBuffer + /// vkCreateImage + /// + /// Callback function signature: bool (api::device *device, api::resource_desc &desc, api::subresource_data *initial_data, api::resource_usage initial_state) + /// + /// + /// To overwrite the resource description, modify desc in the callback and return , otherwise return . + /// + CreateResource, + + /// + /// Called on resource destruction, before: + /// + /// IDirect3DResource9::Release + /// ID3D10Resource::Release + /// ID3D11Resource::Release + /// ID3D12Resource::Release + /// glDeleteBuffers + /// glDeleteTextures + /// glDeleteRenderbuffers + /// vkDestroyBuffer + /// vkDestroyImage + /// + /// Callback function signature: void (api::device *device, api::resource resource) + /// + DestroyResource, + + /// + /// Called after successful resource view creation from: + /// + /// IDirect3DDevice9::CreateTexture + /// IDirect3DDevice9::CreateCubeTexture + /// IDirect3DDevice9::CreateVolumeTexture + /// ID3D10Device::CreateShaderResourceView + /// ID3D10Device::CreateRenderTargetView + /// ID3D10Device::CreateDepthStencilView + /// ID3D10Device1::CreateShaderResourceView1 + /// ID3D11Device::CreateShaderResourceView + /// ID3D11Device::CreateUnorderedAccessView + /// ID3D11Device::CreateRenderTargetView + /// ID3D11Device::CreateDepthStencilView + /// ID3D11Device3::CreateShaderResourceView1 + /// ID3D11Device3::CreateUnorderedAccessView1 + /// ID3D11Device3::CreateRenderTargetView1 + /// ID3D12Device::CreateShaderResourceView + /// ID3D12Device::CreateUnorderedAccessView + /// ID3D12Device::CreateRenderTargetView + /// ID3D12Device::CreateDepthStencilView + /// glTexBuffer + /// glTextureBuffer + /// glTextureView + /// vkCreateBufferView + /// vkCreateImageView + /// vkCreateAccelerationStructureKHR + /// + /// Callback function signature: void (api::device *device, api::resource resource, api::resource_usage usage_type, const api::resource_view_desc &desc, api::resource_view view) + /// + /// + /// May be called multiple times with the same resource view handle (whenever the resource view is updated). + /// + InitResourceView, + + /// + /// Called on resource view creation, before: + /// + /// ID3D10Device::CreateShaderResourceView + /// ID3D10Device::CreateRenderTargetView + /// ID3D10Device::CreateDepthStencilView + /// ID3D10Device1::CreateShaderResourceView1 + /// ID3D11Device::CreateShaderResourceView + /// ID3D11Device::CreateUnorderedAccessView + /// ID3D11Device::CreateRenderTargetView + /// ID3D11Device::CreateDepthStencilView + /// ID3D11Device3::CreateShaderResourceView1 + /// ID3D11Device3::CreateUnorderedAccessView1 + /// ID3D11Device3::CreateRenderTargetView1 + /// ID3D12Device::CreateShaderResourceView + /// ID3D12Device::CreateUnorderedAccessView + /// ID3D12Device::CreateRenderTargetView + /// ID3D12Device::CreateDepthStencilView + /// glTexBuffer + /// glTextureBuffer + /// glTextureView + /// vkCreateBufferView + /// vkCreateImageView + /// vkCreateAccelerationStructureKHR + /// + /// Callback function signature: bool (api::device *device, api::resource resource, api::resource_usage usage_type, api::resource_view_desc &desc) + /// + /// + /// To overwrite the resource view description, modify desc in the callback and return , otherwise return . + /// Is not called in D3D9 (since resource views are tied to resources there). + /// + CreateResourceView, + + /// + /// Called on resource view destruction, before: + /// + /// IDirect3DResource9::Release + /// ID3D10View::Release + /// ID3D11View::Release + /// glDeleteTextures + /// vkDestroyBufferView + /// vkDestroyImageView + /// vkDestroyAccelerationStructureKHR + /// + /// Callback function signature: void (api::device *device, api::resource_view view) + /// + /// + /// Is not called in D3D12 (since resource views are descriptor handles instead of objects there). + /// + DestroyResourceView, + + /// + /// Called after: + /// + /// IDirect3DVertexBuffer9::Lock + /// IDirect3DIndexBuffer9::Lock + /// ID3D10Resource::Map + /// ID3D11DeviceContext::Map + /// ID3D12Resource::Map + /// glMapBuffer + /// glMapBufferRange + /// glMapNamedBuffer + /// glMapNamedBufferRange + /// + /// Callback function signature: void (api::device *device, api::resource resource, uint64_t offset, uint64_t size, api::map_access access, void **data) + /// + MapBufferRegion, + + /// + /// Called before: + /// + /// IDirect3DVertexBuffer9::Unlock + /// IDirect3DIndexBuffer9::Unlock + /// ID3D10Resource::Unmap + /// ID3D11DeviceContext::Unmap + /// ID3D12Resource::Unmap + /// glUnmapBuffer + /// glUnmapNamedBuffer + /// + /// Callback function signature: void (api::device *device, api::resource resource) + /// + UnmapBufferRegion, + + /// + /// Called after: + /// + /// IDirect3DSurface9::LockRect + /// IDirect3DVolume9::LockBox + /// IDirect3DTexture9::LockRect + /// IDirect3DVolumeTexture9::LockBox + /// IDirect3DCubeTexture9::LockRect + /// ID3D10Resource::Map + /// ID3D11DeviceContext::Map + /// ID3D12Resource::Map + /// + /// Callback function signature: void (api::device *device, api::resource resource, uint32_t subresource, const api::subresource_box *box, api::map_access access, api::subresource_data *data) + /// + MapTextureRegion, + + /// + /// Called before: + /// + /// IDirect3DSurface9::UnlockRect + /// IDirect3DVolume9::UnlockBox + /// IDirect3DTexture9::UnlockRect + /// IDirect3DVolumeTexture9::UnlockBox + /// IDirect3DCubeTexture9::UnlockRect + /// ID3D10Resource::Unmap + /// ID3D11DeviceContext::Unmap + /// ID3D12Resource::Unmap + /// + /// Callback function signature: void (api::device *device, api::resource resource, uint32_t subresource) + /// + UnmapTextureRegion, + + /// + /// Called before: + /// + /// ID3D10Device::UpdateSubresource + /// ID3D11DeviceContext::UpdateSubresource + /// glBufferSubData + /// glNamedBufferSubData + /// + /// Callback function signature: bool (api::device *device, const void *data, api::resource resource, uint64_t offset, uint64_t size) + /// + /// + /// To prevent this command from being executed, return , otherwise return . + /// Destination resource will be in the state. + /// + UpdateBufferRegion, + + /// + /// Called before: + /// + /// ID3D10Device::UpdateSubresource + /// ID3D11DeviceContext::UpdateSubresource + /// glTexSubData1D + /// glTexSubData2D + /// glTexSubData3D + /// glTextureSubData1D + /// glTextureSubData2D + /// glTextureSubData3D + /// glCompressedTexSubData1D + /// glCompressedTexSubData2D + /// glCompressedTexSubData3D + /// glCompressedTextureSubData1D + /// glCompressedTextureSubData2D + /// glCompressedTextureSubData3D + /// + /// Callback function signature: bool (api::device *device, const api::subresource_data &data, api::resource resource, uint32_t subresource, const api::subresource_box *box) + /// + /// + /// To prevent this command from being executed, return , otherwise return . + /// Destination resource will be in the state. + /// + UpdateTextureRegion, + + /// + /// Called after successful pipeline creation from: + /// + /// IDirect3DDevice9::CreateVertexShader + /// IDirect3DDevice9::CreatePixelShader + /// IDirect3DDevice9::CreateVertexDeclaration + /// ID3D10Device::CreateVertexShader + /// ID3D10Device::CreateGeometryShader + /// ID3D10Device::CreateGeometryShaderWithStreamOutput + /// ID3D10Device::CreatePixelShader + /// ID3D10Device::CreateInputLayout + /// ID3D10Device::CreateBlendState + /// ID3D10Device::CreateDepthStencilState + /// ID3D10Device::CreateRasterizerState + /// ID3D10Device1::CreateBlendState1 + /// ID3D11Device::CreateVertexShader + /// ID3D11Device::CreateHullShader + /// ID3D11Device::CreateDomainShader + /// ID3D11Device::CreateGeometryShader + /// ID3D11Device::CreateGeometryShaderWithStreamOutput + /// ID3D11Device::CreatePixelShader + /// ID3D11Device::CreateComputeShader + /// ID3D11Device::CreateInputLayout + /// ID3D11Device::CreateBlendState + /// ID3D11Device::CreateDepthStencilState + /// ID3D11Device::CreateRasterizerState + /// ID3D11Device1::CreateBlendState1 + /// ID3D11Device1::CreateRasterizerState1 + /// ID3D11Device3::CreateRasterizerState2 + /// ID3D12Device::CreateComputePipelineState + /// ID3D12Device::CreateGraphicsPipelineState + /// ID3D12Device2::CreatePipelineState + /// ID3D12Device5::CreateStateObject + /// ID3D12Device7::AddToStateObject + /// ID3D12PipelineLibrary::LoadComputePipeline + /// ID3D12PipelineLibrary::LoadGraphicsPipeline + /// ID3D12PipelineLibrary1::LoadPipeline + /// glLinkProgram + /// vkCreateComputePipelines + /// vkCreateGraphicsPipelines + /// + /// Callback function signature: void (api::device *device, api::pipeline_layout layout, uint32_t subobject_count, const api::pipeline_subobject *subobjects, api::pipeline pipeline) + /// + /// + /// May be called multiple times with the same pipeline handle (whenever the pipeline is updated or its reference count is incremented). + /// + InitPipeline, + + /// + /// Called on pipeline creation, before: + /// + /// IDirect3DDevice9::CreateVertexShader + /// IDirect3DDevice9::CreatePixelShader + /// IDirect3DDevice9::CreateVertexDeclaration + /// ID3D10Device::CreateVertexShader + /// ID3D10Device::CreateGeometryShader + /// ID3D10Device::CreateGeometryShaderWithStreamOutput + /// ID3D10Device::CreatePixelShader + /// ID3D10Device::CreateInputLayout + /// ID3D10Device::CreateBlendState + /// ID3D10Device::CreateDepthStencilState + /// ID3D10Device::CreateRasterizerState + /// ID3D10Device1::CreateBlendState1 + /// ID3D11Device::CreateVertexShader + /// ID3D11Device::CreateHullShader + /// ID3D11Device::CreateDomainShader + /// ID3D11Device::CreateGeometryShader + /// ID3D11Device::CreateGeometryShaderWithStreamOutput + /// ID3D11Device::CreatePixelShader + /// ID3D11Device::CreateComputeShader + /// ID3D11Device::CreateInputLayout + /// ID3D11Device::CreateBlendState + /// ID3D11Device::CreateDepthStencilState + /// ID3D11Device::CreateRasterizerState + /// ID3D11Device1::CreateBlendState1 + /// ID3D11Device1::CreateRasterizerState1 + /// ID3D11Device3::CreateRasterizerState2 + /// ID3D12Device::CreateComputePipelineState + /// ID3D12Device::CreateGraphicsPipelineState + /// ID3D12Device2::CreatePipelineState + /// ID3D12Device5::CreateStateObject + /// glShaderSource + /// vkCreateComputePipelines + /// vkCreateGraphicsPipelines + /// + /// Callback function signature: bool (api::device *device, api::pipeline_layout layout, uint32_t subobject_count, const api::pipeline_subobject *subobjects) + /// + /// + /// To overwrite the pipeline description, modify desc in the callback and return , otherwise return . + /// + CreatePipeline, + + /// + /// Called on pipeline destruction, before: + /// + /// ID3D10VertexShader::Release + /// ID3D10GeometryShader::Release + /// ID3D10PixelShader::Release + /// ID3D10InputLayout::Release + /// ID3D10BlendState::Release + /// ID3D10DepthStencilState::Release + /// ID3D10RasterizerState::Release + /// ID3D11VertexShader::Release + /// ID3D11HullShader::Release + /// ID3D11DomainShader::Release + /// ID3D11GeometryShader::Release + /// ID3D11PixelShader::Release + /// ID3D11ComputeShader::Release + /// ID3D11InputLayout::Release + /// ID3D11BlendState::Release + /// ID3D11DepthStencilState::Release + /// ID3D11RasterizerState::Release + /// ID3D12PipelineState::Release + /// ID3D12StateObject::Release + /// glDeleteProgram + /// vkDestroyPipeline + /// + /// Callback function signature: void (api::device *device, api::pipeline pipeline) + /// + /// + /// Is not called in D3D9. + /// + DestroyPipeline, + + /// + /// Called after successful pipeline layout creation from: + /// + /// ID3D12Device::CreateRootSignature + /// vkCreatePipelineLayout + /// + /// Callback function signature: void (api::device *device, uint32_t param_count, const api::pipeline_layout_param *params, api::pipeline_layout layout) + /// + /// + /// In case of D3D9, D3D10, D3D11 and OpenGL this is called during device initialization as well and behaves as if an implicit global pipeline layout was created. + /// + InitPipelineLayout, + + /// + /// Called on pipeline layout creation, before: + /// + /// ID3D12Device::CreateRootSignature + /// vkCreatePipelineLayout + /// + /// Callback function signature: bool (api::device *device, uint32_t &param_count, api::pipeline_layout_param *&params) + /// + /// + /// Is not called in D3D9, D3D10, D3D11 or OpenGL. + /// + CreatePipelineLayout, + + /// + /// Called on pipeline layout destruction, before: + /// + /// ID3D12RootSignature::Release + /// VkDestroyPipelineLayout + /// + /// Callback function signature: void (api::device *device, api::pipeline_layout layout) + /// + DestroyPipelineLayout, + + /// + /// Called before: + /// + /// ID3D12Device::CopyDescriptors + /// ID3D12Device::CopyDescriptorsSimple + /// vkUpdateDescriptorSets + /// + /// Callback function signature: bool (api::device *device, uint32_t count, const api::descriptor_table_copy *copies) + /// + /// + /// To prevent this command from being executed, return , otherwise return . + /// + CopyDescriptorTables, + + /// + /// Called before: + /// + /// ID3D12Device::CreateConstantBufferView + /// ID3D12Device::CreateShaderResourceView + /// ID3D12Device::CreateUnorderedAccessView + /// ID3D12Device::CreateSampler + /// vkUpdateDescriptorSets + /// + /// Callback function signature: bool (api::device *device, uint32_t count, const api::descriptor_table_update *updates) + /// + /// + /// To prevent this command from being executed, return , otherwise return . + /// + UpdateDescriptorTables, + + /// + /// Called after successful query heap creation from: + /// + /// ID3D12Device::CreateQueryHeap + /// vkCreateQueryPool + /// + /// Callback function signature: void (api::device *device, api::query_type type, uint32_t size, api::query_heap heap) + /// + InitQueryHeap, + + /// + /// Called on query heap creation, before: + /// + /// ID3D12Device::CreateQueryHeap + /// vkCreateQueryPool + /// + /// Callback function signature: bool (api::device *device, api::query_type type, uint32_t &size) + /// + CreateQueryHeap, + + /// + /// Called on query heap destruction, before: + /// + /// ID3D12QueryHeap::Release + /// vkDestroyQueryPool + /// + /// Callback function signature: void (api::device *device, api::query_heap heap) + /// + DestroyQueryHeap, + + /// + /// Called before: + /// + /// vkGetQueryPoolResults + /// + /// Callback function signature: bool (api::device *device, api::query_heap heap, uint32_t first, uint32_t count, void *results, uint32_t stride) + /// + GetQueryHeapResults, + + /// + /// Called after: + /// + /// ID3D12GraphicsCommandList::ResourceBarrier + /// ID3D12GraphicsCommandList7::Barrier + /// vkCmdPipelineBarrier + /// vkCmdPipelineBarrier2 + /// + /// Callback function signature: void (api::command_list *cmd_list, uint32_t count, const api::resource *resources, const api::resource_usage *old_states, const api::resource_usage *new_states) + /// + Barrier, + + /// + /// Called before: + /// + /// ID3D12GraphicsCommandList4::BeginRenderPass + /// vkCmdBeginRenderPass + /// vkCmdBeginRenderPass2 + /// vkCmdNextSubpass + /// vkCmdNextSubpass2 + /// vkCmdBeginRendering + /// + /// Callback function signature: void (api::command_list *cmd_list, uint32_t count, const api::render_pass_render_target_desc *rts, const api::render_pass_depth_stencil_desc *ds) + /// + /// + /// The depth-stencil description argument is optional and may be (which indicates that no depth-stencil is used). + /// + BeginRenderPass, + + /// + /// Called before: + /// + /// ID3D12GraphicsCommandList4::EndRenderPass + /// vkCmdEndRenderPass + /// vkCmdEndRenderPass2 + /// vkCmdNextSubpass + /// vkCmdNextSubpass2 + /// vkCmdEndRendering + /// + /// Callback function signature: void (api::command_list *cmd_list) + /// + EndRenderPass, + + /// + /// Called after: + /// + /// IDirect3DDevice9::SetRenderTarget + /// IDirect3DDevice9::SetDepthStencilSurface + /// ID3D10Device::OMSetRenderTargets + /// ID3D11DeviceContext::OMSetRenderTargets + /// ID3D11DeviceContext::OMSetRenderTargetsAndUnorderedAccessViews + /// ID3D12GraphicsCommandList::OMSetRenderTargets + /// glBindFramebuffer + /// + /// Callback function signature: void (api::command_list *cmd_list, uint32_t count, const api::resource_view *rtvs, api::resource_view dsv) + /// + BindRenderTargetsAndDepthStencil, + + /// + /// Called after: + /// + /// IDirect3DDevice9::SetVertexShader + /// IDirect3DDevice9::SetPixelShader + /// IDirect3DDevice9::SetVertexDeclaration + /// IDirect3DDevice9::ProcessVertices + /// ID3D10Device::VSSetShader + /// ID3D10Device::GSSetShader + /// ID3D10Device::PSSetShader + /// ID3D10Device::IASetInputLayout + /// ID3D10Device::OMSetBlendState + /// ID3D10Device::OMSetDepthStencilState + /// ID3D10Device::RSSetState + /// ID3D11DeviceContext::VSSetShader + /// ID3D11DeviceContext::HSSetShader + /// ID3D11DeviceContext::DSSetShader + /// ID3D11DeviceContext::GSSetShader + /// ID3D11DeviceContext::PSSetShader + /// ID3D11DeviceContext::CSSetShader + /// ID3D11DeviceContext::IASetInputLayout + /// ID3D11DeviceContext::OMSetBlendState + /// ID3D11DeviceContext::OMSetDepthStencilState + /// ID3D11DeviceContext::RSSetState + /// ID3D12GraphicsCommandList::Reset + /// ID3D12GraphicsCommandList::SetPipelineState + /// ID3D12GraphicsCommandList4::SetPipelineState1 + /// glUseProgram + /// glBindVertexArray + /// vkCmdBindPipeline + /// + /// Callback function signature: void (api::command_list *cmd_list, api::pipeline_stage stages, api::pipeline pipeline) + /// + BindPipeline, + + /// + /// Called after: + /// + /// IDirect3DDevice9::SetRenderState + /// ID3D10Device::IASetPrimitiveTopology + /// ID3D10Device::OMSetBlendState + /// ID3D10Device::OMSetDepthStencilState + /// ID3D11DeviceContext::IASetPrimitiveTopology + /// ID3D11DeviceContext::OMSetBlendState + /// ID3D11DeviceContext::OMSetDepthStencilState + /// ID3D12GraphicsCommandList::IASetPrimitiveTopology + /// ID3D12GraphicsCommandList::OMSetBlendFactor + /// ID3D12GraphicsCommandList::OMSetStencilRef + /// gl(...) + /// vkCmdSetDepthBias + /// vkCmdSetBlendConstants + /// vkCmdSetStencilCompareMask + /// vkCmdSetStencilWriteMask + /// vkCmdSetStencilReference + /// + /// Callback function signature: void (api::command_list *cmd_list, uint32_t count, const api::dynamic_state *states, const uint32_t *values) + /// + BindPipelineStates, + + /// + /// Called after: + /// + /// IDirect3DDevice9::SetViewport + /// IDirect3DDevice9::SetRenderTarget (implicitly updates the viewport) + /// ID3D10Device::RSSetViewports + /// ID3D11DeviceContext::RSSetViewports + /// ID3D12GraphicsCommandList::RSSetViewports + /// glViewport + /// glViewportArrayv + /// glViewportIndexedf + /// glViewportIndexedfv + /// vkCmdSetViewport + /// + /// Callback function signature: void (api::command_list *cmd_list, uint32_t first, uint32_t count, const api::viewport *viewports) + /// + BindViewports, + + /// + /// Called after: + /// + /// IDirect3DDevice9::SetScissorRect + /// ID3D10Device::RSSetScissorRects + /// ID3D11DeviceContext::RSSetScissorRects + /// ID3D12GraphicsCommandList::RSSetScissorRects + /// glScissor + /// glScissorArrayv + /// glScissorIndexed + /// glScissorIndexedv + /// vkCmdSetScissor + /// + /// Callback function signature: void (api::command_list *cmd_list, uint32_t first, uint32_t count, const api::rect *rects) + /// + BindScissorRects, + + /// + /// Called after: + /// + /// IDirect3DDevice9::SetVertexShaderConstantF + /// IDirect3DDevice9::SetPixelShaderConstantF + /// ID3D12GraphicsCommandList::SetComputeRoot32BitConstant + /// ID3D12GraphicsCommandList::SetComputeRoot32BitConstants + /// ID3D12GraphicsCommandList::SetGraphicsRoot32BitConstant + /// ID3D12GraphicsCommandList::SetGraphicsRoot32BitConstants + /// glUniform(...) + /// vkCmdPushConstants + /// + /// Callback function signature: void (api::command_list *cmd_list, api::shader_stage stages, api::pipeline_layout layout, uint32_t layout_param, uint32_t first, uint32_t count, const void *values) + /// + PushConstants, + + /// + /// Called after: + /// + /// IDirect3DDevice9::SetTexture + /// ID3D10Device::VSSetSamplers + /// ID3D10Device::VSSetShaderResources + /// ID3D10Device::VSSetConstantBuffers + /// ID3D10Device::GSSetSamplers + /// ID3D10Device::GSSetShaderResources + /// ID3D10Device::GSSetConstantBuffers + /// ID3D10Device::PSSetSamplers + /// ID3D10Device::PSSetShaderResources + /// ID3D10Device::PSSetConstantBuffers + /// ID3D11DeviceContext::VSSetSamplers + /// ID3D11DeviceContext::VSSetShaderResources + /// ID3D11DeviceContext::VSSetConstantBuffers + /// ID3D11DeviceContext::HSSetSamplers + /// ID3D11DeviceContext::HSSetShaderResources + /// ID3D11DeviceContext::HSSetConstantBuffers + /// ID3D11DeviceContext::DSSetSamplers + /// ID3D11DeviceContext::DSSetShaderResources + /// ID3D11DeviceContext::DSSetConstantBuffers + /// ID3D11DeviceContext::GSSetSamplers + /// ID3D11DeviceContext::GSSetShaderResources + /// ID3D11DeviceContext::GSSetConstantBuffers + /// ID3D11DeviceContext::PSSetSamplers + /// ID3D11DeviceContext::PSSetShaderResources + /// ID3D11DeviceContext::PSSetConstantBuffers + /// ID3D11DeviceContext::CSSetSamplers + /// ID3D11DeviceContext::CSSetShaderResources + /// ID3D11DeviceContext::CSSetUnorderedAccessViews + /// ID3D11DeviceContext::CSSetConstantBuffers + /// ID3D12GraphicsCommandList::SetComputeRootConstantBufferView + /// ID3D12GraphicsCommandList::SetGraphicsRootConstantBufferView + /// ID3D12GraphicsCommandList::SetComputeRootShaderResourceView + /// ID3D12GraphicsCommandList::SetGraphicsRootShaderResourceView + /// ID3D12GraphicsCommandList::SetComputeRootUnorderedAccessView + /// ID3D12GraphicsCommandList::SetGraphicsRootUnorderedAccessView + /// glBindBufferBase + /// glBindBufferRange + /// glBindBuffersBase + /// glBindBuffersRange + /// glBindTexture + /// glBindImageTexture + /// glBindTextures + /// glBindImageTextures + /// glBindTextureUnit + /// glBindMultiTextureEXT + /// vkCmdPushDescriptorSetKHR + /// + /// Callback function signature: void (api::command_list *cmd_list, api::shader_stage stages, api::pipeline_layout layout, uint32_t layout_param, const api::descriptor_table_update &update) + /// + PushDescriptors, + + /// + /// Called after: + /// + /// ID3D12GraphicsCommandList::SetComputeRootSignature + /// ID3D12GraphicsCommandList::SetGraphicsRootSignature + /// ID3D12GraphicsCommandList::SetComputeRootDescriptorTable + /// ID3D12GraphicsCommandList::SetGraphicsRootDescriptorTable + /// vkCmdBindDescriptorSets + /// + /// Callback function signature: void (api::command_list *cmd_list, api::shader_stage stages, api::pipeline_layout layout, uint32_t first, uint32_t count, const api::descriptor_table *tables) + /// + BindDescriptorTables, + + /// + /// Called after: + /// + /// IDirect3DDevice9::SetIndices + /// ID3D10Device::IASetIndexBuffer + /// ID3D11DeviceContext::IASetIndexBuffer + /// ID3D12GraphicsCommandList::IASetIndexBuffer + /// glBindBuffer + /// vkCmdBindIndexBuffer + /// + /// Callback function signature: void (api::command_list *cmd_list, api::resource buffer, uint64_t offset, uint32_t index_size) + /// + BindIndexBuffer, + + /// + /// Called after: + /// + /// IDirect3DDevice9::SetStreamSource + /// ID3D10Device::IASetVertexBuffers + /// ID3D11DeviceContext::IASetVertexBuffers + /// ID3D12GraphicsCommandList::IASetVertexBuffers + /// glBindBuffer + /// glBindVertexBuffer + /// glBindVertexBuffers + /// vkCmdBindVertexBuffers + /// vkCmdBindVertexBuffers2 + /// + /// Callback function signature: void (api::command_list *cmd_list, uint32_t first, uint32_t count, const api::resource *buffers, const uint64_t *offsets, const uint32_t *strides) + /// + /// + /// The strides argument is optional and may be . + /// + BindVertexBuffers, + + /// + /// Called after: + /// + /// IDirect3DDevice9::ProcessVertices + /// ID3D10Device::SOSetTargets + /// ID3D11DeviceContext::SOSetTargets + /// ID3D12GraphicsCommandList::SOSetTargets + /// glBindBufferBase + /// glBindBufferRange + /// glBindBuffersBase + /// glBindBuffersRange + /// vkCmdBindTransformFeedbackBuffersEXT + /// + /// Callback function signature: void (api::command_list *cmd_list, uint32_t first, uint32_t count, const api::resource *buffers, const uint64_t *offsets, const uint64_t *max_sizes, const api::resource *counter_buffers, const uint64_t *counter_offsets) + /// + /// + /// The counter arguments are optional and may be . + /// + BindStreamOutputBuffers, + + /// + /// Called before: + /// + /// IDirect3DDevice9::DrawPrimitive + /// IDirect3DDevice9::DrawPrimitiveUP + /// IDirect3DDevice9::ProcessVertices + /// ID3D10Device::Draw + /// ID3D10Device::DrawInstanced + /// ID3D11DeviceContext::Draw + /// ID3D11DeviceContext::DrawInstanced + /// ID3D12GraphicsCommandList::DrawInstanced + /// glDrawArrays + /// glDrawArraysInstanced + /// glDrawArraysInstancedBaseInstance + /// glMultiDrawArrays + /// vkCmdDraw + /// + /// Callback function signature: bool (api::command_list *cmd_list, uint32_t vertex_count, uint32_t instance_count, uint32_t first_vertex, uint32_t first_instance) + /// + /// + /// To prevent this command from being executed, return , otherwise return . + /// + Draw, + + /// + /// Called before: + /// + /// IDirect3DDevice9::DrawIndexedPrimitive + /// IDirect3DDevice9::DrawIndexedPrimitiveUP + /// ID3D10Device::DrawIndexed + /// ID3D10Device::DrawIndexedInstanced + /// ID3D11DeviceContext::DrawIndexed + /// ID3D11DeviceContext::DrawIndexedInstanced + /// ID3D12GraphicsCommandList::DrawIndexedInstanced + /// glDrawElements + /// glDrawElementsBaseVertex + /// glDrawElementsInstanced + /// glDrawElementsInstancedBaseVertex + /// glDrawElementsInstancedBaseInstance + /// glDrawElementsInstancedBaseVertexBaseInstance + /// glMultiDrawElements + /// glMultiDrawElementsBaseVertex + /// vkCmdDrawIndexed + /// + /// Callback function signature: bool (api::command_list *cmd_list, uint32_t index_count, uint32_t instance_count, uint32_t first_index, int32_t vertex_offset, uint32_t first_instance) + /// + /// + /// To prevent this command from being executed, return , otherwise return . + /// + DrawIndexed, + + /// + /// Called before: + /// + /// ID3D11DeviceContext::Dispatch + /// ID3D12GraphicsCommandList::Dispatch + /// glDispatchCompute + /// vkCmdDispatch + /// + /// Callback function signature: bool (api::command_list *cmd_list, uint32_t group_count_x, uint32_t group_count_y, uint32_t group_count_z) + /// + /// + /// To prevent this command from being executed, return , otherwise return . + /// + Dispatch = 54, + + /// + /// Called before: + /// + /// ID3D12GraphicsCommandList::DispatchMesh + /// vkCmdDrawMeshTasksEXT + /// + /// Callback function signature: bool (api::command_list *cmd_list, uint32_t group_count_x, uint32_t group_count_y, uint32_t group_count_z) + /// + /// + /// To prevent this command from being executed, return , otherwise return . + /// + DispatchMesh = 89, + + /// + /// Called before: + /// + /// ID3D12GraphicsCommandList::DispatchRays + /// vkCmdTraceRaysKHR + /// + /// Callback function signature: bool (api::command_list *cmd_list, api::resource raygen, uint64_t raygen_offset, uint64_t raygen_size, api::resource miss, uint64_t miss_offset, uint64_t miss_size, uint64_t miss_stride, api::resource hit_group, uint64_t hit_group_offset, uint64_t hit_group_size, uint64_t hit_group_stride, api::resource callable, uint64_t callable_offset, uint64_t callable_size, uint64_t callable_stride, uint32_t width, uint32_t height, uint32_t depth) + /// + /// + /// To prevent this command from being executed, return , otherwise return . + /// In case of D3D12 and Vulkan, the shader handle buffer handles may be zero with the buffers instead referred to via a device address passed in the related offset argument. + /// + DispatchRays = 90, + + /// + /// Called before: + /// + /// ID3D11DeviceContext::DrawInstancedIndirect + /// ID3D11DeviceContext::DrawIndexedInstancedIndirect + /// ID3D11DeviceContext::DispatchIndirect + /// ID3D12GraphicsCommandList::ExecuteIndirect + /// glDrawArraysIndirect + /// glDrawElementsIndirect + /// glMultiDrawArraysIndirect + /// glMultiDrawElementsIndirect + /// glDispatchComputeIndirect + /// vkCmdDrawIndirect + /// vkCmdDrawIndexedIndirect + /// vkCmdDispatchIndirect + /// vkCmdTraceRaysIndirect2KHR + /// vkCmdDrawMeshTasksIndirectEXT + /// vkCmdDrawMeshTasksIndirectCountEXT + /// + /// Callback function signature: bool (api::command_list *cmd_list, api::indirect_command type, api::resource buffer, uint64_t offset, uint32_t draw_count, uint32_t stride) + /// + /// + /// To prevent this command from being executed, return , otherwise return . + /// + DrawOrDispatchIndirect = 55, + + /// + /// Called before: + /// + /// IDirect3DDevice9::UpdateTexture + /// IDirect3DDevice9::GetRenderTargetData + /// ID3D10Device::CopyResource + /// ID3D11DeviceContext::CopyResource + /// ID3D12GraphicsCommandList::CopyResource + /// + /// Callback function signature: bool (api::command_list *cmd_list, api::resource source, api::resource dest) + /// + /// + /// To prevent this command from being executed, return , otherwise return . + /// Source resource will be in the state. + /// Destination resource will be in the state. + /// + CopyResource, + + /// + /// Called before: + /// + /// ID3D12GraphicsCommandList::CopyBufferRegion + /// glCopyBufferSubData + /// glCopyNamedBufferSubData + /// vkCmdCopyBuffer + /// vkCmdCopyBuffer2 + /// + /// Callback function signature: bool (api::command_list *cmd_list, api::resource source, uint64_t source_offset, api::resource dest, uint64_t dest_offset, uint64_t size) + /// + /// + /// To prevent this command from being executed, return , otherwise return . + /// Source resource will be in the state. + /// Destination resource will be in the state. + /// + CopyBufferRegion, + + /// + /// Called before: + /// + /// ID3D12GraphicsCommandList::CopyTextureRegion + /// vkCmdCopyBufferToImage + /// vkCmdCopyBufferToImage2 + /// + /// Callback function signature: bool (api::command_list *cmd_list, api::resource source, uint64_t source_offset, uint32_t row_length, uint32_t slice_height, api::resource dest, uint32_t dest_subresource, const api::subresource_box *dest_box) + /// + /// + /// To prevent this command from being executed, return , otherwise return . + /// Source resource will be in the state. + /// Destination resource will be in the state. + /// The subresource box argument is optional and may be (which indicates the entire subresource is referenced). + /// + CopyBufferToTexture, + + /// + /// Called before: + /// + /// IDirect3DDevice9::UpdateSurface + /// IDirect3DDevice9::StretchRect + /// ID3D10Device::CopySubresourceRegion + /// ID3D11DeviceContext::CopySubresourceRegion + /// ID3D12GraphicsCommandList::CopyTextureRegion + /// glBlitFramebuffer + /// glBlitNamedFramebuffer + /// glCopyImageSubData + /// glCopyTexSubImage1D + /// glCopyTexSubImage2D + /// glCopyTexSubImage3D + /// glCopyTextureSubImage1D + /// glCopyTextureSubImage2D + /// glCopyTextureSubImage3D + /// vkCmdBlitImage + /// vkCmdBlitImage2 + /// vkCmdCopyImage + /// vkCmdCopyImage2 + /// + /// Callback function signature: bool (api::command_list *cmd_list, api::resource source, uint32_t source_subresource, const api::subresource_box *source_box, api::resource dest, uint32_t dest_subresource, const api::subresource_box *dest_box, api::filter_mode filter) + /// + /// + /// To prevent this command from being executed, return , otherwise return . + /// Source resource will be in the state. + /// Destination resource will be in the state. + /// The subresource box arguments are optional and may be (which indicates the entire subresource is used). + /// + CopyTextureRegion, + + /// + /// Called before: + /// + /// ID3D12GraphicsCommandList::CopyTextureRegion + /// vkCmdCopyImageToBuffer + /// vkCmdCopyImageToBuffer2 + /// + /// Callback function signature: bool (api::command_list *cmd_list, api::resource source, uint32_t source_subresource, const api::subresource_box *source_box, api::resource dest, uint64_t dest_offset, uint32_t row_length, uint32_t slice_height) + /// + /// + /// To prevent this command from being executed, return , otherwise return . + /// Source resource will be in the state. + /// Destination resource will be in the state. + /// The subresource box argument is optional and may be (which indicates the entire subresource is used). + /// + CopyTextureToBuffer, + + /// + /// Called before: + /// + /// IDirect3DDevice9::StretchRect + /// ID3D10Device::ResolveSubresource + /// ID3D11DeviceContext::ResolveSubresource + /// ID3D12GraphicsCommandList::ResolveSubresource + /// ID3D12GraphicsCommandList1::ResolveSubresourceRegion + /// glBlitFramebuffer + /// glBlitNamedFramebuffer + /// vkCmdResolveImage + /// vkCmdResolveImage2 + /// + /// Callback function signature: bool (api::command_list *cmd_list, api::resource source, uint32_t source_subresource, const api::subresource_box *source_box, api::resource dest, uint32_t dest_subresource, int32_t dest_x, int32_t dest_y, int32_t dest_z, api::format format) + /// + /// + /// To prevent this command from being executed, return , otherwise return . + /// Source resource will be in the state. + /// Destination resource will be in the state. + /// The subresource box argument is optional and may be (which indicates the entire subresource is used). + /// + ResolveTextureRegion, + + /// + /// Called before: + /// + /// IDirect3DDevice9::Clear + /// ID3D10Device::ClearDepthStencilView + /// ID3D11DeviceContext::ClearDepthStencilView + /// ID3D11DeviceContext1::ClearView (for depth-stencil views) + /// ID3D12GraphicsCommandList::ClearDepthStencilView + /// glClear + /// glClearBufferfi + /// glClearBufferfv + /// glClearNamedFramebufferfi + /// glClearNamedFramebufferfv + /// vkCmdClearDepthStencilImage + /// vkCmdClearAttachments + /// + /// Callback function signature: bool (api::command_list *cmd_list, api::resource_view dsv, const float *depth, const uint8_t *stencil, uint32_t rect_count, const api::rect *rects) + /// + /// + /// To prevent this command from being executed, return , otherwise return . + /// Resource will be in the state. + /// One of the depth or stencil clear value arguments may be when the respective component is not cleared. + /// + ClearDepthStencilView, + + /// + /// Called before: + /// + /// IDirect3DDevice9::Clear + /// IDirect3DDevice9::ColorFill + /// ID3D10Device::ClearRenderTargetView + /// ID3D11DeviceContext::ClearRenderTargetView + /// ID3D11DeviceContext1::ClearView (for render target views) + /// ID3D12GraphicsCommandList::ClearRenderTargetView + /// glClear + /// glClearBufferfv + /// glClearNamedFramebufferfv + /// vkCmdClearColorImage + /// vkCmdClearAttachments + /// + /// Callback function signature: bool (api::command_list *cmd_list, api::resource_view rtv, const float color[4], uint32_t rect_count, const api::rect *rects) + /// + /// + /// To prevent this command from being executed, return , otherwise return . + /// Resources will be in the state. + /// + ClearRenderTargetView, + + /// + /// Called before: + /// + /// ID3D11DeviceContext::ClearUnorderedAccessViewUint + /// ID3D12GraphicsCommandList::ClearUnorderedAccessViewUint + /// + /// Callback function signature: bool (api::command_list *cmd_list, api::resource_view uav, const uint32_t values[4], uint32_t rect_count, const api::rect *rects) + /// + /// + /// To prevent this command from being executed, return , otherwise return . + /// Resource will be in the state. + /// + ClearUnorderedAccessViewUint, + + /// + /// Called before: + /// + /// ID3D11DeviceContext::ClearUnorderedAccessViewFloat + /// ID3D11DeviceContext1::ClearView (for unordered access views) + /// ID3D12GraphicsCommandList::ClearUnorderedAccessViewFloat + /// + /// Callback function signature: bool (api::command_list *cmd_list, api::resource_view uav, const float values[4], uint32_t rect_count, const api::rect *rects) + /// + /// + /// To prevent this command from being executed, return , otherwise return . + /// Resource will be in the state. + /// + ClearUnorderedAccessViewFloat, + + /// + /// Called before: + /// + /// ID3D10Device::GenerateMips + /// ID3D11DeviceContext::GenerateMips + /// glGenerateMipmap + /// glGenerateTextureMipmap + /// + /// Callback function signature: bool (api::command_list *cmd_list, api::resource_view srv) + /// + /// + /// To prevent this command from being executed, return , otherwise return . + /// + GenerateMipmaps, + + /// + /// Called before: + /// + /// ID3D12GraphicsCommandList::BeginQuery + /// vkCmdBeginQuery + /// vkCmdBeginQueryIndexedEXT + /// + /// Callback function signature: bool (api::command_list *cmd_list, api::query_heap heap, api::query_type type, uint32_t index) + /// + /// + /// To prevent this command from being executed, return , otherwise return . + /// + BeginQuery, + + /// + /// Called before: + /// + /// ID3D12GraphicsCommandList::EndQuery + /// vkCmdEndQuery + /// vkCmdEndQueryIndexedEXT + /// vkCmdWriteTimestamp + /// vkCmdWriteTimestamp2 + /// + /// Callback function signature: bool (api::command_list *cmd_list, api::query_heap heap, api::query_type type, uint32_t index) + /// + /// + /// To prevent this command from being executed, return , otherwise return . + /// + EndQuery, + + /// + /// Called before: + /// + /// ID3D12GraphicsCommandList::ResolveQueryData + /// vkCmdCopyQueryPoolResults + /// + /// Callback function signature: bool (api::command_list *cmd_list, api::query_heap heap, api::query_type type, uint32_t first, uint32_t count, api::resource dest, uint64_t dest_offset, uint32_t stride) + /// + /// + /// To prevent this command from being executed, return , otherwise return . + /// + CopyQueryHeapResults = 69, + + /// + /// Called before: + /// + /// ID3D12GraphicsCommandList4::CopyRaytracingAccelerationStructure + /// vkCmdCopyAccelerationStructureKHR + /// + /// Callback function signature: bool (api::command_list *cmd_list, api::resource_view source, api::resource_view dest, api::acceleration_structure_copy_mode mode) + /// + /// + /// To prevent this command from being executed, return , otherwise return . + /// + CopyAccelerationStructure = 91, + + /// + /// Called before: + /// + /// ID3D12GraphicsCommandList4::BuildRaytracingAccelerationStructure + /// vkCmdBuildAccelerationStructuresKHR + /// + /// Callback function signature: bool (api::command_list *cmd_list, api::acceleration_structure_type type, api::acceleration_structure_build_flags flags, uint32_t input_count, const api::acceleration_structure_build_input *inputs, api::resource scratch, uint64_t scratch_offset, api::resource_view source, api::resource_view dest, api::acceleration_structure_build_mode mode) + /// + /// + /// To prevent this command from being executed, return , otherwise return . + /// In case of D3D12 and Vulkan, the scratch buffer handle may be zero with the buffer instead referred to via a device address passed in the related offset argument. + /// Scratch buffer will be in the resource state. + /// + BuildAccelerationStructure = 92, + + /// + /// Called before: + /// + /// ID3D12GraphicsCommandList::Reset + /// vkBeginCommandBuffer + /// + /// Callback function signature: void (api::command_list *cmd_list) + /// + /// + /// Is not called for immediate command lists (since they cannot be reset). + /// + ResetCommandList = 70, + + /// + /// Called before: + /// + /// ID3D11DeviceContext::FinishCommandList + /// ID3D12GraphicsCommandList::Close + /// vkEndCommandBuffer + /// + /// Callback function signature: void (api::command_list *cmd_list) + /// + /// + /// Is not called for immediate command lists (since they cannot be closed). + /// + CloseCommandList, + + /// + /// Called when a command list is submitted to a command queue (or an immediate command list is flushed), before: + /// + /// IDirect3DDevice9::EndScene + /// ID3D10Device::Flush + /// ID3D11DeviceContext::Flush + /// ID3D11DeviceContext3::Flush1 + /// ID3D12CommandQueue::ExecuteCommandLists + /// glFlush + /// vkQueueSubmit + /// + /// Callback function signature: void (api::command_queue *queue, api::command_list *cmd_list) + /// + ExecuteCommandList, + + /// + /// Called when a secondary command list is executed on a primary command list, before: + /// + /// ID3D11DeviceContext::ExecuteCommandList + /// ID3D12GraphicsCommandList::ExecuteBundle + /// vkCmdExecuteCommands + /// + /// In addition, called after: + /// + /// ID3D11DeviceContext::FinishCommandList + /// + /// Callback function signature: void (api::command_list *cmd_list, api::command_list *secondary_cmd_list) + /// + ExecuteSecondaryCommandList, + + /// + /// Called before: + /// + /// IDirect3DDevice9::Present + /// IDirect3DDevice9Ex::PresentEx + /// IDirect3DSwapChain9::Present + /// IDXGISwapChain::Present + /// IDXGISwapChain3::Present1 + /// ID3D12CommandQueueDownlevel::Present + /// wglSwapBuffers + /// vkQueuePresentKHR + /// IVRCompositor::Submit + /// xrEndFrame + /// + /// Callback function signature: void (api::command_queue *queue, api::swapchain *swapchain, const api::rect *source_rect, const api::rect *dest_rect, uint32_t dirty_rect_count, const api::rect *dirty_rects) + /// + /// + /// The source and destination rectangle arguments are optional and may be (which indicates the swap chain is presented in its entirety). + /// + Present, + + /// + /// Called before: + /// + /// IDXGISwapChain::SetFullscreenState + /// vkAcquireFullScreenExclusiveModeEXT + /// vkReleaseFullScreenExclusiveModeEXT + /// + /// Callback function signature: bool (api::swapchain *swapchain, bool fullscreen, void *hmonitor) + /// + /// + /// To prevent the fullscreen state from being changed, return , otherwise return . + /// + SetFullscreenState = 93, + + /// + /// Called after ReShade has rendered its overlay. + /// Callback function signature: void (api::effect_runtime *runtime) + /// + ReShadePresent = 75, + + /// + /// Called right before ReShade effects are rendered. + /// Callback function signature: void (api::effect_runtime *runtime, api::command_list *cmd_list, api::resource_view rtv, api::resource_view rtv_srgb) + /// + ReShadeBeginEffects, + + /// + /// Called right after ReShade effects were rendered. + /// Callback function signature: void (api::effect_runtime *runtime, api::command_list *cmd_list, api::resource_view rtv, api::resource_view rtv_srgb) + /// + ReShadeFinishEffects, + + /// + /// Called right after all ReShade effects were reloaded. + /// This occurs during effect runtime initialization or because the user pressed the "Reload" button in the overlay. + /// Any , and handles are invalidated when this event occurs and need to be queried again. + /// Callback function signature: void (api::effect_runtime *runtime) + /// + ReShadeReloadedEffects, + + /// + /// Called before a uniform variable is changed, with the new value. + /// Callback function signature: bool (api::effect_runtime *runtime, api::effect_uniform_variable variable, const void *new_value, size_t new_value_size) + /// + /// + /// To prevent the variable value from being changed, return , otherwise return . + /// The new value has the data type reported by . The new value size is in bytes. + /// + ReShadeSetUniformValue, + + /// + /// Called before a technique is enabled or disabled, with the new state. + /// Callback function signature: bool (api::effect_runtime *runtime, api::effect_technique technique, bool enabled) + /// + /// + /// To prevent the technique state from being changed, return , otherwise return . + /// + ReShadeSetTechniqueState, + + /// + /// Called between the ImGui::NewFrame and ImGui::EndFrame calls for the ReShade overlay. + /// Can be used to perform custom Dear ImGui calls, but it is recommended to instead use to register a dedicated overlay. + /// Callback function signature: void (api::effect_runtime *runtime) + /// + /// + /// This is not called for effect runtimes in VR. + /// + ReShadeOverlay, + + /// + /// Called after a screenshot was taken and saved to disk, with the path to the saved image file. + /// Callback function signature: void (api::effect_runtime *runtime, const char *path) + /// + ReShadeScreenshot, + + /// + /// Called for each technique after it was rendered, usually between and . + /// Callback function signature: void (api::effect_runtime *runtime, api::effect_technique technique, api::command_list *cmd_list, api::resource_view rtv, api::resource_view rtv_srgb) + /// + ReShadeRenderTechnique, + + /// + /// Called when all effects are about to be enabled or disabled. + /// Callback function signature: bool (api::effect_runtime *runtime, bool enabled) + /// + /// + /// To prevent the effects state from being changed, return , otherwise return . + /// + ReShadeSetEffectsState = 94, + + /// + /// Called after a preset was loaded and applied. + /// This occurs after effect reloading or when the user chooses a new preset in the overlay. + /// Callback function signature: void (api::effect_runtime *runtime, const char *path) + /// + ReShadeSetCurrentPresetPath = 84, + + /// + /// Called when the rendering order of loaded techniques is changed, with a handle array specifying the new order. + /// Callback function signature: bool (api::effect_runtime *runtime, size_t count, api::effect_technique *techniques) + /// + /// + /// To prevent the order from being changed, return , otherwise return . + /// + ReShadeReorderTechniques, + + /// + /// Called when the ReShade overlay is about to be opened or closed. + /// Callback function signature: bool (api::effect_runtime *runtime, bool open, api::input_source source) + /// + /// + /// To prevent the overlay state from being changed, return , otherwise return . + /// + ReShadeOpenOverlay, + + /// + /// Called when a uniform variable widget is added to the variable list in the overlay. + /// Can be used to replace with custom one or add widgets for specific uniform variables. + /// Callback function signature: bool (api::effect_runtime *runtime, api::effect_uniform_variable variable) + /// + /// + /// To prevent the normal widget from being added to the overlay, return , otherwise return . + /// + ReShadeOverlayUniformVariable, + + /// + /// Called when a technique is added to the technique list in the overlay. + /// Can be used to replace with custom one or add widgets for specific techniques. + /// Callback function signature: bool (api::effect_runtime *runtime, api::effect_technique technique) + /// + /// + /// To prevent the normal widget from being added to the overlay, return , otherwise return . + /// + ReShadeOverlayTechnique, + } +} diff --git a/Dalamud/Interface/Internal/ReShadeAddonInterface.Exports.cs b/Dalamud/Interface/Internal/ReShadeAddonInterface.Exports.cs new file mode 100644 index 000000000..3dce1b79b --- /dev/null +++ b/Dalamud/Interface/Internal/ReShadeAddonInterface.Exports.cs @@ -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; + +/// ReShade interface. +[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()) + { + 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 name, void* res) + { + Span 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; + } + } + + /// Gets the active ReShade module. + public static ProcessModule? ReShadeModule { get; private set; } + + private struct ExportsStruct + { + public delegate* unmanaged ReShadeRegisterAddon; + public delegate* unmanaged ReShadeUnregisterAddon; + public delegate* unmanaged ReShadeRegisterEvent; + public delegate* unmanaged ReShadeUnregisterEvent; + } +} diff --git a/Dalamud/Interface/Internal/ReShadeAddonInterface.cs b/Dalamud/Interface/Internal/ReShadeAddonInterface.cs new file mode 100644 index 000000000..156688c27 --- /dev/null +++ b/Dalamud/Interface/Internal/ReShadeAddonInterface.cs @@ -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; + +/// ReShade interface. +internal sealed unsafe partial class ReShadeAddonInterface : IDisposable +{ + private const int ReShadeApiVersion = 12; + + private readonly HMODULE hDalamudModule; + + private readonly Hook addonModuleResolverHook; + + private readonly DelegateStorage reShadeOverlayDelegate; + private readonly DelegateStorage initSwapChainDelegate; + private readonly DelegateStorage 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.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))); + } + + /// Finalizes an instance of the class. + ~ReShadeAddonInterface() => this.ReleaseUnmanagedResources(); + + /// Delegate for . + /// Reference to the ReShade runtime. + public delegate void ReShadeOverlayDelegate(ref ApiObject effectRuntime); + + /// Delegate for . + /// Reference to the ReShade SwapChain wrapper. + public delegate void ReShadeInitSwapChain(ref ApiObject swapChain); + + /// Delegate for . + /// Reference to the ReShade SwapChain wrapper. + public delegate void ReShadeDestroySwapChain(ref ApiObject swapChain); + + private delegate BOOL GetModuleHandleExWDelegate(uint dwFlags, ushort* lpModuleName, HMODULE* phModule); + + /// Called on . + public event ReShadeOverlayDelegate? ReShadeOverlay; + + /// Called on . + public event ReShadeInitSwapChain? InitSwapChain; + + /// Called on . + public event ReShadeDestroySwapChain? DestroySwapChain; + + /// Registers Dalamud as a ReShade addon. + /// Initialized interface. + /// true on success. + 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; + } + } + + /// + 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); + } + + /// ReShade effect runtime object. + [StructLayout(LayoutKind.Sequential)] + public struct ApiObject + { + /// The vtable. + public VTable* Vtbl; + + /// Gets this object as a typed pointer. + /// Address of this instance. + /// This call is invalid if this object is not already fixed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ApiObject* AsPointer() => (ApiObject*)Unsafe.AsPointer(ref this); + + /// Gets the native object. + /// The native object. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public nint GetNative() => this.Vtbl->GetNative(this.AsPointer()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T* GetNative() where T : unmanaged => (T*)this.GetNative(); + + /// VTable of . + [StructLayout(LayoutKind.Sequential)] + public struct VTable + { + /// + public delegate* unmanaged GetNative; + } + } + + private readonly struct DelegateStorage 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 sto) => sto.Address; + } +} diff --git a/Dalamud/Interface/Internal/SwapChainHelper.cs b/Dalamud/Interface/Internal/SwapChainHelper.cs index 09c1f52fd..e0c3f21a8 100644 --- a/Dalamud/Interface/Internal/SwapChainHelper.cs +++ b/Dalamud/Interface/Internal/SwapChainHelper.cs @@ -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; /// Helper for dealing with swap chains. internal static unsafe class SwapChainHelper { - /// - /// Gets the function pointer for ReShade's DXGISwapChain::on_present. - /// Source. - /// - public static delegate* unmanaged ReshadeOnPresent { get; private set; } - /// Gets the game's active instance of IDXGISwapChain that is initialized. /// Address of the game's instance of IDXGISwapChain, or null if not available (yet.) public static IDXGISwapChain* GameDeviceSwapChain @@ -92,102 +80,4 @@ internal static unsafe class SwapChainHelper while (GameDeviceSwapChain is null) Thread.Yield(); } - - /// Detects ReShade and populate . - 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)reShadeDxgiPresent; - } - - break; - } - catch (Exception e) - { - Log.Error(e, "Failed to get ReShade version info"); - break; - } - } - } } From 80ac97fea874cccffe907b8fd203dab53d7dec53 Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Mon, 22 Jul 2024 19:24:34 +0900 Subject: [PATCH 2/8] Move ReShadeAddonInterface into sep. ns. --- .../Internal/InterfaceManager.AsReShadeAddon.cs | 6 +++--- Dalamud/Interface/Internal/InterfaceManager.cs | 8 ++++---- .../ReShadeAddonInterface.AddonEvent.cs | 2 +- .../ReShadeAddonInterface.Exports.cs | 2 +- .../{ => ReShadeHandling}/ReShadeAddonInterface.cs | 14 +++++++------- 5 files changed, 16 insertions(+), 16 deletions(-) rename Dalamud/Interface/Internal/{ => ReShadeHandling}/ReShadeAddonInterface.AddonEvent.cs (99%) rename Dalamud/Interface/Internal/{ => ReShadeHandling}/ReShadeAddonInterface.Exports.cs (97%) rename Dalamud/Interface/Internal/{ => ReShadeHandling}/ReShadeAddonInterface.cs (90%) diff --git a/Dalamud/Interface/Internal/InterfaceManager.AsReShadeAddon.cs b/Dalamud/Interface/Internal/InterfaceManager.AsReShadeAddon.cs index 0f1eeb707..9c08aaf06 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.AsReShadeAddon.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.AsReShadeAddon.cs @@ -11,7 +11,7 @@ namespace Dalamud.Interface.Internal; /// internal partial class InterfaceManager { - private unsafe void ReShadeAddonInterfaceOnDestroySwapChain(ref ReShadeAddonInterface.ApiObject swapchain) + private unsafe void ReShadeAddonInterfaceOnDestroySwapChain(ref ReShadeHandling.ReShadeAddonInterface.ApiObject swapchain) { var swapChain = swapchain.GetNative(); if (this.scene?.SwapChain.NativePointer != (nint)swapChain) @@ -20,7 +20,7 @@ internal partial class InterfaceManager this.scene?.OnPreResize(); } - private unsafe void ReShadeAddonInterfaceOnInitSwapChain(ref ReShadeAddonInterface.ApiObject swapchain) + private unsafe void ReShadeAddonInterfaceOnInitSwapChain(ref ReShadeHandling.ReShadeAddonInterface.ApiObject swapchain) { var swapChain = swapchain.GetNative(); if (this.scene?.SwapChain.NativePointer != (nint)swapChain) @@ -33,7 +33,7 @@ internal partial class InterfaceManager this.scene?.OnPostResize((int)desc.BufferDesc.Width, (int)desc.BufferDesc.Height); } - private void ReShadeAddonInterfaceOnReShadeOverlay(ref ReShadeAddonInterface.ApiObject runtime) + private void ReShadeAddonInterfaceOnReShadeOverlay(ref ReShadeHandling.ReShadeAddonInterface.ApiObject runtime) { var swapChain = runtime.GetNative(); diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs index 10d508d99..6bbded0f9 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.cs @@ -90,7 +90,7 @@ internal partial class InterfaceManager : IInternalDisposableService private Hook? setCursorHook; private Hook? dxgiPresentHook; private Hook? resizeBuffersHook; - private ReShadeAddonInterface? reShadeAddonInterface; + private ReShadeHandling.ReShadeAddonInterface? reShadeAddonInterface; private IFontAtlas? dalamudAtlas; private ILockedImFont? defaultFontResourceLock; @@ -759,7 +759,7 @@ internal partial class InterfaceManager : IInternalDisposableService this.SetCursorDetour); Log.Verbose("===== S W A P C H A I N ====="); - if (ReShadeAddonInterface.TryRegisterAddon(out this.reShadeAddonInterface)) + if (ReShadeHandling.ReShadeAddonInterface.TryRegisterAddon(out this.reShadeAddonInterface)) { this.resizeBuffersHook = Hook.FromAddress( (nint)SwapChainHelper.GameDeviceSwapChainVtbl->ResizeBuffers, @@ -768,8 +768,8 @@ internal partial class InterfaceManager : IInternalDisposableService Log.Verbose( "Registered as a ReShade({name}: 0x{addr:X}) addon.", - ReShadeAddonInterface.ReShadeModule!.FileName, - ReShadeAddonInterface.ReShadeModule!.BaseAddress); + ReShadeHandling.ReShadeAddonInterface.ReShadeModule!.FileName, + ReShadeHandling.ReShadeAddonInterface.ReShadeModule!.BaseAddress); this.reShadeAddonInterface.InitSwapChain += this.ReShadeAddonInterfaceOnInitSwapChain; this.reShadeAddonInterface.DestroySwapChain += this.ReShadeAddonInterfaceOnDestroySwapChain; this.reShadeAddonInterface.ReShadeOverlay += this.ReShadeAddonInterfaceOnReShadeOverlay; diff --git a/Dalamud/Interface/Internal/ReShadeAddonInterface.AddonEvent.cs b/Dalamud/Interface/Internal/ReShadeHandling/ReShadeAddonInterface.AddonEvent.cs similarity index 99% rename from Dalamud/Interface/Internal/ReShadeAddonInterface.AddonEvent.cs rename to Dalamud/Interface/Internal/ReShadeHandling/ReShadeAddonInterface.AddonEvent.cs index 23f01875d..c68cf4fb6 100644 --- a/Dalamud/Interface/Internal/ReShadeAddonInterface.AddonEvent.cs +++ b/Dalamud/Interface/Internal/ReShadeHandling/ReShadeAddonInterface.AddonEvent.cs @@ -1,4 +1,4 @@ -namespace Dalamud.Interface.Internal; +namespace Dalamud.Interface.Internal.ReShadeHandling; /// ReShade interface. internal sealed partial class ReShadeAddonInterface diff --git a/Dalamud/Interface/Internal/ReShadeAddonInterface.Exports.cs b/Dalamud/Interface/Internal/ReShadeHandling/ReShadeAddonInterface.Exports.cs similarity index 97% rename from Dalamud/Interface/Internal/ReShadeAddonInterface.Exports.cs rename to Dalamud/Interface/Internal/ReShadeHandling/ReShadeAddonInterface.Exports.cs index 3dce1b79b..46d3cc1af 100644 --- a/Dalamud/Interface/Internal/ReShadeAddonInterface.Exports.cs +++ b/Dalamud/Interface/Internal/ReShadeHandling/ReShadeAddonInterface.Exports.cs @@ -8,7 +8,7 @@ using TerraFX.Interop.Windows; using static TerraFX.Interop.Windows.Windows; -namespace Dalamud.Interface.Internal; +namespace Dalamud.Interface.Internal.ReShadeHandling; /// ReShade interface. [SuppressMessage( diff --git a/Dalamud/Interface/Internal/ReShadeAddonInterface.cs b/Dalamud/Interface/Internal/ReShadeHandling/ReShadeAddonInterface.cs similarity index 90% rename from Dalamud/Interface/Internal/ReShadeAddonInterface.cs rename to Dalamud/Interface/Internal/ReShadeHandling/ReShadeAddonInterface.cs index 156688c27..df324941e 100644 --- a/Dalamud/Interface/Internal/ReShadeAddonInterface.cs +++ b/Dalamud/Interface/Internal/ReShadeHandling/ReShadeAddonInterface.cs @@ -10,7 +10,7 @@ using TerraFX.Interop.Windows; using static TerraFX.Interop.Windows.Windows; -namespace Dalamud.Interface.Internal; +namespace Dalamud.Interface.Internal.ReShadeHandling; /// ReShade interface. internal sealed unsafe partial class ReShadeAddonInterface : IDisposable @@ -53,27 +53,27 @@ internal sealed unsafe partial class ReShadeAddonInterface : IDisposable /// Finalizes an instance of the class. ~ReShadeAddonInterface() => this.ReleaseUnmanagedResources(); - /// Delegate for . + /// Delegate for . /// Reference to the ReShade runtime. public delegate void ReShadeOverlayDelegate(ref ApiObject effectRuntime); - /// Delegate for . + /// Delegate for . /// Reference to the ReShade SwapChain wrapper. public delegate void ReShadeInitSwapChain(ref ApiObject swapChain); - /// Delegate for . + /// Delegate for . /// Reference to the ReShade SwapChain wrapper. public delegate void ReShadeDestroySwapChain(ref ApiObject swapChain); private delegate BOOL GetModuleHandleExWDelegate(uint dwFlags, ushort* lpModuleName, HMODULE* phModule); - /// Called on . + /// Called on . public event ReShadeOverlayDelegate? ReShadeOverlay; - /// Called on . + /// Called on . public event ReShadeInitSwapChain? InitSwapChain; - /// Called on . + /// Called on . public event ReShadeDestroySwapChain? DestroySwapChain; /// Registers Dalamud as a ReShade addon. From d71fbc52fb72a9afc6774db00bbfd39f7f720dda Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Mon, 22 Jul 2024 20:29:26 +0900 Subject: [PATCH 3/8] Add reshade unwrapping options --- .../Internal/DalamudConfiguration.cs | 4 + .../Interface/Internal/InterfaceManager.cs | 34 ++- .../ReShadeHandling/ReShadeHandlingMode.cs | 14 ++ .../ReShadeHandling/ReShadeUnwrapper.cs | 205 ++++++++++++++++++ Dalamud/Interface/Internal/SwapChainHelper.cs | 23 +- .../Settings/Tabs/SettingsTabExperimental.cs | 78 ++++++- .../Settings/Tabs/SettingsTabGeneral.cs | 2 +- .../Settings/Widgets/EnumSettingsEntry{T}.cs | 175 +++++++++++++++ .../Settings/Widgets/SettingsEntry{T}.cs | 53 +---- 9 files changed, 513 insertions(+), 75 deletions(-) create mode 100644 Dalamud/Interface/Internal/ReShadeHandling/ReShadeHandlingMode.cs create mode 100644 Dalamud/Interface/Internal/ReShadeHandling/ReShadeUnwrapper.cs create mode 100644 Dalamud/Interface/Internal/Windows/Settings/Widgets/EnumSettingsEntry{T}.cs diff --git a/Dalamud/Configuration/Internal/DalamudConfiguration.cs b/Dalamud/Configuration/Internal/DalamudConfiguration.cs index 6bff5720f..9d54f4562 100644 --- a/Dalamud/Configuration/Internal/DalamudConfiguration.cs +++ b/Dalamud/Configuration/Internal/DalamudConfiguration.cs @@ -8,6 +8,7 @@ using System.Runtime.InteropServices; using Dalamud.Game.Text; using Dalamud.Interface; using Dalamud.Interface.FontIdentifier; +using Dalamud.Interface.Internal.ReShadeHandling; using Dalamud.Interface.Style; using Dalamud.IoC.Internal; using Dalamud.Plugin.Internal.AutoUpdate; @@ -441,6 +442,9 @@ internal sealed class DalamudConfiguration : IInternalDisposableService /// public bool WindowIsImmersive { get; set; } = false; + /// Gets or sets the mode specifying how to handle ReShade. + public ReShadeHandlingMode ReShadeHandlingMode { get; set; } = ReShadeHandlingMode.ReShadeAddon; + /// /// Gets or sets hitch threshold for game network up in milliseconds. /// diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs index 6bbded0f9..b7c2f8765 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.cs @@ -17,6 +17,7 @@ using Dalamud.Hooking.Internal; using Dalamud.Hooking.WndProcHook; using Dalamud.Interface.ImGuiNotification.Internal; using Dalamud.Interface.Internal.ManagedAsserts; +using Dalamud.Interface.Internal.ReShadeHandling; using Dalamud.Interface.ManagedFontAtlas; using Dalamud.Interface.ManagedFontAtlas.Internals; using Dalamud.Interface.Style; @@ -72,7 +73,7 @@ internal partial class InterfaceManager : IInternalDisposableService private readonly ConcurrentBag deferredDisposeDisposables = new(); [ServiceManager.ServiceDependency] - private readonly WndProcHookManager wndProcHookManager = Service.Get(); + private readonly DalamudConfiguration dalamudConfiguration = Service.Get(); [ServiceManager.ServiceDependency] private readonly Framework framework = Service.Get(); @@ -82,6 +83,9 @@ internal partial class InterfaceManager : IInternalDisposableService [UsedImplicitly] private readonly HookManager hookManager = Service.Get(); + [ServiceManager.ServiceDependency] + private readonly WndProcHookManager wndProcHookManager = Service.Get(); + private readonly ConcurrentQueue runBeforeImGuiRender = new(); private readonly ConcurrentQueue runAfterImGuiRender = new(); @@ -90,7 +94,7 @@ internal partial class InterfaceManager : IInternalDisposableService private Hook? setCursorHook; private Hook? dxgiPresentHook; private Hook? resizeBuffersHook; - private ReShadeHandling.ReShadeAddonInterface? reShadeAddonInterface; + private ReShadeAddonInterface? reShadeAddonInterface; private IFontAtlas? dalamudAtlas; private ILockedImFont? defaultFontResourceLock; @@ -759,17 +763,23 @@ internal partial class InterfaceManager : IInternalDisposableService this.SetCursorDetour); Log.Verbose("===== S W A P C H A I N ====="); - if (ReShadeHandling.ReShadeAddonInterface.TryRegisterAddon(out this.reShadeAddonInterface)) + if (this.dalamudConfiguration.ReShadeHandlingMode == ReShadeHandlingMode.UnwrapReShade) + { + if (SwapChainHelper.UnwrapReShade()) + Log.Verbose("Unwrapped ReShade."); + } + + if (this.dalamudConfiguration.ReShadeHandlingMode == ReShadeHandlingMode.ReShadeAddon && + ReShadeAddonInterface.TryRegisterAddon(out this.reShadeAddonInterface)) { this.resizeBuffersHook = Hook.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.", - ReShadeHandling.ReShadeAddonInterface.ReShadeModule!.FileName, - ReShadeHandling.ReShadeAddonInterface.ReShadeModule!.BaseAddress); + ReShadeAddonInterface.ReShadeModule!.FileName, + ReShadeAddonInterface.ReShadeModule!.BaseAddress); this.reShadeAddonInterface.InitSwapChain += this.ReShadeAddonInterfaceOnInitSwapChain; this.reShadeAddonInterface.DestroySwapChain += this.ReShadeAddonInterfaceOnDestroySwapChain; this.reShadeAddonInterface.ReShadeOverlay += this.ReShadeAddonInterfaceOnReShadeOverlay; @@ -779,16 +789,18 @@ internal partial class InterfaceManager : IInternalDisposableService this.resizeBuffersHook = Hook.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.FromAddress(addr, this.PresentDetour); - Log.Verbose($"IDXGISwapChain::Present address {Util.DescribeAddress(addr)}"); + this.dxgiPresentHook = Hook.FromAddress( + (nint)SwapChainHelper.GameDeviceSwapChainVtbl->Present, + this.PresentDetour); } + Log.Verbose($"IDXGISwapChain::ResizeBuffers address: {Util.DescribeAddress(this.resizeBuffersHook.Address)}"); + Log.Verbose($"IDXGISwapChain::Present address: {Util.DescribeAddress(this.dxgiPresentHook?.Address ?? 0)}"); + this.setCursorHook.Enable(); this.dxgiPresentHook?.Enable(); - this.resizeBuffersHook?.Enable(); + this.resizeBuffersHook.Enable(); } private IntPtr SetCursorDetour(IntPtr hCursor) diff --git a/Dalamud/Interface/Internal/ReShadeHandling/ReShadeHandlingMode.cs b/Dalamud/Interface/Internal/ReShadeHandling/ReShadeHandlingMode.cs new file mode 100644 index 000000000..e4561ca46 --- /dev/null +++ b/Dalamud/Interface/Internal/ReShadeHandling/ReShadeHandlingMode.cs @@ -0,0 +1,14 @@ +namespace Dalamud.Interface.Internal.ReShadeHandling; + +/// Available handling modes for working with ReShade. +internal enum ReShadeHandlingMode +{ + /// Register as a ReShade addon, and draw on reshade_overlay event. + ReShadeAddon, + + /// Unwraps ReShade from the swap chain obtained from the game. + UnwrapReShade, + + /// Do not do anything special about it. ReShade will process Dalamud rendered stuff. + None = -1, +} diff --git a/Dalamud/Interface/Internal/ReShadeHandling/ReShadeUnwrapper.cs b/Dalamud/Interface/Internal/ReShadeHandling/ReShadeUnwrapper.cs new file mode 100644 index 000000000..5e6cd28a6 --- /dev/null +++ b/Dalamud/Interface/Internal/ReShadeHandling/ReShadeUnwrapper.cs @@ -0,0 +1,205 @@ +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using TerraFX.Interop.Windows; + +using static TerraFX.Interop.Windows.Windows; + +namespace Dalamud.Interface.Internal.ReShadeHandling; + +/// +/// Peels ReShade off stuff. +/// +[SuppressMessage( + "StyleCop.CSharp.LayoutRules", + "SA1519:Braces should not be omitted from multi-line child statement", + Justification = "Multiple fixed blocks")] +internal static unsafe class ReShadeUnwrapper +{ + /// Unwraps if it is wrapped by ReShade. + /// [inout] The COM pointer to an instance of . + /// A COM type that is or extends . + /// true if peeled. + public static bool Unwrap(ComPtr* comptr) + where T : unmanaged, IUnknown.Interface + { + if (typeof(T).GetNestedType("Vtbl`1") is not { } vtblType) + return false; + + nint vtblSize = vtblType.GetFields().Length * sizeof(nint); + var changed = false; + while (comptr->Get() != null && IsReShadedComObject(comptr->Get())) + { + // Expectation: the pointer to the underlying object should come early after the overriden vtable. + for (nint i = sizeof(nint); i <= 0x20; i += sizeof(nint)) + { + var ppObjectBehind = (nint)comptr->Get() + i; + + // Is the thing directly pointed from the address an actual something in the memory? + if (!IsValidReadableMemoryAddress(ppObjectBehind, 8)) + continue; + + var pObjectBehind = *(nint*)ppObjectBehind; + + // Is the address of vtable readable? + if (!IsValidReadableMemoryAddress(pObjectBehind, sizeof(nint))) + continue; + var pObjectBehindVtbl = *(nint*)pObjectBehind; + + // Is the vtable itself readable? + if (!IsValidReadableMemoryAddress(pObjectBehindVtbl, vtblSize)) + continue; + + // Are individual functions in vtable executable? + var valid = true; + for (var j = 0; valid && j < vtblSize; j += sizeof(nint)) + valid &= IsValidExecutableMemoryAddress(*(nint*)(pObjectBehindVtbl + j), 1); + if (!valid) + continue; + + // Interpret the object as an IUnknown. + // Note that `using` is not used, and `Attach` is used. We do not alter the reference count yet. + var punk = default(ComPtr); + punk.Attach((IUnknown*)pObjectBehind); + + // Is the IUnknown object also the type we want? + using var comptr2 = default(ComPtr); + if (punk.As(&comptr2).FAILED) + continue; + + comptr2.Swap(comptr); + changed = true; + break; + } + + if (!changed) + break; + } + + return changed; + } + + private static bool BelongsInReShadeDll(nint ptr) + { + foreach (ProcessModule processModule in Process.GetCurrentProcess().Modules) + { + if (ptr < processModule.BaseAddress || ptr >= processModule.BaseAddress + processModule.ModuleMemorySize) + continue; + + fixed (byte* pfn0 = "ReShadeRegisterAddon"u8) + fixed (byte* pfn1 = "ReShadeUnregisterAddon"u8) + fixed (byte* pfn2 = "ReShadeRegisterEvent"u8) + fixed (byte* pfn3 = "ReShadeUnregisterEvent"u8) + { + if (GetProcAddress((HMODULE)processModule.BaseAddress, (sbyte*)pfn0) == 0) + continue; + if (GetProcAddress((HMODULE)processModule.BaseAddress, (sbyte*)pfn1) == 0) + continue; + if (GetProcAddress((HMODULE)processModule.BaseAddress, (sbyte*)pfn2) == 0) + continue; + if (GetProcAddress((HMODULE)processModule.BaseAddress, (sbyte*)pfn3) == 0) + continue; + } + + return true; + } + + return false; + } + + private static bool IsReShadedComObject(T* obj) + where T : unmanaged, IUnknown.Interface + { + if (!IsValidReadableMemoryAddress((nint)obj, sizeof(nint))) + return false; + + try + { + var vtbl = (nint**)Marshal.ReadIntPtr((nint)obj); + if (!IsValidReadableMemoryAddress((nint)vtbl, sizeof(nint) * 3)) + return false; + + for (var i = 0; i < 3; i++) + { + var pfn = Marshal.ReadIntPtr((nint)(vtbl + i)); + if (!IsValidExecutableMemoryAddress(pfn, 1)) + return false; + if (!BelongsInReShadeDll(pfn)) + return false; + } + + return true; + } + catch + { + return false; + } + } + + private static bool IsValidReadableMemoryAddress(nint p, nint size) + { + while (size > 0) + { + if (!IsValidUserspaceMemoryAddress(p)) + return false; + + MEMORY_BASIC_INFORMATION mbi; + if (VirtualQuery((void*)p, &mbi, (nuint)sizeof(MEMORY_BASIC_INFORMATION)) == 0) + return false; + + if (mbi is not + { + State: MEM.MEM_COMMIT, + Protect: PAGE.PAGE_READONLY or PAGE.PAGE_READWRITE or PAGE.PAGE_EXECUTE_READ + or PAGE.PAGE_EXECUTE_READWRITE, + }) + return false; + + var regionSize = (nint)((mbi.RegionSize + 0xFFFUL) & ~0x1000UL); + var checkedSize = ((nint)mbi.BaseAddress + regionSize) - p; + size -= checkedSize; + p += checkedSize; + } + + return true; + } + + private static bool IsValidExecutableMemoryAddress(nint p, nint size) + { + while (size > 0) + { + if (!IsValidUserspaceMemoryAddress(p)) + return false; + + MEMORY_BASIC_INFORMATION mbi; + if (VirtualQuery((void*)p, &mbi, (nuint)sizeof(MEMORY_BASIC_INFORMATION)) == 0) + return false; + + if (mbi is not + { + State: MEM.MEM_COMMIT, + Protect: PAGE.PAGE_EXECUTE or PAGE.PAGE_EXECUTE_READ or PAGE.PAGE_EXECUTE_READWRITE + or PAGE.PAGE_EXECUTE_WRITECOPY, + }) + return false; + + var regionSize = (nint)((mbi.RegionSize + 0xFFFUL) & ~0x1000UL); + var checkedSize = ((nint)mbi.BaseAddress + regionSize) - p; + size -= checkedSize; + p += checkedSize; + } + + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsValidUserspaceMemoryAddress(nint p) + { + // https://learn.microsoft.com/en-us/windows-hardware/drivers/gettingstarted/virtual-address-spaces + // A 64-bit process on 64-bit Windows has a virtual address space within the 128-terabyte range + // 0x000'00000000 through 0x7FFF'FFFFFFFF. + return p >= 0x10000 && p <= unchecked((nint)0x7FFF_FFFFFFFFUL); + } +} diff --git a/Dalamud/Interface/Internal/SwapChainHelper.cs b/Dalamud/Interface/Internal/SwapChainHelper.cs index e0c3f21a8..051e348e0 100644 --- a/Dalamud/Interface/Internal/SwapChainHelper.cs +++ b/Dalamud/Interface/Internal/SwapChainHelper.cs @@ -1,5 +1,7 @@ using System.Threading; +using Dalamud.Interface.Internal.ReShadeHandling; + using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using TerraFX.Interop.DirectX; @@ -10,12 +12,17 @@ namespace Dalamud.Interface.Internal; /// Helper for dealing with swap chains. internal static unsafe class SwapChainHelper { + private static IDXGISwapChain* foundGameDeviceSwapChain; + /// Gets the game's active instance of IDXGISwapChain that is initialized. /// Address of the game's instance of IDXGISwapChain, or null if not available (yet.) public static IDXGISwapChain* GameDeviceSwapChain { get { + if (foundGameDeviceSwapChain is not null) + return foundGameDeviceSwapChain; + var kernelDev = Device.Instance(); if (kernelDev == null) return null; @@ -29,7 +36,7 @@ internal static unsafe class SwapChainHelper if (swapChain->BackBuffer == null) return null; - return (IDXGISwapChain*)swapChain->DXGISwapChain; + return foundGameDeviceSwapChain = (IDXGISwapChain*)swapChain->DXGISwapChain; } } @@ -80,4 +87,18 @@ internal static unsafe class SwapChainHelper while (GameDeviceSwapChain is null) Thread.Yield(); } + + /// + /// Make store address of unwrapped swap chain, if it was wrapped with ReShade. + /// + /// true if it was wrapped with ReShade. + public static bool UnwrapReShade() + { + using var swapChain = new ComPtr(GameDeviceSwapChain); + if (!ReShadeUnwrapper.Unwrap(&swapChain)) + return false; + + foundGameDeviceSwapChain = swapChain.Get(); + return true; + } } diff --git a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabExperimental.cs b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabExperimental.cs index 2707f67df..c51f465f9 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabExperimental.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabExperimental.cs @@ -1,8 +1,10 @@ using System.Diagnostics.CodeAnalysis; using CheapLoc; + using Dalamud.Configuration.Internal; using Dalamud.Interface.Colors; +using Dalamud.Interface.Internal.ReShadeHandling; using Dalamud.Interface.Internal.Windows.PluginInstaller; using Dalamud.Interface.Internal.Windows.Settings.Widgets; using Dalamud.Interface.Utility; @@ -11,28 +13,39 @@ using Dalamud.Utility; namespace Dalamud.Interface.Internal.Windows.Settings.Tabs; -[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Internals")] +[SuppressMessage( + "StyleCop.CSharp.DocumentationRules", + "SA1600:Elements should be documented", + Justification = "Internals")] public class SettingsTabExperimental : SettingsTab { public override SettingsEntry[] Entries { get; } = - { + [ new SettingsEntry( Loc.Localize("DalamudSettingsPluginTest", "Get plugin testing builds"), string.Format( - Loc.Localize("DalamudSettingsPluginTestHint", "Receive testing prereleases for selected plugins.\nTo opt-in to testing builds for a plugin, you have to right click it in the \"{0}\" tab of the plugin installer and select \"{1}\"."), + Loc.Localize( + "DalamudSettingsPluginTestHint", + "Receive testing prereleases for selected plugins.\nTo opt-in to testing builds for a plugin, you have to right click it in the \"{0}\" tab of the plugin installer and select \"{1}\"."), PluginCategoryManager.Locs.Group_Installed, PluginInstallerWindow.Locs.PluginContext_TestingOptIn), c => c.DoPluginTest, (v, c) => c.DoPluginTest = v), new HintSettingsEntry( - Loc.Localize("DalamudSettingsPluginTestWarning", "Testing plugins may contain bugs or crash your game. Please only enable this if you are aware of the risks."), + Loc.Localize( + "DalamudSettingsPluginTestWarning", + "Testing plugins may contain bugs or crash your game. Please only enable this if you are aware of the risks."), ImGuiColors.DalamudRed), - + new GapSettingsEntry(5), - + new SettingsEntry( - Loc.Localize("DalamudSettingEnablePluginUIAdditionalOptions", "Add a button to the title bar of plugin windows to open additional options"), - Loc.Localize("DalamudSettingEnablePluginUIAdditionalOptionsHint", "This will allow you to pin certain plugin windows, make them clickthrough or adjust their opacity.\nThis may not be supported by all of your plugins. Contact the plugin author if you want them to support this feature."), + Loc.Localize( + "DalamudSettingEnablePluginUIAdditionalOptions", + "Add a button to the title bar of plugin windows to open additional options"), + Loc.Localize( + "DalamudSettingEnablePluginUIAdditionalOptionsHint", + "This will allow you to pin certain plugin windows, make them clickthrough or adjust their opacity.\nThis may not be supported by all of your plugins. Contact the plugin author if you want them to support this feature."), c => c.EnablePluginUiAdditionalOptions, (v, c) => c.EnablePluginUiAdditionalOptions = v), @@ -40,7 +53,9 @@ public class SettingsTabExperimental : SettingsTab new ButtonSettingsEntry( Loc.Localize("DalamudSettingsClearHidden", "Clear hidden plugins"), - Loc.Localize("DalamudSettingsClearHiddenHint", "Restore plugins you have previously hidden from the plugin installer."), + Loc.Localize( + "DalamudSettingsClearHiddenHint", + "Restore plugins you have previously hidden from the plugin installer."), () => { Service.Get().HiddenPluginInternalName.Clear(); @@ -55,6 +70,45 @@ public class SettingsTabExperimental : SettingsTab new ThirdRepoSettingsEntry(), + new GapSettingsEntry(5, true), + + new EnumSettingsEntry( + Loc.Localize("DalamudSettingsReShadeHandlingMode", "ReShade handling mode"), + Loc.Localize( + "DalamudSettingsReShadeHandlingModeHint", + "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) + { + FriendlyEnumNameGetter = x => x switch + { + ReShadeHandlingMode.ReShadeAddon => Loc.Localize( + "DalamudSettingsReShadeHandlingModeReShadeAddon", + "ReShade addon"), + ReShadeHandlingMode.UnwrapReShade => Loc.Localize( + "DalamudSettingsReShadeHandlingModeUnwrapReShade", + "Unwrap ReShade"), + ReShadeHandlingMode.None => Loc.Localize( + "DalamudSettingsReShadeHandlingModeNone", + "Do not handle"), + _ => "", + }, + 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 won't work too well."), + 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.None => Loc.Localize( + "DalamudSettingsReShadeHandlingModeNoneDescription", + "No special handling will be done for ReShade. Dalamud will be under the effect of ReShade postprocessing."), + _ => "", + }, + }, + /* Disabling profiles after they've been enabled doesn't make much sense, at least not if the user has already created profiles. new GapSettingsEntry(5, true), @@ -64,7 +118,7 @@ public class SettingsTabExperimental : SettingsTab c => c.ProfilesEnabled, (v, c) => c.ProfilesEnabled = v), */ - }; + ]; public override string Title => Loc.Localize("DalamudSettingsExperimental", "Experimental"); @@ -72,7 +126,9 @@ public class SettingsTabExperimental : SettingsTab { base.Draw(); - ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, "Total memory used by Dalamud & Plugins: " + Util.FormatBytes(GC.GetTotalMemory(false))); + ImGuiHelpers.SafeTextColoredWrapped( + ImGuiColors.DalamudGrey, + "Total memory used by Dalamud & Plugins: " + Util.FormatBytes(GC.GetTotalMemory(false))); ImGuiHelpers.ScaledDummy(15); } } diff --git a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabGeneral.cs b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabGeneral.cs index c991907ec..5e3648ac6 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabGeneral.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabGeneral.cs @@ -15,7 +15,7 @@ public class SettingsTabGeneral : SettingsTab new GapSettingsEntry(5), - new SettingsEntry( + new EnumSettingsEntry( Loc.Localize("DalamudSettingsChannel", "Dalamud Chat Channel"), Loc.Localize("DalamudSettingsChannelHint", "Select the chat channel that is to be used for general Dalamud messages."), c => c.GeneralChatType, diff --git a/Dalamud/Interface/Internal/Windows/Settings/Widgets/EnumSettingsEntry{T}.cs b/Dalamud/Interface/Internal/Windows/Settings/Widgets/EnumSettingsEntry{T}.cs new file mode 100644 index 000000000..f40654542 --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/Settings/Widgets/EnumSettingsEntry{T}.cs @@ -0,0 +1,175 @@ +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +using Dalamud.Configuration.Internal; + +using Dalamud.Interface.Colors; +using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; + +using ImGuiNET; + +namespace Dalamud.Interface.Internal.Windows.Settings.Widgets; + +[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Internals")] +internal sealed class EnumSettingsEntry : SettingsEntry + where T : struct, Enum +{ + private readonly LoadSettingDelegate load; + private readonly SaveSettingDelegate save; + private readonly Action? change; + + private readonly T fallbackValue; + + private T valueBacking; + + public EnumSettingsEntry( + string name, + string description, + LoadSettingDelegate load, + SaveSettingDelegate save, + Action? change = null, + Func? warning = null, + Func? validity = null, + Func? visibility = null, + T fallbackValue = default) + { + this.load = load; + this.save = save; + this.change = change; + this.Name = name; + this.Description = description; + this.CheckWarning = warning; + this.CheckValidity = validity; + this.CheckVisibility = visibility; + + this.fallbackValue = fallbackValue; + } + + public delegate T LoadSettingDelegate(DalamudConfiguration config); + + public delegate void SaveSettingDelegate(T value, DalamudConfiguration config); + + public T Value + { + get => this.valueBacking; + set + { + if (Equals(value, this.valueBacking)) + return; + this.valueBacking = value; + this.change?.Invoke(value); + } + } + + public string Description { get; } + + public Action>? CustomDraw { get; init; } + + public Func? CheckValidity { get; init; } + + public Func? CheckWarning { get; init; } + + public Func? CheckVisibility { get; init; } + + public Func FriendlyEnumNameGetter { get; init; } = x => x.ToString(); + + public Func FriendlyEnumDescriptionGetter { get; init; } = _ => string.Empty; + + public override bool IsVisible => this.CheckVisibility?.Invoke() ?? true; + + public override void Draw() + { + Debug.Assert(this.Name != null, "this.Name != null"); + + if (this.CustomDraw is not null) + { + this.CustomDraw.Invoke(this); + } + else + { + ImGuiHelpers.SafeTextWrapped(this.Name); + + var idx = this.valueBacking; + var values = Enum.GetValues(); + + if (!values.Contains(idx)) + { + idx = Enum.IsDefined(this.fallbackValue) + ? this.fallbackValue + : throw new InvalidOperationException("No fallback value for enum"); + this.valueBacking = idx; + } + + if (ImGui.BeginCombo($"###{this.Id.ToString()}", this.FriendlyEnumNameGetter(idx))) + { + foreach (var value in values) + { + if (ImGui.Selectable(this.FriendlyEnumNameGetter(value), idx.Equals(value))) + { + this.valueBacking = value; + } + } + + ImGui.EndCombo(); + } + } + + using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudGrey)) + { + var desc = this.FriendlyEnumDescriptionGetter(this.valueBacking); + if (!string.IsNullOrWhiteSpace(desc)) + { + ImGuiHelpers.SafeTextWrapped(desc); + ImGuiHelpers.ScaledDummy(2); + } + + ImGuiHelpers.SafeTextWrapped(this.Description); + } + + if (this.CheckValidity != null) + { + var validityMsg = this.CheckValidity.Invoke(this.Value); + this.IsValid = string.IsNullOrEmpty(validityMsg); + + if (!this.IsValid) + { + using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed)) + { + ImGui.Text(validityMsg); + } + } + } + else + { + this.IsValid = true; + } + + var warningMessage = this.CheckWarning?.Invoke(this.Value); + + if (warningMessage != null) + { + using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed)) + { + ImGui.Text(warningMessage); + } + } + } + + public override void Load() + { + this.valueBacking = this.load(Service.Get()); + + if (this.CheckValidity != null) + { + this.IsValid = this.CheckValidity(this.Value) == null; + } + else + { + this.IsValid = true; + } + } + + public override void Save() => this.save(this.Value, Service.Get()); +} diff --git a/Dalamud/Interface/Internal/Windows/Settings/Widgets/SettingsEntry{T}.cs b/Dalamud/Interface/Internal/Windows/Settings/Widgets/SettingsEntry{T}.cs index 2ac4187cf..cffe0a5da 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/Widgets/SettingsEntry{T}.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/Widgets/SettingsEntry{T}.cs @@ -1,15 +1,13 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Linq; -using System.Numerics; using Dalamud.Configuration.Internal; using Dalamud.Interface.Colors; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; -using Dalamud.Utility; + using ImGuiNET; namespace Dalamud.Interface.Internal.Windows.Settings.Widgets; @@ -22,7 +20,6 @@ internal sealed class SettingsEntry : SettingsEntry private readonly Action? change; private object? valueBacking; - private object? fallbackValue; public SettingsEntry( string name, @@ -32,8 +29,7 @@ internal sealed class SettingsEntry : SettingsEntry Action? change = null, Func? warning = null, Func? validity = null, - Func? visibility = null, - object? fallbackValue = null) + Func? visibility = null) { this.load = load; this.save = save; @@ -43,8 +39,6 @@ internal sealed class SettingsEntry : SettingsEntry this.CheckWarning = warning; this.CheckValidity = validity; this.CheckVisibility = visibility; - - this.fallbackValue = fallbackValue; } public delegate T? LoadSettingDelegate(DalamudConfiguration config); @@ -118,34 +112,6 @@ internal sealed class SettingsEntry : SettingsEntry this.change?.Invoke(this.Value); } } - else if (type.IsEnum) - { - ImGuiHelpers.SafeTextWrapped(this.Name); - - var idx = (Enum)(this.valueBacking ?? 0); - var values = Enum.GetValues(type); - var descriptions = - values.Cast().ToDictionary(x => x, x => x.GetAttribute() ?? new SettingsAnnotationAttribute(x.ToString(), string.Empty)); - - if (!descriptions.ContainsKey(idx)) - { - idx = (Enum)this.fallbackValue ?? throw new Exception("No fallback value for enum"); - this.valueBacking = idx; - } - - if (ImGui.BeginCombo($"###{this.Id.ToString()}", descriptions[idx].FriendlyName)) - { - foreach (Enum value in values) - { - if (ImGui.Selectable(descriptions[value].FriendlyName, idx.Equals(value))) - { - this.valueBacking = value; - } - } - - ImGui.EndCombo(); - } - } using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudGrey)) { @@ -197,18 +163,3 @@ internal sealed class SettingsEntry : SettingsEntry public override void Save() => this.save(this.Value, Service.Get()); } - -[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Internals")] -[AttributeUsage(AttributeTargets.Field)] -internal class SettingsAnnotationAttribute : Attribute -{ - public SettingsAnnotationAttribute(string friendlyName, string description) - { - this.FriendlyName = friendlyName; - this.Description = description; - } - - public string FriendlyName { get; set; } - - public string Description { get; set; } -} From 6c8ec0ab4d2fb14d30d60161ece8443d74ca3a66 Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Mon, 22 Jul 2024 20:34:43 +0900 Subject: [PATCH 4/8] Fix random typo --- Dalamud/Utility/Util.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Utility/Util.cs b/Dalamud/Utility/Util.cs index b9c81acfd..7d4de1a02 100644 --- a/Dalamud/Utility/Util.cs +++ b/Dalamud/Utility/Util.cs @@ -269,7 +269,7 @@ public static class Util { if ((mbi.Protect & (1 << i)) == 0) continue; - if (c++ == 0) + if (c++ != 0) sb.Append(" | "); sb.Append(PageProtectionFlagNames[i]); } From e1a7caa2cffb0e2e0e68440081045aaffc8ad411 Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Mon, 22 Jul 2024 22:43:02 +0900 Subject: [PATCH 5/8] fix cpm rc --- Dalamud/Support/CurrentProcessModules.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dalamud/Support/CurrentProcessModules.cs b/Dalamud/Support/CurrentProcessModules.cs index cd73ceb04..b89d2eb63 100644 --- a/Dalamud/Support/CurrentProcessModules.cs +++ b/Dalamud/Support/CurrentProcessModules.cs @@ -8,7 +8,7 @@ namespace Dalamud.Support; /// Tracks the loaded process modules. internal static unsafe partial class CurrentProcessModules { - private static Process? process; + private static ProcessModuleCollection? moduleCollection; /// Gets all the loaded modules, up to date. public static ProcessModuleCollection ModuleCollection @@ -19,13 +19,13 @@ internal static unsafe partial class CurrentProcessModules if (t != 0) { t = 0; - process = null; + moduleCollection = null; Log.Verbose("{what}: Fetching fresh copy of current process modules.", nameof(CurrentProcessModules)); } try { - return (process ??= Process.GetCurrentProcess()).Modules; + return moduleCollection ??= Process.GetCurrentProcess().Modules; } catch (Exception e) { From 6fd19638e958e852bedb50a72517635819cd71c9 Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Tue, 23 Jul 2024 10:14:34 +0900 Subject: [PATCH 6/8] Reviewed changed --- .../Internal/ReShadeHandling/ReShadeAddonInterface.cs | 2 ++ .../Internal/ReShadeHandling/ReShadeUnwrapper.cs | 9 +-------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/Dalamud/Interface/Internal/ReShadeHandling/ReShadeAddonInterface.cs b/Dalamud/Interface/Internal/ReShadeHandling/ReShadeAddonInterface.cs index df324941e..de7629276 100644 --- a/Dalamud/Interface/Internal/ReShadeHandling/ReShadeAddonInterface.cs +++ b/Dalamud/Interface/Internal/ReShadeHandling/ReShadeAddonInterface.cs @@ -31,6 +31,8 @@ internal sealed unsafe partial class ReShadeAddonInterface : IDisposable if (!Exports.ReShadeRegisterAddon(this.hDalamudModule, ReShadeApiVersion)) throw new InvalidOperationException("ReShadeRegisterAddon failure."); + // https://github.com/crosire/reshade/commit/eaaa2a2c5adf5749ad17b358305da3f2d0f6baf4 + // TODO: when ReShade gets a proper release with this commit, make this hook optional this.addonModuleResolverHook = Hook.FromImport( ReShadeModule!, "kernel32.dll", diff --git a/Dalamud/Interface/Internal/ReShadeHandling/ReShadeUnwrapper.cs b/Dalamud/Interface/Internal/ReShadeHandling/ReShadeUnwrapper.cs index 5e6cd28a6..3682b03c0 100644 --- a/Dalamud/Interface/Internal/ReShadeHandling/ReShadeUnwrapper.cs +++ b/Dalamud/Interface/Internal/ReShadeHandling/ReShadeUnwrapper.cs @@ -1,5 +1,4 @@ using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -9,13 +8,7 @@ using static TerraFX.Interop.Windows.Windows; namespace Dalamud.Interface.Internal.ReShadeHandling; -/// -/// Peels ReShade off stuff. -/// -[SuppressMessage( - "StyleCop.CSharp.LayoutRules", - "SA1519:Braces should not be omitted from multi-line child statement", - Justification = "Multiple fixed blocks")] +/// Unwraps IUnknown wrapped by ReShade. internal static unsafe class ReShadeUnwrapper { /// Unwraps if it is wrapped by ReShade. From 3215b6dddf5da60fa9a7b634f8511a424d01f939 Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Tue, 23 Jul 2024 10:57:09 +0900 Subject: [PATCH 7/8] Add optional vtable swapchain hook mode --- .../Internal/DalamudConfiguration.cs | 4 + Dalamud/Hooking/Internal/ObjectVTableHook.cs | 286 ++++++++++++++++++ .../Interface/Internal/InterfaceManager.cs | 62 +++- .../ReShadeHandling/ReShadeUnwrapper.cs | 28 +- Dalamud/Interface/Internal/SwapChainHelper.cs | 10 + .../Settings/Tabs/SettingsTabExperimental.cs | 13 +- 6 files changed, 374 insertions(+), 29 deletions(-) create mode 100644 Dalamud/Hooking/Internal/ObjectVTableHook.cs diff --git a/Dalamud/Configuration/Internal/DalamudConfiguration.cs b/Dalamud/Configuration/Internal/DalamudConfiguration.cs index 9d54f4562..d5f1299fd 100644 --- a/Dalamud/Configuration/Internal/DalamudConfiguration.cs +++ b/Dalamud/Configuration/Internal/DalamudConfiguration.cs @@ -8,6 +8,7 @@ using System.Runtime.InteropServices; using Dalamud.Game.Text; using Dalamud.Interface; using Dalamud.Interface.FontIdentifier; +using Dalamud.Interface.Internal; using Dalamud.Interface.Internal.ReShadeHandling; using Dalamud.Interface.Style; using Dalamud.IoC.Internal; @@ -445,6 +446,9 @@ internal sealed class DalamudConfiguration : IInternalDisposableService /// Gets or sets the mode specifying how to handle ReShade. public ReShadeHandlingMode ReShadeHandlingMode { get; set; } = ReShadeHandlingMode.ReShadeAddon; + /// Gets or sets the swap chain hook mode. + public SwapChainHelper.HookMode SwapChainHookMode { get; set; } = SwapChainHelper.HookMode.ByteCode; + /// /// Gets or sets hitch threshold for game network up in milliseconds. /// diff --git a/Dalamud/Hooking/Internal/ObjectVTableHook.cs b/Dalamud/Hooking/Internal/ObjectVTableHook.cs new file mode 100644 index 000000000..b4500bb5f --- /dev/null +++ b/Dalamud/Hooking/Internal/ObjectVTableHook.cs @@ -0,0 +1,286 @@ +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using Dalamud.Utility; + +using Serilog; + +namespace Dalamud.Hooking.Internal; + +/// Manages a hook that works by replacing the vtable of target object. +internal unsafe class ObjectVTableHook : IDisposable +{ + private readonly nint** ppVtbl; + private readonly int numMethods; + + private readonly nint* pVtblOriginal; + private readonly nint[] vtblOverriden; + + /// Extra data for overriden vtable entries, primarily for keeping references to delegates that are used + /// with . + private readonly object?[] vtblOverridenTag; + + private bool released; + + /// Initializes a new instance of the class. + /// Address to vtable. Usually the address of the object itself. + /// Number of methods in this vtable. + public ObjectVTableHook(nint ppVtbl, int numMethods) + { + this.ppVtbl = (nint**)ppVtbl; + this.numMethods = numMethods; + this.vtblOverridenTag = new object?[numMethods]; + this.pVtblOriginal = *this.ppVtbl; + this.vtblOverriden = GC.AllocateArray(numMethods, true); + this.OriginalVTableSpan.CopyTo(this.vtblOverriden); + } + + /// Initializes a new instance of the class. + /// Address to vtable. Usually the address of the object itself. + /// Number of methods in this vtable. + public ObjectVTableHook(void* ppVtbl, int numMethods) + : this((nint)ppVtbl, numMethods) + { + } + + /// Finalizes an instance of the class. + ~ObjectVTableHook() => this.ReleaseUnmanagedResources(); + + /// Gets the span view of original vtable. + public ReadOnlySpan OriginalVTableSpan => new(this.pVtblOriginal, this.numMethods); + + /// Gets the span view of overriden vtable. + public ReadOnlySpan OverridenVTableSpan => this.vtblOverriden.AsSpan(); + + /// Disables the hook. + public void Disable() + { + // already disabled + if (*this.ppVtbl == this.pVtblOriginal) + return; + + if (*this.ppVtbl != Unsafe.AsPointer(ref this.vtblOverriden[0])) + { + Log.Warning( + "[{who}]: the object was hooked by something else; disabling may result in a crash.", + this.GetType().Name); + } + + *this.ppVtbl = this.pVtblOriginal; + } + + /// + public void Dispose() + { + this.ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); + } + + /// Enables the hook. + public void Enable() + { + // already enabled + if (*this.ppVtbl == Unsafe.AsPointer(ref this.vtblOverriden[0])) + return; + + if (*this.ppVtbl != this.pVtblOriginal) + { + Log.Warning( + "[{who}]: the object was hooked by something else; enabling may result in a crash.", + this.GetType().Name); + } + + *this.ppVtbl = (nint*)Unsafe.AsPointer(ref this.vtblOverriden[0]); + } + + /// Gets the original method address of the given method index. + /// Index of the method. + /// Address of the original method. + public nint GetOriginalMethodAddress(int methodIndex) + { + this.EnsureMethodIndex(methodIndex); + return this.pVtblOriginal[methodIndex]; + } + + /// Gets the original method of the given method index, as a delegate of given type. + /// Index of the method. + /// Type of delegate. + /// Delegate to the original method. + public T GetOriginalMethodDelegate(int methodIndex) + where T : Delegate + { + this.EnsureMethodIndex(methodIndex); + return Marshal.GetDelegateForFunctionPointer(this.pVtblOriginal[methodIndex]); + } + + /// Resets a method to the original function. + /// Index of the method. + public void ResetVtableEntry(int methodIndex) + { + this.EnsureMethodIndex(methodIndex); + this.vtblOverriden[methodIndex] = this.pVtblOriginal[methodIndex]; + this.vtblOverridenTag[methodIndex] = null; + } + + /// Sets a method in vtable to the given address of function. + /// Index of the method. + /// Address of the detour function. + /// Additional reference to keep in memory. + public void SetVtableEntry(int methodIndex, nint pfn, object? refkeep) + { + this.EnsureMethodIndex(methodIndex); + this.vtblOverriden[methodIndex] = pfn; + this.vtblOverridenTag[methodIndex] = refkeep; + } + + /// Sets a method in vtable to the given delegate. + /// Index of the method. + /// Detour delegate. + /// Type of delegate. + public void SetVtableEntry(int methodIndex, T detourDelegate) + where T : Delegate => + this.SetVtableEntry(methodIndex, Marshal.GetFunctionPointerForDelegate(detourDelegate), detourDelegate); + + /// Sets a method in vtable to the given delegate. + /// Index of the method. + /// Detour delegate. + /// Original method delegate. + /// Type of delegate. + public void SetVtableEntry(int methodIndex, T detourDelegate, out T originalMethodDelegate) + where T : Delegate + { + originalMethodDelegate = this.GetOriginalMethodDelegate(methodIndex); + this.SetVtableEntry(methodIndex, Marshal.GetFunctionPointerForDelegate(detourDelegate), detourDelegate); + } + + /// Creates a new instance of that manages one entry in the vtable hook. + /// Index of the method. + /// Detour delegate. + /// Type of delegate. + /// A new instance of . + /// Even if a single hook is enabled, without , the hook will remain disabled. + /// + public Hook CreateHook(int methodIndex, T detourDelegate) where T : Delegate => + new SingleHook(this, methodIndex, detourDelegate); + + private void EnsureMethodIndex(int methodIndex) + { + ArgumentOutOfRangeException.ThrowIfNegative(methodIndex); + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(methodIndex, this.numMethods); + } + + private void ReleaseUnmanagedResources() + { + if (!this.released) + { + this.Disable(); + this.released = true; + } + } + + private sealed class SingleHook(ObjectVTableHook hook, int methodIndex, T detourDelegate) + : Hook((nint)hook.ppVtbl) + where T : Delegate + { + /// + public override T Original { get; } = hook.GetOriginalMethodDelegate(methodIndex); + + /// + public override bool IsEnabled => + hook.OriginalVTableSpan[methodIndex] != hook.OverridenVTableSpan[methodIndex]; + + /// + public override string BackendName => nameof(ObjectVTableHook); + + /// + public override void Enable() => hook.SetVtableEntry(methodIndex, detourDelegate); + + /// + public override void Disable() => hook.ResetVtableEntry(methodIndex); + } +} + +/// Typed version of . +/// VTable struct. +internal unsafe class ObjectVTableHook : ObjectVTableHook + where TVTable : unmanaged +{ + private static readonly string[] Fields = + typeof(TVTable).GetFields(BindingFlags.Instance | BindingFlags.Public).Select(x => x.Name).ToArray(); + + /// Initializes a new instance of the class. + /// Address to vtable. Usually the address of the object itself. + public ObjectVTableHook(void* ppVtbl) + : base(ppVtbl, Fields.Length) + { + } + + /// Gets the original vtable. + public ref readonly TVTable OriginalVTable => ref MemoryMarshal.Cast(this.OriginalVTableSpan)[0]; + + /// Gets the overriden vtable. + public ref readonly TVTable OverridenVTable => ref MemoryMarshal.Cast(this.OverridenVTableSpan)[0]; + + /// Gets the index of the method by method name. + /// Name of the method. + /// Index of the method. + public int GetMethodIndex(string methodName) => Fields.IndexOf(methodName); + + /// Gets the original method address of the given method index. + /// Name of the method. + /// Address of the original method. + public nint GetOriginalMethodAddress(string methodName) => + this.GetOriginalMethodAddress(this.GetMethodIndex(methodName)); + + /// Gets the original method of the given method index, as a delegate of given type. + /// Name of the method. + /// Type of delegate. + /// Delegate to the original method. + public T GetOriginalMethodDelegate(string methodName) + where T : Delegate + => this.GetOriginalMethodDelegate(this.GetMethodIndex(methodName)); + + /// Resets a method to the original function. + /// Name of the method. + public void ResetVtableEntry(string methodName) + => this.ResetVtableEntry(this.GetMethodIndex(methodName)); + + /// Sets a method in vtable to the given address of function. + /// Name of the method. + /// Address of the detour function. + /// Additional reference to keep in memory. + public void SetVtableEntry(string methodName, nint pfn, object? refkeep) + => this.SetVtableEntry(this.GetMethodIndex(methodName), pfn, refkeep); + + /// Sets a method in vtable to the given delegate. + /// Name of the method. + /// Detour delegate. + /// Type of delegate. + public void SetVtableEntry(string methodName, T detourDelegate) + where T : Delegate => + this.SetVtableEntry( + this.GetMethodIndex(methodName), + Marshal.GetFunctionPointerForDelegate(detourDelegate), + detourDelegate); + + /// Sets a method in vtable to the given delegate. + /// Name of the method. + /// Detour delegate. + /// Original method delegate. + /// Type of delegate. + public void SetVtableEntry(string methodName, T detourDelegate, out T originalMethodDelegate) + where T : Delegate + => this.SetVtableEntry(this.GetMethodIndex(methodName), detourDelegate, out originalMethodDelegate); + + /// Creates a new instance of that manages one entry in the vtable hook. + /// Name of the method. + /// Detour delegate. + /// Type of delegate. + /// A new instance of . + /// Even if a single hook is enabled, without , the hook will remain + /// disabled. + public Hook CreateHook(string methodName, T detourDelegate) where T : Delegate => + this.CreateHook(this.GetMethodIndex(methodName), detourDelegate); +} diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs index b7c2f8765..4941ea46c 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.cs @@ -35,6 +35,7 @@ using JetBrains.Annotations; using PInvoke; +using TerraFX.Interop.DirectX; using TerraFX.Interop.Windows; // general dev notes, here because it's easiest @@ -94,6 +95,7 @@ internal partial class InterfaceManager : IInternalDisposableService private Hook? setCursorHook; private Hook? dxgiPresentHook; private Hook? resizeBuffersHook; + private ObjectVTableHook>? swapChainHook; private ReShadeAddonInterface? reShadeAddonInterface; private IFontAtlas? dalamudAtlas; @@ -308,6 +310,7 @@ internal partial class InterfaceManager : IInternalDisposableService 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.reShadeAddonInterface, null)?.Dispose(); } } @@ -769,12 +772,13 @@ internal partial class InterfaceManager : IInternalDisposableService Log.Verbose("Unwrapped ReShade."); } + ResizeBuffersDelegate resizeBuffersDelegate; + DxgiPresentDelegate? dxgiPresentDelegate; if (this.dalamudConfiguration.ReShadeHandlingMode == ReShadeHandlingMode.ReShadeAddon && ReShadeAddonInterface.TryRegisterAddon(out this.reShadeAddonInterface)) { - this.resizeBuffersHook = Hook.FromAddress( - (nint)SwapChainHelper.GameDeviceSwapChainVtbl->ResizeBuffers, - this.AsReShadeAddonResizeBuffersDetour); + resizeBuffersDelegate = this.AsReShadeAddonResizeBuffersDetour; + dxgiPresentDelegate = null; Log.Verbose( "Registered as a ReShade({name}: 0x{addr:X}) addon.", @@ -786,21 +790,55 @@ internal partial class InterfaceManager : IInternalDisposableService } else { - this.resizeBuffersHook = Hook.FromAddress( - (nint)SwapChainHelper.GameDeviceSwapChainVtbl->ResizeBuffers, - this.AsHookResizeBuffersDetour); - - this.dxgiPresentHook = Hook.FromAddress( - (nint)SwapChainHelper.GameDeviceSwapChainVtbl->Present, - this.PresentDetour); + resizeBuffersDelegate = this.AsHookResizeBuffersDetour; + dxgiPresentDelegate = this.PresentDetour; } - Log.Verbose($"IDXGISwapChain::ResizeBuffers address: {Util.DescribeAddress(this.resizeBuffersHook.Address)}"); + switch (this.dalamudConfiguration.SwapChainHookMode) + { + case SwapChainHelper.HookMode.ByteCode: + default: + { + this.resizeBuffersHook = Hook.FromAddress( + (nint)SwapChainHelper.GameDeviceSwapChainVtbl->ResizeBuffers, + resizeBuffersDelegate); + + if (dxgiPresentDelegate is not null) + { + this.dxgiPresentHook = Hook.FromAddress( + (nint)SwapChainHelper.GameDeviceSwapChainVtbl->Present, + dxgiPresentDelegate); + } + + break; + } + + case SwapChainHelper.HookMode.VTable: + { + this.swapChainHook = new(SwapChainHelper.GameDeviceSwapChain); + this.resizeBuffersHook = this.swapChainHook.CreateHook( + nameof(IDXGISwapChain.ResizeBuffers), + resizeBuffersDelegate); + + if (dxgiPresentDelegate is not null) + { + this.dxgiPresentHook = this.swapChainHook.CreateHook( + nameof(IDXGISwapChain.Present), + dxgiPresentDelegate); + } + + break; + } + } + + Log.Verbose( + $"IDXGISwapChain::ResizeBuffers address: {Util.DescribeAddress(this.resizeBuffersHook.Address)}"); Log.Verbose($"IDXGISwapChain::Present address: {Util.DescribeAddress(this.dxgiPresentHook?.Address ?? 0)}"); this.setCursorHook.Enable(); - this.dxgiPresentHook?.Enable(); this.resizeBuffersHook.Enable(); + this.dxgiPresentHook?.Enable(); + this.swapChainHook?.Enable(); } private IntPtr SetCursorDetour(IntPtr hCursor) diff --git a/Dalamud/Interface/Internal/ReShadeHandling/ReShadeUnwrapper.cs b/Dalamud/Interface/Internal/ReShadeHandling/ReShadeUnwrapper.cs index 3682b03c0..f1210425d 100644 --- a/Dalamud/Interface/Internal/ReShadeHandling/ReShadeUnwrapper.cs +++ b/Dalamud/Interface/Internal/ReShadeHandling/ReShadeUnwrapper.cs @@ -78,28 +78,24 @@ internal static unsafe class ReShadeUnwrapper { foreach (ProcessModule processModule in Process.GetCurrentProcess().Modules) { - if (ptr < processModule.BaseAddress || ptr >= processModule.BaseAddress + processModule.ModuleMemorySize) + if (ptr < processModule.BaseAddress || + ptr >= processModule.BaseAddress + processModule.ModuleMemorySize || + !HasProcExported(processModule, "ReShadeRegisterAddon"u8) || + !HasProcExported(processModule, "ReShadeUnregisterAddon"u8) || + !HasProcExported(processModule, "ReShadeRegisterEvent"u8) || + !HasProcExported(processModule, "ReShadeUnregisterEvent"u8)) continue; - fixed (byte* pfn0 = "ReShadeRegisterAddon"u8) - fixed (byte* pfn1 = "ReShadeUnregisterAddon"u8) - fixed (byte* pfn2 = "ReShadeRegisterEvent"u8) - fixed (byte* pfn3 = "ReShadeUnregisterEvent"u8) - { - if (GetProcAddress((HMODULE)processModule.BaseAddress, (sbyte*)pfn0) == 0) - continue; - if (GetProcAddress((HMODULE)processModule.BaseAddress, (sbyte*)pfn1) == 0) - continue; - if (GetProcAddress((HMODULE)processModule.BaseAddress, (sbyte*)pfn2) == 0) - continue; - if (GetProcAddress((HMODULE)processModule.BaseAddress, (sbyte*)pfn3) == 0) - continue; - } - return true; } return false; + + static bool HasProcExported(ProcessModule m, ReadOnlySpan name) + { + fixed (byte* p = name) + return GetProcAddress((HMODULE)m.BaseAddress, (sbyte*)p) != 0; + } } private static bool IsReShadedComObject(T* obj) diff --git a/Dalamud/Interface/Internal/SwapChainHelper.cs b/Dalamud/Interface/Internal/SwapChainHelper.cs index 051e348e0..4a336ee9f 100644 --- a/Dalamud/Interface/Internal/SwapChainHelper.cs +++ b/Dalamud/Interface/Internal/SwapChainHelper.cs @@ -14,6 +14,16 @@ internal static unsafe class SwapChainHelper { private static IDXGISwapChain* foundGameDeviceSwapChain; + /// Describes how to hook methods. + public enum HookMode + { + /// Hooks by rewriting the native bytecode. + ByteCode, + + /// Hooks by providing an alternative vtable. + VTable, + } + /// Gets the game's active instance of IDXGISwapChain that is initialized. /// Address of the game's instance of IDXGISwapChain, or null if not available (yet.) public static IDXGISwapChain* GameDeviceSwapChain diff --git a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabExperimental.cs b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabExperimental.cs index c51f465f9..faefe418c 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabExperimental.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabExperimental.cs @@ -98,7 +98,7 @@ public class SettingsTabExperimental : SettingsTab { ReShadeHandlingMode.ReShadeAddon => Loc.Localize( "DalamudSettingsReShadeHandlingModeReShadeAddonDescription", - "Dalamud will register itself as a ReShade addon. Most compatibility is expected, but multi-monitor window option won't work too well."), + "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.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."), @@ -109,6 +109,17 @@ public class SettingsTabExperimental : SettingsTab }, }, + new GapSettingsEntry(5, true), + + new EnumSettingsEntry( + Loc.Localize("DalamudSettingsSwapChainHookMode", "Swap chain hooking mode"), + Loc.Localize( + "DalamudSettingsSwapChainHookModeHint", + "Depending on addons aside from Dalamud you use, you may have to use different options for Dalamud and other addons to cooperate.\nRestart is required for changes to take effect."), + c => c.SwapChainHookMode, + (v, c) => c.SwapChainHookMode = v, + fallbackValue: SwapChainHelper.HookMode.ByteCode), + /* Disabling profiles after they've been enabled doesn't make much sense, at least not if the user has already created profiles. new GapSettingsEntry(5, true), From 97069dff270c6dd59911e955543db9852ccbf885 Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Tue, 23 Jul 2024 17:48:11 +0900 Subject: [PATCH 8/8] Detect and warn reshade w/o addon support --- .../Interface/Internal/InterfaceManager.cs | 58 ++++++++++++++----- .../ReShadeAddonInterface.Exports.cs | 31 ++++++++++ 2 files changed, 75 insertions(+), 14 deletions(-) diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs index 4941ea46c..cfbc23bc9 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.cs @@ -8,6 +8,8 @@ using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; +using CheapLoc; + using Dalamud.Configuration.Internal; using Dalamud.Game; using Dalamud.Game.ClientState.GamePad; @@ -15,6 +17,7 @@ using Dalamud.Game.ClientState.Keys; using Dalamud.Hooking; using Dalamud.Hooking.Internal; using Dalamud.Hooking.WndProcHook; +using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.ImGuiNotification.Internal; using Dalamud.Interface.Internal.ManagedAsserts; using Dalamud.Interface.Internal.ReShadeHandling; @@ -746,6 +749,9 @@ internal partial class InterfaceManager : IInternalDisposableService _ = this.dalamudAtlas.BuildFontsAsync(); SwapChainHelper.BusyWaitForGameDeviceSwapChain(); + var swapChainDesc = default(DXGI_SWAP_CHAIN_DESC); + if (SwapChainHelper.GameDeviceSwapChain->GetDesc(&swapChainDesc).SUCCEEDED) + this.gameWindowHandle = swapChainDesc.OutputWindow; try { @@ -765,6 +771,28 @@ internal partial class InterfaceManager : IInternalDisposableService 0, this.SetCursorDetour); + if (ReShadeAddonInterface.ReShadeHasSignature) + { + Log.Warning("Signed ReShade binary detected."); + Service + .GetAsync() + .ContinueWith( + nmt => nmt.Result.AddNotification( + new() + { + MinimizedText = Loc.Localize( + "ReShadeNoAddonSupportNotificationMinimizedText", + "Wrong ReShade installation"), + Content = Loc.Localize( + "ReShadeNoAddonSupportNotificationContent", + "Your installation of ReShade does not have full addon support, and may not work with Dalamud and/or the game.\n" + + "Download and install ReShade with full addon-support."), + Type = NotificationType.Warning, + InitialDuration = TimeSpan.MaxValue, + ShowIndeterminateIfNoExpiry = false, + })); + } + Log.Verbose("===== S W A P C H A I N ====="); if (this.dalamudConfiguration.ReShadeHandlingMode == ReShadeHandlingMode.UnwrapReShade) { @@ -772,23 +800,25 @@ internal partial class InterfaceManager : IInternalDisposableService Log.Verbose("Unwrapped ReShade."); } - ResizeBuffersDelegate resizeBuffersDelegate; - DxgiPresentDelegate? dxgiPresentDelegate; - if (this.dalamudConfiguration.ReShadeHandlingMode == ReShadeHandlingMode.ReShadeAddon && - ReShadeAddonInterface.TryRegisterAddon(out this.reShadeAddonInterface)) + ResizeBuffersDelegate? resizeBuffersDelegate = null; + DxgiPresentDelegate? dxgiPresentDelegate = null; + if (this.dalamudConfiguration.ReShadeHandlingMode == ReShadeHandlingMode.ReShadeAddon) { - resizeBuffersDelegate = this.AsReShadeAddonResizeBuffersDetour; - dxgiPresentDelegate = null; + if (ReShadeAddonInterface.TryRegisterAddon(out this.reShadeAddonInterface)) + { + resizeBuffersDelegate = this.AsReShadeAddonResizeBuffersDetour; - 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; + 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 + + if (resizeBuffersDelegate is null) { resizeBuffersDelegate = this.AsHookResizeBuffersDetour; dxgiPresentDelegate = this.PresentDetour; diff --git a/Dalamud/Interface/Internal/ReShadeHandling/ReShadeAddonInterface.Exports.cs b/Dalamud/Interface/Internal/ReShadeHandling/ReShadeAddonInterface.Exports.cs index 46d3cc1af..60bbc37cd 100644 --- a/Dalamud/Interface/Internal/ReShadeHandling/ReShadeAddonInterface.Exports.cs +++ b/Dalamud/Interface/Internal/ReShadeHandling/ReShadeAddonInterface.Exports.cs @@ -30,8 +30,35 @@ internal sealed unsafe partial class ReShadeAddonInterface !GetProcAddressInto(m, nameof(e.ReShadeUnregisterEvent), &e.ReShadeUnregisterEvent)) continue; + fixed (void* pwszFile = m.FileName) + fixed (Guid* pguid = &WINTRUST_ACTION_GENERIC_VERIFY_V2) + { + var wtfi = new WINTRUST_FILE_INFO + { + cbStruct = (uint)sizeof(WINTRUST_FILE_INFO), + pcwszFilePath = (ushort*)pwszFile, + hFile = default, + pgKnownSubject = null, + }; + var wtd = new WINTRUST_DATA + { + cbStruct = (uint)sizeof(WINTRUST_DATA), + pPolicyCallbackData = null, + pSIPClientData = null, + dwUIChoice = WTD.WTD_UI_NONE, + fdwRevocationChecks = WTD.WTD_REVOKE_NONE, + dwUnionChoice = WTD.WTD_STATEACTION_VERIFY, + hWVTStateData = default, + pwszURLReference = null, + dwUIContext = 0, + pFile = &wtfi, + }; + ReShadeHasSignature = WinVerifyTrust(default, pguid, &wtd) != TRUST.TRUST_E_NOSIGNATURE; + } + ReShadeModule = m; Exports = e; + return; } @@ -49,6 +76,10 @@ internal sealed unsafe partial class ReShadeAddonInterface /// Gets the active ReShade module. public static ProcessModule? ReShadeModule { 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 ReShadeHasSignature { get; private set; } + private struct ExportsStruct { public delegate* unmanaged ReShadeRegisterAddon;