*shade: Support variations in offset for underlying objects

This commit is contained in:
Soreepeong 2024-07-13 20:13:04 +09:00
parent 184463a056
commit 733f79b5cb

View file

@ -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
/// <returns><c>true</c> if peeled.</returns>
public static bool PeelSwapChain<T>(ComPtr<T>* comptr)
where T : unmanaged, IDXGISwapChain.Interface =>
PeelIUnknown(comptr, 0x10);
PeelIUnknown(comptr, sizeof(IDXGISwapChain.Vtbl<IDXGISwapChain>));
/// <summary>
/// Peels <see cref="ID3D12Device"/> if it is wrapped by ReShade.
@ -36,7 +37,7 @@ internal static unsafe class ReShadePeeler
/// <returns><c>true</c> if peeled.</returns>
public static bool PeelD3D12Device<T>(ComPtr<T>* comptr)
where T : unmanaged, ID3D12Device.Interface =>
PeelIUnknown(comptr, 0x10);
PeelIUnknown(comptr, sizeof(ID3D12Device.Vtbl<ID3D12Device>));
/// <summary>
/// Peels <see cref="ID3D12CommandQueue"/> if it is wrapped by ReShade.
@ -46,20 +47,58 @@ internal static unsafe class ReShadePeeler
/// <returns><c>true</c> if peeled.</returns>
public static bool PeelD3D12CommandQueue<T>(ComPtr<T>* comptr)
where T : unmanaged, ID3D12CommandQueue.Interface =>
PeelIUnknown(comptr, 0x10);
PeelIUnknown(comptr, sizeof(ID3D12Device.Vtbl<ID3D12Device>));
private static bool PeelIUnknown<T>(ComPtr<T>* comptr, nint offset)
private static bool PeelIUnknown<T>(ComPtr<T>* 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>(*(IUnknown**)((nint)comptr->Get() + offset));
using var comptr2 = default(ComPtr<T>);
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<IUnknown>);
punk.Attach((IUnknown*)pObjectBehind);
// Is the IUnknown object also the type we want?
using var comptr2 = default(ComPtr<T>);
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>(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);
}
}