From 5c6ed9e9bfb19fef747e1e2596d5ec8893a06197 Mon Sep 17 00:00:00 2001 From: Flashmyname Date: Tue, 1 Sep 2026 09:38:06 +0200 Subject: [PATCH 1/3] Fix pending TXD 'removed' events being swallowed by a later 'added' Part of #4956 --- Client/game_sa/CRenderWareSA.ShaderSupport.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Client/game_sa/CRenderWareSA.ShaderSupport.cpp b/Client/game_sa/CRenderWareSA.ShaderSupport.cpp index aa2927788c9..50b3a228d93 100644 --- a/Client/game_sa/CRenderWareSA.ShaderSupport.cpp +++ b/Client/game_sa/CRenderWareSA.ShaderSupport.cpp @@ -77,8 +77,7 @@ static CMappedArray ms_txdStreamEventList; __declspec(noinline) void _cdecl OnStreamingAddedTxd(DWORD dwTxdId) { ushort usTxdId = (ushort)dwTxdId; - // Ensure there are no previous events for this txd - ms_txdStreamEventList.remove(STxdStreamEvent(false, usTxdId)); + // Drop a duplicate 'added' only - a pending 'removed' still has to reach the watch ms_txdStreamEventList.remove(STxdStreamEvent(true, usTxdId)); // Append 'added' ms_txdStreamEventList.push_back(STxdStreamEvent(true, usTxdId)); From 1940d6efcffcfb46b796cb1a9abf250aa709c977 Mon Sep 17 00:00:00 2001 From: Flashmyname Date: Tue, 1 Sep 2026 09:38:07 +0200 Subject: [PATCH 2/3] Fix HOOK_CTxdStore_RemoveTxd reading the txd id from the wrong place The hook read the id from ESI, which at 0x731E90 still holds whatever the caller left there. The function is __cdecl with the id at [esp+4]; read that, and stop subtracting the TXD base since it is already a raw id. Part of #4956 --- Client/game_sa/CRenderWareSA.ShaderSupport.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Client/game_sa/CRenderWareSA.ShaderSupport.cpp b/Client/game_sa/CRenderWareSA.ShaderSupport.cpp index 50b3a228d93..c4308c0ef64 100644 --- a/Client/game_sa/CRenderWareSA.ShaderSupport.cpp +++ b/Client/game_sa/CRenderWareSA.ShaderSupport.cpp @@ -112,7 +112,7 @@ static void __declspec(naked) HOOK_CTxdStore_SetupTxdParent() //////////////////////////////////////////////////////////////// __declspec(noinline) void _cdecl OnStreamingRemoveTxd(DWORD dwTxdId) { - ushort usTxdId = (ushort)dwTxdId - pGame->GetBaseIDforTXD(); + ushort usTxdId = (ushort)dwTxdId; // Ensure there are no previous events for this txd ms_txdStreamEventList.remove(STxdStreamEvent(true, usTxdId)); ms_txdStreamEventList.remove(STxdStreamEvent(false, usTxdId)); @@ -130,9 +130,9 @@ static void __declspec(naked) HOOK_CTxdStore_RemoveTxd() { // Hooked from 731E90 6 bytes - // esi - txd id + 20000 + // __cdecl txd id - esi still holds the caller's value at this address pushad - push esi + push [esp+32+4*1] call OnStreamingRemoveTxd add esp, 4 popad From 27c7e07d2d05872271dcc2f67644a9c9dd61026d Mon Sep 17 00:00:00 2001 From: Flashmyname Date: Tue, 1 Sep 2026 09:38:07 +0200 Subject: [PATCH 3/3] Reclaim STexNameInfo entries once nothing refers to them m_AllTextureList entries were created the first time a texture name was seen and never freed. Delete one once both of its reference lists are empty; together they cover every reference that can exist. Part of #4956 --- .../game_sa/CRenderWareSA.ShaderMatching.cpp | 21 +++++++++++++++++++ Client/game_sa/CRenderWareSA.ShaderMatching.h | 1 + 2 files changed, 22 insertions(+) diff --git a/Client/game_sa/CRenderWareSA.ShaderMatching.cpp b/Client/game_sa/CRenderWareSA.ShaderMatching.cpp index 8af89402d86..753156923e2 100644 --- a/Client/game_sa/CRenderWareSA.ShaderMatching.cpp +++ b/Client/game_sa/CRenderWareSA.ShaderMatching.cpp @@ -112,6 +112,25 @@ void CMatchChannelManager::RemoveTexture(STexInfo* pTexInfo) dassert(MapContains(pTexNameInfo->usedByTexInfoList, pTexInfo)); MapRemove(pTexNameInfo->usedByTexInfoList, pTexInfo); pTexInfo->pAssociatedTexNameInfo = NULL; + + MaybeDeleteTexNameInfo(pTexNameInfo); +} + +////////////////////////////////////////////////////////////////// +// +// CMatchChannelManager::MaybeDeleteTexNameInfo +// +// Both lists together are every reference that can exist: the texinfos +// pointing back at it, and the channels holding it in m_MatchedTextureList. +// +////////////////////////////////////////////////////////////////// +void CMatchChannelManager::MaybeDeleteTexNameInfo(STexNameInfo* pTexNameInfo) +{ + if (pTexNameInfo->usedByTexInfoList.empty() && pTexNameInfo->matchChannelList.empty()) + { + MapRemove(m_AllTextureList, pTexNameInfo->strTextureName); + delete pTexNameInfo; + } } ////////////////////////////////////////////////////////////////// @@ -614,6 +633,7 @@ void CMatchChannelManager::ProcessRematchTexturesQueue() pChannel->RemoveTexture(pTexNameInfo); MapRemove(pTexNameInfo->matchChannelList, pChannel); pTexNameInfo->ResetReplacementResults(); // Do this here as it won't get picked up in RecalcEverything now + MaybeDeleteTexNameInfo(pTexNameInfo); } // Rematch against texture list @@ -822,6 +842,7 @@ void CMatchChannelManager::DeleteChannel(CMatchChannel* pChannel) // Reset shader matches now as this channel is going pTexNameInfo->ResetReplacementResults(); + MaybeDeleteTexNameInfo(pTexNameInfo); } #ifdef SHADER_DEBUG_CHECKS diff --git a/Client/game_sa/CRenderWareSA.ShaderMatching.h b/Client/game_sa/CRenderWareSA.ShaderMatching.h index dceb1e3f124..0f31c961509 100644 --- a/Client/game_sa/CRenderWareSA.ShaderMatching.h +++ b/Client/game_sa/CRenderWareSA.ShaderMatching.h @@ -292,6 +292,7 @@ class CMatchChannelManager STexShaderReplacement* UpdateTexShaderReplacement(STexNameInfo* pTexNameInfo, CClientEntityBase* pClientEntity, int iEntityType); void UpdateTexShaderReplacementNoEntity(STexNameInfo* pTexNameInfo, STexShaderReplacement& texNoEntityShader, int iEntityType); + void MaybeDeleteTexNameInfo(STexNameInfo* pTexNameInfo); void FinalizeLayers(SShaderInfoLayers& shaderLayers); bool m_bChangesPending;