@@ -608,7 +608,8 @@ void CModelInfoSA::Remove()
608608 // Remove our reference
609609 if (m_pInterface->usNumberOfRefs > 0 )
610610 {
611- if (CTxdStore_GetTxd (m_pInterface->usTextureDictionary ) != nullptr )
611+ if (pGame && !pGame->GetPools ()->GetTxdPool ().IsFreeTextureDictonarySlot (m_pInterface->usTextureDictionary ) &&
612+ CTxdStore_GetTxd (m_pInterface->usTextureDictionary ) != nullptr )
612613 CTxdStore_RemoveRef (m_pInterface->usTextureDictionary );
613614 m_pInterface->usNumberOfRefs --;
614615 }
@@ -655,6 +656,33 @@ bool CModelInfoSA::IsLoaded()
655656 return false ;
656657}
657658
659+ static void UnlinkStreamingInfoNeighbors (CStreamingInfo* pStreamInfo)
660+ {
661+ if (!pStreamInfo || !pGame)
662+ return ;
663+
664+ if (pStreamInfo->loadState == eModelLoadState::LOADSTATE_NOT_LOADED)
665+ return ;
666+
667+ CStreaming* pStreaming = pGame->GetStreaming ();
668+ if (!pStreaming)
669+ return ;
670+
671+ constexpr unsigned short kInvalid = static_cast <unsigned short >(-1 );
672+ const unsigned short prev = pStreamInfo->prevId ;
673+ const unsigned short next = pStreamInfo->nextId ;
674+
675+ CStreamingInfo* pPrev = (prev != kInvalid ) ? pStreaming->GetStreamingInfo (prev) : nullptr ;
676+ CStreamingInfo* pNext = (next != kInvalid ) ? pStreaming->GetStreamingInfo (next) : nullptr ;
677+
678+ // If one side already points outside the streaming array, cut the
679+ // reachable side instead of copying the bad link forward.
680+ if (pPrev)
681+ pPrev->nextId = (next == kInvalid || pNext) ? next : kInvalid ;
682+ if (pNext)
683+ pNext->prevId = (prev == kInvalid || pPrev) ? prev : kInvalid ;
684+ }
685+
658686bool CModelInfoSA::DoIsLoaded ()
659687{
660688 // return (BOOL)*(BYTE *)(ARRAY_ModelLoaded + 20*dwModelID);
@@ -673,24 +701,7 @@ bool CModelInfoSA::DoIsLoaded()
673701 if (pStreamInfo)
674702 {
675703 // Unlink from SA's loaded-entry list before zeroing the link fields to avoid linked list corruptin
676- if (pStreamInfo->loadState != eModelLoadState::LOADSTATE_NOT_LOADED)
677- {
678- constexpr unsigned short kInvalid = static_cast <unsigned short >(-1 );
679- const unsigned short prev = pStreamInfo->prevId ;
680- const unsigned short next = pStreamInfo->nextId ;
681- if (prev != kInvalid )
682- {
683- CStreamingInfo* pPrev = pGame->GetStreaming ()->GetStreamingInfo (prev);
684- if (pPrev)
685- pPrev->nextId = next;
686- }
687- if (next != kInvalid )
688- {
689- CStreamingInfo* pNext = pGame->GetStreaming ()->GetStreamingInfo (next);
690- if (pNext)
691- pNext->prevId = prev;
692- }
693- }
704+ UnlinkStreamingInfoNeighbors (pStreamInfo);
694705 pStreamInfo->prevId = static_cast <unsigned short >(-1 );
695706 pStreamInfo->nextId = static_cast <unsigned short >(-1 );
696707 pStreamInfo->nextInImg = static_cast <unsigned short >(-1 );
@@ -1094,25 +1105,39 @@ void CModelInfoSA::SetTextureDictionaryID(unsigned short usID)
10941105 if (usOldTxdId == usID)
10951106 return ;
10961107
1108+ // CTxdStore functions (GetTxd, AddRef, RemoveRef) crash on free pool entries.
1109+ if (!pGame || pGame->GetPools ()->GetTxdPool ().IsFreeTextureDictonarySlot (usID))
1110+ return ;
1111+
10971112 // Slot allocated (e.g. via engineRequestTXD) but TXD data isn't loaded yet.
1098- // For unloaded models, just record the new TXD ID; SA streaming handles refs
1099- // and texture resolution when it loads both the model and its TXD dependency.
1100- // Loaded models fall through to the ref-transfer path below. CTxdStore_AddRef
1101- // only touches usUsagesCount (never dereferences rwTexDictonary), and
1102- // BuildTxdTextureMap returns an empty map for null TXDs, making the rebind a
1103- // no-op. The real texture data arrives later via engineImageLinkTXD + restream.
1113+ // For unloaded models without geometry, record the new TXD ID and transfer
1114+ // entity refs so callers' pre-added refs are consumed. Loaded models fall
1115+ // through to the ref-transfer path below. CTxdStore_AddRef only touches
1116+ // usUsagesCount (never dereferences rwTexDictonary), and BuildTxdTextureMap
1117+ // returns an empty map for null TXDs, making the rebind a no-op. The real
1118+ // texture data arrives later via engineImageLinkTXD + restream.
11041119 if (CTxdStore_GetTxd (usID) == nullptr )
11051120 {
1106- if (!pGame || pGame->GetPools ()->GetTxdPool ().IsFreeTextureDictonarySlot (usID))
1107- return ;
1108-
11091121 if (!m_pInterface->pRwObject )
11101122 {
11111123 if (!MapContains (ms_DefaultTxdIDMap, static_cast <unsigned short >(m_dwModelID)))
11121124 ms_DefaultTxdIDMap[static_cast <unsigned short >(m_dwModelID)] = usOldTxdId;
11131125
11141126 ++ms_uiTxdAssignmentGeneration;
11151127 m_pInterface->usTextureDictionary = usID;
1128+
1129+ // Transfer entity refs between TXDs even without geometry loaded.
1130+ // Callers that pre-add refs to prevent underflow expect them to be
1131+ // consumed here; skipping this leaks refs on the old TXD.
1132+ size_t referencesCount = m_pInterface->usNumberOfRefs ;
1133+ for (size_t i = 0 ; i < referencesCount; i++)
1134+ CTxdStore_AddRef (usID);
1135+
1136+ if (!pGame->GetPools ()->GetTxdPool ().IsFreeTextureDictonarySlot (usOldTxdId) && CTxdStore_GetTxd (usOldTxdId) != nullptr )
1137+ {
1138+ for (size_t i = 0 ; i < referencesCount; i++)
1139+ CTxdStore_RemoveRef (usOldTxdId);
1140+ }
11161141 return ;
11171142 }
11181143
@@ -1173,9 +1198,9 @@ void CModelInfoSA::SetTextureDictionaryID(unsigned short usID)
11731198 }
11741199 }
11751200
1176- // Release old TXD refs after rebinding completes
1177- // Only release if old slot is still valid to avoid crash on stale/orphaned TXD slots
1178- if (CTxdStore_GetTxd (usOldTxdId) != nullptr )
1201+ // Release old TXD refs after rebinding. Skip if the slot was freed between
1202+ // capturing usOldTxdId and here - CTxdStore functions crash on free entries.
1203+ if (!pGame-> GetPools ()-> GetTxdPool (). IsFreeTextureDictonarySlot (usOldTxdId) && CTxdStore_GetTxd (usOldTxdId) != nullptr )
11791204 {
11801205 for (size_t i = 0 ; i < referencesCount; i++)
11811206 CTxdStore_RemoveRef (usOldTxdId);
@@ -2173,6 +2198,13 @@ void CModelInfoSA::ResetVehicleDummies(bool bRemoveFromDummiesMap)
21732198 pVehicleModel = static_cast <CVehicleModelInfoSAInterface*>(GetInterface ());
21742199 if (!pVehicleModel || !pVehicleModel->pVisualInfo )
21752200 {
2201+ if (pVehicleModel && pVehicleModel->usNumberOfRefs > 0 )
2202+ {
2203+ if (pGame && !pGame->GetPools ()->GetTxdPool ().IsFreeTextureDictonarySlot (pVehicleModel->usTextureDictionary ) &&
2204+ CTxdStore_GetTxd (pVehicleModel->usTextureDictionary ) != nullptr )
2205+ CTxdStore_RemoveRef (pVehicleModel->usTextureDictionary );
2206+ pVehicleModel->usNumberOfRefs --;
2207+ }
21762208 if (bRemoveFromDummiesMap)
21772209 ms_ModelDefaultDummiesPosition.erase (iter);
21782210 return ;
@@ -2186,7 +2218,8 @@ void CModelInfoSA::ResetVehicleDummies(bool bRemoveFromDummiesMap)
21862218
21872219 if (pVehicleModel->usNumberOfRefs > 0 )
21882220 {
2189- if (CTxdStore_GetTxd (pVehicleModel->usTextureDictionary ) != nullptr )
2221+ if (pGame && !pGame->GetPools ()->GetTxdPool ().IsFreeTextureDictonarySlot (pVehicleModel->usTextureDictionary ) &&
2222+ CTxdStore_GetTxd (pVehicleModel->usTextureDictionary ) != nullptr )
21902223 CTxdStore_RemoveRef (pVehicleModel->usTextureDictionary );
21912224 pVehicleModel->usNumberOfRefs --;
21922225 }
@@ -2215,6 +2248,13 @@ void CModelInfoSA::ResetAllVehicleDummies()
22152248 pVehicleModel = static_cast <CVehicleModelInfoSAInterface*>(pModelInfoSA->GetInterface ());
22162249 if (!pVehicleModel || !pVehicleModel->pVisualInfo )
22172250 {
2251+ if (pVehicleModel && pVehicleModel->usNumberOfRefs > 0 )
2252+ {
2253+ if (pGame && !pGame->GetPools ()->GetTxdPool ().IsFreeTextureDictonarySlot (pVehicleModel->usTextureDictionary ) &&
2254+ CTxdStore_GetTxd (pVehicleModel->usTextureDictionary ) != nullptr )
2255+ CTxdStore_RemoveRef (pVehicleModel->usTextureDictionary );
2256+ pVehicleModel->usNumberOfRefs --;
2257+ }
22182258 it = ms_ModelDefaultDummiesPosition.erase (it);
22192259 continue ;
22202260 }
@@ -2225,7 +2265,8 @@ void CModelInfoSA::ResetAllVehicleDummies()
22252265
22262266 if (pVehicleModel->usNumberOfRefs > 0 )
22272267 {
2228- if (CTxdStore_GetTxd (pVehicleModel->usTextureDictionary ) != nullptr )
2268+ if (pGame && !pGame->GetPools ()->GetTxdPool ().IsFreeTextureDictonarySlot (pVehicleModel->usTextureDictionary ) &&
2269+ CTxdStore_GetTxd (pVehicleModel->usTextureDictionary ) != nullptr )
22292270 CTxdStore_RemoveRef (pVehicleModel->usTextureDictionary );
22302271 pVehicleModel->usNumberOfRefs --;
22312272 }
@@ -2764,24 +2805,7 @@ static void UnlinkAndResetStreamingInfo(CStreamingInfo* pStreamInfo)
27642805 if (!pStreamInfo)
27652806 return ;
27662807
2767- if (pStreamInfo->loadState != eModelLoadState::LOADSTATE_NOT_LOADED && pGame && pGame->GetStreaming ())
2768- {
2769- constexpr unsigned short kInvalid = static_cast <unsigned short >(-1 );
2770- const unsigned short prev = pStreamInfo->prevId ;
2771- const unsigned short next = pStreamInfo->nextId ;
2772- if (prev != kInvalid )
2773- {
2774- CStreamingInfo* pPrev = pGame->GetStreaming ()->GetStreamingInfo (prev);
2775- if (pPrev)
2776- pPrev->nextId = next;
2777- }
2778- if (next != kInvalid )
2779- {
2780- CStreamingInfo* pNext = pGame->GetStreaming ()->GetStreamingInfo (next);
2781- if (pNext)
2782- pNext->prevId = prev;
2783- }
2784- }
2808+ UnlinkStreamingInfoNeighbors (pStreamInfo);
27852809 *pStreamInfo = CStreamingInfo{};
27862810 pStreamInfo->prevId = static_cast <unsigned short >(-1 );
27872811 pStreamInfo->nextId = static_cast <unsigned short >(-1 );
@@ -2915,7 +2939,7 @@ void CModelInfoSA::MakeTimedObjectModel(ushort usBaseID)
29152939 pNewInterface->pRwObject = nullptr ;
29162940 pNewInterface->usUnknown = 65535 ;
29172941 pNewInterface->usDynamicIndex = 65535 ;
2918- pNewInterface->timeInfo .m_wOtherTimeModel = 0 ;
2942+ pNewInterface->timeInfo .m_wOtherTimeModel = - 1 ;
29192943
29202944 ppModelInfo[m_dwModelID] = pNewInterface;
29212945
0 commit comments