From e9e7928dac6ff288f35e19f1de936c59bb5b6685 Mon Sep 17 00:00:00 2001 From: FileEX Date: Sun, 23 Feb 2025 02:50:10 +0100 Subject: [PATCH 01/10] Fix old bug --- Client/game_sa/CFileLoaderSA.cpp | 52 +------- Client/game_sa/CModelInfoSA.cpp | 79 ++++++++++++ Client/game_sa/CModelInfoSA.h | 35 +++++- Client/game_sa/CRenderWareSA.cpp | 126 +++++++++++++++++--- Client/game_sa/CRenderWareSA.h | 10 ++ Client/game_sa/CVisibilityPluginsSA.cpp | 8 ++ Client/game_sa/CVisibilityPluginsSA.h | 2 + Client/mods/deathmatch/logic/CClientDFF.cpp | 8 ++ Client/sdk/game/CModelInfo.h | 4 + Client/sdk/game/CRenderWare.h | 3 + Client/sdk/game/CVisibilityPlugins.h | 2 + 11 files changed, 262 insertions(+), 67 deletions(-) diff --git a/Client/game_sa/CFileLoaderSA.cpp b/Client/game_sa/CFileLoaderSA.cpp index 67cb476cf51..feb6d89be41 100644 --- a/Client/game_sa/CFileLoaderSA.cpp +++ b/Client/game_sa/CFileLoaderSA.cpp @@ -12,6 +12,7 @@ #include "gamesa_renderware.h" #include "CFileLoaderSA.h" #include "CModelInfoSA.h" +#include "CRenderWareSA.h" CFileLoaderSA::CFileLoaderSA() { @@ -48,53 +49,6 @@ class CDamagableModelInfo void CDamagableModelInfo::SetDamagedAtomic(RpAtomic* atomic) { ((void(__thiscall*)(CDamagableModelInfo*, RpAtomic*))0x4C48D0)(this, atomic); } }; -static char* GetFrameNodeName(RwFrame* frame) -{ - return ((char*(__cdecl*)(RwFrame*))0x72FB30)(frame); -} - -// Originally there was a possibility for this function to cause buffer overflow -// It should be fixed here. -template -void GetNameAndDamage(const char* nodeName, char (&outName)[OutBuffSize], bool& outDamage) -{ - const auto nodeNameLen = strlen(nodeName); - - const auto NodeNameEndsWith = [=](const char* with) { - const auto withLen = strlen(with); - // dassert(withLen <= nodeNameLen); - return withLen <= nodeNameLen /*dont bother checking otherwise, because it might cause a crash*/ - && strncmp(nodeName + nodeNameLen - withLen, with, withLen) == 0; - }; - - // Copy `nodeName` into `outName` with `off` trimmed from the end - // Eg.: `dmg_dam` with `off = 4` becomes `dmg` - const auto TerminatedCopy = [&](size_t off) { - dassert(nodeNameLen - off < OutBuffSize); - strncpy_s(outName, nodeName, - std::min(nodeNameLen - off, OutBuffSize - 1)); // By providing `OutBuffSize - 1` it is ensured the array will be null terminated - }; - - if (NodeNameEndsWith("_dam")) - { - outDamage = true; - TerminatedCopy(sizeof("_dam") - 1); - } - else - { - outDamage = false; - if (NodeNameEndsWith("_l0") || NodeNameEndsWith("_L0")) - { - TerminatedCopy(sizeof("_l0") - 1); - } - else - { - dassert(nodeNameLen < OutBuffSize); - strncpy_s(outName, OutBuffSize, nodeName, OutBuffSize - 1); - } - } -} - static void CVisibilityPlugins_SetAtomicRenderCallback(RpAtomic* pRpAtomic, RpAtomic* (*renderCB)(RpAtomic*)) { return ((void(__cdecl*)(RpAtomic*, RpAtomic * (*renderCB)(RpAtomic*)))0x7328A0)(pRpAtomic, renderCB); @@ -170,9 +124,9 @@ RpAtomic* CFileLoader_SetRelatedModelInfoCB(RpAtomic* atomic, SRelatedModelInfo* CBaseModelInfoSAInterface* pBaseModelInfo = CModelInfo_ms_modelInfoPtrs[gAtomicModelId]; auto pAtomicModelInfo = reinterpret_cast(pBaseModelInfo); RwFrame* pOldFrame = reinterpret_cast(atomic->object.object.parent); - char* frameNodeName = GetFrameNodeName(pOldFrame); + char* frameNodeName = CRenderWareSA::GetFrameNodeName(pOldFrame); bool bDamage = false; - GetNameAndDamage(frameNodeName, name, bDamage); + CRenderWareSA::GetNameAndDamage(frameNodeName, name, bDamage); CVisibilityPlugins_SetAtomicRenderCallback(atomic, 0); RpAtomic* pOldAtomic = reinterpret_cast(pBaseModelInfo->pRwObject); diff --git a/Client/game_sa/CModelInfoSA.cpp b/Client/game_sa/CModelInfoSA.cpp index 8f1b624b906..1e59d6fd9f8 100644 --- a/Client/game_sa/CModelInfoSA.cpp +++ b/Client/game_sa/CModelInfoSA.cpp @@ -35,6 +35,7 @@ std::map CModelInfo std::unordered_map CModelInfoSA::ms_OriginalObjectPropertiesGroups; std::unordered_map> CModelInfoSA::ms_VehicleModelDefaultWheelSizes; std::map CModelInfoSA::ms_DefaultTxdIDMap; +std::unordered_map CModelInfoSA::m_convertedModelInterfaces; union tIdeFlags { @@ -1512,6 +1513,7 @@ bool CModelInfoSA::SetCustomModel(RpClump* pClump) case eModelInfoType::ATOMIC: case eModelInfoType::LOD_ATOMIC: case eModelInfoType::TIME: + case eModelInfoType::CLUMP: success = pGame->GetRenderWare()->ReplaceAllAtomicsInModel(pClump, static_cast(m_dwModelID)); break; default: @@ -1530,6 +1532,16 @@ void CModelInfoSA::RestoreOriginalModel() pGame->GetStreaming()->RemoveModel(m_dwModelID); } + // Restore original interface if model was converted + if (MapContains(m_convertedModelInterfaces, m_dwModelID)) + { + CBaseModelInfoSAInterface* currentInterface = ppModelInfo[m_dwModelID]; + ppModelInfo[m_dwModelID] = MapGet(m_convertedModelInterfaces, m_dwModelID); + + delete currentInterface; + MapRemove(m_convertedModelInterfaces, m_dwModelID); + } + // Reset the stored custom vehicle clump m_pCustomClump = NULL; } @@ -1799,6 +1811,73 @@ void CModelInfoSA::MakeClumpModel(ushort usBaseID) CopyStreamingInfoFromModel(usBaseID); } +bool CModelInfoSA::ConvertToClump() +{ + if (GetModelType() != eModelInfoType::ATOMIC) + return false; + + // Get current interface + CAtomicModelInfoSAInterface* currentAtomicInterface = static_cast(ppModelInfo[m_dwModelID]); + if (!currentAtomicInterface) + return false; + + // Create new clump interface + CClumpModelInfoSAInterface* newClumpInterface = new CClumpModelInfoSAInterface(); + MemCpyFast(newClumpInterface, currentAtomicInterface, sizeof(CClumpModelInfoSAInterface)); + newClumpInterface->m_nAnimFileIndex = -1; + + // (FileEX): We do not destroy or set pRwObject to nullptr here + // because our IsLoaded code expects the RwObject to exist. + // We destroy the old RwObject in CRenderWareSA::ReplaceAllAtomicsInModel after passing the IsLoaded condition in the SetCustomModel. + + // Set CClumpModelInfo vtbl after copying data + newClumpInterface->VFTBL = reinterpret_cast(VTBL_CClumpModelInfo); + + // Set new interface for ModelInfo + ppModelInfo[m_dwModelID] = newClumpInterface; + + // Store original interface + MapSet(m_convertedModelInterfaces, m_dwModelID, static_cast(currentAtomicInterface)); + return true; +} + +bool CModelInfoSA::ConvertToAtomic() +{ + if (GetModelType() != eModelInfoType::CLUMP) + return false; + + // Get current interface + CClumpModelInfoSAInterface* currentClumpInterface = static_cast(ppModelInfo[m_dwModelID]); + if (!currentClumpInterface) + return false; + + // Create new atomic interface + CAtomicModelInfoSAInterface* newAtomicInterface = new CAtomicModelInfoSAInterface(); + MemCpyFast(newAtomicInterface, currentClumpInterface, sizeof(CAtomicModelInfoSAInterface)); + + // (FileEX): We do not destroy or set pRwObject to nullptr here + // because our IsLoaded code expects the RwObject to exist. + // We destroy the old RwObject in CFileLoader_SetRelatedModelInfoCB after passing the IsLoaded condition in the SetCustomModel. + + // Set CAtomicModelInfo vtbl after copying data + newAtomicInterface->VFTBL = reinterpret_cast(VTBL_CAtomicModelInfo); + + // Set new interface for ModelInfo + ppModelInfo[m_dwModelID] = newAtomicInterface; + + // Store original interface + MapSet(m_convertedModelInterfaces, m_dwModelID, currentClumpInterface); + return true; +} + +CBaseModelInfoSAInterface* CModelInfoSA::GetOriginalInterface() const +{ + if (!MapContains(m_convertedModelInterfaces, m_dwModelID)) + return nullptr; + + return MapGet(m_convertedModelInterfaces, m_dwModelID); +} + void CModelInfoSA::MakeVehicleAutomobile(ushort usBaseID) { CVehicleModelInfoSAInterface* m_pInterface = new CVehicleModelInfoSAInterface(); diff --git a/Client/game_sa/CModelInfoSA.h b/Client/game_sa/CModelInfoSA.h index 6d0e00c758e..6f81f4a4a26 100644 --- a/Client/game_sa/CModelInfoSA.h +++ b/Client/game_sa/CModelInfoSA.h @@ -64,6 +64,15 @@ static void* ARRAY_ModelInfo = *(void**)(0x403DA4 + 3); #define VAR_CTempColModels_ModelPed1 0x968DF0 +#define FUNC_CClumpModelInfo_SetClump 0x4C4F70 +#define FUNC_CClumpModelInfo_DeleteRwObject 0x4C4E70 + +#define FUNC_CAtomicModelInfo_SetAtomic 0x4C4360 +#define FUNC_CAtomicModelInfo_DeleteRwObject 0x4C4440 + +#define VTBL_CClumpModelInfo 0x85BD30 +#define VTBL_CAtomicModelInfo 0x85BBF0 + class CBaseModelInfoSAInterface; class CModelInfoSAInterface { @@ -252,9 +261,19 @@ class CClumpModelInfoSAInterface : public CBaseModelInfoSAInterface char* m_animFileName; uint32_t m_nAnimFileIndex; }; + + void DeleteRwObject() { ((void(__thiscall*)(CClumpModelInfoSAInterface*))FUNC_CClumpModelInfo_DeleteRwObject)(this); } + void SetClump(RpClump* clump) { ((void(__thiscall*)(CClumpModelInfoSAInterface*, RpClump*))FUNC_CClumpModelInfo_SetClump)(this, clump); } }; -class CTimeModelInfoSAInterface : public CBaseModelInfoSAInterface +class CAtomicModelInfoSAInterface : public CBaseModelInfoSAInterface +{ +public: + void DeleteRwObject() { ((void(__thiscall*)(CAtomicModelInfoSAInterface*))FUNC_CAtomicModelInfo_DeleteRwObject)(this); } + void SetAtomic(RpAtomic* atomic) { ((void(__thiscall*)(CAtomicModelInfoSAInterface*, RpAtomic*))FUNC_CAtomicModelInfo_SetAtomic)(this, atomic); } +}; + +class CTimeModelInfoSAInterface : public CAtomicModelInfoSAInterface { public: CTimeInfoSAInterface timeInfo; @@ -268,10 +287,12 @@ class CVehicleModelUpgradePosnDesc }; static_assert(sizeof(CVehicleModelUpgradePosnDesc) == 0x20, "Invalid size of CVehicleModelUpgradePosnDesc class"); -class CDamageableModelInfoSAInterface : public CBaseModelInfoSAInterface +class CDamageableModelInfoSAInterface : public CAtomicModelInfoSAInterface { public: - void* m_damagedAtomic; + RpAtomic* m_damagedAtomic; + + void SetDamagedAtomic(RpAtomic* atomic) { ((void(__thiscall*)(RpAtomic*))FUNC_CAtomicModelInfo_SetAtomic)(atomic); } }; class CVehicleModelVisualInfoSAInterface // Not sure about this name. If somebody knows more, please change @@ -356,6 +377,9 @@ class CModelInfoSA : public CModelInfo static std::map ms_DefaultTxdIDMap; SVehicleSupportedUpgrades m_ModelSupportedUpgrades; + // Store original model interfaces after conersion clump->atomic or atomic->clump + static std::unordered_map m_convertedModelInterfaces; + public: CModelInfoSA(); @@ -489,6 +513,11 @@ class CModelInfoSA : public CModelInfo void RestoreObjectPropertiesGroup(); static void RestoreAllObjectsPropertiesGroups(); + // Model type conversion functions + bool ConvertToClump(); + bool ConvertToAtomic(); + CBaseModelInfoSAInterface* GetOriginalInterface() const; + // Vehicle towing functions bool IsTowableBy(CModelInfo* towingModel) override; diff --git a/Client/game_sa/CRenderWareSA.cpp b/Client/game_sa/CRenderWareSA.cpp index 56f5ce34e43..32fdb0d2236 100644 --- a/Client/game_sa/CRenderWareSA.cpp +++ b/Client/game_sa/CRenderWareSA.cpp @@ -23,6 +23,7 @@ #include "CRenderWareSA.ShaderMatching.h" #include "gamesa_renderware.h" #include "gamesa_renderware.hpp" +#include "CVisibilityPluginsSA.h" extern CCoreInterface* g_pCore; extern CGameSA* pGame; @@ -496,30 +497,66 @@ bool AtomicsReplacer(RpAtomic* pAtomic, void* data) return true; } -bool CRenderWareSA::ReplaceAllAtomicsInModel(RpClump* pNew, unsigned short usModelID) +bool CRenderWareSA::ReplaceAllAtomicsInModel(RpClump* pSrc, unsigned short usModelID) { CModelInfo* pModelInfo = pGame->GetModelInfo(usModelID); + if (!pModelInfo) + return true; - if (pModelInfo) + RpAtomic* oldAtomic = reinterpret_cast(pModelInfo->GetRwObject()); + if (reinterpret_cast(oldAtomic) == pSrc || DoContainTheSameGeometry(pSrc, nullptr, oldAtomic)) + return true; + + // Check if new model is clump or atomic + // pSrc->object.type is always RP_TYPE_CLUMP so check number of atomics + // to check if new model is clump or atomic + if (RpClumpGetNumAtomics(pSrc) > 1) { - RpAtomic* pOldAtomic = (RpAtomic*)pModelInfo->GetRwObject(); + // Get new interface but with old RwObject + auto* currentNewInterface = static_cast(pModelInfo->GetInterface()); - if (reinterpret_cast(pOldAtomic) != pNew && !DoContainTheSameGeometry(pNew, NULL, pOldAtomic)) - { - // Clone the clump that's to be replaced (FUNC_AtomicsReplacer removes the atomics from the source clump) - RpClump* pCopy = RpClumpClone(pNew); + // Destroy old RwObject (atomic type) + // We need to remove the RwObject from the original interface because + // the new interface points to the CClumpModelInfo vtbl + CBaseModelInfoSAInterface* originalInterface = pModelInfo->GetOriginalInterface(); + if (!originalInterface) + return false; - // Replace the atomics - SAtomicsReplacer data; - data.usTxdID = ((CBaseModelInfoSAInterface**)ARRAY_ModelInfo)[usModelID]->usTextureDictionary; - data.pClump = pCopy; + reinterpret_cast(originalInterface)->DeleteRwObject(); + currentNewInterface->pRwObject = nullptr; - MemPutFast((DWORD*)DWORD_AtomicsReplacerModelID, usModelID); - RpClumpForAllAtomics(pCopy, AtomicsReplacer, &data); + // Init new RwObject (clump type) + currentNewInterface->SetClump(pSrc); + } + else + { + auto* currentInterface = static_cast(pModelInfo->GetInterface()); - // Get rid of the now empty copied clump - RpClumpDestroy(pCopy); + // Destroy old RwObject (clump type) + // We need to remove the RwObject from the original interface because + // the new interface points to the CAtomicModelInfo vtbl + CBaseModelInfoSAInterface* originalInterface = pModelInfo->GetOriginalInterface(); + if (originalInterface) + { + reinterpret_cast(originalInterface)->DeleteRwObject(); + currentInterface->pRwObject = nullptr; } + else + currentInterface->DeleteRwObject(); + + RpAtomic* atomic = GetFirstAtomic(pSrc); + RwFrame* frame = RpGetFrame(atomic); + + char name[24]; + bool damage = false; + GetNameAndDamage(GetFrameNodeName(frame), name, damage); + + if (damage) + static_cast(currentInterface)->SetDamagedAtomic(atomic); + else + currentInterface->SetAtomic(atomic); + + pGame->GetVisibilityPlugins()->SetAtomicId(atomic, usModelID); } return true; @@ -927,6 +964,65 @@ RwFrame* CRenderWareSA::GetFrameFromName(RpClump* pRoot, SString strName) return RwFrameFindFrame(RpGetFrame(pRoot), strName); } +int CRenderWareSA::GetClumpNumOfAtomics(RpClump* clump) +{ + return RpClumpGetNumAtomics(clump); +} + +RpAtomic* CRenderWareSA::GetFirstAtomic(RpClump* clump) +{ + return ((RpAtomic*(__cdecl*)(RpClump*))FUNC_GetFirstAtomic)(clump); +} + +char* CRenderWareSA::GetFrameNodeName(RwFrame* frame) +{ + return ((char*(__cdecl*)(RwFrame*))FUNC_GetFrameNodeName)(frame); +} + +// Originally there was a possibility for this function to cause buffer overflow +// It should be fixed here. +template +void CRenderWareSA::GetNameAndDamage(const char* nodeName, char (&outName)[OutBuffSize], bool& outDamage) +{ + const auto nodeNameLen = strlen(nodeName); + + const auto NodeNameEndsWith = [=](const char* with) + { + const auto withLen = strlen(with); + // dassert(withLen <= nodeNameLen); + return withLen <= nodeNameLen /*dont bother checking otherwise, because it might cause a crash*/ + && strncmp(nodeName + nodeNameLen - withLen, with, withLen) == 0; + }; + + // Copy `nodeName` into `outName` with `off` trimmed from the end + // Eg.: `dmg_dam` with `off = 4` becomes `dmg` + const auto TerminatedCopy = [&](size_t off) + { + dassert(nodeNameLen - off < OutBuffSize); + strncpy_s(outName, nodeName, + std::min(nodeNameLen - off, OutBuffSize - 1)); // By providing `OutBuffSize - 1` it is ensured the array will be null terminated + }; + + if (NodeNameEndsWith("_dam")) + { + outDamage = true; + TerminatedCopy(sizeof("_dam") - 1); + } + else + { + outDamage = false; + if (NodeNameEndsWith("_l0") || NodeNameEndsWith("_L0")) + { + TerminatedCopy(sizeof("_l0") - 1); + } + else + { + dassert(nodeNameLen < OutBuffSize); + strncpy_s(outName, OutBuffSize, nodeName, OutBuffSize - 1); + } + } +} + void CRenderWareSA::CMatrixToRwMatrix(const CMatrix& mat, RwMatrix& rwOutMatrix) { rwOutMatrix.right = (RwV3d&)mat.vRight; diff --git a/Client/game_sa/CRenderWareSA.h b/Client/game_sa/CRenderWareSA.h index d79d9307206..a21d3f1b725 100644 --- a/Client/game_sa/CRenderWareSA.h +++ b/Client/game_sa/CRenderWareSA.h @@ -22,6 +22,9 @@ struct SShaderReplacementStats; struct STexInfo; struct STexTag; +#define FUNC_GetFirstAtomic 0x734820 +#define FUNC_GetFrameNodeName 0x72FB30 + class CRenderWareSA : public CRenderWare { public: @@ -122,6 +125,13 @@ class CRenderWareSA : public CRenderWare CModelTexturesInfo* GetModelTexturesInfo(ushort usModelId); RwFrame* GetFrameFromName(RpClump* pRoot, SString strName); + int GetClumpNumOfAtomics(RpClump* clump); + RpAtomic* GetFirstAtomic(RpClump* clump); + + static char* GetFrameNodeName(RwFrame* frame); + + template + static void GetNameAndDamage(const char* nodeName, char (&outName)[OutBuffSize], bool& outDamage); static void StaticSetHooks(); static void StaticSetClothesReplacingHooks(); diff --git a/Client/game_sa/CVisibilityPluginsSA.cpp b/Client/game_sa/CVisibilityPluginsSA.cpp index b5fe2d92c6f..c4cab5c1732 100644 --- a/Client/game_sa/CVisibilityPluginsSA.cpp +++ b/Client/game_sa/CVisibilityPluginsSA.cpp @@ -54,6 +54,14 @@ int CVisibilityPluginsSA::GetAtomicId(RwObject* pAtomic) return iResult; } +void CVisibilityPluginsSA::SetAtomicId(RpAtomic* atomic, int id) +{ + if (!atomic) + return; + + ((void(__cdecl*)(RpAtomic*, int))FUNC_CVisibilityPlugins_SetAtomicId)(atomic, id); +} + bool CVisibilityPluginsSA::InsertEntityIntoEntityList(void* entity, float distance, void* callback) { return ((bool(_cdecl*)(void*, float, void*))FUNC_CVisibilityPlugins_InsertEntityIntoEntityList)(entity, distance, callback); diff --git a/Client/game_sa/CVisibilityPluginsSA.h b/Client/game_sa/CVisibilityPluginsSA.h index f4a583bcf29..81ffc736ebe 100644 --- a/Client/game_sa/CVisibilityPluginsSA.h +++ b/Client/game_sa/CVisibilityPluginsSA.h @@ -15,12 +15,14 @@ #define FUNC_CVisiblityPlugins_SetClumpAlpha 0x732B00 #define FUNC_CVisibilityPlugins_GetAtomicId 0x732370 +#define FUNC_CVisibilityPlugins_SetAtomicId 0x732230 class CVisibilityPluginsSA : public CVisibilityPlugins { public: void SetClumpAlpha(RpClump* pClump, int iAlpha); int GetAtomicId(RwObject* pAtomic); + void SetAtomicId(RpAtomic* atomic, int id) override; bool InsertEntityIntoEntityList(void* entity, float distance, void* callback); }; diff --git a/Client/mods/deathmatch/logic/CClientDFF.cpp b/Client/mods/deathmatch/logic/CClientDFF.cpp index 07e718a8b92..20ced90daa4 100644 --- a/Client/mods/deathmatch/logic/CClientDFF.cpp +++ b/Client/mods/deathmatch/logic/CClientDFF.cpp @@ -332,6 +332,14 @@ bool CClientDFF::ReplaceObjectModel(RpClump* pClump, ushort usModel, bool bAlpha // Grab the model info for that model and replace the model CModelInfo* pModelInfo = g_pGame->GetModelInfo(usModel); + // If new model is clump or atomic then we need to convert existing model + // pClump->object.type is always RP_TYPE_CLUMP so check number of atomics + // to check if new model is clump or atomic + if (g_pGame->GetRenderWare()->GetClumpNumOfAtomics(pClump) > 1 && pModelInfo->GetModelType() == eModelInfoType::ATOMIC) + pModelInfo->ConvertToClump(); + else if (g_pGame->GetRenderWare()->GetClumpNumOfAtomics(pClump) == 1 && pModelInfo->GetModelType() == eModelInfoType::CLUMP) + pModelInfo->ConvertToAtomic(); + if (!pModelInfo->SetCustomModel(pClump)) return false; diff --git a/Client/sdk/game/CModelInfo.h b/Client/sdk/game/CModelInfo.h index 8b27fc03047..2291813ff71 100644 --- a/Client/sdk/game/CModelInfo.h +++ b/Client/sdk/game/CModelInfo.h @@ -192,6 +192,10 @@ class CModelInfo virtual bool IsAlphaTransparencyEnabled() = 0; virtual void ResetAlphaTransparency() = 0; + virtual bool ConvertToClump() = 0; + virtual bool ConvertToAtomic() = 0; + virtual CBaseModelInfoSAInterface* GetOriginalInterface() const = 0; + // ONLY use for CVehicleModelInfos virtual short GetAvailableVehicleMod(unsigned short usSlot) = 0; virtual bool IsUpgradeAvailable(eVehicleUpgradePosn posn) = 0; diff --git a/Client/sdk/game/CRenderWare.h b/Client/sdk/game/CRenderWare.h index 92686978b14..3ff9b53b7c6 100644 --- a/Client/sdk/game/CRenderWare.h +++ b/Client/sdk/game/CRenderWare.h @@ -26,6 +26,7 @@ struct RwMatrix; struct RwTexDictionary; struct RwTexture; struct RpClump; +struct RpAtomic; typedef CShaderItem CSHADERDUMMY; @@ -110,6 +111,8 @@ class CRenderWare virtual void RemoveClientEntityRefs(CClientEntityBase* pClientEntity) = 0; virtual void RemoveShaderRefs(CSHADERDUMMY* pShaderItem) = 0; virtual RwFrame* GetFrameFromName(RpClump* pRoot, SString strName) = 0; + virtual int GetClumpNumOfAtomics(RpClump* clump) = 0; + virtual RpAtomic* GetFirstAtomic(RpClump* clump) = 0; virtual bool RightSizeTxd(const SString& strInTxdFilename, const SString& strOutTxdFilename, uint uiSizeLimit) = 0; virtual void TxdForceUnload(ushort usTxdId, bool bDestroyTextures) = 0; diff --git a/Client/sdk/game/CVisibilityPlugins.h b/Client/sdk/game/CVisibilityPlugins.h index f2e948f65a3..1cb16960c91 100644 --- a/Client/sdk/game/CVisibilityPlugins.h +++ b/Client/sdk/game/CVisibilityPlugins.h @@ -15,6 +15,7 @@ #define ATOMIC_ID_FLAG_TWO_VERSIONS_DAMAGED 2 struct RpClump; +struct RpAtomic; struct RwObject; class CVisibilityPlugins @@ -22,6 +23,7 @@ class CVisibilityPlugins public: virtual void SetClumpAlpha(RpClump* pClump, int iAlpha) = 0; virtual int GetAtomicId(RwObject* pAtomic) = 0; + virtual void SetAtomicId(RpAtomic* atomic, int id) = 0; virtual bool InsertEntityIntoEntityList(void* entity, float distance, void* callback) = 0; }; From 7ba32758ea6a989aa14ca48a4a99b20b468d6aa7 Mon Sep 17 00:00:00 2001 From: FileEX Date: Sun, 23 Feb 2025 03:08:38 +0100 Subject: [PATCH 02/10] Small changes --- Client/game_sa/CModelInfoSA.cpp | 6 ------ Client/mods/deathmatch/logic/CClientDFF.cpp | 4 ++-- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/Client/game_sa/CModelInfoSA.cpp b/Client/game_sa/CModelInfoSA.cpp index 1e59d6fd9f8..526f71827d0 100644 --- a/Client/game_sa/CModelInfoSA.cpp +++ b/Client/game_sa/CModelInfoSA.cpp @@ -1813,9 +1813,6 @@ void CModelInfoSA::MakeClumpModel(ushort usBaseID) bool CModelInfoSA::ConvertToClump() { - if (GetModelType() != eModelInfoType::ATOMIC) - return false; - // Get current interface CAtomicModelInfoSAInterface* currentAtomicInterface = static_cast(ppModelInfo[m_dwModelID]); if (!currentAtomicInterface) @@ -1843,9 +1840,6 @@ bool CModelInfoSA::ConvertToClump() bool CModelInfoSA::ConvertToAtomic() { - if (GetModelType() != eModelInfoType::CLUMP) - return false; - // Get current interface CClumpModelInfoSAInterface* currentClumpInterface = static_cast(ppModelInfo[m_dwModelID]); if (!currentClumpInterface) diff --git a/Client/mods/deathmatch/logic/CClientDFF.cpp b/Client/mods/deathmatch/logic/CClientDFF.cpp index 20ced90daa4..5cb636156f6 100644 --- a/Client/mods/deathmatch/logic/CClientDFF.cpp +++ b/Client/mods/deathmatch/logic/CClientDFF.cpp @@ -335,9 +335,9 @@ bool CClientDFF::ReplaceObjectModel(RpClump* pClump, ushort usModel, bool bAlpha // If new model is clump or atomic then we need to convert existing model // pClump->object.type is always RP_TYPE_CLUMP so check number of atomics // to check if new model is clump or atomic - if (g_pGame->GetRenderWare()->GetClumpNumOfAtomics(pClump) > 1 && pModelInfo->GetModelType() == eModelInfoType::ATOMIC) + if (g_pGame->GetRenderWare()->GetClumpNumOfAtomics(pClump) > 1) pModelInfo->ConvertToClump(); - else if (g_pGame->GetRenderWare()->GetClumpNumOfAtomics(pClump) == 1 && pModelInfo->GetModelType() == eModelInfoType::CLUMP) + else if (g_pGame->GetRenderWare()->GetClumpNumOfAtomics(pClump) == 1) pModelInfo->ConvertToAtomic(); if (!pModelInfo->SetCustomModel(pClump)) From b85414f4e22e2a7ae5d9508799f320da6d0f66b2 Mon Sep 17 00:00:00 2001 From: FileEX Date: Sun, 23 Feb 2025 03:31:07 +0100 Subject: [PATCH 03/10] Fix crash when replace clump with clump or atomic with atomic --- Client/game_sa/CRenderWareSA.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Client/game_sa/CRenderWareSA.cpp b/Client/game_sa/CRenderWareSA.cpp index 32fdb0d2236..68724934dd8 100644 --- a/Client/game_sa/CRenderWareSA.cpp +++ b/Client/game_sa/CRenderWareSA.cpp @@ -510,7 +510,7 @@ bool CRenderWareSA::ReplaceAllAtomicsInModel(RpClump* pSrc, unsigned short usMod // Check if new model is clump or atomic // pSrc->object.type is always RP_TYPE_CLUMP so check number of atomics // to check if new model is clump or atomic - if (RpClumpGetNumAtomics(pSrc) > 1) + if (RpClumpGetNumAtomics(pSrc) > 1 || (RpClumpGetNumAtomics(pSrc) == 1 && pModelInfo->GetModelType() == eModelInfoType::CLUMP)) { // Get new interface but with old RwObject auto* currentNewInterface = static_cast(pModelInfo->GetInterface()); @@ -522,7 +522,11 @@ bool CRenderWareSA::ReplaceAllAtomicsInModel(RpClump* pSrc, unsigned short usMod if (!originalInterface) return false; - reinterpret_cast(originalInterface)->DeleteRwObject(); + if (RpClumpGetNumAtomics(reinterpret_cast(originalInterface->pRwObject)) == 1) + reinterpret_cast(originalInterface)->DeleteRwObject(); + else + currentNewInterface->DeleteRwObject(); + currentNewInterface->pRwObject = nullptr; // Init new RwObject (clump type) @@ -536,7 +540,7 @@ bool CRenderWareSA::ReplaceAllAtomicsInModel(RpClump* pSrc, unsigned short usMod // We need to remove the RwObject from the original interface because // the new interface points to the CAtomicModelInfo vtbl CBaseModelInfoSAInterface* originalInterface = pModelInfo->GetOriginalInterface(); - if (originalInterface) + if (originalInterface && RpClumpGetNumAtomics(reinterpret_cast(originalInterface->pRwObject)) > 1) { reinterpret_cast(originalInterface)->DeleteRwObject(); currentInterface->pRwObject = nullptr; From a411d772208cd32effabc73a6deb8bc6af1afedc Mon Sep 17 00:00:00 2001 From: FileEX Date: Sun, 23 Feb 2025 16:03:51 +0100 Subject: [PATCH 04/10] Review --- Client/game_sa/CModelInfoSA.cpp | 17 +++++++++++------ Client/game_sa/CModelInfoSA.h | 14 ++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/Client/game_sa/CModelInfoSA.cpp b/Client/game_sa/CModelInfoSA.cpp index 526f71827d0..b7be8b34302 100644 --- a/Client/game_sa/CModelInfoSA.cpp +++ b/Client/game_sa/CModelInfoSA.cpp @@ -1538,7 +1538,12 @@ void CModelInfoSA::RestoreOriginalModel() CBaseModelInfoSAInterface* currentInterface = ppModelInfo[m_dwModelID]; ppModelInfo[m_dwModelID] = MapGet(m_convertedModelInterfaces, m_dwModelID); - delete currentInterface; + if (currentInterface) + { + ppModelInfo[m_dwModelID]->usNumberOfRefs = currentInterface->usNumberOfRefs; + delete currentInterface; + } + MapRemove(m_convertedModelInterfaces, m_dwModelID); } @@ -1814,13 +1819,13 @@ void CModelInfoSA::MakeClumpModel(ushort usBaseID) bool CModelInfoSA::ConvertToClump() { // Get current interface - CAtomicModelInfoSAInterface* currentAtomicInterface = static_cast(ppModelInfo[m_dwModelID]); - if (!currentAtomicInterface) + CBaseModelInfoSAInterface* currentModelInterface = ppModelInfo[m_dwModelID]; + if (!currentModelInterface) return false; // Create new clump interface CClumpModelInfoSAInterface* newClumpInterface = new CClumpModelInfoSAInterface(); - MemCpyFast(newClumpInterface, currentAtomicInterface, sizeof(CClumpModelInfoSAInterface)); + MemCpyFast(newClumpInterface, currentModelInterface, sizeof(CBaseModelInfoSAInterface)); newClumpInterface->m_nAnimFileIndex = -1; // (FileEX): We do not destroy or set pRwObject to nullptr here @@ -1834,7 +1839,7 @@ bool CModelInfoSA::ConvertToClump() ppModelInfo[m_dwModelID] = newClumpInterface; // Store original interface - MapSet(m_convertedModelInterfaces, m_dwModelID, static_cast(currentAtomicInterface)); + MapSet(m_convertedModelInterfaces, m_dwModelID, currentModelInterface); return true; } @@ -1851,7 +1856,7 @@ bool CModelInfoSA::ConvertToAtomic() // (FileEX): We do not destroy or set pRwObject to nullptr here // because our IsLoaded code expects the RwObject to exist. - // We destroy the old RwObject in CFileLoader_SetRelatedModelInfoCB after passing the IsLoaded condition in the SetCustomModel. + // We destroy the old RwObject in CRenderWareSA::ReplaceAllAtomicsInModel after passing the IsLoaded condition in the SetCustomModel. // Set CAtomicModelInfo vtbl after copying data newAtomicInterface->VFTBL = reinterpret_cast(VTBL_CAtomicModelInfo); diff --git a/Client/game_sa/CModelInfoSA.h b/Client/game_sa/CModelInfoSA.h index 6f81f4a4a26..be4807b3350 100644 --- a/Client/game_sa/CModelInfoSA.h +++ b/Client/game_sa/CModelInfoSA.h @@ -64,12 +64,6 @@ static void* ARRAY_ModelInfo = *(void**)(0x403DA4 + 3); #define VAR_CTempColModels_ModelPed1 0x968DF0 -#define FUNC_CClumpModelInfo_SetClump 0x4C4F70 -#define FUNC_CClumpModelInfo_DeleteRwObject 0x4C4E70 - -#define FUNC_CAtomicModelInfo_SetAtomic 0x4C4360 -#define FUNC_CAtomicModelInfo_DeleteRwObject 0x4C4440 - #define VTBL_CClumpModelInfo 0x85BD30 #define VTBL_CAtomicModelInfo 0x85BBF0 @@ -262,15 +256,15 @@ class CClumpModelInfoSAInterface : public CBaseModelInfoSAInterface uint32_t m_nAnimFileIndex; }; - void DeleteRwObject() { ((void(__thiscall*)(CClumpModelInfoSAInterface*))FUNC_CClumpModelInfo_DeleteRwObject)(this); } - void SetClump(RpClump* clump) { ((void(__thiscall*)(CClumpModelInfoSAInterface*, RpClump*))FUNC_CClumpModelInfo_SetClump)(this, clump); } + void DeleteRwObject() { ((void(__thiscall*)(CClumpModelInfoSAInterface*))0x4C4E70)(this); } + void SetClump(RpClump* clump) { ((void(__thiscall*)(CClumpModelInfoSAInterface*, RpClump*))0x4C4F70)(this, clump); } }; class CAtomicModelInfoSAInterface : public CBaseModelInfoSAInterface { public: - void DeleteRwObject() { ((void(__thiscall*)(CAtomicModelInfoSAInterface*))FUNC_CAtomicModelInfo_DeleteRwObject)(this); } - void SetAtomic(RpAtomic* atomic) { ((void(__thiscall*)(CAtomicModelInfoSAInterface*, RpAtomic*))FUNC_CAtomicModelInfo_SetAtomic)(this, atomic); } + void DeleteRwObject() { ((void(__thiscall*)(CAtomicModelInfoSAInterface*))0x4C4440)(this); } + void SetAtomic(RpAtomic* atomic) { ((void(__thiscall*)(CAtomicModelInfoSAInterface*, RpAtomic*))0x4C4360)(this, atomic); } }; class CTimeModelInfoSAInterface : public CAtomicModelInfoSAInterface From 6fea97c4ed9e5442407aed3e4b8623378eb7465e Mon Sep 17 00:00:00 2001 From: FileEX Date: Mon, 24 Feb 2025 20:24:28 +0100 Subject: [PATCH 05/10] engineConvertModelToType --- Client/game_sa/CModelInfoSA.cpp | 89 +++++++- Client/game_sa/CModelInfoSA.h | 27 ++- Client/game_sa/CRenderWareSA.cpp | 215 +++++++++--------- Client/game_sa/CRenderWareSA.h | 46 +++- Client/mods/deathmatch/logic/CClientDFF.cpp | 11 +- .../logic/luadefs/CLuaEngineDefs.cpp | 32 +++ .../deathmatch/logic/luadefs/CLuaEngineDefs.h | 2 + Client/sdk/game/CModelInfo.h | 5 +- 8 files changed, 287 insertions(+), 140 deletions(-) diff --git a/Client/game_sa/CModelInfoSA.cpp b/Client/game_sa/CModelInfoSA.cpp index b7be8b34302..34a91824955 100644 --- a/Client/game_sa/CModelInfoSA.cpp +++ b/Client/game_sa/CModelInfoSA.cpp @@ -1541,6 +1541,8 @@ void CModelInfoSA::RestoreOriginalModel() if (currentInterface) { ppModelInfo[m_dwModelID]->usNumberOfRefs = currentInterface->usNumberOfRefs; + ppModelInfo[m_dwModelID]->pColModel = currentInterface->pColModel; + delete currentInterface; } @@ -1818,6 +1820,9 @@ void CModelInfoSA::MakeClumpModel(ushort usBaseID) bool CModelInfoSA::ConvertToClump() { + if (GetModelType() == eModelInfoType::CLUMP) + return false; + // Get current interface CBaseModelInfoSAInterface* currentModelInterface = ppModelInfo[m_dwModelID]; if (!currentModelInterface) @@ -1838,34 +1843,94 @@ bool CModelInfoSA::ConvertToClump() // Set new interface for ModelInfo ppModelInfo[m_dwModelID] = newClumpInterface; - // Store original interface - MapSet(m_convertedModelInterfaces, m_dwModelID, currentModelInterface); + // Store original (only) interface + if (!MapContains(m_convertedModelInterfaces, m_dwModelID)) + MapSet(m_convertedModelInterfaces, m_dwModelID, currentModelInterface); + else + m_lastConversionInterface = currentModelInterface; + return true; } -bool CModelInfoSA::ConvertToAtomic() +bool CModelInfoSA::ConvertToAtomic(bool damageable) { // Get current interface - CClumpModelInfoSAInterface* currentClumpInterface = static_cast(ppModelInfo[m_dwModelID]); - if (!currentClumpInterface) + CBaseModelInfoSAInterface* currentModelInterface = ppModelInfo[m_dwModelID]; + if (!currentModelInterface) + return false; + + if (GetModelType() == eModelInfoType::ATOMIC && ((damageable && currentModelInterface->IsDamageAtomicVTBL()) || (!damageable && currentModelInterface->IsAtomicVTBL()))) return false; // Create new atomic interface - CAtomicModelInfoSAInterface* newAtomicInterface = new CAtomicModelInfoSAInterface(); - MemCpyFast(newAtomicInterface, currentClumpInterface, sizeof(CAtomicModelInfoSAInterface)); + CAtomicModelInfoSAInterface* newAtomicInterface = nullptr; + CDamageableModelInfoSAInterface* newDamageableAtomicInterface = nullptr; // (FileEX): We do not destroy or set pRwObject to nullptr here // because our IsLoaded code expects the RwObject to exist. // We destroy the old RwObject in CRenderWareSA::ReplaceAllAtomicsInModel after passing the IsLoaded condition in the SetCustomModel. - // Set CAtomicModelInfo vtbl after copying data - newAtomicInterface->VFTBL = reinterpret_cast(VTBL_CAtomicModelInfo); + if (damageable) + { + newDamageableAtomicInterface = new CDamageableModelInfoSAInterface(); + MemCpyFast(newDamageableAtomicInterface, currentModelInterface, sizeof(CDamageableModelInfoSAInterface)); + newDamageableAtomicInterface->m_damagedAtomic = nullptr; + + // Set CDamageAtomicModelInfo vtbl after copying data + newDamageableAtomicInterface->VFTBL = reinterpret_cast(VTBL_CDamageAtomicModelInfo); + } + else + { + newAtomicInterface = new CAtomicModelInfoSAInterface(); + MemCpyFast(newAtomicInterface, currentModelInterface, sizeof(CAtomicModelInfoSAInterface)); + + // Set CAtomicModelInfo vtbl after copying data + newAtomicInterface->VFTBL = reinterpret_cast(VTBL_CAtomicModelInfo); + } // Set new interface for ModelInfo - ppModelInfo[m_dwModelID] = newAtomicInterface; + ppModelInfo[m_dwModelID] = damageable ? newDamageableAtomicInterface : newAtomicInterface; + + // Store original (only) interface + if (!MapContains(m_convertedModelInterfaces, m_dwModelID)) + MapSet(m_convertedModelInterfaces, m_dwModelID, currentModelInterface); + else + m_lastConversionInterface = currentModelInterface; + + return true; +} + +bool CModelInfoSA::ConvertToTimedObject() +{ + if (GetModelType() == eModelInfoType::TIME) + return false; + + // Get current interface + CBaseModelInfoSAInterface* currentModelInterface = ppModelInfo[m_dwModelID]; + if (!currentModelInterface) + return false; + + // Create new interface + CTimeModelInfoSAInterface* newTimedInterface = new CTimeModelInfoSAInterface(); + MemCpyFast(newTimedInterface, currentModelInterface, sizeof(CTimeModelInfoSAInterface)); + newTimedInterface->timeInfo.m_wOtherTimeModel = 0; + + // (FileEX): We do not destroy or set pRwObject to nullptr here + // because our IsLoaded code expects the RwObject to exist. + // We destroy the old RwObject in CRenderWareSA::ReplaceAllAtomicsInModel after passing the IsLoaded condition in the SetCustomModel. + + // Set CTimeModelInfo vtbl after copying data + newTimedInterface->VFTBL = reinterpret_cast(VTBL_CTimeModelInfo); + + // Set new interface for ModelInfo + ppModelInfo[m_dwModelID] = newTimedInterface; + + // Store original (only) interface + if (!MapContains(m_convertedModelInterfaces, m_dwModelID)) + MapSet(m_convertedModelInterfaces, m_dwModelID, currentModelInterface); + else + m_lastConversionInterface = currentModelInterface; - // Store original interface - MapSet(m_convertedModelInterfaces, m_dwModelID, currentClumpInterface); return true; } diff --git a/Client/game_sa/CModelInfoSA.h b/Client/game_sa/CModelInfoSA.h index be4807b3350..294bb846c9c 100644 --- a/Client/game_sa/CModelInfoSA.h +++ b/Client/game_sa/CModelInfoSA.h @@ -66,6 +66,8 @@ static void* ARRAY_ModelInfo = *(void**)(0x403DA4 + 3); #define VTBL_CClumpModelInfo 0x85BD30 #define VTBL_CAtomicModelInfo 0x85BBF0 +#define VTBL_CDamageAtomicModelInfo 0x85BC30 +#define VTBL_CTimeModelInfo 0x85BCB0 class CBaseModelInfoSAInterface; class CModelInfoSAInterface @@ -235,6 +237,12 @@ class CBaseModelInfoSAInterface // +726 = Word array as referenced in CVehicleModelInfo::GetVehicleUpgrade(int) // +762 = Array of WORD containing something relative to paintjobs // +772 = Anim file index + + void DeleteRwObject() { ((void(__thiscall*)(void*))VFTBL->DeleteRwObject)(this); } + + bool IsAtomicVTBL() const { return VFTBL == reinterpret_cast(VTBL_CAtomicModelInfo); } + bool IsDamageAtomicVTBL() const { return VFTBL == reinterpret_cast(VTBL_CDamageAtomicModelInfo); } + bool IsClumpVTBL() const { return VFTBL == reinterpret_cast(VTBL_CClumpModelInfo); } }; static_assert(sizeof(CBaseModelInfoSAInterface) == 0x20, "Invalid size for CBaseModelInfoSAInterface"); @@ -267,6 +275,13 @@ class CAtomicModelInfoSAInterface : public CBaseModelInfoSAInterface void SetAtomic(RpAtomic* atomic) { ((void(__thiscall*)(CAtomicModelInfoSAInterface*, RpAtomic*))0x4C4360)(this, atomic); } }; +class CLodAtomicModelInfoSAInterface : public CAtomicModelInfoSAInterface +{ +public: + std::int16_t numChildren; // num child higher level LODs + std::int16_t numChildrenRendered; // num child higher level LODs that have been rendered +}; + class CTimeModelInfoSAInterface : public CAtomicModelInfoSAInterface { public: @@ -286,7 +301,7 @@ class CDamageableModelInfoSAInterface : public CAtomicModelInfoSAInterface public: RpAtomic* m_damagedAtomic; - void SetDamagedAtomic(RpAtomic* atomic) { ((void(__thiscall*)(RpAtomic*))FUNC_CAtomicModelInfo_SetAtomic)(atomic); } + void SetDamagedAtomic(RpAtomic* atomic) { ((void(__thiscall*)(CDamageableModelInfoSAInterface*, RpAtomic*))0x4C48D0)(this, atomic); } }; class CVehicleModelVisualInfoSAInterface // Not sure about this name. If somebody knows more, please change @@ -371,6 +386,7 @@ class CModelInfoSA : public CModelInfo static std::map ms_DefaultTxdIDMap; SVehicleSupportedUpgrades m_ModelSupportedUpgrades; + CBaseModelInfoSAInterface* m_lastConversionInterface{nullptr}; // Store original model interfaces after conersion clump->atomic or atomic->clump static std::unordered_map m_convertedModelInterfaces; @@ -508,9 +524,12 @@ class CModelInfoSA : public CModelInfo static void RestoreAllObjectsPropertiesGroups(); // Model type conversion functions - bool ConvertToClump(); - bool ConvertToAtomic(); - CBaseModelInfoSAInterface* GetOriginalInterface() const; + bool ConvertToClump() override; + bool ConvertToAtomic(bool damageable) override; + bool ConvertToTimedObject() override; + CBaseModelInfoSAInterface* GetLastConversionInterface() const noexcept override { return m_lastConversionInterface; } + void SetLastConversionInterface(CBaseModelInfoSAInterface* lastInterace) noexcept override { m_lastConversionInterface = lastInterace; } + CBaseModelInfoSAInterface* GetOriginalInterface() const override; // Vehicle towing functions bool IsTowableBy(CModelInfo* towingModel) override; diff --git a/Client/game_sa/CRenderWareSA.cpp b/Client/game_sa/CRenderWareSA.cpp index 68724934dd8..bb404838e91 100644 --- a/Client/game_sa/CRenderWareSA.cpp +++ b/Client/game_sa/CRenderWareSA.cpp @@ -477,90 +477,127 @@ unsigned int CRenderWareSA::LoadAtomics(RpClump* pClump, RpAtomicContainer* pAto return data.uiReplacements; } -// Replaces all atomics for a specific model -typedef struct +struct SCDamageableModelinfo { - unsigned short usTxdID; - RpClump* pClump; -} SAtomicsReplacer; -bool AtomicsReplacer(RpAtomic* pAtomic, void* data) -{ - SAtomicsReplacer* pData = reinterpret_cast(data); - SRelatedModelInfo relatedModelInfo = {0}; - relatedModelInfo.pClump = pData->pClump; - 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); - return true; -} + CBaseModelInfoSAInterface* baseInterface; + std::uint32_t modelId; + RpClump* clump; +}; -bool CRenderWareSA::ReplaceAllAtomicsInModel(RpClump* pSrc, unsigned short usModelID) +bool CRenderWareSA::ReplaceAllAtomicsInModel(RpClump* pNew, unsigned short usModelID) { CModelInfo* pModelInfo = pGame->GetModelInfo(usModelID); if (!pModelInfo) - return true; + return false; RpAtomic* oldAtomic = reinterpret_cast(pModelInfo->GetRwObject()); - if (reinterpret_cast(oldAtomic) == pSrc || DoContainTheSameGeometry(pSrc, nullptr, oldAtomic)) + if (reinterpret_cast(oldAtomic) == pNew || DoContainTheSameGeometry(pNew, nullptr, oldAtomic)) return true; - // Check if new model is clump or atomic - // pSrc->object.type is always RP_TYPE_CLUMP so check number of atomics - // to check if new model is clump or atomic - if (RpClumpGetNumAtomics(pSrc) > 1 || (RpClumpGetNumAtomics(pSrc) == 1 && pModelInfo->GetModelType() == eModelInfoType::CLUMP)) - { - // Get new interface but with old RwObject - auto* currentNewInterface = static_cast(pModelInfo->GetInterface()); - - // Destroy old RwObject (atomic type) - // We need to remove the RwObject from the original interface because - // the new interface points to the CClumpModelInfo vtbl - CBaseModelInfoSAInterface* originalInterface = pModelInfo->GetOriginalInterface(); - if (!originalInterface) - return false; - - if (RpClumpGetNumAtomics(reinterpret_cast(originalInterface->pRwObject)) == 1) - reinterpret_cast(originalInterface)->DeleteRwObject(); - else - currentNewInterface->DeleteRwObject(); - - currentNewInterface->pRwObject = nullptr; + CBaseModelInfoSAInterface* currentModelInterface = pModelInfo->GetInterface(); - // Init new RwObject (clump type) - currentNewInterface->SetClump(pSrc); + // Destroy old RwObject + // We need to remove the RwObject from the original interface because (if exists) + // the new interface may points to the new vtbl after conversion + CBaseModelInfoSAInterface* originalInterface = pModelInfo->GetOriginalInterface(); + if (originalInterface && originalInterface->pRwObject) + { + originalInterface->DeleteRwObject(); + currentModelInterface->pRwObject = nullptr; } - else + else if (!originalInterface) + currentModelInterface->DeleteRwObject(); + + // Prevent memory leaks + // The user can convert the same model multiple times, but its original model is saved only once, + // so we remove the RwObject and the interface created during the last conversion + CBaseModelInfoSAInterface* lastConversionInterface = pModelInfo->GetLastConversionInterface(); + if (lastConversionInterface) { - auto* currentInterface = static_cast(pModelInfo->GetInterface()); + lastConversionInterface->DeleteRwObject(); + delete lastConversionInterface; + + pModelInfo->SetLastConversionInterface(nullptr); + currentModelInterface->pRwObject = nullptr; + } + + // We need to make a copy here because DeleteRwObject removes atomics from our loaded clump (pNew). + RpClump* clonedClump = RpClumpClone(pNew); + if (!clonedClump) + return false; - // Destroy old RwObject (clump type) - // We need to remove the RwObject from the original interface because - // the new interface points to the CAtomicModelInfo vtbl - CBaseModelInfoSAInterface* originalInterface = pModelInfo->GetOriginalInterface(); - if (originalInterface && RpClumpGetNumAtomics(reinterpret_cast(originalInterface->pRwObject)) > 1) + // Check model type + switch (pModelInfo->GetModelType()) + { + case eModelInfoType::ATOMIC: { - reinterpret_cast(originalInterface)->DeleteRwObject(); - currentInterface->pRwObject = nullptr; - } - else - currentInterface->DeleteRwObject(); + RpAtomic* atomic = GetFirstAtomic(clonedClump); + if (!atomic) + return false; - RpAtomic* atomic = GetFirstAtomic(pSrc); - RwFrame* frame = RpGetFrame(atomic); + if (currentModelInterface->IsDamageAtomicVTBL()) + { + SCDamageableModelinfo damagedInfo{}; + damagedInfo.modelId = usModelID; + damagedInfo.baseInterface = currentModelInterface; + damagedInfo.clump = pNew; + + RpClumpForAllAtomics(clonedClump, + [](RpAtomic* atomic, void* data) -> bool { + auto* damagedInfo = static_cast(data); + + bool damage = false; + char name[24]; + GetNameAndDamage(GetFrameNodeName(RpGetFrame(atomic)), name, damage); + + if (damage) + static_cast(damagedInfo->baseInterface)->SetDamagedAtomic(atomic); + else + static_cast(damagedInfo->baseInterface)->SetAtomic(atomic); + + pGame->GetVisibilityPlugins()->SetAtomicId(atomic, damagedInfo->modelId); + + // Remove our atomic from temp clump + RpClumpRemoveAtomic(damagedInfo->clump, atomic); + return false; + }, &damagedInfo); + } + else + { + static_cast(currentModelInterface)->SetAtomic(atomic); + pGame->GetVisibilityPlugins()->SetAtomicId(atomic, usModelID); - char name[24]; - bool damage = false; - GetNameAndDamage(GetFrameNodeName(frame), name, damage); + // Remove our atomic from temp clump + RpClumpRemoveAtomic(clonedClump, atomic); + } - if (damage) - static_cast(currentInterface)->SetDamagedAtomic(atomic); - else - currentInterface->SetAtomic(atomic); + // Destroy empty clump + RpClumpDestroy(clonedClump); + break; + } + case eModelInfoType::TIME: + { + RpAtomic* atomic = GetFirstAtomic(clonedClump); + if (!atomic) + return false; + + static_cast(currentModelInterface)->SetAtomic(atomic); + pGame->GetVisibilityPlugins()->SetAtomicId(atomic, usModelID); + + // Remove our atomic from temp clump + RpClumpRemoveAtomic(clonedClump, atomic); - pGame->GetVisibilityPlugins()->SetAtomicId(atomic, usModelID); + // Destroy empty clump + RpClumpDestroy(clonedClump); + break; + } + case eModelInfoType::CLUMP: + { + // We do not delete or clear the clump copy here. + // The copy will be removed when DestroyRwObject is called in CClumpModelInfo + static_cast(currentModelInterface)->SetClump(clonedClump); + break; + } } return true; @@ -970,7 +1007,7 @@ RwFrame* CRenderWareSA::GetFrameFromName(RpClump* pRoot, SString strName) int CRenderWareSA::GetClumpNumOfAtomics(RpClump* clump) { - return RpClumpGetNumAtomics(clump); + return clump ? RpClumpGetNumAtomics(clump) : 1; } RpAtomic* CRenderWareSA::GetFirstAtomic(RpClump* clump) @@ -983,50 +1020,6 @@ char* CRenderWareSA::GetFrameNodeName(RwFrame* frame) return ((char*(__cdecl*)(RwFrame*))FUNC_GetFrameNodeName)(frame); } -// Originally there was a possibility for this function to cause buffer overflow -// It should be fixed here. -template -void CRenderWareSA::GetNameAndDamage(const char* nodeName, char (&outName)[OutBuffSize], bool& outDamage) -{ - const auto nodeNameLen = strlen(nodeName); - - const auto NodeNameEndsWith = [=](const char* with) - { - const auto withLen = strlen(with); - // dassert(withLen <= nodeNameLen); - return withLen <= nodeNameLen /*dont bother checking otherwise, because it might cause a crash*/ - && strncmp(nodeName + nodeNameLen - withLen, with, withLen) == 0; - }; - - // Copy `nodeName` into `outName` with `off` trimmed from the end - // Eg.: `dmg_dam` with `off = 4` becomes `dmg` - const auto TerminatedCopy = [&](size_t off) - { - dassert(nodeNameLen - off < OutBuffSize); - strncpy_s(outName, nodeName, - std::min(nodeNameLen - off, OutBuffSize - 1)); // By providing `OutBuffSize - 1` it is ensured the array will be null terminated - }; - - if (NodeNameEndsWith("_dam")) - { - outDamage = true; - TerminatedCopy(sizeof("_dam") - 1); - } - else - { - outDamage = false; - if (NodeNameEndsWith("_l0") || NodeNameEndsWith("_L0")) - { - TerminatedCopy(sizeof("_l0") - 1); - } - else - { - dassert(nodeNameLen < OutBuffSize); - strncpy_s(outName, OutBuffSize, nodeName, OutBuffSize - 1); - } - } -} - void CRenderWareSA::CMatrixToRwMatrix(const CMatrix& mat, RwMatrix& rwOutMatrix) { rwOutMatrix.right = (RwV3d&)mat.vRight; diff --git a/Client/game_sa/CRenderWareSA.h b/Client/game_sa/CRenderWareSA.h index a21d3f1b725..5d7f4efed0f 100644 --- a/Client/game_sa/CRenderWareSA.h +++ b/Client/game_sa/CRenderWareSA.h @@ -64,7 +64,7 @@ class CRenderWareSA : public CRenderWare unsigned int LoadAtomics(RpClump* pClump, RpAtomicContainer* pAtomics); // Replaces all atomics for a specific model - bool ReplaceAllAtomicsInModel(RpClump* pSrc, unsigned short usModelID) override; + bool ReplaceAllAtomicsInModel(RpClump* pNew, unsigned short usModelID) override; // Replaces all atomics in a clump void ReplaceAllAtomicsInClump(RpClump* pDst, RpAtomicContainer* pAtomics, unsigned int uiAtomics); @@ -130,8 +130,50 @@ class CRenderWareSA : public CRenderWare static char* GetFrameNodeName(RwFrame* frame); + // Originally there was a possibility for this function to cause buffer overflow + // It should be fixed here. template - static void GetNameAndDamage(const char* nodeName, char (&outName)[OutBuffSize], bool& outDamage); + static void GetNameAndDamage(const char* nodeName, char (&outName)[OutBuffSize], bool& outDamage) + { + const auto nodeNameLen = strlen(nodeName); + + const auto NodeNameEndsWith = [=](const char* with) + { + const auto withLen = strlen(with); + // dassert(withLen <= nodeNameLen); + return withLen <= nodeNameLen /*dont bother checking otherwise, because it might cause a crash*/ + && strncmp(nodeName + nodeNameLen - withLen, with, withLen) == 0; + }; + + // Copy `nodeName` into `outName` with `off` trimmed from the end + // Eg.: `dmg_dam` with `off = 4` becomes `dmg` + const auto TerminatedCopy = [&](size_t off) + { + dassert(nodeNameLen - off < OutBuffSize); + strncpy_s( + outName, nodeName, + std::min(nodeNameLen - off, OutBuffSize - 1)); // By providing `OutBuffSize - 1` it is ensured the array will be null terminated + }; + + if (NodeNameEndsWith("_dam")) + { + outDamage = true; + TerminatedCopy(sizeof("_dam") - 1); + } + else + { + outDamage = false; + if (NodeNameEndsWith("_l0") || NodeNameEndsWith("_L0")) + { + TerminatedCopy(sizeof("_l0") - 1); + } + else + { + dassert(nodeNameLen < OutBuffSize); + strncpy_s(outName, OutBuffSize, nodeName, OutBuffSize - 1); + } + } + } static void StaticSetHooks(); static void StaticSetClothesReplacingHooks(); diff --git a/Client/mods/deathmatch/logic/CClientDFF.cpp b/Client/mods/deathmatch/logic/CClientDFF.cpp index 5cb636156f6..84283706a90 100644 --- a/Client/mods/deathmatch/logic/CClientDFF.cpp +++ b/Client/mods/deathmatch/logic/CClientDFF.cpp @@ -330,16 +330,7 @@ bool CClientDFF::ReplaceObjectModel(RpClump* pClump, ushort usModel, bool bAlpha g_pGame->GetModelInfo(usModel)->RestreamIPL(); // Grab the model info for that model and replace the model - CModelInfo* pModelInfo = g_pGame->GetModelInfo(usModel); - - // If new model is clump or atomic then we need to convert existing model - // pClump->object.type is always RP_TYPE_CLUMP so check number of atomics - // to check if new model is clump or atomic - if (g_pGame->GetRenderWare()->GetClumpNumOfAtomics(pClump) > 1) - pModelInfo->ConvertToClump(); - else if (g_pGame->GetRenderWare()->GetClumpNumOfAtomics(pClump) == 1) - pModelInfo->ConvertToAtomic(); - + CModelInfo* pModelInfo = g_pGame->GetModelInfo(usModel); if (!pModelInfo->SetCustomModel(pClump)) return false; diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp index 0ad88113b11..03adb862c8c 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp +++ b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp @@ -147,6 +147,7 @@ void CLuaEngineDefs::LoadFunctions() {"engineGetPoolUsedCapacity", ArgumentParser}, {"engineSetPoolCapacity", ArgumentParser}, {"enginePreloadWorldArea", ArgumentParser}, + {"engineConvertModelToType", ArgumentParser}, // CLuaCFunctions::AddFunction ( "engineReplaceMatchingAtomics", EngineReplaceMatchingAtomics ); // CLuaCFunctions::AddFunction ( "engineReplaceWheelAtomics", EngineReplaceWheelAtomics ); @@ -2568,3 +2569,34 @@ void CLuaEngineDefs::EnginePreloadWorldArea(CVector position, std::optionalGetStreaming()->LoadSceneCollision(&position); } + +bool CLuaEngineDefs::EngineConvertModelToType(std::uint32_t model, eClientModelType type) +{ + if (!CClientObjectManager::IsValidModel(model)) + throw LuaFunctionError("Invalid model"); + + if (type == eClientModelType::PED || type == eClientModelType::VEHICLE || type == eClientModelType::TXD) + throw LuaFunctionError("The argument 'model-type' is invalid"); + + CModelInfo* modelInfo = g_pGame->GetModelInfo(model); + if (!modelInfo) + return false; + + // We need to stream out the model, otherwise it will crash + g_pClientGame->GetObjectManager()->RestreamObjects(model); + modelInfo->RestreamIPL(); + + switch (type) + { + case eClientModelType::OBJECT: + return modelInfo->ConvertToAtomic(false); + case eClientModelType::OBJECT_DAMAGEABLE: + return modelInfo->ConvertToAtomic(true); + case eClientModelType::CLUMP: + return modelInfo->ConvertToClump(); + case eClientModelType::TIMED_OBJECT: + return modelInfo->ConvertToTimedObject(); + } + + return false; +} diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h index a67ecfc68d0..85d013a4263 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h +++ b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h @@ -94,6 +94,8 @@ class CLuaEngineDefs : public CLuaDefs static void EnginePreloadWorldArea(CVector position, std::optional option); + static bool EngineConvertModelToType(std::uint32_t model, eClientModelType type); + private: static void AddEngineColClass(lua_State* luaVM); static void AddEngineTxdClass(lua_State* luaVM); diff --git a/Client/sdk/game/CModelInfo.h b/Client/sdk/game/CModelInfo.h index 2291813ff71..9dd4b914500 100644 --- a/Client/sdk/game/CModelInfo.h +++ b/Client/sdk/game/CModelInfo.h @@ -193,7 +193,10 @@ class CModelInfo virtual void ResetAlphaTransparency() = 0; virtual bool ConvertToClump() = 0; - virtual bool ConvertToAtomic() = 0; + virtual bool ConvertToAtomic(bool damageable) = 0; + virtual bool ConvertToTimedObject() = 0; + virtual CBaseModelInfoSAInterface* GetLastConversionInterface() const noexcept = 0; + virtual void SetLastConversionInterface(CBaseModelInfoSAInterface* lastInterface) noexcept = 0; virtual CBaseModelInfoSAInterface* GetOriginalInterface() const = 0; // ONLY use for CVehicleModelInfos From a17f3e6cffe898d1bfb4cbcf1828868614a078c3 Mon Sep 17 00:00:00 2001 From: FileEX Date: Tue, 4 Mar 2025 09:39:38 +0100 Subject: [PATCH 06/10] Fix some crashes --- Client/game_sa/CModelInfoSA.cpp | 25 +++- Client/game_sa/CModelInfoSA.h | 6 +- Client/game_sa/CRenderWareSA.cpp | 121 +++++++++++------- Client/game_sa/CRenderWareSA.h | 19 ++- Client/mods/deathmatch/logic/CClientDFF.cpp | 1 + .../logic/luadefs/CLuaEngineDefs.cpp | 2 +- Client/sdk/game/CModelInfo.h | 1 + Client/sdk/game/CRenderWare.h | 8 +- 8 files changed, 121 insertions(+), 62 deletions(-) diff --git a/Client/game_sa/CModelInfoSA.cpp b/Client/game_sa/CModelInfoSA.cpp index 34a91824955..6a2517bb7bc 100644 --- a/Client/game_sa/CModelInfoSA.cpp +++ b/Client/game_sa/CModelInfoSA.cpp @@ -1514,7 +1514,7 @@ bool CModelInfoSA::SetCustomModel(RpClump* pClump) case eModelInfoType::LOD_ATOMIC: case eModelInfoType::TIME: case eModelInfoType::CLUMP: - success = pGame->GetRenderWare()->ReplaceAllAtomicsInModel(pClump, static_cast(m_dwModelID)); + success = pGame->GetRenderWare()->ReplaceObjectModel(pClump, static_cast(m_dwModelID)); break; default: break; @@ -1542,6 +1542,9 @@ void CModelInfoSA::RestoreOriginalModel() { ppModelInfo[m_dwModelID]->usNumberOfRefs = currentInterface->usNumberOfRefs; ppModelInfo[m_dwModelID]->pColModel = currentInterface->pColModel; + ppModelInfo[m_dwModelID]->ucAlpha = currentInterface->ucAlpha; + ppModelInfo[m_dwModelID]->usFlags = currentInterface->usFlags; + ppModelInfo[m_dwModelID]->usTextureDictionary = currentInterface->usTextureDictionary; delete currentInterface; } @@ -1828,12 +1831,14 @@ bool CModelInfoSA::ConvertToClump() if (!currentModelInterface) return false; + m_lastInterfaceVTBL = currentModelInterface->VFTBL; + // Create new clump interface CClumpModelInfoSAInterface* newClumpInterface = new CClumpModelInfoSAInterface(); MemCpyFast(newClumpInterface, currentModelInterface, sizeof(CBaseModelInfoSAInterface)); newClumpInterface->m_nAnimFileIndex = -1; - // (FileEX): We do not destroy or set pRwObject to nullptr here + // We do not destroy or set pRwObject to nullptr here // because our IsLoaded code expects the RwObject to exist. // We destroy the old RwObject in CRenderWareSA::ReplaceAllAtomicsInModel after passing the IsLoaded condition in the SetCustomModel. @@ -1862,11 +1867,13 @@ bool CModelInfoSA::ConvertToAtomic(bool damageable) if (GetModelType() == eModelInfoType::ATOMIC && ((damageable && currentModelInterface->IsDamageAtomicVTBL()) || (!damageable && currentModelInterface->IsAtomicVTBL()))) return false; + m_lastInterfaceVTBL = currentModelInterface->VFTBL; + // Create new atomic interface CAtomicModelInfoSAInterface* newAtomicInterface = nullptr; CDamageableModelInfoSAInterface* newDamageableAtomicInterface = nullptr; - // (FileEX): We do not destroy or set pRwObject to nullptr here + // We do not destroy or set pRwObject to nullptr here // because our IsLoaded code expects the RwObject to exist. // We destroy the old RwObject in CRenderWareSA::ReplaceAllAtomicsInModel after passing the IsLoaded condition in the SetCustomModel. @@ -1910,12 +1917,14 @@ bool CModelInfoSA::ConvertToTimedObject() if (!currentModelInterface) return false; + m_lastInterfaceVTBL = currentModelInterface->VFTBL; + // Create new interface CTimeModelInfoSAInterface* newTimedInterface = new CTimeModelInfoSAInterface(); MemCpyFast(newTimedInterface, currentModelInterface, sizeof(CTimeModelInfoSAInterface)); newTimedInterface->timeInfo.m_wOtherTimeModel = 0; - // (FileEX): We do not destroy or set pRwObject to nullptr here + // We do not destroy or set pRwObject to nullptr here // because our IsLoaded code expects the RwObject to exist. // We destroy the old RwObject in CRenderWareSA::ReplaceAllAtomicsInModel after passing the IsLoaded condition in the SetCustomModel. @@ -1942,6 +1951,14 @@ CBaseModelInfoSAInterface* CModelInfoSA::GetOriginalInterface() const return MapGet(m_convertedModelInterfaces, m_dwModelID); } +bool CModelInfoSA::IsSameModelType() +{ + if (!m_lastInterfaceVTBL) + m_lastInterfaceVTBL = m_pInterface->VFTBL; + + return m_lastInterfaceVTBL == m_pInterface->VFTBL; +} + void CModelInfoSA::MakeVehicleAutomobile(ushort usBaseID) { CVehicleModelInfoSAInterface* m_pInterface = new CVehicleModelInfoSAInterface(); diff --git a/Client/game_sa/CModelInfoSA.h b/Client/game_sa/CModelInfoSA.h index 294bb846c9c..8f47f3fbf22 100644 --- a/Client/game_sa/CModelInfoSA.h +++ b/Client/game_sa/CModelInfoSA.h @@ -242,6 +242,7 @@ class CBaseModelInfoSAInterface bool IsAtomicVTBL() const { return VFTBL == reinterpret_cast(VTBL_CAtomicModelInfo); } bool IsDamageAtomicVTBL() const { return VFTBL == reinterpret_cast(VTBL_CDamageAtomicModelInfo); } + bool IsTimedObjectVTBL() const { return VFTBL == reinterpret_cast(VTBL_CTimeModelInfo); } bool IsClumpVTBL() const { return VFTBL == reinterpret_cast(VTBL_CClumpModelInfo); } }; static_assert(sizeof(CBaseModelInfoSAInterface) == 0x20, "Invalid size for CBaseModelInfoSAInterface"); @@ -386,8 +387,10 @@ class CModelInfoSA : public CModelInfo static std::map ms_DefaultTxdIDMap; SVehicleSupportedUpgrades m_ModelSupportedUpgrades; + void* m_lastInterfaceVTBL{nullptr}; CBaseModelInfoSAInterface* m_lastConversionInterface{nullptr}; - // Store original model interfaces after conersion clump->atomic or atomic->clump + + // Store original model interfaces after type conersion static std::unordered_map m_convertedModelInterfaces; public: @@ -530,6 +533,7 @@ class CModelInfoSA : public CModelInfo CBaseModelInfoSAInterface* GetLastConversionInterface() const noexcept override { return m_lastConversionInterface; } void SetLastConversionInterface(CBaseModelInfoSAInterface* lastInterace) noexcept override { m_lastConversionInterface = lastInterace; } CBaseModelInfoSAInterface* GetOriginalInterface() const override; + bool IsSameModelType() override; // Vehicle towing functions bool IsTowableBy(CModelInfo* towingModel) override; diff --git a/Client/game_sa/CRenderWareSA.cpp b/Client/game_sa/CRenderWareSA.cpp index bb404838e91..37cd8197279 100644 --- a/Client/game_sa/CRenderWareSA.cpp +++ b/Client/game_sa/CRenderWareSA.cpp @@ -484,45 +484,27 @@ struct SCDamageableModelinfo RpClump* clump; }; -bool CRenderWareSA::ReplaceAllAtomicsInModel(RpClump* pNew, unsigned short usModelID) +bool CRenderWareSA::ReplaceObjectModel(RpClump* newClump, std::uint16_t modelID) { - CModelInfo* pModelInfo = pGame->GetModelInfo(usModelID); + CModelInfo* pModelInfo = pGame->GetModelInfo(modelID); if (!pModelInfo) return false; - RpAtomic* oldAtomic = reinterpret_cast(pModelInfo->GetRwObject()); - if (reinterpret_cast(oldAtomic) == pNew || DoContainTheSameGeometry(pNew, nullptr, oldAtomic)) + auto* oldObject = reinterpret_cast(pModelInfo->GetRwObject()); + if (DoContainTheSameGeometry(newClump, oldObject, nullptr) && pModelInfo->IsSameModelType()) return true; CBaseModelInfoSAInterface* currentModelInterface = pModelInfo->GetInterface(); - // Destroy old RwObject - // We need to remove the RwObject from the original interface because (if exists) - // the new interface may points to the new vtbl after conversion - CBaseModelInfoSAInterface* originalInterface = pModelInfo->GetOriginalInterface(); - if (originalInterface && originalInterface->pRwObject) - { - originalInterface->DeleteRwObject(); - currentModelInterface->pRwObject = nullptr; - } - else if (!originalInterface) - currentModelInterface->DeleteRwObject(); - - // Prevent memory leaks - // The user can convert the same model multiple times, but its original model is saved only once, - // so we remove the RwObject and the interface created during the last conversion - CBaseModelInfoSAInterface* lastConversionInterface = pModelInfo->GetLastConversionInterface(); - if (lastConversionInterface) - { - lastConversionInterface->DeleteRwObject(); - delete lastConversionInterface; - - pModelInfo->SetLastConversionInterface(nullptr); - currentModelInterface->pRwObject = nullptr; - } + // We create a copy of the current interface to later call DeleteRwObject on it, + // so we can remove the old RwObject after setting the new one + // Old RwObject must be deleted after setting the new one to avoid crashes during restream + CBaseModelInfoSAInterface* copyCurrentModelInterface = new CBaseModelInfoSAInterface(); + MemCpyFast(copyCurrentModelInterface, currentModelInterface, sizeof(CBaseModelInfoSAInterface)); + currentModelInterface->pRwObject = nullptr; - // We need to make a copy here because DeleteRwObject removes atomics from our loaded clump (pNew). - RpClump* clonedClump = RpClumpClone(pNew); + // We need to make a copy here because DeleteRwObject removes atomics from our new clump + RpClump* clonedClump = RpClumpClone(newClump); if (!clonedClump) return false; @@ -535,20 +517,24 @@ bool CRenderWareSA::ReplaceAllAtomicsInModel(RpClump* pNew, unsigned short usMod if (!atomic) return false; + // Is damageable object? if (currentModelInterface->IsDamageAtomicVTBL()) { SCDamageableModelinfo damagedInfo{}; - damagedInfo.modelId = usModelID; + damagedInfo.modelId = modelID; damagedInfo.baseInterface = currentModelInterface; - damagedInfo.clump = pNew; + damagedInfo.clump = clonedClump; RpClumpForAllAtomics(clonedClump, [](RpAtomic* atomic, void* data) -> bool { auto* damagedInfo = static_cast(data); + RwFrame* frame = RpGetFrame(atomic); + if (!frame) + return false; bool damage = false; char name[24]; - GetNameAndDamage(GetFrameNodeName(RpGetFrame(atomic)), name, damage); + GetNameAndDamage(GetFrameNodeName(frame), name, damage); if (damage) static_cast(damagedInfo->baseInterface)->SetDamagedAtomic(atomic); @@ -562,10 +548,10 @@ bool CRenderWareSA::ReplaceAllAtomicsInModel(RpClump* pNew, unsigned short usMod return false; }, &damagedInfo); } - else + else // or standard object? { static_cast(currentModelInterface)->SetAtomic(atomic); - pGame->GetVisibilityPlugins()->SetAtomicId(atomic, usModelID); + pGame->GetVisibilityPlugins()->SetAtomicId(atomic, modelID); // Remove our atomic from temp clump RpClumpRemoveAtomic(clonedClump, atomic); @@ -582,7 +568,7 @@ bool CRenderWareSA::ReplaceAllAtomicsInModel(RpClump* pNew, unsigned short usMod return false; static_cast(currentModelInterface)->SetAtomic(atomic); - pGame->GetVisibilityPlugins()->SetAtomicId(atomic, usModelID); + pGame->GetVisibilityPlugins()->SetAtomicId(atomic, modelID); // Remove our atomic from temp clump RpClumpRemoveAtomic(clonedClump, atomic); @@ -593,13 +579,53 @@ bool CRenderWareSA::ReplaceAllAtomicsInModel(RpClump* pNew, unsigned short usMod } case eModelInfoType::CLUMP: { - // We do not delete or clear the clump copy here. + // We do not delete or clear the clump copy here // The copy will be removed when DestroyRwObject is called in CClumpModelInfo static_cast(currentModelInterface)->SetClump(clonedClump); break; } } + // Destroy old RwObject + // RwObject from original interface after type conversion + CBaseModelInfoSAInterface* originalInterface = pModelInfo->GetOriginalInterface(); + if (originalInterface) + originalInterface->DeleteRwObject(); + + // The user can convert the same model multiple times, but its original model interface is saved only once, + // so we remove the RwObject and the interface created during the last conversion + CBaseModelInfoSAInterface* lastConversionInterface = pModelInfo->GetLastConversionInterface(); + if (lastConversionInterface) + { + lastConversionInterface->DeleteRwObject(); + delete lastConversionInterface; + + pModelInfo->SetLastConversionInterface(nullptr); + } + + // Delete old RwObject (after setting the new one) + if (copyCurrentModelInterface) + { + // Update 2dfx counter + RwObject* object = copyCurrentModelInterface->pRwObject; + if (object) + { + if (object->type == RP_TYPE_ATOMIC) + currentModelInterface->ucNumOf2DEffects -= RpGeometryGet2dFxCount(reinterpret_cast(object)->geometry); + else if (object->type == RP_TYPE_CLUMP) + { + RpAtomic* atomic = Get2DEffectAtomic(reinterpret_cast(object)); + if (atomic) + currentModelInterface->ucNumOf2DEffects -= RpGeometryGet2dFxCount(atomic->geometry); + } + } + + if (!originalInterface) + copyCurrentModelInterface->DeleteRwObject(); + + delete copyCurrentModelInterface; + } + return true; } @@ -1005,19 +1031,28 @@ RwFrame* CRenderWareSA::GetFrameFromName(RpClump* pRoot, SString strName) return RwFrameFindFrame(RpGetFrame(pRoot), strName); } -int CRenderWareSA::GetClumpNumOfAtomics(RpClump* clump) +RpAtomic* CRenderWareSA::GetFirstAtomic(RpClump* clump) { - return clump ? RpClumpGetNumAtomics(clump) : 1; + return ((RpAtomic*(__cdecl*)(RpClump*))0x734820)(clump); } -RpAtomic* CRenderWareSA::GetFirstAtomic(RpClump* clump) +char* CRenderWareSA::GetFrameNodeName(RwFrame* frame) { - return ((RpAtomic*(__cdecl*)(RpClump*))FUNC_GetFirstAtomic)(clump); + return ((char*(__cdecl*)(RwFrame*))0x72FB30)(frame); } -char* CRenderWareSA::GetFrameNodeName(RwFrame* frame) +std::uint32_t CRenderWareSA::RpGeometryGet2dFxCount(RpGeometry* geometry) +{ + if (!geometry) + return 0; + + auto* plugin = RWPLUGINOFFSET(std::uint32_t, geometry, *(int*)0xC3A1E0); // g2dEffectPluginOffset + return plugin ? *plugin : 0; +} + +RpAtomic* CRenderWareSA::Get2DEffectAtomic(RpClump* clump) { - return ((char*(__cdecl*)(RwFrame*))FUNC_GetFrameNodeName)(frame); + return ((RpAtomic*(__cdecl*)(RpClump*))0x734880)(clump); } void CRenderWareSA::CMatrixToRwMatrix(const CMatrix& mat, RwMatrix& rwOutMatrix) diff --git a/Client/game_sa/CRenderWareSA.h b/Client/game_sa/CRenderWareSA.h index 5d7f4efed0f..3be13b17a02 100644 --- a/Client/game_sa/CRenderWareSA.h +++ b/Client/game_sa/CRenderWareSA.h @@ -22,9 +22,6 @@ struct SShaderReplacementStats; struct STexInfo; struct STexTag; -#define FUNC_GetFirstAtomic 0x734820 -#define FUNC_GetFrameNodeName 0x72FB30 - class CRenderWareSA : public CRenderWare { public: @@ -63,9 +60,6 @@ class CRenderWareSA : public CRenderWare // Loads all atomics from a clump into a container struct and returns the number of atomics it loaded unsigned int LoadAtomics(RpClump* pClump, RpAtomicContainer* pAtomics); - // Replaces all atomics for a specific model - bool ReplaceAllAtomicsInModel(RpClump* pNew, unsigned short usModelID) override; - // Replaces all atomics in a clump void ReplaceAllAtomicsInClump(RpClump* pDst, RpAtomicContainer* pAtomics, unsigned int uiAtomics); @@ -83,11 +77,12 @@ class CRenderWareSA : public CRenderWare // Replaces a CClumpModelInfo clump with a new clump bool ReplaceWeaponModel(RpClump* pNew, unsigned short usModelID) override; - bool ReplacePedModel(RpClump* pNew, unsigned short usModelID) override; - bool ReplaceModel(RpClump* pNew, unsigned short usModelID, DWORD dwSetClumpFunction); + // Replaces object model (clump or atomic) + bool ReplaceObjectModel(RpClump* newClump, std::uint16_t modelID) override; + // Replaces dynamic parts of the vehicle (models that have two different versions: 'ok' and 'dam'), such as doors // szName should be without the part suffix (e.g. 'door_lf' or 'door_rf', and not 'door_lf_dummy') bool ReplacePartModels(RpClump* pClump, RpAtomicContainer* pAtomics, unsigned int uiAtomics, const char* szName); @@ -125,10 +120,12 @@ class CRenderWareSA : public CRenderWare CModelTexturesInfo* GetModelTexturesInfo(ushort usModelId); RwFrame* GetFrameFromName(RpClump* pRoot, SString strName); - int GetClumpNumOfAtomics(RpClump* clump); - RpAtomic* GetFirstAtomic(RpClump* clump); + RpAtomic* GetFirstAtomic(RpClump* clump) override; + + static char* GetFrameNodeName(RwFrame* frame); - static char* GetFrameNodeName(RwFrame* frame); + std::uint32_t RpGeometryGet2dFxCount(RpGeometry* geometry) override; + RpAtomic* Get2DEffectAtomic(RpClump* clump) override; // Originally there was a possibility for this function to cause buffer overflow // It should be fixed here. diff --git a/Client/mods/deathmatch/logic/CClientDFF.cpp b/Client/mods/deathmatch/logic/CClientDFF.cpp index 84283706a90..97e143e26cd 100644 --- a/Client/mods/deathmatch/logic/CClientDFF.cpp +++ b/Client/mods/deathmatch/logic/CClientDFF.cpp @@ -331,6 +331,7 @@ bool CClientDFF::ReplaceObjectModel(RpClump* pClump, ushort usModel, bool bAlpha // Grab the model info for that model and replace the model CModelInfo* pModelInfo = g_pGame->GetModelInfo(usModel); + if (!pModelInfo->SetCustomModel(pClump)) return false; diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp index 03adb862c8c..4a57ab30c53 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp +++ b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp @@ -2582,7 +2582,7 @@ bool CLuaEngineDefs::EngineConvertModelToType(std::uint32_t model, eClientModelT if (!modelInfo) return false; - // We need to stream out the model, otherwise it will crash + // We need to stream out the model, otherwise it can crash g_pClientGame->GetObjectManager()->RestreamObjects(model); modelInfo->RestreamIPL(); diff --git a/Client/sdk/game/CModelInfo.h b/Client/sdk/game/CModelInfo.h index 9dd4b914500..d42589ee6fb 100644 --- a/Client/sdk/game/CModelInfo.h +++ b/Client/sdk/game/CModelInfo.h @@ -198,6 +198,7 @@ class CModelInfo virtual CBaseModelInfoSAInterface* GetLastConversionInterface() const noexcept = 0; virtual void SetLastConversionInterface(CBaseModelInfoSAInterface* lastInterface) noexcept = 0; virtual CBaseModelInfoSAInterface* GetOriginalInterface() const = 0; + virtual bool IsSameModelType() = 0; // ONLY use for CVehicleModelInfos virtual short GetAvailableVehicleMod(unsigned short usSlot) = 0; diff --git a/Client/sdk/game/CRenderWare.h b/Client/sdk/game/CRenderWare.h index 3ff9b53b7c6..e65c1c12089 100644 --- a/Client/sdk/game/CRenderWare.h +++ b/Client/sdk/game/CRenderWare.h @@ -27,6 +27,7 @@ struct RwTexDictionary; struct RwTexture; struct RpClump; struct RpAtomic; +struct RpGeometry; typedef CShaderItem CSHADERDUMMY; @@ -88,7 +89,6 @@ class CRenderWare virtual void DestroyTexture(RwTexture* pTex) = 0; virtual void ReplaceCollisions(CColModel* pColModel, unsigned short usModelID) = 0; virtual unsigned int LoadAtomics(RpClump* pClump, RpAtomicContainer* pAtomics) = 0; - virtual bool ReplaceAllAtomicsInModel(RpClump* pSrc, unsigned short usModelID) = 0; virtual void ReplaceAllAtomicsInClump(RpClump* pDst, RpAtomicContainer* pAtomics, unsigned int uiAtomics) = 0; virtual void ReplaceWheels(RpClump* pClump, RpAtomicContainer* pAtomics, unsigned int uiAtomics, const char* szWheel) = 0; virtual void RepositionAtomic(RpClump* pDst, RpClump* pSrc, const char* szName) = 0; @@ -96,6 +96,7 @@ class CRenderWare virtual bool ReplaceVehicleModel(RpClump* pNew, unsigned short usModelID) = 0; virtual bool ReplaceWeaponModel(RpClump* pNew, unsigned short usModelID) = 0; virtual bool ReplacePedModel(RpClump* pNew, unsigned short usModelID) = 0; + virtual bool ReplaceObjectModel(RpClump* newClump, std::uint16_t modelID) = 0; virtual bool ReplacePartModels(RpClump* pClump, RpAtomicContainer* pAtomics, unsigned int uiAtomics, const char* szName) = 0; virtual void PulseWorldTextureWatch() = 0; virtual void GetModelTextureNames(std::vector& outNameList, ushort usModelID) = 0; @@ -111,8 +112,11 @@ class CRenderWare virtual void RemoveClientEntityRefs(CClientEntityBase* pClientEntity) = 0; virtual void RemoveShaderRefs(CSHADERDUMMY* pShaderItem) = 0; virtual RwFrame* GetFrameFromName(RpClump* pRoot, SString strName) = 0; - virtual int GetClumpNumOfAtomics(RpClump* clump) = 0; virtual RpAtomic* GetFirstAtomic(RpClump* clump) = 0; + + virtual std::uint32_t RpGeometryGet2dFxCount(RpGeometry* geometry) = 0; + virtual RpAtomic* Get2DEffectAtomic(RpClump* clump) = 0; + virtual bool RightSizeTxd(const SString& strInTxdFilename, const SString& strOutTxdFilename, uint uiSizeLimit) = 0; virtual void TxdForceUnload(ushort usTxdId, bool bDestroyTextures) = 0; From 2d6b43b94e551b552e0ce279dd759385ada79c65 Mon Sep 17 00:00:00 2001 From: FileEX Date: Fri, 7 Mar 2025 00:08:16 +0100 Subject: [PATCH 07/10] Fix next crashes --- Client/game_sa/CModelInfoSA.h | 2 +- Client/game_sa/CRenderWareSA.cpp | 125 +++++++++++++++++++++++-------- 2 files changed, 95 insertions(+), 32 deletions(-) diff --git a/Client/game_sa/CModelInfoSA.h b/Client/game_sa/CModelInfoSA.h index 8f47f3fbf22..9ccbf43aae1 100644 --- a/Client/game_sa/CModelInfoSA.h +++ b/Client/game_sa/CModelInfoSA.h @@ -262,7 +262,7 @@ class CClumpModelInfoSAInterface : public CBaseModelInfoSAInterface union { char* m_animFileName; - uint32_t m_nAnimFileIndex; + std::int32_t m_nAnimFileIndex; }; void DeleteRwObject() { ((void(__thiscall*)(CClumpModelInfoSAInterface*))0x4C4E70)(this); } diff --git a/Client/game_sa/CRenderWareSA.cpp b/Client/game_sa/CRenderWareSA.cpp index 37cd8197279..f0d61a9b48e 100644 --- a/Client/game_sa/CRenderWareSA.cpp +++ b/Client/game_sa/CRenderWareSA.cpp @@ -325,34 +325,59 @@ bool CRenderWareSA::DoContainTheSameGeometry(RpClump* pClumpA, RpClump* pClumpB, // Fast check if comparing one atomic if (pAtomicB) { - RpGeometry* pGeometryA = ((RpAtomic*)((pClumpA->atomics.root.next) - 0x8))->geometry; - RpGeometry* pGeometryB = pAtomicB->geometry; - return pGeometryA == pGeometryB; + RpAtomic* atomicA = pGame->GetRenderWare()->GetFirstAtomic(pClumpA); + if (!atomicA) + return false; + + return atomicA->geometry == pAtomicB->geometry; } - // Get atomic list from both sides - std::vector atomicListA; - std::vector atomicListB; - GetClumpAtomicList(pClumpA, atomicListA); - if (pClumpB) - GetClumpAtomicList(pClumpB, atomicListB); - if (pAtomicB) - atomicListB.push_back(pAtomicB); + // Check number of atomics in clumps + int numAtomicsA = RpClumpGetNumAtomics(pClumpA); + int numAtomicsB = RpClumpGetNumAtomics(pClumpB); + if (numAtomicsA != numAtomicsB) + return false; + + std::vector geometryListA; + std::vector geometryListB; + geometryListA.reserve(numAtomicsA * sizeof(RpGeometry*)); + geometryListB.reserve(numAtomicsB * sizeof(RpGeometry*)); + + // Get geometry from clump A + RpClumpForAllAtomics( + pClumpA, [](RpAtomic* atomic, void* data) -> bool { + auto* geometryList = static_cast*>(data); + geometryList->push_back(atomic->geometry); - // Count geometries that exist in both sides - std::set geometryListA; - for (uint i = 0; i < atomicListA.size(); i++) - MapInsert(geometryListA, atomicListA[i]->geometry); + return true; + }, + &geometryListA); - uint uiInBoth = 0; - for (uint i = 0; i < atomicListB.size(); i++) - if (MapContains(geometryListA, atomicListB[i]->geometry)) - uiInBoth++; + // Get geometry from clump B + RpClumpForAllAtomics( + pClumpB, + [](RpAtomic* atomic, void* data) -> bool + { + auto* geometryList = static_cast*>(data); + geometryList->push_back(atomic->geometry); - // If less than 50% match then assume it is not the same - if (uiInBoth * 2 < atomicListB.size() || atomicListB.size() == 0) + return true; + }, + &geometryListB); + + if (geometryListA.size() != geometryListB.size()) return false; + std::unordered_set geometrySetB; + geometrySetB.reserve(geometryListB.size()); + geometrySetB.insert(geometryListB.begin(), geometryListB.end()); + + for (const auto& geomA : geometryListA) + { + if (geometrySetB.find(geomA) == geometrySetB.end()) + return false; + } + return true; } @@ -490,17 +515,49 @@ bool CRenderWareSA::ReplaceObjectModel(RpClump* newClump, std::uint16_t modelID) if (!pModelInfo) return false; - auto* oldObject = reinterpret_cast(pModelInfo->GetRwObject()); - if (DoContainTheSameGeometry(newClump, oldObject, nullptr) && pModelInfo->IsSameModelType()) - return true; - CBaseModelInfoSAInterface* currentModelInterface = pModelInfo->GetInterface(); + // Comparing two clumps here leads to an unknown crash during object creation (std::bad_alloc) in the SA code + if ((newClump == reinterpret_cast(currentModelInterface->pRwObject) || DoContainTheSameGeometry(newClump, nullptr, reinterpret_cast(currentModelInterface->pRwObject))) && pModelInfo->IsSameModelType()) + return true; + // We create a copy of the current interface to later call DeleteRwObject on it, // so we can remove the old RwObject after setting the new one // Old RwObject must be deleted after setting the new one to avoid crashes during restream - CBaseModelInfoSAInterface* copyCurrentModelInterface = new CBaseModelInfoSAInterface(); - MemCpyFast(copyCurrentModelInterface, currentModelInterface, sizeof(CBaseModelInfoSAInterface)); + CBaseModelInfoSAInterface* copyCurrentModelInterface = nullptr; + switch (reinterpret_cast(currentModelInterface->VFTBL)) + { + case VTBL_CAtomicModelInfo: + { + copyCurrentModelInterface = new CAtomicModelInfoSAInterface(); + MemCpyFast(copyCurrentModelInterface, currentModelInterface, sizeof(CAtomicModelInfoSAInterface)); + + break; + } + case VTBL_CDamageAtomicModelInfo: + { + copyCurrentModelInterface = new CDamageableModelInfoSAInterface(); + MemCpyFast(copyCurrentModelInterface, currentModelInterface, sizeof(CDamageableModelInfoSAInterface)); + + break; + } + case VTBL_CClumpModelInfo: + { + copyCurrentModelInterface = new CClumpModelInfoSAInterface(); + MemCpyFast(copyCurrentModelInterface, currentModelInterface, sizeof(CClumpModelInfoSAInterface)); + + break; + } + case VTBL_CTimeModelInfo: + { + copyCurrentModelInterface = new CTimeModelInfoSAInterface(); + MemCpyFast(copyCurrentModelInterface, currentModelInterface, sizeof(CTimeModelInfoSAInterface)); + + break; + } + } + + copyCurrentModelInterface->usNumberOfRefs = 0; currentModelInterface->pRwObject = nullptr; // We need to make a copy here because DeleteRwObject removes atomics from our new clump @@ -589,14 +646,22 @@ bool CRenderWareSA::ReplaceObjectModel(RpClump* newClump, std::uint16_t modelID) // Destroy old RwObject // RwObject from original interface after type conversion CBaseModelInfoSAInterface* originalInterface = pModelInfo->GetOriginalInterface(); - if (originalInterface) + if (originalInterface && originalInterface->pRwObject) + { originalInterface->DeleteRwObject(); + // If the originalInterface has an existing RwObject, it points to the same one as copyCurrentModelInterface + copyCurrentModelInterface->pRwObject = nullptr; + } + // The user can convert the same model multiple times, but its original model interface is saved only once, // so we remove the RwObject and the interface created during the last conversion CBaseModelInfoSAInterface* lastConversionInterface = pModelInfo->GetLastConversionInterface(); if (lastConversionInterface) { + if (lastConversionInterface->pRwObject == copyCurrentModelInterface->pRwObject) + copyCurrentModelInterface->pRwObject = nullptr; + lastConversionInterface->DeleteRwObject(); delete lastConversionInterface; @@ -620,9 +685,7 @@ bool CRenderWareSA::ReplaceObjectModel(RpClump* newClump, std::uint16_t modelID) } } - if (!originalInterface) - copyCurrentModelInterface->DeleteRwObject(); - + copyCurrentModelInterface->DeleteRwObject(); delete copyCurrentModelInterface; } From bd44f1d259732bffb3b2d56ab9be750d7bb6c2b5 Mon Sep 17 00:00:00 2001 From: FileEX Date: Mon, 10 Mar 2025 00:51:05 +0100 Subject: [PATCH 08/10] Back to original DoContainTheSameGeometry --- Client/game_sa/CRenderWareSA.cpp | 67 ++++++++++---------------------- 1 file changed, 21 insertions(+), 46 deletions(-) diff --git a/Client/game_sa/CRenderWareSA.cpp b/Client/game_sa/CRenderWareSA.cpp index f0d61a9b48e..cff90dc4b7e 100644 --- a/Client/game_sa/CRenderWareSA.cpp +++ b/Client/game_sa/CRenderWareSA.cpp @@ -325,59 +325,34 @@ bool CRenderWareSA::DoContainTheSameGeometry(RpClump* pClumpA, RpClump* pClumpB, // Fast check if comparing one atomic if (pAtomicB) { - RpAtomic* atomicA = pGame->GetRenderWare()->GetFirstAtomic(pClumpA); - if (!atomicA) - return false; - - return atomicA->geometry == pAtomicB->geometry; + RpGeometry* pGeometryA = ((RpAtomic*)((pClumpA->atomics.root.next) - 0x8))->geometry; + RpGeometry* pGeometryB = pAtomicB->geometry; + return pGeometryA == pGeometryB; } - // Check number of atomics in clumps - int numAtomicsA = RpClumpGetNumAtomics(pClumpA); - int numAtomicsB = RpClumpGetNumAtomics(pClumpB); - if (numAtomicsA != numAtomicsB) - return false; + // Get atomic list from both sides + std::vector atomicListA; + std::vector atomicListB; + GetClumpAtomicList(pClumpA, atomicListA); + if (pClumpB) + GetClumpAtomicList(pClumpB, atomicListB); + if (pAtomicB) + atomicListB.push_back(pAtomicB); - std::vector geometryListA; - std::vector geometryListB; - geometryListA.reserve(numAtomicsA * sizeof(RpGeometry*)); - geometryListB.reserve(numAtomicsB * sizeof(RpGeometry*)); + // Count geometries that exist in both sides + std::set geometryListA; + for (uint i = 0; i < atomicListA.size(); i++) + MapInsert(geometryListA, atomicListA[i]->geometry); - // Get geometry from clump A - RpClumpForAllAtomics( - pClumpA, [](RpAtomic* atomic, void* data) -> bool { - auto* geometryList = static_cast*>(data); - geometryList->push_back(atomic->geometry); + uint uiInBoth = 0; + for (uint i = 0; i < atomicListB.size(); i++) + if (MapContains(geometryListA, atomicListB[i]->geometry)) + uiInBoth++; - return true; - }, - &geometryListA); - - // Get geometry from clump B - RpClumpForAllAtomics( - pClumpB, - [](RpAtomic* atomic, void* data) -> bool - { - auto* geometryList = static_cast*>(data); - geometryList->push_back(atomic->geometry); - - return true; - }, - &geometryListB); - - if (geometryListA.size() != geometryListB.size()) + // If less than 50% match then assume it is not the same + if (uiInBoth * 2 < atomicListB.size() || atomicListB.size() == 0) return false; - std::unordered_set geometrySetB; - geometrySetB.reserve(geometryListB.size()); - geometrySetB.insert(geometryListB.begin(), geometryListB.end()); - - for (const auto& geomA : geometryListA) - { - if (geometrySetB.find(geomA) == geometrySetB.end()) - return false; - } - return true; } From 40a0d1d3544f9c4b104a48c9eb5b882026d2aefa Mon Sep 17 00:00:00 2001 From: FileEX Date: Tue, 11 Mar 2025 23:17:35 +0100 Subject: [PATCH 09/10] Cleanup & crash fix --- Client/game_sa/CRenderWareSA.cpp | 127 +++++++++++-------------------- win-create-projects.bat | 7 -- 2 files changed, 45 insertions(+), 89 deletions(-) diff --git a/Client/game_sa/CRenderWareSA.cpp b/Client/game_sa/CRenderWareSA.cpp index cff90dc4b7e..c95e45cfa32 100644 --- a/Client/game_sa/CRenderWareSA.cpp +++ b/Client/game_sa/CRenderWareSA.cpp @@ -496,49 +496,46 @@ bool CRenderWareSA::ReplaceObjectModel(RpClump* newClump, std::uint16_t modelID) if ((newClump == reinterpret_cast(currentModelInterface->pRwObject) || DoContainTheSameGeometry(newClump, nullptr, reinterpret_cast(currentModelInterface->pRwObject))) && pModelInfo->IsSameModelType()) return true; - // We create a copy of the current interface to later call DeleteRwObject on it, - // so we can remove the old RwObject after setting the new one - // Old RwObject must be deleted after setting the new one to avoid crashes during restream - CBaseModelInfoSAInterface* copyCurrentModelInterface = nullptr; - switch (reinterpret_cast(currentModelInterface->VFTBL)) + // We need to make a copy here because DeleteRwObject removes atomics from our new clump + RpClump* clonedClump = RpClumpClone(newClump); + if (!clonedClump) + return false; + + // Add extra reference to prevent the TXD from being unloaded after calling DeleteRwObject + bool extraRefAdded = false; + if (currentModelInterface->pRwObject) { - case VTBL_CAtomicModelInfo: - { - copyCurrentModelInterface = new CAtomicModelInfoSAInterface(); - MemCpyFast(copyCurrentModelInterface, currentModelInterface, sizeof(CAtomicModelInfoSAInterface)); + CTxdStore_AddRef(currentModelInterface->usTextureDictionary); + extraRefAdded = true; + } - break; - } - case VTBL_CDamageAtomicModelInfo: - { - copyCurrentModelInterface = new CDamageableModelInfoSAInterface(); - MemCpyFast(copyCurrentModelInterface, currentModelInterface, sizeof(CDamageableModelInfoSAInterface)); + // Destroy RwObject from original interface after type conversion + CBaseModelInfoSAInterface* originalInterface = pModelInfo->GetOriginalInterface(); + if (originalInterface && originalInterface->pRwObject) + { + originalInterface->DeleteRwObject(); - break; - } - case VTBL_CClumpModelInfo: - { - copyCurrentModelInterface = new CClumpModelInfoSAInterface(); - MemCpyFast(copyCurrentModelInterface, currentModelInterface, sizeof(CClumpModelInfoSAInterface)); + // If the originalInterface has an existing RwObject, it points to the same one as copyCurrentModelInterface + currentModelInterface->pRwObject = nullptr; + } - break; - } - case VTBL_CTimeModelInfo: - { - copyCurrentModelInterface = new CTimeModelInfoSAInterface(); - MemCpyFast(copyCurrentModelInterface, currentModelInterface, sizeof(CTimeModelInfoSAInterface)); + // The user can convert the same model multiple times, but its original model interface is saved only once, + // so we remove the RwObject and the interface created during the last conversion + CBaseModelInfoSAInterface* lastConversionInterface = pModelInfo->GetLastConversionInterface(); + if (lastConversionInterface) + { + if (lastConversionInterface->pRwObject == currentModelInterface->pRwObject) + currentModelInterface->pRwObject = nullptr; - break; - } - } + lastConversionInterface->DeleteRwObject(); + delete lastConversionInterface; - copyCurrentModelInterface->usNumberOfRefs = 0; - currentModelInterface->pRwObject = nullptr; + pModelInfo->SetLastConversionInterface(nullptr); + } - // We need to make a copy here because DeleteRwObject removes atomics from our new clump - RpClump* clonedClump = RpClumpClone(newClump); - if (!clonedClump) - return false; + // Destroy current (old) RwObject + if (currentModelInterface->pRwObject) + currentModelInterface->DeleteRwObject(); // Check model type switch (pModelInfo->GetModelType()) @@ -549,6 +546,9 @@ bool CRenderWareSA::ReplaceObjectModel(RpClump* newClump, std::uint16_t modelID) if (!atomic) return false; + // Create main root frame + RpAtomicSetFrame(atomic, RwFrameCreate()); + // Is damageable object? if (currentModelInterface->IsDamageAtomicVTBL()) { @@ -573,6 +573,8 @@ bool CRenderWareSA::ReplaceObjectModel(RpClump* newClump, std::uint16_t modelID) else static_cast(damagedInfo->baseInterface)->SetAtomic(atomic); + // Create main root frame + RpAtomicSetFrame(atomic, RwFrameCreate()); pGame->GetVisibilityPlugins()->SetAtomicId(atomic, damagedInfo->modelId); // Remove our atomic from temp clump @@ -599,6 +601,9 @@ bool CRenderWareSA::ReplaceObjectModel(RpClump* newClump, std::uint16_t modelID) if (!atomic) return false; + // Create main root frame + RpAtomicSetFrame(atomic, RwFrameCreate()); + static_cast(currentModelInterface)->SetAtomic(atomic); pGame->GetVisibilityPlugins()->SetAtomicId(atomic, modelID); @@ -618,52 +623,10 @@ bool CRenderWareSA::ReplaceObjectModel(RpClump* newClump, std::uint16_t modelID) } } - // Destroy old RwObject - // RwObject from original interface after type conversion - CBaseModelInfoSAInterface* originalInterface = pModelInfo->GetOriginalInterface(); - if (originalInterface && originalInterface->pRwObject) - { - originalInterface->DeleteRwObject(); - - // If the originalInterface has an existing RwObject, it points to the same one as copyCurrentModelInterface - copyCurrentModelInterface->pRwObject = nullptr; - } - - // The user can convert the same model multiple times, but its original model interface is saved only once, - // so we remove the RwObject and the interface created during the last conversion - CBaseModelInfoSAInterface* lastConversionInterface = pModelInfo->GetLastConversionInterface(); - if (lastConversionInterface) - { - if (lastConversionInterface->pRwObject == copyCurrentModelInterface->pRwObject) - copyCurrentModelInterface->pRwObject = nullptr; - - lastConversionInterface->DeleteRwObject(); - delete lastConversionInterface; - - pModelInfo->SetLastConversionInterface(nullptr); - } - - // Delete old RwObject (after setting the new one) - if (copyCurrentModelInterface) - { - // Update 2dfx counter - RwObject* object = copyCurrentModelInterface->pRwObject; - if (object) - { - if (object->type == RP_TYPE_ATOMIC) - currentModelInterface->ucNumOf2DEffects -= RpGeometryGet2dFxCount(reinterpret_cast(object)->geometry); - else if (object->type == RP_TYPE_CLUMP) - { - RpAtomic* atomic = Get2DEffectAtomic(reinterpret_cast(object)); - if (atomic) - currentModelInterface->ucNumOf2DEffects -= RpGeometryGet2dFxCount(atomic->geometry); - } - } - - copyCurrentModelInterface->DeleteRwObject(); - delete copyCurrentModelInterface; - } - + // Remove extra ref + if (extraRefAdded) + CTxdStore_RemoveRef(currentModelInterface->usTextureDictionary); + return true; } diff --git a/win-create-projects.bat b/win-create-projects.bat index cabad31c5d3..9691271981e 100644 --- a/win-create-projects.bat +++ b/win-create-projects.bat @@ -1,13 +1,6 @@ @echo off -rem Update CEF eventually -utils\premake5.exe install_cef -rem Update Unifont -utils\premake5.exe install_unifont - -rem Update discord-rpc -utils\premake5.exe install_discord rem Generate solutions utils\premake5.exe vs2022 From 252218e44528765f81a06b4dcd92280e6b24b8bf Mon Sep 17 00:00:00 2001 From: FileEX Date: Tue, 11 Mar 2025 23:26:10 +0100 Subject: [PATCH 10/10] Small fixes --- Client/mods/deathmatch/logic/CClientDFF.cpp | 2 +- win-create-projects.bat | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Client/mods/deathmatch/logic/CClientDFF.cpp b/Client/mods/deathmatch/logic/CClientDFF.cpp index 97e143e26cd..07e718a8b92 100644 --- a/Client/mods/deathmatch/logic/CClientDFF.cpp +++ b/Client/mods/deathmatch/logic/CClientDFF.cpp @@ -330,7 +330,7 @@ bool CClientDFF::ReplaceObjectModel(RpClump* pClump, ushort usModel, bool bAlpha g_pGame->GetModelInfo(usModel)->RestreamIPL(); // Grab the model info for that model and replace the model - CModelInfo* pModelInfo = g_pGame->GetModelInfo(usModel); + CModelInfo* pModelInfo = g_pGame->GetModelInfo(usModel); if (!pModelInfo->SetCustomModel(pClump)) return false; diff --git a/win-create-projects.bat b/win-create-projects.bat index 9691271981e..cabad31c5d3 100644 --- a/win-create-projects.bat +++ b/win-create-projects.bat @@ -1,6 +1,13 @@ @echo off +rem Update CEF eventually +utils\premake5.exe install_cef +rem Update Unifont +utils\premake5.exe install_unifont + +rem Update discord-rpc +utils\premake5.exe install_discord rem Generate solutions utils\premake5.exe vs2022