@@ -63,8 +63,8 @@ namespace TextureIndex
6363
6464 inline std::size_t HashCanonical (const char * str, std::size_t len)
6565 {
66- const std::size_t kFnvOffset = 2166136261u ;
67- const std::size_t kFnvPrime = 16777619u ;
66+ constexpr std::size_t kFnvOffset = 2166136261u ;
67+ constexpr std::size_t kFnvPrime = 16777619u ;
6868
6969 std::size_t hash = kFnvOffset ;
7070 for (std::size_t i = 0 ; i < len; ++i)
@@ -152,7 +152,7 @@ static std::recursive_mutex ms_ModelTexturesInfoMutex;
152152
153153namespace
154154{
155- const unsigned int kReplacementTextureFilterFlags = 0x1102u ;
155+ constexpr unsigned int kReplacementTextureFilterFlags = 0x1102u ;
156156
157157 using TextureSwapMap = std::unordered_map<RwTexture*, RwTexture*>;
158158
@@ -178,6 +178,11 @@ namespace
178178 m_owned = false ;
179179 }
180180
181+ bool IsOwned () const
182+ {
183+ return m_owned;
184+ }
185+
181186 ~ScopedTxdRef ()
182187 {
183188 if (m_owned)
@@ -585,7 +590,7 @@ namespace
585590 return ;
586591
587592 // RenderWare guarantees materials.entries reflects the allocation size of the materials array
588- const int32_t MAX_MATERIALS = 10000 ;
593+ constexpr int32_t MAX_MATERIALS = 10000 ;
589594 if (materials.entries < 0 || materials.entries > MAX_MATERIALS)
590595 return ;
591596
@@ -696,27 +701,46 @@ ModelTexturesInfoGuard AcquireModelTexturesInfo(ushort usModelId)
696701
697702 if (!pTxd)
698703 {
699- guard.modelInfo ->Request (BLOCKING, " CRenderWareSA::GetModelTexturesInfo" );
700- txdRefGuard.Acquire (usTxdId);
701- ((void (__cdecl*)(unsigned short ))FUNC_RemoveModel)(usModelId);
702- pTxd = CTxdStore_GetTxd (usTxdId);
704+ // TXD not loaded - try to request it based on model type
705+ const eModelInfoType modelType = guard.modelInfo ->GetModelType ();
706+
707+ // For vehicles, we need the TXD loaded to prevent white textures
708+ // For weapons and other models, use lazy loading to avoid freezes with large texture packs
709+ if (modelType == eModelInfoType::VEHICLE)
710+ {
711+ // Request the model non-blocking first to avoid freezing
712+ guard.modelInfo ->Request (NON_BLOCKING, " CRenderWareSA::AcquireModelTexturesInfo" );
713+ pTxd = CTxdStore_GetTxd (usTxdId);
703714
715+ // If still not available, try blocking as last resort for vehicles
716+ if (!pTxd)
717+ {
718+ guard.modelInfo ->Request (BLOCKING, " CRenderWareSA::AcquireModelTexturesInfo" );
719+ // Remove model after blocking request to ensure proper reload
720+ ((void (__cdecl*)(unsigned short ))FUNC_RemoveModel)(usModelId);
721+ pTxd = CTxdStore_GetTxd (usTxdId);
722+ }
723+ }
724+
725+ // If still unavailable after attempts, fail gracefully
704726 if (!pTxd)
705727 return guard;
706728 }
707- else
729+
730+ // Acquire TXD reference only after confirming TXD is available
731+ txdRefGuard.Acquire (usTxdId);
732+
733+ const eModelInfoType modelType = guard.modelInfo ->GetModelType ();
734+ if (modelType == eModelInfoType::PED)
708735 {
709- txdRefGuard.Acquire (usTxdId);
710- if (guard.modelInfo ->GetModelType () == eModelInfoType::PED)
711- {
712- // Mystery fix for #9336: (MTA sometimes fails at loading custom textures)
713- // Possibly forces the ped model to be reloaded in some way
714- ((void (__cdecl*)(unsigned short ))FUNC_RemoveModel)(usModelId);
715- }
736+ // Mystery fix for #9336: (MTA sometimes fails at loading custom textures)
737+ // Possibly forces the ped model to be reloaded in some way
738+ ((void (__cdecl*)(unsigned short ))FUNC_RemoveModel)(usModelId);
716739 }
717740
718- auto insertResult = ms_ModelTexturesInfoMap.emplace (usTxdId, CModelTexturesInfo ());
719- guard.info = &insertResult.first ->second ;
741+ auto result = ms_ModelTexturesInfoMap.emplace (usTxdId, CModelTexturesInfo ());
742+ auto insertIt = result.first ;
743+ guard.info = &insertIt->second ;
720744
721745 guard.info ->usTxdId = usTxdId;
722746 guard.info ->pTxd = pTxd;
@@ -727,8 +751,10 @@ ModelTexturesInfoGuard AcquireModelTexturesInfo(ushort usModelId)
727751 // Revalidate pTxd before passing to GetTxdTextures to prevent crashes from stale pointers
728752 if (!IsValidTexDictionary (pTxd))
729753 {
730- ms_ModelTexturesInfoMap.erase (insertResult. first );
754+ ms_ModelTexturesInfoMap.erase (insertIt );
731755 guard.info = nullptr ;
756+ txdRefGuard.Release ();
757+ CTxdStore_RemoveRef (usTxdId);
732758 return guard;
733759 }
734760
@@ -737,8 +763,10 @@ ModelTexturesInfoGuard AcquireModelTexturesInfo(ushort usModelId)
737763 // If texture enumeration failed
738764 if (originals.empty ())
739765 {
740- ms_ModelTexturesInfoMap.erase (insertResult. first );
766+ ms_ModelTexturesInfoMap.erase (insertIt );
741767 guard.info = nullptr ;
768+ txdRefGuard.Release ();
769+ CTxdStore_RemoveRef (usTxdId);
742770 return guard;
743771 }
744772
@@ -765,37 +793,50 @@ ModelTexturesInfoGuard AcquireModelTexturesInfo(ushort usModelId)
765793bool CRenderWareSA::ModelInfoTXDLoadTextures (SReplacementTextures* pReplacementTextures, const SString& strFilename, const SString& buffer,
766794 bool bFilteringEnabled)
767795{
796+ // Validate input pointer first
797+ if (!pReplacementTextures)
798+ return false ;
799+
768800 // Are we already loaded?
769- if (!pReplacementTextures->textures .empty ())
801+ if (!pReplacementTextures->ownedTextures .empty ())
770802 return false ;
771803
772804 // Try to load it
773805 RwTexDictionary* pTxd = ReadTXD (strFilename, buffer);
774806 if (pTxd)
775807 {
776- // Get the list of textures into our own list
777- GetTxdTextures (pReplacementTextures->textures , pTxd);
808+ // Get the list of textures into temporary vector
809+ std::vector<RwTexture*> loadedTextures;
810+ GetTxdTextures (loadedTextures, pTxd);
778811
779- // Remove any null textures and configure valid ones
780- auto newEnd = std::remove_if (pReplacementTextures->textures .begin (), pReplacementTextures->textures .end (),
781- [](RwTexture* const pTex) { return pTex == nullptr ; });
782- pReplacementTextures->textures .erase (newEnd, pReplacementTextures->textures .end ());
812+ // Make the txd forget it has any textures before destroying it
813+ pTxd->textures .root .next = &pTxd->textures .root ;
814+ pTxd->textures .root .prev = &pTxd->textures .root ;
815+ RwTexDictionaryDestroy (pTxd);
816+ pTxd = nullptr ;
783817
784- for (RwTexture* pTexture : pReplacementTextures->textures )
818+ // Transfer ownership to unique_ptr with custom deleter
819+ pReplacementTextures->ownedTextures .reserve (loadedTextures.size ());
820+ pReplacementTextures->textures .reserve (loadedTextures.size ());
821+
822+ for (RwTexture* pTexture : loadedTextures)
785823 {
824+ if (!pTexture)
825+ continue ;
826+
827+ // Textures are now orphaned (txd was destroyed), we own them
786828 pTexture->txd = nullptr ;
787829 if (bFilteringEnabled)
788830 pTexture->flags = kReplacementTextureFilterFlags ;
789- }
790831
791- // Make the txd forget it has any textures and destroy it
792- pTxd-> textures . root . next = &pTxd-> textures . root ;
793- pTxd-> textures . root . prev = &pTxd-> textures . root ;
794- RwTexDictionaryDestroy (pTxd );
795- pTxd = nullptr ;
832+ // Take ownership with unique_ptr for automatic cleanup
833+ pReplacementTextures-> ownedTextures . emplace_back (pTexture) ;
834+ // Keep raw pointer for compatibility with existing code
835+ pReplacementTextures-> textures . push_back (pTexture );
836+ }
796837
797838 // We succeeded if we got any textures
798- return pReplacementTextures->textures .size () > 0 ;
839+ return ! pReplacementTextures->textures .empty () ;
799840 }
800841
801842 return false ;
@@ -811,6 +852,10 @@ bool CRenderWareSA::ModelInfoTXDLoadTextures(SReplacementTextures* pReplacementT
811852// //////////////////////////////////////////////////////////////
812853bool CRenderWareSA::ModelInfoTXDAddTextures (SReplacementTextures* pReplacementTextures, ushort usModelId)
813854{
855+ // Validate input pointer first
856+ if (!pReplacementTextures)
857+ return false ;
858+
814859 ModelTexturesInfoGuard infoGuard = AcquireModelTexturesInfo (usModelId);
815860 if (!infoGuard)
816861 return false ;
@@ -833,7 +878,7 @@ bool CRenderWareSA::ModelInfoTXDAddTextures(SReplacementTextures* pReplacementTe
833878
834879 // Prevent unbounded growth from spammy texture replacements on different TXD IDs(Like at client load complete on a very unoptimized server).
835880 // Reasonable limit: one SReplacementTextures shouldn't be applied to hundreds of different TXDs.
836- const std::size_t kMaxTxdEntriesPerReplacement = 64 ;
881+ constexpr std::size_t kMaxTxdEntriesPerReplacement = 64 ;
837882 if (pReplacementTextures && pReplacementTextures->perTxdList .size () >= kMaxTxdEntriesPerReplacement )
838883 {
839884 // Defensive check for pInfo validity before accessing its members
@@ -1065,6 +1110,10 @@ bool CRenderWareSA::ModelInfoTXDAddTextures(SReplacementTextures* pReplacementTe
10651110// //////////////////////////////////////////////////////////////
10661111void CRenderWareSA::ModelInfoTXDRemoveTextures (SReplacementTextures* pReplacementTextures)
10671112{
1113+ // Validate input pointer first
1114+ if (!pReplacementTextures)
1115+ return ;
1116+
10681117 std::unique_lock<std::recursive_mutex> guard (ms_ModelTexturesInfoMutex);
10691118 // For each using txd
10701119 for (std::size_t i = 0 ; i < pReplacementTextures->perTxdList .size (); ++i)
@@ -1148,17 +1197,11 @@ void CRenderWareSA::ModelInfoTXDRemoveTextures(SReplacementTextures* pReplacemen
11481197 }
11491198 }
11501199
1151- // Destroy replacement textures with validation to prevent crashes
1152- if (pReplacementTextures)
1153- {
1154- for (std::size_t i = 0 ; i < pReplacementTextures->textures .size (); ++i)
1155- {
1156- RwTexture* pTexture = pReplacementTextures->textures [i];
1157- if (pTexture && IsValidTexturePtr (pTexture))
1158- DestroyTexture (pTexture);
1159- }
1160- }
1161- pReplacementTextures->textures .clear ();
1200+ // Destroy replacement textures - ownedTextures vector handles cleanup automatically via unique_ptr
1201+ // Textures that were added to TXDs (txd != nullptr) won't be destroyed by unique_ptr's deleter
1202+ // because they're managed by the TXD. Orphaned textures (txd == nullptr) are destroyed safely.
1203+ pReplacementTextures->ownedTextures .clear (); // Automatic cleanup via unique_ptr destructors
1204+ pReplacementTextures->textures .clear (); // Clear raw pointers
11621205
11631206 pReplacementTextures->perTxdList .clear ();
11641207 pReplacementTextures->usedInTxdIds .clear ();
0 commit comments