Skip to content

Commit 92c6b11

Browse files
henderkesclaude
andcommitted
Make D3D9 detours fall through after unhook to avoid teardown crash
Keep the o_* trampoline pointers valid on RemoveAllD3D9Hooks and add a g_unhooked flag each detour checks after calling the original, so a surviving vtable patch invoked during/after teardown just returns the real result instead of touching freed TextureClient state. Tear down off the loader lock via a new Shutdown export and skip teardown on process exit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 2b87e0e commit 92c6b11

2 files changed

Lines changed: 42 additions & 16 deletions

File tree

source/D3D9Hooks.cpp

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include "D3D9Hooks.h"
33
#include "MinHook.h"
44

5+
#include <atomic>
56
#include <mutex>
67
#include <unordered_map>
78
#include <vector>
@@ -72,6 +73,9 @@ namespace {
7273

7374
std::vector<void*> g_hooked_targets;
7475

76+
// Set when teardown begins; a detour still reached after this just calls the original.
77+
std::atomic<bool> g_unhooked{false};
78+
7579
// device -> owning TextureClient
7680
std::mutex g_devices_mutex;
7781
std::unordered_map<IDirect3DDevice9*, TextureClient*> g_devices;
@@ -174,6 +178,7 @@ namespace {
174178
D3DPRESENT_PARAMETERS* pPresentationParameters, IDirect3DDevice9** ppReturnedDeviceInterface)
175179
{
176180
const HRESULT hr = o_CreateDevice(self, Adapter, DeviceType, hFocusWindow, BehaviorFlags, pPresentationParameters, ppReturnedDeviceInterface);
181+
if (g_unhooked) return hr;
177182
if (SUCCEEDED(hr) && ppReturnedDeviceInterface && *ppReturnedDeviceInterface) {
178183
OnDeviceCreated(*ppReturnedDeviceInterface);
179184
}
@@ -184,6 +189,7 @@ namespace {
184189
D3DPRESENT_PARAMETERS* pPresentationParameters, D3DDISPLAYMODEEX* pFullscreenDisplayMode, IDirect3DDevice9Ex** ppReturnedDeviceInterface)
185190
{
186191
const HRESULT hr = o_CreateDeviceEx(self, Adapter, DeviceType, hFocusWindow, BehaviorFlags, pPresentationParameters, pFullscreenDisplayMode, ppReturnedDeviceInterface);
192+
if (g_unhooked) return hr;
187193
if (SUCCEEDED(hr) && ppReturnedDeviceInterface && *ppReturnedDeviceInterface) {
188194
OnDeviceCreated(*ppReturnedDeviceInterface);
189195
}
@@ -193,6 +199,7 @@ namespace {
193199
ULONG STDMETHODCALLTYPE h_DeviceRelease(IDirect3DDevice9* self)
194200
{
195201
const ULONG count = o_DeviceRelease(self);
202+
if (g_unhooked) return count;
196203
if (count == 0) {
197204
TextureClient* client = nullptr;
198205
{
@@ -212,6 +219,7 @@ namespace {
212219
IDirect3DTexture9** ppTexture, HANDLE* pSharedHandle)
213220
{
214221
const HRESULT hr = o_CreateTexture(self, Width, Height, Levels, Usage, Format, Pool, ppTexture, pSharedHandle);
222+
if (g_unhooked) return hr;
215223
if (SUCCEEDED(hr) && ppTexture && *ppTexture) {
216224
InstallTextureReleaseHook(*ppTexture, TexType::Tex2D);
217225
if (auto* client = ClientFor(self))
@@ -224,6 +232,7 @@ namespace {
224232
IDirect3DVolumeTexture9** ppVolumeTexture, HANDLE* pSharedHandle)
225233
{
226234
const HRESULT hr = o_CreateVolumeTexture(self, Width, Height, Depth, Levels, Usage, Format, Pool, ppVolumeTexture, pSharedHandle);
235+
if (g_unhooked) return hr;
227236
if (SUCCEEDED(hr) && ppVolumeTexture && *ppVolumeTexture) {
228237
InstallTextureReleaseHook(*ppVolumeTexture, TexType::Volume);
229238
if (auto* client = ClientFor(self))
@@ -236,6 +245,7 @@ namespace {
236245
IDirect3DCubeTexture9** ppCubeTexture, HANDLE* pSharedHandle)
237246
{
238247
const HRESULT hr = o_CreateCubeTexture(self, EdgeLength, Levels, Usage, Format, Pool, ppCubeTexture, pSharedHandle);
248+
if (g_unhooked) return hr;
239249
if (SUCCEEDED(hr) && ppCubeTexture && *ppCubeTexture) {
240250
InstallTextureReleaseHook(*ppCubeTexture, TexType::Cube);
241251
if (auto* client = ClientFor(self))
@@ -248,6 +258,7 @@ namespace {
248258
{
249259
// Pass the real textures through, then re-evaluate mods on the changed destination.
250260
const HRESULT hr = o_UpdateTexture(self, pSourceTexture, pDestinationTexture);
261+
if (g_unhooked) return hr;
251262
if (SUCCEEDED(hr)) {
252263
if (auto* client = ClientFor(self))
253264
client->OnUpdateTexture(pSourceTexture, pDestinationTexture);
@@ -257,13 +268,15 @@ namespace {
257268

258269
HRESULT STDMETHODCALLTYPE h_BeginScene(IDirect3DDevice9* self)
259270
{
271+
if (g_unhooked) return o_BeginScene(self);
260272
if (auto* client = ClientFor(self))
261273
client->OnBeginScene();
262274
return o_BeginScene(self);
263275
}
264276

265277
HRESULT STDMETHODCALLTYPE h_SetTexture(IDirect3DDevice9* self, DWORD Stage, IDirect3DBaseTexture9* pTexture)
266278
{
279+
if (g_unhooked) return o_SetTexture(self, Stage, pTexture);
267280
IDirect3DBaseTexture9* bind = pTexture;
268281
if (auto* client = ClientFor(self))
269282
bind = client->ResolveBinding(pTexture); // substitute the fake when modded
@@ -273,6 +286,7 @@ namespace {
273286
HRESULT STDMETHODCALLTYPE h_GetTexture(IDirect3DDevice9* self, DWORD Stage, IDirect3DBaseTexture9** ppTexture)
274287
{
275288
const HRESULT hr = o_GetTexture(self, Stage, ppTexture);
289+
if (g_unhooked) return hr;
276290
if (SUCCEEDED(hr) && ppTexture && *ppTexture) {
277291
if (auto* client = ClientFor(self)) {
278292
if (auto* original = client->ResolveOriginalFromFake(*ppTexture)) {
@@ -295,20 +309,23 @@ namespace {
295309
ULONG STDMETHODCALLTYPE h_Tex2DRelease(IUnknown* self)
296310
{
297311
const ULONG count = o_Tex2DRelease(self);
312+
if (g_unhooked) return count;
298313
ReleaseTextureCleanup(self, count);
299314
return count;
300315
}
301316

302317
ULONG STDMETHODCALLTYPE h_VolumeRelease(IUnknown* self)
303318
{
304319
const ULONG count = o_VolumeRelease(self);
320+
if (g_unhooked) return count;
305321
ReleaseTextureCleanup(self, count);
306322
return count;
307323
}
308324

309325
ULONG STDMETHODCALLTYPE h_CubeRelease(IUnknown* self)
310326
{
311327
const ULONG count = o_CubeRelease(self);
328+
if (g_unhooked) return count;
312329
ReleaseTextureCleanup(self, count);
313330
return count;
314331
}
@@ -317,6 +334,7 @@ namespace {
317334
void InstallD3D9Hooks(IDirect3D9* d3d9, const bool is_ex)
318335
{
319336
if (d3d9 == nullptr || g_d3d9_hooks_installed) return;
337+
g_unhooked = false; // re-arm in case a prior teardown left the flag set
320338
void** vt = GetVTable(d3d9);
321339
Hook(vt[kIDirect3D9_CreateDevice], &h_CreateDevice, reinterpret_cast<void**>(&o_CreateDevice));
322340
if (is_ex) {
@@ -333,12 +351,16 @@ bool RegisterExistingDevice(IDirect3DDevice9* device)
333351
if (g_devices.contains(device)) return false; // already known
334352
if (!g_devices.empty()) return false; // gMod supports a single device at a time
335353
}
354+
g_unhooked = false; // re-arm in case a prior teardown left the flag set
336355
OnDeviceCreated(device); // installs device hooks + TextureClient (idempotent)
337356
return ClientFor(device) != nullptr;
338357
}
339358

340359
void RemoveAllD3D9Hooks()
341360
{
361+
// Signal first: a detour reached after this (e.g. a surviving patch) falls through.
362+
g_unhooked = true;
363+
342364
for (void* target : g_hooked_targets) {
343365
MH_DisableHook(target);
344366
MH_RemoveHook(target);
@@ -351,19 +373,7 @@ void RemoveAllD3D9Hooks()
351373
g_volume_release_installed = false;
352374
g_cube_release_installed = false;
353375

354-
o_CreateDevice = nullptr;
355-
o_CreateDeviceEx = nullptr;
356-
o_DeviceRelease = nullptr;
357-
o_CreateTexture = nullptr;
358-
o_CreateVolumeTexture = nullptr;
359-
o_CreateCubeTexture = nullptr;
360-
o_UpdateTexture = nullptr;
361-
o_BeginScene = nullptr;
362-
o_SetTexture = nullptr;
363-
o_GetTexture = nullptr;
364-
o_Tex2DRelease = nullptr;
365-
o_VolumeRelease = nullptr;
366-
o_CubeRelease = nullptr;
376+
// Leave the o_* trampoline pointers intact so a surviving detour can still call through.
367377
}
368378

369379
void DestroyAllTextureClients()

source/dll_main.cpp

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <Psapi.h>
33
#include "MinHook.h"
44
#include "D3D9Hooks.h"
5+
#include <atomic>
56

67
import TextureClient;
78

@@ -194,9 +195,12 @@ BOOL WINAPI DllMain(HINSTANCE hModule, DWORD ul_reason_for_call, LPVOID lpReserv
194195
break;
195196
}
196197
case DLL_PROCESS_DETACH: {
197-
// lpReserved == nullptr means FreeLibrary (device still alive); non-null
198-
// means process exit, where d3d9/the device may already be gone.
199-
ExitInstance(lpReserved == nullptr);
198+
// Process exit (lpReserved != nullptr): other threads are gone and the OS
199+
// reclaims everything, so touching MinHook/the device here only risks a hang.
200+
if (lpReserved != nullptr)
201+
break;
202+
// FreeLibrary unload: the device is still live, so tear down cleanly.
203+
ExitInstance(true);
200204
break;
201205
}
202206
default: break;
@@ -314,8 +318,20 @@ extern "C" __declspec(dllexport) int __cdecl GetFiles(wchar_t* buffer, const siz
314318
}
315319
}
316320

321+
// Optional clean-shutdown entry: call this from the host (on a normal thread) BEFORE
322+
// FreeLibrary so teardown runs off the loader lock, where MinHook can safely suspend
323+
// the render thread. A later FreeLibrary then unloads with nothing left to undo.
324+
extern "C" __declspec(dllexport) void __cdecl Shutdown()
325+
{
326+
ExitInstance(true);
327+
}
328+
317329
void ExitInstance(bool is_unloading)
318330
{
331+
// Teardown must run exactly once, whether reached via Shutdown() or DllMain detach.
332+
static std::atomic<bool> torn_down{false};
333+
if (torn_down.exchange(true)) return;
334+
319335
DISABLE_HOOK(GetProcAddress_fn);
320336

321337
// Revert every D3D9 vtable hook so the original objects are left pristine.

0 commit comments

Comments
 (0)