@@ -33,6 +33,38 @@ namespace
3333{
3434 using TextureSwapMap = std::unordered_map<RwTexture*, RwTexture*>;
3535
36+ void CleanupStalePerTxd (SReplacementTextures::SPerTxd& perTxdInfo, const std::unordered_set<RwTexture*>& texturesToKeep)
37+ {
38+ // TXD is dead/stale. Perform safe cleanup.
39+ for (RwTexture* pOldTexture : perTxdInfo.usingTextures )
40+ {
41+ if (pOldTexture && SharedUtil::IsReadablePointer (pOldTexture, sizeof (RwTexture)))
42+ {
43+ pOldTexture->txd = nullptr ; // Detach from dead dictionary
44+ if (perTxdInfo.bTexturesAreCopies )
45+ {
46+ std::exchange (pOldTexture->raster , nullptr );
47+ RwTextureDestroy (pOldTexture);
48+ }
49+ }
50+ }
51+ perTxdInfo.usingTextures .clear ();
52+
53+ for (RwTexture* pReplacedTexture : perTxdInfo.replacedOriginals )
54+ {
55+ if (pReplacedTexture && SharedUtil::IsReadablePointer (pReplacedTexture, sizeof (RwTexture)))
56+ {
57+ pReplacedTexture->txd = nullptr ; // Detach from dead dictionary
58+ // Destroy only the textures that are not present in texturesToKeep (originals or owned by other replacements)
59+ if (texturesToKeep.find (pReplacedTexture) == texturesToKeep.end ())
60+ {
61+ RwTextureDestroy (pReplacedTexture);
62+ }
63+ }
64+ }
65+ perTxdInfo.replacedOriginals .clear ();
66+ }
67+
3668 void ReplaceTextureInGeometry (RpGeometry* pGeometry, const TextureSwapMap& swapMap)
3769 {
3870 if (!pGeometry || swapMap.empty ())
@@ -119,8 +151,99 @@ CModelTexturesInfo* CRenderWareSA::GetModelTexturesInfo(ushort usModelId)
119151
120152 const ushort usTxdId = pModelInfo->GetTextureDictionaryID ();
121153
122- if (auto it = ms_ModelTexturesInfoMap.find (usTxdId); it != ms_ModelTexturesInfoMap.end ())
123- return &it->second ;
154+ auto it = ms_ModelTexturesInfoMap.find (usTxdId);
155+ if (it != ms_ModelTexturesInfoMap.end ())
156+ {
157+ // Validate the cached TXD pointer
158+ CModelTexturesInfo& info = it->second ;
159+ RwTexDictionary* pCurrentTxd = CTxdStore_GetTxd (usTxdId);
160+
161+ if (info.pTxd != pCurrentTxd)
162+ {
163+ // The TXD has been reloaded or unloaded by the game
164+
165+ // Clear old replacement references when swapping TXDs
166+ std::unordered_set<RwTexture*> texturesToKeep;
167+ texturesToKeep.insert (info.originalTextures .begin (), info.originalTextures .end ());
168+
169+ // Collect all textures currently in use by all replacements for this TXD
170+ for (SReplacementTextures* pReplacement : info.usedByReplacements )
171+ {
172+ auto itPerTxd = std::find_if (pReplacement->perTxdList .begin (), pReplacement->perTxdList .end (),
173+ [usTxdId](const SReplacementTextures::SPerTxd& item) { return item.usTxdId == usTxdId; });
174+
175+ if (itPerTxd != pReplacement->perTxdList .end ())
176+ {
177+ texturesToKeep.insert (itPerTxd->usingTextures .begin (), itPerTxd->usingTextures .end ());
178+ }
179+ }
180+
181+ for (SReplacementTextures* pReplacement : info.usedByReplacements )
182+ {
183+ // Find the per-TXD info for this stale TXD
184+ auto itPerTxd = std::find_if (pReplacement->perTxdList .begin (), pReplacement->perTxdList .end (),
185+ [usTxdId](const SReplacementTextures::SPerTxd& item) { return item.usTxdId == usTxdId; });
186+
187+ if (itPerTxd != pReplacement->perTxdList .end ())
188+ {
189+ CleanupStalePerTxd (*itPerTxd, texturesToKeep);
190+
191+ // Remove this TXD from the replacement's list
192+ pReplacement->perTxdList .erase (itPerTxd);
193+ }
194+
195+ // Remove from usedInTxdIds
196+ ListRemove (pReplacement->usedInTxdIds , usTxdId);
197+
198+ // Remove associated models from usedInModelIds to allow re-application
199+ for (auto itModel = pReplacement->usedInModelIds .begin (); itModel != pReplacement->usedInModelIds .end (); )
200+ {
201+ ushort modelId = *itModel;
202+ CModelInfoSA* pModInfo = dynamic_cast <CModelInfoSA*>(pGame->GetModelInfo (modelId));
203+ if (pModInfo && pModInfo->GetTextureDictionaryID () == usTxdId)
204+ {
205+ itModel = pReplacement->usedInModelIds .erase (itModel);
206+ }
207+ else
208+ {
209+ ++itModel;
210+ }
211+ }
212+ }
213+ info.usedByReplacements .clear ();
214+
215+ if (pCurrentTxd)
216+ {
217+ // Reloaded: Update our pointer and refresh original textures
218+ info.pTxd = pCurrentTxd;
219+ info.originalTextures .clear ();
220+ GetTxdTextures (info.originalTextures , pCurrentTxd);
221+ CTxdStore_RemoveRef (usTxdId);
222+ CTxdStore_AddRef (usTxdId);
223+ }
224+ else
225+ {
226+ // Unloaded: We need to reload it
227+ pModelInfo->Request (BLOCKING, " CRenderWareSA::GetModelTexturesInfo" );
228+ CTxdStore_RemoveRef (usTxdId);
229+ CTxdStore_AddRef (usTxdId);
230+ pCurrentTxd = CTxdStore_GetTxd (usTxdId);
231+
232+ if (pCurrentTxd)
233+ {
234+ info.pTxd = pCurrentTxd;
235+ info.originalTextures .clear ();
236+ GetTxdTextures (info.originalTextures , pCurrentTxd);
237+ }
238+ else
239+ {
240+ // Failed to load
241+ return nullptr ;
242+ }
243+ }
244+ }
245+ return &info;
246+ }
124247
125248 // Get txd
126249 RwTexDictionary* pTxd = CTxdStore_GetTxd (usTxdId);
@@ -147,8 +270,8 @@ CModelTexturesInfo* CRenderWareSA::GetModelTexturesInfo(ushort usModelId)
147270 return nullptr ;
148271
149272 // Add new info
150- auto [it , inserted] = ms_ModelTexturesInfoMap.emplace (usTxdId, CModelTexturesInfo{});
151- auto & newInfo = it ->second ;
273+ auto [iter , inserted] = ms_ModelTexturesInfoMap.emplace (usTxdId, CModelTexturesInfo{});
274+ auto & newInfo = iter ->second ;
152275 newInfo.usTxdId = usTxdId;
153276 newInfo.pTxd = pTxd;
154277
@@ -371,6 +494,44 @@ void CRenderWareSA::ModelInfoTXDRemoveTextures(SReplacementTextures* pReplacemen
371494 dassert (MapFind (ms_ModelTexturesInfoMap, usTxdId));
372495 dassert (ListContains (pInfo->usedByReplacements , pReplacementTextures));
373496
497+ // Check for stale TXD
498+ RwTexDictionary* pCurrentTxd = CTxdStore_GetTxd (usTxdId);
499+ bool bTxdIsValid = (pInfo->pTxd == pCurrentTxd) && (pInfo->pTxd != nullptr );
500+
501+ if (!bTxdIsValid)
502+ {
503+ std::unordered_set<RwTexture*> texturesToKeep;
504+ texturesToKeep.insert (pInfo->originalTextures .begin (), pInfo->originalTextures .end ());
505+
506+ // Collect textures from other replacements
507+ for (SReplacementTextures* pOtherReplacement : pInfo->usedByReplacements )
508+ {
509+ if (pOtherReplacement == pReplacementTextures)
510+ continue ;
511+
512+ auto itPerTxd = std::find_if (pOtherReplacement->perTxdList .begin (), pOtherReplacement->perTxdList .end (),
513+ [usTxdId](const SReplacementTextures::SPerTxd& item) { return item.usTxdId == usTxdId; });
514+
515+ if (itPerTxd != pOtherReplacement->perTxdList .end ())
516+ {
517+ texturesToKeep.insert (itPerTxd->usingTextures .begin (), itPerTxd->usingTextures .end ());
518+ }
519+ }
520+
521+ CleanupStalePerTxd (perTxdInfo, texturesToKeep);
522+
523+ ListRemove (pInfo->usedByReplacements , pReplacementTextures);
524+ ListRemove (pReplacementTextures->usedInTxdIds , usTxdId);
525+
526+ if (pInfo->usedByReplacements .empty ())
527+ {
528+ pInfo->originalTextures .clear ();
529+ CTxdStore_RemoveRef (pInfo->usTxdId );
530+ MapRemove (ms_ModelTexturesInfoMap, usTxdId);
531+ }
532+ continue ;
533+ }
534+
374535 TextureSwapMap swapMap;
375536 swapMap.reserve (perTxdInfo.usingTextures .size ());
376537
0 commit comments