Skip to content

Commit db6445f

Browse files
committed
Texmod: free gMod.dll when unused or on shutdown
We previously LoadLibrary'd gMod.dll but never freed it, leaking the module (and leaving its D3D9 vtable hooks installed) for the life of the process. FreeLibrary is gMod's clean shutdown path: its DllMain detach runs RemoveAllD3D9Hooks + MH_Uninitialize, reverting every hook. Track whether we own the reference (gmodLoadedByUs) so we only free a gMod we loaded ourselves - a gMod injected as the d3d9 proxy belongs to the game and must never be freed. ShutdownGMod now clears the resolved exports and frees the dll; it runs on toolbox close and when the last texture pack is removed (deferred to Update, off the draw pass). InitGMod no longer loads our own copy when no packs are configured, so an unloaded gMod stays unloaded until a pack is added again.
1 parent b8a7d7b commit db6445f

1 file changed

Lines changed: 47 additions & 8 deletions

File tree

GWToolboxdll/Modules/TexmodModule.cpp

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,19 @@ namespace {
8888
// =========================================================================
8989

9090
HMODULE gmodDll = nullptr;
91+
// True only when this module called LoadLibrary on gmodDll, i.e. we own the
92+
// reference and must FreeLibrary it on teardown. False when gMod was already
93+
// present (e.g. injected as the d3d9.dll proxy) - that copy belongs to the game.
94+
bool gmodLoadedByUs = false;
9195

9296
std::vector<TexturePackEntry> packs;
9397

9498
bool gmodReady = false;
9599
std::string statusMessage;
96100
bool gmodLoadAttempted = false;
101+
// Set when gMod is no longer needed (last pack removed); the unload is performed
102+
// from Update() rather than the draw pass.
103+
bool gmodUnloadRequested = false;
97104

98105
constexpr const char* INI_SECTION = "TexmodModule";
99106
constexpr const char* INI_PACK_COUNT = "PackCount";
@@ -323,6 +330,7 @@ namespace {
323330
HMODULE h = GetModuleHandleW(name);
324331
if (!h) continue;
325332
gmodDll = h;
333+
gmodLoadedByUs = false; // already in the process; we don't own this reference
326334
if (!ResolveTextureClientFunctions()) {
327335
gmodDll = nullptr;
328336
continue;
@@ -334,6 +342,12 @@ namespace {
334342
return true;
335343
}
336344

345+
// Only load our own copy of gMod when there is a pack to serve. With no packs
346+
// there is nothing to mod, so we leave gMod unloaded rather than have it hook
347+
// the device for nothing. (A gMod already injected as the d3d9 proxy was
348+
// adopted above regardless of this.)
349+
if (packs.empty()) return false;
350+
337351
if (gmodLoadAttempted) {
338352
// statusMessage = "Error: gMod initialization already attempted and failed. Please fix the issue and click Retry.";
339353
return false;
@@ -351,11 +365,13 @@ namespace {
351365
statusMessage = "Error: Could not load gMod.dll. Make sure it is next to GWToolbox.dll.";
352366
return false;
353367
}
368+
gmodLoadedByUs = true; // we own this reference and must FreeLibrary it on teardown
354369

355370
// 3. Resolve TextureClient shim exports.
356371
if (!ResolveTextureClientFunctions()) {
357372
FreeLibrary(gmodDll);
358373
gmodDll = nullptr;
374+
gmodLoadedByUs = false;
359375
return false;
360376
}
361377
pfnSetDevice(GuildWars_IDirect3DDevice9_Instance);
@@ -479,13 +495,11 @@ namespace {
479495

480496
void ShutdownGMod()
481497
{
482-
if (!gmodReady) return;
483-
484-
// Unload synchronously: on teardown the worker threads are stopping, so a
485-
// queued ApplyLoadOrder might never run. Holding apply_mutex waits out any
486-
// in-flight worker reconcile; bumping the generation makes queued/running
487-
// ones skip; then we remove everything here, last, so nothing reloads after.
488-
{
498+
if (gmodReady) {
499+
// Unload synchronously: on teardown the worker threads are stopping, so a
500+
// queued ApplyLoadOrder might never run. Holding apply_mutex waits out any
501+
// in-flight worker reconcile; bumping the generation makes queued/running
502+
// ones skip; then we remove everything here, last, so nothing reloads after.
489503
std::lock_guard lk(apply_mutex);
490504
++apply_generation;
491505
for (auto& pack : packs) pack.loaded = false;
@@ -495,6 +509,21 @@ namespace {
495509
}
496510

497511
gmodReady = false;
512+
pfnAddFile = nullptr;
513+
pfnRemoveFile = nullptr;
514+
pfnGetFiles = nullptr;
515+
pfnSetDevice = nullptr;
516+
517+
// Free the dll only if we loaded it. gMod's DllMain(DLL_PROCESS_DETACH) reverts
518+
// all its D3D9 vtable hooks (RemoveAllD3D9Hooks), so this is its clean shutdown
519+
// path. A gMod injected as the d3d9 proxy is owned by the game - never free it.
520+
if (gmodLoadedByUs && gmodDll) {
521+
FreeLibrary(gmodDll);
522+
}
523+
gmodDll = nullptr;
524+
gmodLoadedByUs = false;
525+
gmodLoadAttempted = false; // allow a fresh load later (e.g. when a pack is added)
526+
gmodLocalVersionChecked = false;
498527
}
499528

500529
// =========================================================================
@@ -856,6 +885,8 @@ namespace {
856885
if (ImGui::ConfirmButton(ICON_FA_TRASH, &del_bool)) {
857886
if (pack.loaded) UnloadTexturePack(pack.path);
858887
packs.erase(packs.begin() + i);
888+
// No packs left to serve: drop gMod so it stops hooking the device.
889+
if (packs.empty()) gmodUnloadRequested = true;
859890
ImGui::PopID();
860891
break;
861892
}
@@ -1142,6 +1173,14 @@ void TexmodModule::Update(float)
11421173
// Keep the D3D9 CreateTexture capture hook matched to the recording toggle.
11431174
SetTextureCapture(recording);
11441175

1176+
// Unload gMod once nothing needs it (requested when the last pack was removed).
1177+
if (gmodUnloadRequested) {
1178+
gmodUnloadRequested = false;
1179+
// Only unload what we own; an injected proxy is left to the game. ShutdownGMod
1180+
// already no-ops the FreeLibrary in that case, but skip the pack teardown too.
1181+
if (gmodLoadedByUs) ShutdownGMod();
1182+
}
1183+
11451184
if (gmodReady) return;
11461185
InitGMod();
11471186
}
@@ -1154,7 +1193,7 @@ void TexmodModule::Draw(IDirect3DDevice9*)
11541193
void TexmodModule::Terminate()
11551194
{
11561195
TeardownTextureCapture();
1157-
ShutdownGMod(); // synchronously unloads every pack from gMod
1196+
ShutdownGMod(); // unloads every pack and frees gMod.dll if we loaded it
11581197
ToolboxModule::Terminate();
11591198
}
11601199

0 commit comments

Comments
 (0)