diff --git a/CMakeLists.txt b/CMakeLists.txt index b6cdca8..3bf03ab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,9 +10,9 @@ if(NOT(CMAKE_SIZEOF_VOID_P EQUAL 4)) endif() set(VERSION_MAJOR 1) -set(VERSION_MINOR 8) +set(VERSION_MINOR 9) set(VERSION_PATCH 0) -set(VERSION_TWEAK 2) +set(VERSION_TWEAK 0) set(VERSION_RC "${CMAKE_CURRENT_BINARY_DIR}/version.rc") configure_file("${CMAKE_CURRENT_SOURCE_DIR}/version.rc.in" "${VERSION_RC}" @ONLY) diff --git a/header/D3D9Hooks.h b/header/D3D9Hooks.h index 7d59f43..6718cdb 100644 --- a/header/D3D9Hooks.h +++ b/header/D3D9Hooks.h @@ -6,4 +6,8 @@ void InstallD3D9Hooks(IDirect3D9* d3d9, bool is_ex); void RemoveAllD3D9Hooks(); +// Delete every per-device TextureClient, releasing its replacement textures. Only safe +// on a real FreeLibrary (device still alive), not during process termination. +void DestroyAllTextureClients(); + bool RegisterExistingDevice(IDirect3DDevice9* device); diff --git a/modules/TextureClient.ixx b/modules/TextureClient.ixx index 4cff317..6d41f39 100644 --- a/modules/TextureClient.ixx +++ b/modules/TextureClient.ixx @@ -159,10 +159,15 @@ TextureClient::~TextureClient() std::lock_guard lk(registry_mutex); shutting_down = true; // the texture Release hook now no-ops for our textures - // Drop side-state but don't Release the textures: the game already released - // its originals (and our fakes with them), and the device may be gone. - for (const auto state : fakes | std::views::values) { - delete state; + // Release replacements we still own (still partnered) so they aren't leaked when + // torn down with the device alive (FreeLibrary). Orphaned fakes were already + // released; a device-release teardown leaves the maps empty, so this is a no-op. + for (const auto fake : fakes | std::views::values) { + if (fake->partner != nullptr) { + fake->partner->partner = nullptr; // detach the original's back-pointer + if (fake->real) fake->real->Release(); + } + delete fake; } fakes.clear(); for (const auto state : originals | std::views::values) { diff --git a/source/D3D9Hooks.cpp b/source/D3D9Hooks.cpp index 3606a57..928d80a 100644 --- a/source/D3D9Hooks.cpp +++ b/source/D3D9Hooks.cpp @@ -366,6 +366,22 @@ void RemoveAllD3D9Hooks() o_CubeRelease = nullptr; } +void DestroyAllTextureClients() +{ + // Collect under the lock, delete outside it (~TextureClient takes its own locks). + std::vector clients; + { + std::lock_guard lk(g_devices_mutex); + for (const auto& entry : g_devices) { + clients.push_back(entry.second); + } + g_devices.clear(); + } + for (auto* client : clients) { + delete client; + } +} + // All three read state->real directly; gMod never swaps the underlying resource. namespace { diff --git a/source/dll_main.cpp b/source/dll_main.cpp index ad5a588..a98fac2 100644 --- a/source/dll_main.cpp +++ b/source/dll_main.cpp @@ -5,7 +5,7 @@ import TextureClient; -void ExitInstance(); +void ExitInstance(bool is_unloading); void InitInstance(HINSTANCE hModule); namespace { @@ -179,8 +179,6 @@ HRESULT APIENTRY Direct3DCreate9Ex(UINT SDKVersion, IDirect3D9Ex** ppD3D) BOOL WINAPI DllMain(HINSTANCE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { - UNREFERENCED_PARAMETER(lpReserved); - switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: { #ifdef _DEBUG @@ -196,7 +194,9 @@ BOOL WINAPI DllMain(HINSTANCE hModule, DWORD ul_reason_for_call, LPVOID lpReserv break; } case DLL_PROCESS_DETACH: { - ExitInstance(); + // lpReserved == nullptr means FreeLibrary (device still alive); non-null + // means process exit, where d3d9/the device may already be gone. + ExitInstance(lpReserved == nullptr); break; } default: break; @@ -314,13 +314,18 @@ extern "C" __declspec(dllexport) int __cdecl GetFiles(wchar_t* buffer, const siz } } -void ExitInstance() +void ExitInstance(bool is_unloading) { DISABLE_HOOK(GetProcAddress_fn); // Revert every D3D9 vtable hook so the original objects are left pristine. RemoveAllD3D9Hooks(); + // On a real unload the device is still alive; release our replacement textures. + if (is_unloading) { + DestroyAllTextureClients(); + } + MH_Uninitialize(); if (gMod_Loaded_d3d9_Module_Handle)