Skip to content

Commit 65bd0a4

Browse files
committed
1.6 (C++ 14 alternative code).. Addendum to 4efb26e: to include fixes for MTA's top 5 crashes that include SA offsets 0x003F3A17 & 0x003F374A, as well as other bugs and memory leaks.
1 parent 4efb26e commit 65bd0a4

File tree

3 files changed

+142
-54
lines changed

3 files changed

+142
-54
lines changed

Client/game_sa/CRenderWareSA.TextureReplacing.cpp

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

153153
namespace
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)
765793
bool 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
////////////////////////////////////////////////////////////////
812853
bool 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
////////////////////////////////////////////////////////////////
10661111
void 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();

Client/game_sa/CRenderWareSA.cpp

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -905,10 +905,30 @@ void CRenderWareSA::GetTxdTextures(std::vector<RwTexture*>& outTextureList, RwTe
905905
if (!pTXD)
906906
return;
907907

908+
// Validate TXD structure is readable (includes textures member)
908909
if (!SharedUtil::IsReadablePointer(pTXD, sizeof(*pTXD)))
909910
return;
910911

911-
if (!SharedUtil::IsReadablePointer(&pTXD->textures, sizeof(pTXD->textures)))
912+
// Validate the linked list structure to prevent crash at 0x007F374A
913+
// The crash occurs when next pointer is invalid and gets deref'd during iteration
914+
RwListEntry* firstNode = pTXD->textures.root.next;
915+
if (!SharedUtil::IsReadablePointer(firstNode, sizeof(RwListEntry)))
916+
return;
917+
918+
// Check for empty list (next points back to root - valid case)
919+
if (firstNode == &pTXD->textures.root)
920+
return; // Empty TXD is valid
921+
922+
// Validate the first texture node structure
923+
// The texture pointer is at (node - offsetof(RwTexture, TXDList))
924+
// which is (node - 8) since TXDList is at offset 8 in RwTexture
925+
RwTexture* firstTexture = (RwTexture*)((char*)firstNode - 8);
926+
if (!SharedUtil::IsReadablePointer(firstTexture, sizeof(RwTexture)))
927+
return;
928+
929+
// Validate that first node's next pointer is also readable
930+
// This catches most corruption cases where the list is broken
931+
if (!SharedUtil::IsReadablePointer(firstNode->next, sizeof(RwListEntry)))
912932
return;
913933

914934
constexpr std::size_t kMaxReasonableTextures = 8192;
@@ -932,14 +952,22 @@ void CRenderWareSA::GetTxdTextures(std::vector<RwTexture*>& outTextureList, RwTe
932952
////////////////////////////////////////////////////////////////
933953
bool CRenderWareSA::StaticGetTextureCB(RwTexture* texture, std::vector<RwTexture*>* pTextureList)
934954
{
955+
// Fast null check before any heavy validation
935956
if (!texture || !pTextureList)
936957
return false;
937958

938-
// Sanity check: prevent excessive allocations from corrupted/malicious TXDs
959+
// Prevent excessive allocations from corrupted TXDs
939960
constexpr std::size_t kMaxReasonableTextures = 8192;
940961
if (pTextureList->size() >= kMaxReasonableTextures)
941962
return false; // Stop enumeration
942963

964+
// Note: We don't validate readability here for performance reasons.
965+
// The upfront validation in GetTxdTextures catches the first two nodes,
966+
// and the SA function crashes before calling this callback if the
967+
// linked list is corrupted mid-iteration. If we reach here with a bad
968+
// pointer, we'll crash in push_back, but that's acceptable vs the cost
969+
// of VirtualQuery on every texture in every TXD.
970+
943971
pTextureList->push_back(texture);
944972
return true;
945973
}

Client/sdk/game/CRenderWare.h

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,23 @@ struct SReplacementTextures
5050
if (!texture)
5151
return;
5252

53-
if (RwTextureDestroy)
53+
// Fast validation: Check if pointer looks reasonable (not in low invalid range)
54+
// This catches most corruption cases without expensive VirtualQuery
55+
const uintptr_t addr = reinterpret_cast<uintptr_t>(texture);
56+
constexpr uintptr_t kMinUserAddress = 0x10000; // Below this is always invalid (null page protection)
57+
58+
// Note: Upper bound check not needed on x86 with LARGEADDRESSAWARE, as MTA has (full 4GB accessible)
59+
// On x86, uintptr_t cannot exceed 0xFFFFFFFF, so all pointers >= 0x10000 are potentially valid
60+
if (addr < kMinUserAddress)
61+
return;
62+
63+
// Only destroy if texture is orphaned (not owned by any TXD)
64+
// If txd != nullptr, the TXD owns it and will destroy it
65+
// Note: We access texture->txd without IsReadablePointer for performance.
66+
// If the pointer is invalid, we'll crash here, but that's acceptable vs
67+
// the cost of VirtualQuery on every texture destruction (1-2ms per 1000 textures).
68+
// The address range check above catches most corruption cases.
69+
if (texture->txd == nullptr && RwTextureDestroy)
5470
{
5571
RwTextureDestroy(texture);
5672
}
@@ -69,13 +85,14 @@ struct SReplacementTextures
6985
bool bTexturesAreCopies = false;
7086
};
7187

72-
std::vector<RwTexture*> textures; // List of textures we want to inject into TXD's
73-
std::vector<SPerTxd> perTxdList; // TXD's which have been modified
74-
std::vector<ushort> usedInTxdIds;
75-
std::vector<ushort> usedInModelIds;
88+
std::vector<RwTexture*> textures; // Raw pointers to textures (for compatibility)
89+
std::vector<SPerTxd> perTxdList; // TXD's which have been modified
90+
std::vector<ushort> usedInTxdIds;
91+
std::vector<ushort> usedInModelIds;
7692
std::unordered_set<ushort> usedInTxdIdLookup;
7793
std::unordered_set<ushort> usedInModelIdLookup;
7894
std::unordered_map<ushort, std::size_t> perTxdIndexLookup;
95+
std::vector<TextureOwner> ownedTextures; // Owned textures with automatic lifetime management (MUST BE LAST for binary compatibility)
7996
};
8097

8198
// Shader layers to render

0 commit comments

Comments
 (0)