Skip to content

Add ability to replace clump model with atomic and vice-versa #4052

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
52 changes: 3 additions & 49 deletions Client/game_sa/CFileLoaderSA.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "gamesa_renderware.h"
#include "CFileLoaderSA.h"
#include "CModelInfoSA.h"
#include "CRenderWareSA.h"

CFileLoaderSA::CFileLoaderSA()
{
Expand Down Expand Up @@ -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 <size_t OutBuffSize>
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);
Expand Down Expand Up @@ -170,9 +124,9 @@ RpAtomic* CFileLoader_SetRelatedModelInfoCB(RpAtomic* atomic, SRelatedModelInfo*
CBaseModelInfoSAInterface* pBaseModelInfo = CModelInfo_ms_modelInfoPtrs[gAtomicModelId];
auto pAtomicModelInfo = reinterpret_cast<CAtomicModelInfo*>(pBaseModelInfo);
RwFrame* pOldFrame = reinterpret_cast<RwFrame*>(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<RpAtomic*>(pBaseModelInfo->pRwObject);
Expand Down
78 changes: 78 additions & 0 deletions Client/game_sa/CModelInfoSA.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ std::map<CTimeInfoSAInterface*, CTimeInfoSAInterface*> CModelInfo
std::unordered_map<DWORD, unsigned short> CModelInfoSA::ms_OriginalObjectPropertiesGroups;
std::unordered_map<DWORD, std::pair<float, float>> CModelInfoSA::ms_VehicleModelDefaultWheelSizes;
std::map<unsigned short, int> CModelInfoSA::ms_DefaultTxdIDMap;
std::unordered_map<std::uint32_t, CBaseModelInfoSAInterface*> CModelInfoSA::m_convertedModelInterfaces;

union tIdeFlags
{
Expand Down Expand Up @@ -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<unsigned short>(m_dwModelID));
break;
default:
Expand All @@ -1530,6 +1532,21 @@ 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);

if (currentInterface)
{
ppModelInfo[m_dwModelID]->usNumberOfRefs = currentInterface->usNumberOfRefs;
delete currentInterface;
}

MapRemove(m_convertedModelInterfaces, m_dwModelID);
}

// Reset the stored custom vehicle clump
m_pCustomClump = NULL;
}
Expand Down Expand Up @@ -1799,6 +1816,67 @@ void CModelInfoSA::MakeClumpModel(ushort usBaseID)
CopyStreamingInfoFromModel(usBaseID);
}

bool CModelInfoSA::ConvertToClump()
{
// Get current interface
CBaseModelInfoSAInterface* currentModelInterface = ppModelInfo[m_dwModelID];
if (!currentModelInterface)
return false;

// 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
// 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<CBaseModelInfo_SA_VTBL*>(VTBL_CClumpModelInfo);

// Set new interface for ModelInfo
ppModelInfo[m_dwModelID] = newClumpInterface;

// Store original interface
MapSet(m_convertedModelInterfaces, m_dwModelID, currentModelInterface);
return true;
}

bool CModelInfoSA::ConvertToAtomic()
{
// Get current interface
CClumpModelInfoSAInterface* currentClumpInterface = static_cast<CClumpModelInfoSAInterface*>(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 CRenderWareSA::ReplaceAllAtomicsInModel after passing the IsLoaded condition in the SetCustomModel.

// Set CAtomicModelInfo vtbl after copying data
newAtomicInterface->VFTBL = reinterpret_cast<CBaseModelInfo_SA_VTBL*>(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();
Expand Down
29 changes: 26 additions & 3 deletions Client/game_sa/CModelInfoSA.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ static void* ARRAY_ModelInfo = *(void**)(0x403DA4 + 3);

#define VAR_CTempColModels_ModelPed1 0x968DF0

#define VTBL_CClumpModelInfo 0x85BD30
#define VTBL_CAtomicModelInfo 0x85BBF0

class CBaseModelInfoSAInterface;
class CModelInfoSAInterface
{
Expand Down Expand Up @@ -252,9 +255,19 @@ class CClumpModelInfoSAInterface : public CBaseModelInfoSAInterface
char* m_animFileName;
uint32_t m_nAnimFileIndex;
};

void DeleteRwObject() { ((void(__thiscall*)(CClumpModelInfoSAInterface*))0x4C4E70)(this); }
void SetClump(RpClump* clump) { ((void(__thiscall*)(CClumpModelInfoSAInterface*, RpClump*))0x4C4F70)(this, clump); }
};

class CTimeModelInfoSAInterface : public CBaseModelInfoSAInterface
class CAtomicModelInfoSAInterface : public CBaseModelInfoSAInterface
{
public:
void DeleteRwObject() { ((void(__thiscall*)(CAtomicModelInfoSAInterface*))0x4C4440)(this); }
void SetAtomic(RpAtomic* atomic) { ((void(__thiscall*)(CAtomicModelInfoSAInterface*, RpAtomic*))0x4C4360)(this, atomic); }
};

class CTimeModelInfoSAInterface : public CAtomicModelInfoSAInterface
{
public:
CTimeInfoSAInterface timeInfo;
Expand All @@ -268,10 +281,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
Expand Down Expand Up @@ -356,6 +371,9 @@ class CModelInfoSA : public CModelInfo
static std::map<unsigned short, int> ms_DefaultTxdIDMap;
SVehicleSupportedUpgrades m_ModelSupportedUpgrades;

// Store original model interfaces after conersion clump->atomic or atomic->clump
static std::unordered_map<std::uint32_t, CBaseModelInfoSAInterface*> m_convertedModelInterfaces;

public:
CModelInfoSA();

Expand Down Expand Up @@ -489,6 +507,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;

Expand Down
Loading
Loading