*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;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using TerraFX.Interop.DirectX; using TerraFX.Interop.DirectX;
@ -26,7 +27,7 @@ internal static unsafe class ReShadePeeler
/// <returns><c>true</c> if peeled.</returns> /// <returns><c>true</c> if peeled.</returns>
public static bool PeelSwapChain<T>(ComPtr<T>* comptr) public static bool PeelSwapChain<T>(ComPtr<T>* comptr)
where T : unmanaged, IDXGISwapChain.Interface => where T : unmanaged, IDXGISwapChain.Interface =>
PeelIUnknown(comptr, 0x10); PeelIUnknown(comptr, sizeof(IDXGISwapChain.Vtbl<IDXGISwapChain>));
/// <summary> /// <summary>
/// Peels <see cref="ID3D12Device"/> if it is wrapped by ReShade. /// 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> /// <returns><c>true</c> if peeled.</returns>
public static bool PeelD3D12Device<T>(ComPtr<T>* comptr) public static bool PeelD3D12Device<T>(ComPtr<T>* comptr)
where T : unmanaged, ID3D12Device.Interface => where T : unmanaged, ID3D12Device.Interface =>
PeelIUnknown(comptr, 0x10); PeelIUnknown(comptr, sizeof(ID3D12Device.Vtbl<ID3D12Device>));
/// <summary> /// <summary>
/// Peels <see cref="ID3D12CommandQueue"/> if it is wrapped by ReShade. /// 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> /// <returns><c>true</c> if peeled.</returns>
public static bool PeelD3D12CommandQueue<T>(ComPtr<T>* comptr) public static bool PeelD3D12CommandQueue<T>(ComPtr<T>* comptr)
where T : unmanaged, ID3D12CommandQueue.Interface => 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 where T : unmanaged, IUnknown.Interface
{ {
if (comptr->Get() == null || !IsReShadedComObject(comptr->Get())) var changed = false;
return 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)); // Is the thing directly pointed from the address an actual something in the memory?
using var comptr2 = default(ComPtr<T>); if (!IsValidReadableMemoryAddress(ppObjectBehind, 8))
if (punk.As(&comptr2).FAILED) continue;
return false;
comptr2.Swap(comptr); var pObjectBehind = *(nint*)ppObjectBehind;
return true;
// 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) private static bool BelongsInReShadeDll(nint ptr)
@ -115,12 +154,21 @@ internal static unsafe class ReShadePeeler
private static bool IsReShadedComObject<T>(T* obj) private static bool IsReShadedComObject<T>(T* obj)
where T : unmanaged, IUnknown.Interface where T : unmanaged, IUnknown.Interface
{ {
if (!IsValidReadableMemoryAddress((nint)obj, sizeof(nint)))
return false;
try try
{ {
var vtbl = (nint**)Marshal.ReadIntPtr((nint)obj); var vtbl = (nint**)Marshal.ReadIntPtr((nint)obj);
if (!IsValidReadableMemoryAddress((nint)vtbl, sizeof(nint) * 3))
return false;
for (var i = 0; i < 3; i++) 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; return false;
} }
@ -131,4 +179,69 @@ internal static unsafe class ReShadePeeler
return false; 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);
}
} }