Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions header/D3D9Hooks.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
13 changes: 9 additions & 4 deletions modules/TextureClient.ixx
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
16 changes: 16 additions & 0 deletions source/D3D9Hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<TextureClient*> 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 {
Expand Down
15 changes: 10 additions & 5 deletions source/dll_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import TextureClient;

void ExitInstance();
void ExitInstance(bool is_unloading);
void InitInstance(HINSTANCE hModule);

namespace {
Expand Down Expand Up @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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)
Expand Down
Loading