diff --git a/Dalamud/Interface/ImGuiBackend/Helpers/ReShadePeeler.cs b/Dalamud/Interface/ImGuiBackend/Helpers/ReShadePeeler.cs
index c16584024..106da552f 100644
--- a/Dalamud/Interface/ImGuiBackend/Helpers/ReShadePeeler.cs
+++ b/Dalamud/Interface/ImGuiBackend/Helpers/ReShadePeeler.cs
@@ -1,5 +1,6 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using TerraFX.Interop.DirectX;
@@ -26,7 +27,7 @@ internal static unsafe class ReShadePeeler
/// true if peeled.
public static bool PeelSwapChain(ComPtr* comptr)
where T : unmanaged, IDXGISwapChain.Interface =>
- PeelIUnknown(comptr, 0x10);
+ PeelIUnknown(comptr, sizeof(IDXGISwapChain.Vtbl));
///
/// Peels if it is wrapped by ReShade.
@@ -36,7 +37,7 @@ internal static unsafe class ReShadePeeler
/// true if peeled.
public static bool PeelD3D12Device(ComPtr* comptr)
where T : unmanaged, ID3D12Device.Interface =>
- PeelIUnknown(comptr, 0x10);
+ PeelIUnknown(comptr, sizeof(ID3D12Device.Vtbl));
///
/// Peels if it is wrapped by ReShade.
@@ -46,20 +47,58 @@ internal static unsafe class ReShadePeeler
/// true if peeled.
public static bool PeelD3D12CommandQueue(ComPtr* comptr)
where T : unmanaged, ID3D12CommandQueue.Interface =>
- PeelIUnknown(comptr, 0x10);
+ PeelIUnknown(comptr, sizeof(ID3D12Device.Vtbl));
- private static bool PeelIUnknown(ComPtr* comptr, nint offset)
+ private static bool PeelIUnknown(ComPtr* comptr, nint vtblSize)
where T : unmanaged, IUnknown.Interface
{
- if (comptr->Get() == null || !IsReShadedComObject(comptr->Get()))
- return false;
+ 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 = 8; i <= 0x20; i += 8)
+ {
+ var ppObjectBehind = (nint)comptr->Get() + i;
- var punk = new ComPtr(*(IUnknown**)((nint)comptr->Get() + offset));
- using var comptr2 = default(ComPtr);
- if (punk.As(&comptr2).FAILED)
- return false;
- comptr2.Swap(comptr);
- return true;
+ // 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;
+ }
+ }
+
+ return changed;
}
private static bool BelongsInReShadeDll(nint ptr)
@@ -115,12 +154,21 @@ internal static unsafe class ReShadePeeler
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++)
{
- if (!BelongsInReShadeDll(Marshal.ReadIntPtr((nint)(vtbl + i))))
+ var pfn = Marshal.ReadIntPtr((nint)(vtbl + i));
+ if (!IsValidExecutableMemoryAddress(pfn, 1))
+ return false;
+ if (!BelongsInReShadeDll(pfn))
return false;
}
@@ -131,4 +179,69 @@ internal static unsafe class ReShadePeeler
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);
+ }
}