@@ -558,51 +558,33 @@ bool CRenderWareSA::DoContainTheSameGeometry(RpClump* pClumpA, RpClump* pClumpB,
558558 return true ;
559559}
560560
561- // //////////////////////////////////////////////////////////////
562- //
563- // CRenderWareSA::RebindClumpTexturesToTxd
564- //
565- // Rebinds all material textures in a clump to current TXD textures.
566- // This fixes stale texture pointers that occur when a TXD is reloaded
567- // after a custom DFF has been loaded. Without this fix, shader texture
568- // replacement fails because the materials point to old/destroyed textures.
569- //
570- // //////////////////////////////////////////////////////////////
571- void CRenderWareSA::RebindClumpTexturesToTxd (RpClump* pClump, unsigned short usTxdId)
561+ // Helpers for rebinding model textures to a different TXD.
562+ // When a model's TXD slot is changed, its loaded RwObject still holds pointers
563+ // to textures from the old TXD. These helpers update material texture pointers
564+ // to reference matching textures from the new TXD by name lookup.
565+ namespace
572566{
573- if (!pClump || !SharedUtil::IsReadablePointer (pClump, sizeof (RpClump)))
574- return ;
575-
576- RwTexDictionary* pTxd = CTxdStore_GetTxd (usTxdId);
577- if (!pTxd || !SharedUtil::IsReadablePointer (pTxd, sizeof (RwTexDictionary)))
578- return ;
579-
580- // Build a safe name->texture map once.
581- // Calling RW's FindNamedTexture can freeze if the TXD's internal linked list is bad
582- std::vector<RwTexture*> txdTextures;
583- GetTxdTextures (txdTextures, pTxd);
584-
585- struct TextureNameHash
567+ // Hash functor for texture name lookup (bounded to RW_TEXTURE_NAME_LENGTH)
568+ struct TxdTextureNameHash
586569 {
570+ static constexpr std::size_t HASH_INIT = 2166136261u ;
571+ static constexpr std::size_t HASH_MULTIPLIER = 16777619u ;
572+
587573 std::size_t operator ()(const char * s) const noexcept
588574 {
589575 if (!s)
590576 return 0 ;
591-
592- std::size_t h = 2166136261u ;
593- for (std::size_t i = 0 ; i < RW_TEXTURE_NAME_LENGTH; ++i)
577+ std::size_t h = HASH_INIT;
578+ for (std::size_t i = 0 ; i < RW_TEXTURE_NAME_LENGTH && s[i]; ++i)
594579 {
595- const unsigned char c = static_cast <unsigned char >(s[i]);
596- if (c == 0 )
597- break ;
598- h ^= c;
599- h *= 16777619u ;
580+ h ^= static_cast <unsigned char >(s[i]);
581+ h *= HASH_MULTIPLIER;
600582 }
601583 return h;
602584 }
603585 };
604586
605- struct TextureNameEq
587+ struct TxdTextureNameEq
606588 {
607589 bool operator ()(const char * a, const char * b) const noexcept
608590 {
@@ -614,106 +596,126 @@ void CRenderWareSA::RebindClumpTexturesToTxd(RpClump* pClump, unsigned short usT
614596 }
615597 };
616598
617- using TxdTextureMap = std::unordered_map<const char *, RwTexture*, TextureNameHash, TextureNameEq>;
618- TxdTextureMap txdTextureMap;
619- if (!txdTextures.empty ())
599+ using TxdTextureMap = std::unordered_map<const char *, RwTexture*, TxdTextureNameHash, TxdTextureNameEq>;
600+
601+ // Build name->texture map for a TXD slot. Keys point directly into RwTexture::name buffers.
602+ TxdTextureMap BuildTxdTextureMap (unsigned short usTxdId)
620603 {
621- txdTextureMap.reserve (txdTextures.size ());
604+ TxdTextureMap result;
605+
606+ RwTexDictionary* pTxd = CTxdStore_GetTxd (usTxdId);
607+ if (!pTxd)
608+ return result;
609+
610+ std::vector<RwTexture*> txdTextures;
611+ CRenderWareSA::GetTxdTextures (txdTextures, pTxd);
612+
613+ if (txdTextures.empty ())
614+ return result;
615+
616+ result.reserve (txdTextures.size ());
622617 for (RwTexture* pTexture : txdTextures)
623618 {
624- if (!pTexture || ! SharedUtil::IsReadablePointer (pTexture, sizeof (RwTexture)) )
619+ if (!pTexture)
625620 continue ;
626621
627622 const char * name = pTexture->name ;
628- const std::size_t nameLen = strnlen (name, RW_TEXTURE_NAME_LENGTH);
629- if (nameLen >= RW_TEXTURE_NAME_LENGTH)
630- continue ;
631-
632- txdTextureMap[name] = pTexture;
623+ if (strnlen (name, RW_TEXTURE_NAME_LENGTH) < RW_TEXTURE_NAME_LENGTH)
624+ result[name] = pTexture;
633625 }
634- }
635626
636- // Iterate through all atomics in the clump
637- std::vector<RpAtomic*> atomicList;
638- GetClumpAtomicList (pClump, atomicList);
627+ return result;
628+ }
639629
640- for (RpAtomic* pAtomic : atomicList)
630+ // Update each material's texture pointer to the matching texture from txdTextureMap.
631+ // Falls back to internal name mapping if direct lookup fails.
632+ void RebindAtomicMaterials (RpAtomic* pAtomic, const TxdTextureMap& txdTextureMap)
641633 {
642- if (!pAtomic || ! SharedUtil::IsReadablePointer (pAtomic, sizeof (RpAtomic)) )
643- continue ;
634+ if (!pAtomic)
635+ return ;
644636
645637 RpGeometry* pGeometry = pAtomic->geometry ;
646- if (!pGeometry || ! SharedUtil::IsReadablePointer (pGeometry, sizeof (RpGeometry)) )
647- continue ;
638+ if (!pGeometry)
639+ return ;
648640
649641 RpMaterials& materials = pGeometry->materials ;
650-
651- // Validate materials array exists and entries is sane
652642 if (!materials.materials || materials.entries <= 0 )
653- continue ;
643+ return ;
654644
655- // Sanity check - reject obviously corrupted values.
656- // Normal geometry has at most a few hundred materials.
657645 constexpr int MAX_REASONABLE_MATERIALS = 10000 ;
658- int materialCount = materials.entries ;
659- if (materialCount > MAX_REASONABLE_MATERIALS)
660- continue ;
661-
662- // Validate materials array is readable
663- if (!SharedUtil::IsReadablePointer (materials.materials , materialCount * sizeof (RpMaterial*)))
664- continue ;
646+ if (materials.entries > MAX_REASONABLE_MATERIALS)
647+ return ;
665648
666- // Iterate through all materials in the geometry
667- for (int idx = 0 ; idx < materialCount; ++idx)
649+ for (int idx = 0 ; idx < materials.entries ; ++idx)
668650 {
669651 RpMaterial* pMaterial = materials.materials [idx];
670- if (!pMaterial || ! SharedUtil::IsReadablePointer (pMaterial, sizeof (RpMaterial)) )
652+ if (!pMaterial)
671653 continue ;
672654
673655 RwTexture* pOldTexture = pMaterial->texture ;
674- if (!pOldTexture || ! SharedUtil::IsReadablePointer (pOldTexture, sizeof (RwTexture)) )
656+ if (!pOldTexture)
675657 continue ;
676658
677- // Get the current texture's name (RwTexture::name is char[32], always check first char)
678659 const char * szTextureName = pOldTexture->name ;
679660 if (!szTextureName[0 ])
680661 continue ;
681662
682- RwTexture* pCurrentTexture = nullptr ;
683- const std::size_t nameLen = strnlen (szTextureName, RW_TEXTURE_NAME_LENGTH);
684- if (nameLen < RW_TEXTURE_NAME_LENGTH)
685- {
686- auto itFound = txdTextureMap.find (szTextureName);
687- if (itFound != txdTextureMap.end ())
688- pCurrentTexture = itFound->second ;
663+ if (strnlen (szTextureName, RW_TEXTURE_NAME_LENGTH) >= RW_TEXTURE_NAME_LENGTH)
664+ continue ;
689665
690- if (!pCurrentTexture)
666+ RwTexture* pNewTexture = nullptr ;
667+ auto itFound = txdTextureMap.find (szTextureName);
668+ if (itFound != txdTextureMap.end ())
669+ pNewTexture = itFound->second ;
670+
671+ if (!pNewTexture)
672+ {
673+ const char * szInternalName = CRenderWareSA::GetInternalTextureName (szTextureName);
674+ if (szInternalName && szInternalName != szTextureName)
691675 {
692- const char * szInternalName = GetInternalTextureName (szTextureName);
693- if (szInternalName && szInternalName != szTextureName)
676+ if (strnlen (szInternalName, RW_TEXTURE_NAME_LENGTH) < RW_TEXTURE_NAME_LENGTH)
694677 {
695- const std::size_t internalLen = strnlen (szInternalName, RW_TEXTURE_NAME_LENGTH);
696- if (internalLen < RW_TEXTURE_NAME_LENGTH)
697- {
698- auto itInternal = txdTextureMap.find (szInternalName);
699- if (itInternal != txdTextureMap.end ())
700- pCurrentTexture = itInternal->second ;
701- }
678+ auto itInternal = txdTextureMap.find (szInternalName);
679+ if (itInternal != txdTextureMap.end ())
680+ pNewTexture = itInternal->second ;
702681 }
703682 }
704683 }
705684
706- // If we found a texture and it's different from the material's current texture, update it
707- // Validate the found texture to prevent crash from corrupted/freed texture in TXD
708- if (pCurrentTexture && pCurrentTexture != pOldTexture &&
709- SharedUtil::IsReadablePointer (pCurrentTexture, sizeof (RwTexture)))
710- {
711- RpMaterialSetTexture (pMaterial, pCurrentTexture);
712- }
685+ if (pNewTexture && pNewTexture != pOldTexture)
686+ RpMaterialSetTexture (pMaterial, pNewTexture);
713687 }
714688 }
715689}
716690
691+ void CRenderWareSA::RebindClumpTexturesToTxd (RpClump* pClump, unsigned short usTxdId)
692+ {
693+ if (!pClump)
694+ return ;
695+
696+ TxdTextureMap txdTextureMap = BuildTxdTextureMap (usTxdId);
697+ if (txdTextureMap.empty ())
698+ return ;
699+
700+ std::vector<RpAtomic*> atomicList;
701+ GetClumpAtomicList (pClump, atomicList);
702+
703+ for (RpAtomic* pAtomic : atomicList)
704+ RebindAtomicMaterials (pAtomic, txdTextureMap);
705+ }
706+
707+ void CRenderWareSA::RebindAtomicTexturesToTxd (RpAtomic* pAtomic, unsigned short usTxdId)
708+ {
709+ if (!pAtomic || !pAtomic->geometry )
710+ return ;
711+
712+ TxdTextureMap txdTextureMap = BuildTxdTextureMap (usTxdId);
713+ if (txdTextureMap.empty ())
714+ return ;
715+
716+ RebindAtomicMaterials (pAtomic, txdTextureMap);
717+ }
718+
717719// Replaces a vehicle/weapon/ped model
718720bool CRenderWareSA::ReplaceModel (RpClump* pNew, unsigned short usModelID, DWORD dwSetClumpFunction)
719721{
0 commit comments