From 733f79b5cbc9ab85db909c6fd9ec888f6f5d0ee9 Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Sat, 13 Jul 2024 20:13:04 +0900 Subject: [PATCH] *shade: Support variations in offset for underlying objects --- .../ImGuiBackend/Helpers/ReShadePeeler.cs | 139 ++++++++++++++++-- 1 file changed, 126 insertions(+), 13 deletions(-) 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); + } }