Skip to content

Commit 4425e2f

Browse files
Synchronize changes from 1.6 branch [ci skip]
b7362a0 1.6: Improve crash fix from 65bd0a4
2 parents 1a732bf + b7362a0 commit 4425e2f

File tree

1 file changed

+64
-25
lines changed

1 file changed

+64
-25
lines changed

Client/game_sa/CRenderWareSA.TextureReplacing.cpp

Lines changed: 64 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)