@@ -56,39 +56,45 @@ namespace
5656 using TextureSwapMap = std::unordered_map<RwTexture*, RwTexture*>;
5757
5858 // Content-hashed key avoids allocations
59- struct TextureNameHash
59+ struct SafeTextureName
6060 {
61- std::size_t operator ()(const char * s) const noexcept
62- {
63- if (!s)
64- return 0 ;
61+ char m_name[RW_TEXTURE_NAME_LENGTH];
62+ std::size_t m_len;
6563
66- std::size_t h = 2166136261u ;
67- for (std::size_t i = 0 ; i < RW_TEXTURE_NAME_LENGTH; ++i)
64+ SafeTextureName (const char * str)
65+ {
66+ if (str)
6867 {
69- const unsigned char c = static_cast <unsigned char >(s[i]);
70- if (c == 0 )
71- break ;
72- h ^= c;
73- h *= 16777619u ;
68+ m_len = strnlen (str, RW_TEXTURE_NAME_LENGTH);
69+ memcpy (m_name, str, m_len);
70+ if (m_len < RW_TEXTURE_NAME_LENGTH)
71+ m_name[m_len] = ' \0 ' ;
72+ }
73+ else
74+ {
75+ m_len = 0 ;
76+ m_name[0 ] = ' \0 ' ;
7477 }
75- return h;
7678 }
79+
80+ bool operator ==(const SafeTextureName& other) const noexcept { return m_len == other.m_len && memcmp (m_name, other.m_name , m_len) == 0 ; }
7781 };
7882
79- struct TextureNameEq
83+ struct SafeTextureNameHash
8084 {
81- bool operator ()(const char * a, const char * b ) const noexcept
85+ std:: size_t operator ()(const SafeTextureName& key ) const noexcept
8286 {
83- if (a == b)
84- return true ;
85- if (!a || !b)
86- return false ;
87- return strncmp (a, b, RW_TEXTURE_NAME_LENGTH) == 0 ;
87+ std::size_t h = 2166136261u ;
88+ for (std::size_t i = 0 ; i < key.m_len ; ++i)
89+ {
90+ h ^= static_cast <unsigned char >(key.m_name [i]);
91+ h *= 16777619u ;
92+ }
93+ return h;
8894 }
8995 };
9096
91- using TxdTextureMap = std::unordered_map<const char * , RwTexture*, TextureNameHash, TextureNameEq >;
97+ using TxdTextureMap = std::unordered_map<SafeTextureName , RwTexture*, SafeTextureNameHash >;
9298 struct ReplacementShaderKey
9399 {
94100 SReplacementTextures* pReplacement;
@@ -272,7 +278,17 @@ namespace
272278
273279 bool IsValid (RwTexDictionary* pCurrentTxd) const noexcept
274280 {
275- return pTxd != nullptr && pTxd == pCurrentTxd && pListHead == pCurrentTxd->textures .root .next ;
281+ if (pTxd == nullptr || pTxd != pCurrentTxd || pListHead != pCurrentTxd->textures .root .next )
282+ return false ;
283+
284+ if (pListHead && pListHead != &pCurrentTxd->textures .root )
285+ {
286+ RwTexture* pFirstTex = (RwTexture*)((char *)pListHead - offsetof (RwTexture, TXDList));
287+ auto it = textureMap.find (pFirstTex->name );
288+ if (it == textureMap.end () || it->second != pFirstTex)
289+ return false ;
290+ }
291+ return true ;
276292 }
277293 };
278294 std::unordered_map<unsigned short , SCachedTxdTextureMap> g_TxdTextureMapCache;
@@ -756,13 +772,13 @@ namespace
756772
757773 if (!bModelLoaded)
758774 {
759- RequeuePendingReplacement (usModelId, entry, false );
775+ RequeuePendingReplacement (usModelId, entry, true );
760776 continue ;
761777 }
762778
763779 if (bParentStreamingBusy)
764780 {
765- RequeuePendingReplacement (usModelId, entry, false );
781+ RequeuePendingReplacement (usModelId, entry, true );
766782 continue ;
767783 }
768784
@@ -775,18 +791,10 @@ namespace
775791
776792 ++uiProcessedCount;
777793
778- const uint32_t uiStartSerial = g_uiIsolationDeniedSerial;
779- const bool bApplied = pRenderWareSA->ModelInfoTXDAddTextures (entry.pReplacement , usModelId);
794+ const bool bApplied = pRenderWareSA->ModelInfoTXDAddTextures (entry.pReplacement , usModelId);
780795 if (!bApplied)
781796 {
782- if (WasIsolationDenied (uiStartSerial))
783- {
784- RequeuePendingReplacement (usModelId, entry, false );
785- }
786- else
787- {
788- RequeuePendingReplacement (usModelId, entry, true );
789- }
797+ RequeuePendingReplacement (usModelId, entry, true );
790798 }
791799 else
792800 {
@@ -1657,7 +1665,14 @@ namespace
16571665 if (!CanDestroyOrphanedTexture (pTexture))
16581666 return ;
16591667
1660- reinterpret_cast <RwRaster* volatile &>(pTexture->raster ) = nullptr ;
1668+ // If this is the final reference, RwTextureDestroy will attempt to free the raster.
1669+ // We only clear it if refs <= 1. If refs > 1, the struct won't be freed anyway, so clearing
1670+ // the raster just needlessly breaks any other materials still keeping this copy alive.
1671+ if (pTexture->refs <= 1 )
1672+ {
1673+ reinterpret_cast <RwRaster* volatile &>(pTexture->raster ) = nullptr ;
1674+ }
1675+
16611676 RwTextureDestroy (pTexture);
16621677 }
16631678
@@ -1762,7 +1777,22 @@ namespace
17621777 // Streaming can destroy and reload a TXD at the same address (pool/allocator reuse),
17631778 // which leaves the cache with dangling texture name pointers.
17641779 RwListEntry* pListHead = pVehicleTxd->textures .root .next ;
1765- if (pVehicleTxd != g_pCachedVehicleTxd || pListHead != g_pCachedVehicleTxdListHead)
1780+ RwTexture* pFirstTex = nullptr ;
1781+ if (pListHead && pListHead != &pVehicleTxd->textures .root )
1782+ pFirstTex = (RwTexture*)((char *)pListHead - offsetof (RwTexture, TXDList));
1783+
1784+ bool bInvalidate = (pVehicleTxd != g_pCachedVehicleTxd || pListHead != g_pCachedVehicleTxdListHead);
1785+
1786+ if (!bInvalidate && pFirstTex && !g_CachedVehicleTxdMap.empty ())
1787+ {
1788+ auto it = g_CachedVehicleTxdMap.find (pFirstTex->name );
1789+ if (it == g_CachedVehicleTxdMap.end () || it->second != pFirstTex)
1790+ {
1791+ bInvalidate = true ;
1792+ }
1793+ }
1794+
1795+ if (bInvalidate)
17661796 {
17671797 g_CachedVehicleTxdMap.clear ();
17681798 g_pCachedVehicleTxd = pVehicleTxd;
@@ -3998,6 +4028,8 @@ bool CRenderWareSA::ModelInfoTXDAddTextures(SReplacementTextures* pReplacementTe
39984028 CTxdStore_RemoveRef (usParentTxdId);
39994029 ClearIsolatedTxdLastUse (usModelId);
40004030 ClearPendingIsolatedModel (usModelId);
4031+ ClearPendingReplacementStateForModel (usModelId);
4032+ g_PendingReplacementByModel.erase (usModelId);
40014033 g_IsolatedTxdByModel.erase (itPrevIsolated);
40024034 QueuePendingReplacement (usModelId, pReplacementTextures, 0 , 0 );
40034035 return false ;
@@ -4073,6 +4105,8 @@ bool CRenderWareSA::ModelInfoTXDAddTextures(SReplacementTextures* pReplacementTe
40734105 }
40744106 ClearIsolatedTxdLastUse (usModelId);
40754107 ClearPendingIsolatedModel (usModelId);
4108+ ClearPendingReplacementStateForModel (usModelId);
4109+ g_PendingReplacementByModel.erase (usModelId);
40764110 g_IsolatedTxdByModel.erase (itIsolated);
40774111 QueuePendingReplacement (usModelId, pReplacementTextures, 0 , 0 );
40784112 return false ;
@@ -4094,6 +4128,8 @@ bool CRenderWareSA::ModelInfoTXDAddTextures(SReplacementTextures* pReplacementTe
40944128 CTxdStore_RemoveRef (usParentTxdId);
40954129 ClearIsolatedTxdLastUse (usModelId);
40964130 ClearPendingIsolatedModel (usModelId);
4131+ ClearPendingReplacementStateForModel (usModelId);
4132+ g_PendingReplacementByModel.erase (usModelId);
40974133 g_IsolatedTxdByModel.erase (itIsolated);
40984134 QueuePendingReplacement (usModelId, pReplacementTextures, 0 , 0 );
40994135 return false ;
0 commit comments