Skip to content

Commit 3db6667

Browse files
committed
Refactors for safety, common crash, and UB/visual bug paths.
Validate modelinfo pointers, harden streaming/model handling, more reliable texture swaps for late models, and guard flows prone to GTA streaming system race conditions. Fix crashes from corrupt model info pointers in ppModelInfo array. This also fixes a game_sa crash @ 0x0004D022.
1 parent 5e14365 commit 3db6667

File tree

9 files changed

+882
-305
lines changed

9 files changed

+882
-305
lines changed

Client/game_sa/CModelInfoSA.cpp

Lines changed: 632 additions & 223 deletions
Large diffs are not rendered by default.

Client/game_sa/CModelInfoSA.h

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ static void* ARRAY_ModelLoaded = (char*)CStreaming__ms_aInfoForModel + 0x10;
2929

3030
#define FUNC_CStreaming__HasModelLoaded 0x4044C0
3131

32+
#define NUM_INVALID_PTR_THRESHOLD 0x10000
33+
3234
// CModelInfo/ARRAY_ModelInfo __thiscall to load/replace vehicle models
3335
#define FUNC_LoadVehicleModel 0x4C95C0
3436
#define FUNC_LoadWeaponModel 0x4C9910
@@ -343,18 +345,21 @@ class CModelInfoSA : public CModelInfo
343345
DWORD m_dwPendingInterfaceRef;
344346
CColModel* m_pCustomColModel;
345347
CColModelSAInterface* m_pOriginalColModelInterface;
348+
std::uint32_t m_colRefCount = 0;
349+
unsigned short m_usColSlot = 0xFFFF;
346350
std::uint16_t m_originalFlags = 0;
347351
RpClump* m_pCustomClump;
348352
static std::map<unsigned short, int> ms_RestreamTxdIDMap;
349353
static std::map<DWORD, float> ms_ModelDefaultLodDistanceMap;
350354
static std::map<DWORD, unsigned short> ms_ModelDefaultFlagsMap;
351355
static std::map<DWORD, BYTE> ms_ModelDefaultAlphaTransparencyMap;
352356
static std::unordered_map<std::uint32_t, std::map<VehicleDummies, CVector>> ms_ModelDefaultDummiesPosition;
353-
static std::map<CTimeInfoSAInterface*, CTimeInfoSAInterface*> ms_ModelDefaultModelTimeInfo;
357+
static std::map<DWORD, CTimeInfoSAInterface> ms_ModelDefaultModelTimeInfo;
354358
static std::unordered_map<DWORD, unsigned short> ms_OriginalObjectPropertiesGroups;
355359
static std::unordered_map<DWORD, std::pair<float, float>> ms_VehicleModelDefaultWheelSizes;
356360
static std::map<unsigned short, int> ms_DefaultTxdIDMap;
357361
SVehicleSupportedUpgrades m_ModelSupportedUpgrades;
362+
static void ClearModelDefaults(DWORD modelId);
358363

359364
public:
360365
CModelInfoSA();
@@ -470,15 +475,19 @@ class CModelInfoSA : public CModelInfo
470475

471476
void SetModelID(DWORD dwModelID) { m_dwModelID = dwModelID; }
472477

473-
RwObject* GetRwObject() { return m_pInterface ? m_pInterface->pRwObject : NULL; }
478+
RwObject* GetRwObject()
479+
{
480+
auto* pInterface = GetInterface();
481+
return pInterface ? pInterface->pRwObject : NULL;
482+
}
474483

475484
void MakePedModel(char* szTexture);
476485
void MakeObjectModel(ushort usBaseModelID);
477486
void MakeObjectDamageableModel(std::uint16_t usBaseModelID) override;
478487
void MakeVehicleAutomobile(ushort usBaseModelID);
479488
void MakeTimedObjectModel(ushort usBaseModelID);
480489
void MakeClumpModel(ushort usBaseModelID);
481-
void DeallocateModel(void);
490+
void DeallocateModel();
482491
unsigned int GetParentID() { return m_dwParentID; };
483492

484493
SVehicleSupportedUpgrades GetVehicleSupportedUpgrades() { return m_ModelSupportedUpgrades; }

Client/game_sa/CPoolsSA.cpp

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,16 @@ CVehicle* CPoolsSA::AddVehicle(CClientVehicle* pClientVehicle, std::uint16_t mod
8383

8484
// Ensure collision model is fully loaded to prevent crash at 0x002a65ef in SetupSuspensionLines
8585
CModelInfoSA* pModelInfo = static_cast<CModelInfoSA*>(pGame->GetModelInfo(model));
86-
if (!pModelInfo || !pModelInfo->GetInterface())
86+
if (!pModelInfo)
8787
return nullptr;
88+
89+
if (!pModelInfo->GetInterface())
90+
{
91+
pGame->GetStreaming()->RequestModel(model, 0x16);
92+
pGame->GetStreaming()->LoadAllRequestedModels(true, "CPoolsSA::AddVehicle");
93+
if (!pModelInfo->GetInterface())
94+
return nullptr;
95+
}
8896

8997
CBaseModelInfoSAInterface* pModelInterface = pModelInfo->GetInterface();
9098

@@ -270,10 +278,18 @@ CObject* CPoolsSA::AddObject(CClientObject* pClientObject, DWORD dwModelID, bool
270278
if (m_objectPool.ulCount < MAX_OBJECTS)
271279
{
272280
CModelInfoSA* pModelInfo = static_cast<CModelInfoSA*>(pGame->GetModelInfo(dwModelID));
273-
if (!pModelInfo || !pModelInfo->GetInterface())
274-
{
275-
AddReportLog(5552, SString("Failed to create object with model %d - model invalid or deallocated", dwModelID));
281+
if (!pModelInfo)
276282
return nullptr;
283+
284+
if (!pModelInfo->GetInterface())
285+
{
286+
pGame->GetStreaming()->RequestModel(dwModelID, 0x16);
287+
pGame->GetStreaming()->LoadAllRequestedModels(true, "CPoolsSA::AddObject");
288+
if (!pModelInfo->GetInterface())
289+
{
290+
AddReportLog(5552, SString("Failed to create object with model %d - model invalid or deallocated", dwModelID));
291+
return nullptr;
292+
}
277293
}
278294

279295
pObject = new (std::nothrow) CObjectSA(dwModelID, bBreakingDisabled);
@@ -771,10 +787,37 @@ uint CPoolsSA::GetModelIdFromClump(RpClump* pRpClump)
771787

772788
unsigned int NUMBER_OF_MODELS = pGame->GetBaseIDforTXD();
773789

790+
auto isValidPtr = [](const void* ptr) noexcept -> bool {
791+
if (!ptr)
792+
return false;
793+
794+
__try
795+
{
796+
const auto* p = static_cast<const CBaseModelInfoSAInterface*>(ptr);
797+
const auto* v = p->VFTBL;
798+
if (!v)
799+
return false;
800+
801+
volatile DWORD test = v->Destructor;
802+
static_cast<void>(test);
803+
return true;
804+
}
805+
__except (EXCEPTION_EXECUTE_HANDLER)
806+
{
807+
return false;
808+
}
809+
};
810+
774811
for (uint i = 1; i < NUMBER_OF_MODELS; i++)
775812
{
776-
CBaseModelInfoSAInterface* m_pInterface = ppModelInfo[i];
777-
if (m_pInterface && m_pInterface->pRwObject == (RwObject*)pRpClump)
813+
CBaseModelInfoSAInterface* pInterface = ppModelInfo[i];
814+
if (!pInterface)
815+
continue;
816+
817+
if (!isValidPtr(pInterface))
818+
continue;
819+
820+
if (pInterface->pRwObject == (RwObject*)pRpClump)
778821
{
779822
return i;
780823
}

Client/game_sa/CRenderWareSA.TextureReplacing.cpp

Lines changed: 73 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -629,39 +629,68 @@ namespace
629629
return true;
630630
}
631631

632-
void ReplaceTextureInModel(CModelInfoSA* pModelInfo, TextureSwapMap& swapMap)
632+
bool ReplaceTextureInModel(CModelInfoSA* pModelInfo, TextureSwapMap& swapMap)
633633
{
634634
if (!pModelInfo || swapMap.empty())
635-
return;
635+
return false;
636636

637637
RwObject* pRwObject = pModelInfo->GetRwObject();
638638
if (!pRwObject)
639-
return;
639+
return false;
640+
641+
const unsigned char rwType = pRwObject->type;
642+
const eModelInfoType modelType = pModelInfo->GetModelType();
643+
644+
if (modelType == eModelInfoType::UNKNOWN)
645+
{
646+
if (rwType == RP_TYPE_ATOMIC)
647+
{
648+
auto* pAtomic = reinterpret_cast<RpAtomic*>(pRwObject);
649+
ReplaceTextureInGeometry(pAtomic ? pAtomic->geometry : nullptr, swapMap);
650+
return true;
651+
}
652+
653+
if (rwType == RP_TYPE_CLUMP)
654+
{
655+
auto* pClump = reinterpret_cast<RpClump*>(pRwObject);
656+
if (pClump)
657+
RpClumpForAllAtomics(pClump, ReplaceTextureInAtomicCB, &swapMap);
658+
return true;
659+
}
660+
661+
return false;
662+
}
640663

641-
switch (pModelInfo->GetModelType())
664+
switch (modelType)
642665
{
643666
case eModelInfoType::ATOMIC:
644667
case eModelInfoType::TIME:
645668
case eModelInfoType::LOD_ATOMIC:
646669
{
670+
if (rwType != RP_TYPE_ATOMIC)
671+
return false;
672+
647673
RpAtomic* pAtomic = reinterpret_cast<RpAtomic*>(pRwObject);
648-
if (pAtomic)
649-
ReplaceTextureInGeometry(pAtomic->geometry, swapMap);
650-
break;
674+
ReplaceTextureInGeometry(pAtomic ? pAtomic->geometry : nullptr, swapMap);
675+
return true;
651676
}
652677

653678
case eModelInfoType::WEAPON:
654679
case eModelInfoType::CLUMP:
655680
case eModelInfoType::VEHICLE:
656681
case eModelInfoType::PED:
657-
case eModelInfoType::UNKNOWN:
658-
default:
659682
{
683+
if (rwType != RP_TYPE_CLUMP)
684+
return false;
685+
660686
RpClump* pClump = reinterpret_cast<RpClump*>(pRwObject);
661687
if (pClump)
662688
RpClumpForAllAtomics(pClump, ReplaceTextureInAtomicCB, &swapMap);
663-
break;
689+
return true;
664690
}
691+
692+
default:
693+
return false;
665694
}
666695
}
667696

@@ -750,7 +779,7 @@ namespace
750779
// This prevents ModelInfoTXDAddTextures from failing due to a model referencing a dead/reused slot.
751780
if (pModelInfo->GetTextureDictionaryID() == txdId)
752781
{
753-
if (slot && slot->rwTexDictonary && slot->usParentIndex == parentTxdId)
782+
if (slot && slot->rwTexDictonary && slot->usParentIndex == parentTxdId && CTxdStore_GetNumRefs(txdId) == 0)
754783
{
755784
RwTexDictionary* pTxd = slot->rwTexDictonary;
756785
RwListEntry* pRoot = &pTxd->textures.root;
@@ -952,6 +981,17 @@ void CRenderWareSA::ProcessPendingIsolatedTxdParents()
952981
const unsigned short childTxdId = it->second.usTxdId;
953982
const unsigned short parentTxdId = it->second.usParentTxdId;
954983

984+
auto* pModelInfoForCheck = static_cast<CModelInfoSA*>(pGame->GetModelInfo(modelId));
985+
if (!pModelInfoForCheck || !pModelInfoForCheck->IsAllocatedInArchive())
986+
{
987+
g_PendingIsolatedTxdParentsByModelId.erase(modelId);
988+
g_IsolatedTxdSlotsByModelId.erase(modelId);
989+
continue;
990+
}
991+
992+
if (!pModelInfoForCheck->GetRwObject())
993+
continue;
994+
955995
if (CTxdStore_GetTxd(parentTxdId) == nullptr)
956996
continue;
957997

@@ -969,14 +1009,20 @@ void CRenderWareSA::ProcessPendingIsolatedTxdParents()
9691009
RwObject* pRwObject = pModelInfo->GetRwObject();
9701010
if (pRwObject)
9711011
{
972-
const eModelInfoType modelType = pModelInfo->GetModelType();
1012+
eModelInfoType modelType = pModelInfo->GetModelType();
1013+
if (modelType == eModelInfoType::UNKNOWN)
1014+
{
1015+
if (pRwObject->type == RP_TYPE_ATOMIC)
1016+
modelType = eModelInfoType::ATOMIC;
1017+
else if (pRwObject->type == RP_TYPE_CLUMP)
1018+
modelType = eModelInfoType::CLUMP;
1019+
}
9731020
switch (modelType)
9741021
{
9751022
case eModelInfoType::PED:
9761023
case eModelInfoType::WEAPON:
9771024
case eModelInfoType::VEHICLE:
9781025
case eModelInfoType::CLUMP:
979-
case eModelInfoType::UNKNOWN:
9801026
{
9811027
RebindClumpTexturesToTxd(reinterpret_cast<RpClump*>(pRwObject), childTxdId);
9821028
break;
@@ -1112,11 +1158,14 @@ CModelTexturesInfo* CRenderWareSA::GetModelTexturesInfo(unsigned short usModelId
11121158
for (unsigned short modelId : pReplacement->usedInModelIds)
11131159
{
11141160
auto itCache = modelInfoCache.find(modelId);
1115-
if (itCache != modelInfoCache.end() && itCache->second)
1161+
if (!pModelInfo)
11161162
{
11171163
CModelInfoSA* pModInfo = itCache->second;
11181164
if (pModInfo->GetTextureDictionaryID() == usTxdId)
11191165
{
1166+
1167+
if (!pModelInfo->GetRwObject())
1168+
continue;
11201169
modelIds.push_back(modelId);
11211170
}
11221171
}
@@ -1432,8 +1481,6 @@ CModelTexturesInfo* CRenderWareSA::GetModelTexturesInfo(unsigned short usModelId
14321481
else
14331482
{
14341483
CRenderWareSA::DebugTxdAddRef(usTxdId, "GetModelTexturesInfo-cache-hit");
1435-
if (pModelInfo->GetModelType() == eModelInfoType::PED)
1436-
((void(__cdecl*)(unsigned short))FUNC_RemoveModel)(usModelId);
14371484
}
14381485

14391486
if (!pTxd)
@@ -1585,9 +1632,6 @@ bool CRenderWareSA::ModelInfoTXDAddTextures(SReplacementTextures* pReplacementTe
15851632
const unsigned short usParentTxdId = pParentInfo ? pParentInfo->GetTextureDictionaryID() : 0;
15861633

15871634
EnsureIsolatedTxdForRequestedModel(usModelId);
1588-
1589-
if (pModelInfo->GetTextureDictionaryID() == usParentTxdId && usParentTxdId != 0)
1590-
return false;
15911635
}
15921636
}
15931637
}
@@ -1796,25 +1840,13 @@ bool CRenderWareSA::ModelInfoTXDAddTextures(SReplacementTextures* pReplacementTe
17961840
for (unsigned short modelId : pReplacementTextures->usedInModelIds)
17971841
{
17981842
CModelInfoSA* pModelInfo = static_cast<CModelInfoSA*>(pGame->GetModelInfo(modelId));
1799-
if (pModelInfo)
1800-
targetModels.insert(pModelInfo);
1843+
if (!pModelInfo || !pModelInfo->GetRwObject())
1844+
continue;
1845+
targetModels.insert(pModelInfo);
18011846
}
18021847

18031848
for (CModelInfoSA* pModelInfo : targetModels)
18041849
ReplaceTextureInModel(pModelInfo, swapMap);
1805-
1806-
for (CModelInfoSA* pModelInfo : targetModels)
1807-
{
1808-
if (!pModelInfo->GetRwObject())
1809-
{
1810-
for (const auto& entry : swapMap)
1811-
{
1812-
if (entry.first)
1813-
texturesStillReferenced.insert(entry.first);
1814-
}
1815-
break;
1816-
}
1817-
}
18181850
}
18191851

18201852
std::unordered_set<RwTexture*> copiesToDestroy;
@@ -2754,7 +2786,6 @@ void CRenderWareSA::ModelInfoTXDRemoveTextures(SReplacementTextures* pReplacemen
27542786
}
27552787
}
27562788

2757-
bool bSwapFailed = false;
27582789
if (!swapMap.empty())
27592790
{
27602791
std::vector<CModelInfoSA*> targetModels;
@@ -2767,22 +2798,20 @@ void CRenderWareSA::ModelInfoTXDRemoveTextures(SReplacementTextures* pReplacemen
27672798
if (!pModelInfo)
27682799
continue;
27692800

2801+
if (!pModelInfo->GetRwObject())
2802+
continue;
2803+
27702804
if (seenModels.insert(pModelInfo).second)
27712805
targetModels.push_back(pModelInfo);
27722806
}
27732807

27742808
for (CModelInfoSA* pModelInfo : targetModels)
27752809
{
2776-
ReplaceTextureInModel(pModelInfo, swapMap);
2777-
}
2810+
if (!pModelInfo)
2811+
continue;
27782812

2779-
for (CModelInfoSA* pModelInfo : targetModels)
2780-
{
2781-
if (!pModelInfo->GetRwObject())
2782-
{
2783-
bSwapFailed = true;
2784-
break;
2785-
}
2813+
if (pModelInfo->GetRwObject())
2814+
ReplaceTextureInModel(pModelInfo, swapMap);
27862815
}
27872816
}
27882817

@@ -2793,9 +2822,6 @@ void CRenderWareSA::ModelInfoTXDRemoveTextures(SReplacementTextures* pReplacemen
27932822

27942823
bool bWasSwapped = swapMap.find(pOldTexture) != swapMap.end();
27952824

2796-
if (bSwapFailed && bWasSwapped)
2797-
continue;
2798-
27992825
if (!bWasSwapped && !IsReadableTexture(pOldTexture))
28002826
continue;
28012827

0 commit comments

Comments
 (0)