From a37335111ed79df8c0439d978558ab51f16925b9 Mon Sep 17 00:00:00 2001 From: Flashmyname Date: Thu, 10 Sep 2026 18:06:12 +0200 Subject: [PATCH] Fix out of bounds write when replacing a model with _dam parts --- Client/game_sa/CFileLoaderSA.cpp | 13 ++++++++++++- Client/game_sa/CFileLoaderSA.h | 1 + Client/game_sa/CRenderWareSA.cpp | 8 +++++--- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/Client/game_sa/CFileLoaderSA.cpp b/Client/game_sa/CFileLoaderSA.cpp index d5ce6c911ee..8136a202a3d 100644 --- a/Client/game_sa/CFileLoaderSA.cpp +++ b/Client/game_sa/CFileLoaderSA.cpp @@ -54,12 +54,16 @@ CEntitySAInterface* CFileLoaderSA::LoadObjectInstance(const char* szLine) return CFileLoader_LoadObjectInstance(szLine); } +class CDamagableModelInfo; + class CAtomicModelInfo { public: void DeleteRwObject() { ((void(__thiscall*)(CAtomicModelInfo*))(*(void***)this)[8])(this); } void SetAtomic(RpAtomic* atomic) { ((void(__thiscall*)(CAtomicModelInfo*, RpAtomic*))(*(void***)this)[15])(this, atomic); } + + CDamagableModelInfo* AsDamageAtomicModelInfoPtr() { return ((CDamagableModelInfo * (__thiscall*)(CAtomicModelInfo*))(*(void***)this)[2])(this); } }; class CDamagableModelInfo @@ -210,12 +214,19 @@ RpAtomic* CFileLoader_SetRelatedModelInfoCB(RpAtomic* atomic, SRelatedModelInfo* GetNameAndDamage(frameNodeName, name, bDamage); } + CDamagableModelInfo* pDamagableModelInfo = bDamage ? pAtomicModelInfo->AsDamageAtomicModelInfoPtr() : nullptr; + if (bDamage && !pDamagableModelInfo) + { + // Returning null would stop RpClumpForAllAtomics, so leave the atomic with the clump + pRelatedModelInfo->bAtomicNotConsumed = true; + return atomic; + } + CVisibilityPlugins_SetAtomicRenderCallback(atomic, 0); RpAtomic* pOldAtomic = reinterpret_cast(pBaseModelInfo->pRwObject); if (bDamage) { - auto pDamagableModelInfo = reinterpret_cast(pAtomicModelInfo); pDamagableModelInfo->SetDamagedAtomic(atomic); } else diff --git a/Client/game_sa/CFileLoaderSA.h b/Client/game_sa/CFileLoaderSA.h index 7819aebc92c..534c8aa0501 100644 --- a/Client/game_sa/CFileLoaderSA.h +++ b/Client/game_sa/CFileLoaderSA.h @@ -13,6 +13,7 @@ struct SRelatedModelInfo { RpClump* pClump; bool bDeleteOldRwObject; + bool bAtomicNotConsumed; }; struct SFileObjectInstance diff --git a/Client/game_sa/CRenderWareSA.cpp b/Client/game_sa/CRenderWareSA.cpp index 7de9737519d..40d678bfc19 100644 --- a/Client/game_sa/CRenderWareSA.cpp +++ b/Client/game_sa/CRenderWareSA.cpp @@ -527,9 +527,11 @@ bool AtomicsReplacer(RpAtomic* pAtomic, void* data) relatedModelInfo.bDeleteOldRwObject = true; CFileLoader_SetRelatedModelInfoCB(pAtomic, &relatedModelInfo); - // The above function adds a reference to the model's TXD by either - // calling CAtomicModelInfo::SetAtomic or CDamagableModelInfo::SetDamagedAtomic. Remove it again. - CTxdStore_RemoveRef(pData->usTxdID); + // The above function adds a reference to the model's TXD when it calls + // CAtomicModelInfo::SetAtomic or CDamagableModelInfo::SetDamagedAtomic. It calls neither if the + // atomic was left with the clump, so only remove the reference when one was taken. + if (!relatedModelInfo.bAtomicNotConsumed) + CTxdStore_RemoveRef(pData->usTxdID); return true; }