@@ -366,25 +366,57 @@ namespace
366366 return pTexture && SharedUtil::IsReadablePointer (pTexture, sizeof (*pTexture));
367367 }
368368
369- size_t GetTextureNameLength (const RwTexture* const pTexture)
369+ // Safe wrapper for RwTexDictionaryFindNamedTexture that validates TXD structure
370+ // Prevents crash at 0x003F3A17
371+ RwTexture* SafeFindNamedTexture (RwTexDictionary* pTxd, const char * name)
370372 {
371- if (!pTexture)
372- return 0 ;
373+ // Validate TXD structure first
374+ if (!pTxd || !name)
375+ return nullptr ;
376+
377+ if (!IsValidTexDictionary (pTxd))
378+ return nullptr ;
379+
380+ // Read first node safely after validation
381+ RwListEntry* pFirstNode = pTxd->textures .root .next ;
382+
383+ // Check for empty list
384+ if (!pFirstNode || pFirstNode == &pTxd->textures .root )
385+ return nullptr ;
386+
387+ // Validate first texture in list before SA iterates
388+ // Container_of pattern: RwTexture contains RwListEntry at offset
389+ constexpr std::size_t kTxdListOffset = offsetof (RwTexture, TXDList);
390+ RwTexture* pFirstTexture = reinterpret_cast <RwTexture*>(reinterpret_cast <char *>(pFirstNode) - kTxdListOffset );
391+ if (!IsValidTexturePtr (pFirstTexture))
392+ return nullptr ;
393+
394+ // Safe to call SA native - we've validated the linked list structure
395+ return RwTexDictionaryFindNamedTexture (pTxd, name);
396+ }
373397
398+ // Fast path: caller guarantees pTexture is valid (already validated with IsValidTexturePtr)
399+ // Returns pointer and length for C++14 compatibility (no std::string_view in parameters)
400+ __forceinline void GetTextureNameViewUnsafe (const RwTexture* const pTexture, const char *& outName, std::size_t & outLength)
401+ {
374402 const char * p = pTexture->name ;
375403 const char * end = p + RW_TEXTURE_NAME_LENGTH;
376404 while (p < end && *p != ' \0 ' )
377405 ++p;
378- return static_cast <size_t >(p - pTexture->name );
406+
407+ outName = pTexture->name ;
408+ outLength = static_cast <std::size_t >(p - pTexture->name );
379409 }
380410
381411 TextureIndex::Name ExtractTextureName (const RwTexture* pTexture)
382412 {
383413 TextureIndex::Name name;
384- if (pTexture)
414+ if (IsValidTexturePtr ( pTexture) )
385415 {
386- const size_t length = GetTextureNameLength (pTexture);
387- name.Assign (pTexture->name , length);
416+ const char * texName = nullptr ;
417+ std::size_t texLen = 0 ;
418+ GetTextureNameViewUnsafe (pTexture, texName, texLen);
419+ name.Assign (texName, texLen);
388420 }
389421 return name;
390422 }
@@ -418,7 +450,7 @@ namespace
418450 for (auto & record : info.originalTextures )
419451 {
420452 if (!IsValidTexturePtr (record.texture ) && !record.name .Empty ())
421- record.texture = RwTexDictionaryFindNamedTexture (pTxd, record.name .CStr ());
453+ record.texture = SafeFindNamedTexture (pTxd, record.name .CStr ());
422454 }
423455 }
424456
@@ -446,7 +478,7 @@ namespace
446478
447479 auto & record = info.originalTextures [idx];
448480 if (!IsValidTexturePtr (record.texture ) && canReload && !record.name .Empty ())
449- record.texture = RwTexDictionaryFindNamedTexture (pTxd, record.name .CStr ());
481+ record.texture = SafeFindNamedTexture (pTxd, record.name .CStr ());
450482
451483 return IsValidTexturePtr (record.texture ) ? record.texture : nullptr ;
452484 };
@@ -480,7 +512,7 @@ namespace
480512 if (lookupName.Empty ())
481513 return nullptr ;
482514
483- RwTexture* pFound = RwTexDictionaryFindNamedTexture (pTxd, lookupName.CStr ());
515+ RwTexture* pFound = SafeFindNamedTexture (pTxd, lookupName.CStr ());
484516 if (!pFound)
485517 return nullptr ;
486518
@@ -541,8 +573,9 @@ namespace
541573 continue ;
542574
543575 RwTexture* pOriginalTexture = (idx < perTxdInfo.replacedOriginals .size ()) ? perTxdInfo.replacedOriginals [idx] : nullptr ;
544- const std::size_t replacementNameLen = GetTextureNameLength (pReplacementTexture);
545- const char * replacementName = pReplacementTexture ? pReplacementTexture->name : nullptr ;
576+ const char * replacementName = nullptr ;
577+ std::size_t replacementNameLen = 0 ;
578+ GetTextureNameViewUnsafe (pReplacementTexture, replacementName, replacementNameLen); // Already validated above
546579 RwTexture* pResolvedOriginal = ResolveOriginalTexture (info, pTxd, pOriginalTexture, replacementName, replacementNameLen);
547580
548581 if (pResolvedOriginal)
@@ -684,8 +717,12 @@ ModelTexturesInfoGuard AcquireModelTexturesInfo(ushort usModelId)
684717 {
685718 // TXD was unloaded - try to reload it
686719 guard.info ->pTxd = CTxdStore_GetTxd (usTxdId);
687-
688- // If still invalid, the cached entry is stale and needs rebuild
720+
721+ // Refresh cached texture pointers after TXD reload
722+ // This prevents stale texture pointers from causing crashes
723+ RefreshOriginalTextureCache (*guard.info , guard.info ->pTxd );
724+
725+ // If still invalid after refresh, the cached entry is stale and needs rebuild
689726 if (!IsValidTexDictionary (guard.info ->pTxd ))
690727 {
691728 // Invalidate the entry - caller should handle this gracefully
@@ -818,7 +855,7 @@ bool CRenderWareSA::ModelInfoTXDLoadTextures(SReplacementTextures* pReplacementT
818855 // Transfer ownership to unique_ptr with custom deleter
819856 pReplacementTextures->ownedTextures .reserve (loadedTextures.size ());
820857 pReplacementTextures->textures .reserve (loadedTextures.size ());
821-
858+
822859 for (RwTexture* pTexture : loadedTextures)
823860 {
824861 if (!pTexture)
@@ -919,7 +956,7 @@ bool CRenderWareSA::ModelInfoTXDAddTextures(SReplacementTextures* pReplacementTe
919956
920957 // Check if source textures have changed - if so, need to rebuild the entry
921958 bool needsRebuild = (pPerTxdInfo->usingTextures .size () != pReplacementTextures->textures .size ());
922-
959+
923960 if (!needsRebuild)
924961 {
925962 if (pPerTxdInfo->bTexturesAreCopies )
@@ -933,8 +970,8 @@ bool CRenderWareSA::ModelInfoTXDAddTextures(SReplacementTextures* pReplacementTe
933970 RwTexture* pUsing = pPerTxdInfo->usingTextures [i];
934971
935972 // Validate both textures and rasters before accessing to prevent crashes
936- if (pSource && pUsing && IsValidTexturePtr (pSource) && IsValidTexturePtr (pUsing) &&
937- pSource->raster && pUsing-> raster && pSource-> raster != pUsing->raster )
973+ if (pSource && pUsing && IsValidTexturePtr (pSource) && IsValidTexturePtr (pUsing) && pSource-> raster && pUsing-> raster &&
974+ pSource->raster != pUsing->raster )
938975 {
939976 needsRebuild = true ;
940977 break ;
@@ -946,8 +983,7 @@ bool CRenderWareSA::ModelInfoTXDAddTextures(SReplacementTextures* pReplacementTe
946983 // For non-copies, check if pointers changed
947984 for (std::size_t i = 0 ; i < pReplacementTextures->textures .size (); ++i)
948985 {
949- if (i >= pPerTxdInfo->usingTextures .size () ||
950- pReplacementTextures->textures [i] != pPerTxdInfo->usingTextures [i])
986+ if (i >= pPerTxdInfo->usingTextures .size () || pReplacementTextures->textures [i] != pPerTxdInfo->usingTextures [i])
951987 {
952988 needsRebuild = true ;
953989 break ;
@@ -989,7 +1025,8 @@ bool CRenderWareSA::ModelInfoTXDAddTextures(SReplacementTextures* pReplacementTe
9891025 bool cloneFailure = false ;
9901026 for (RwTexture* pSourceTexture : pReplacementTextures->textures )
9911027 {
992- if (!pSourceTexture)
1028+ // Validate texture pointer before dereferencing its fields
1029+ if (!IsValidTexturePtr (pSourceTexture))
9931030 {
9941031 cloneFailure = true ;
9951032 break ;
@@ -1048,16 +1085,18 @@ bool CRenderWareSA::ModelInfoTXDAddTextures(SReplacementTextures* pReplacementTe
10481085 dassert (bHasValidTxd);
10491086 for (RwTexture* pNewTexture : pPerTxdInfo->usingTextures )
10501087 {
1051- const std::size_t replacementNameLen = GetTextureNameLength (pNewTexture);
1052- const char * replacementName = pNewTexture ? pNewTexture->name : nullptr ;
1088+ // All textures in usingTextures were validated during preparation phase
1089+ const char * replacementName = nullptr ;
1090+ std::size_t replacementNameLen = 0 ;
1091+ GetTextureNameViewUnsafe (pNewTexture, replacementName, replacementNameLen);
10531092
10541093 RwTexture* pExistingTexture = LookupOriginalTexture (*pInfo, replacementName, replacementNameLen);
10551094 if (!pExistingTexture && bHasValidTxd && replacementName && replacementNameLen > 0 )
10561095 {
10571096 // Create null-terminated name for safe call
10581097 TextureIndex::Name safeName;
10591098 safeName.Assign (replacementName, replacementNameLen);
1060- pExistingTexture = RwTexDictionaryFindNamedTexture (pInfo->pTxd , safeName.CStr ());
1099+ pExistingTexture = SafeFindNamedTexture (pInfo->pTxd , safeName.CStr ());
10611100 }
10621101
10631102 if (pExistingTexture && bHasValidTxd)
@@ -1177,7 +1216,7 @@ void CRenderWareSA::ModelInfoTXDRemoveTextures(SReplacementTextures* pReplacemen
11771216 continue ;
11781217
11791218 // Use the already-safe record.name instead of pOriginalTexture->name
1180- if (!record.name .Empty () && !RwTexDictionaryFindNamedTexture (pTxd, record.name .CStr ()))
1219+ if (!record.name .Empty () && !SafeFindNamedTexture (pTxd, record.name .CStr ()))
11811220 {
11821221 RwTexture* const pAddedTexture = RwTexDictionaryAddTexture (pTxd, pOriginalTexture);
11831222 dassert (pAddedTexture == pOriginalTexture);
0 commit comments