Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 66 additions & 12 deletions Client/mods/deathmatch/logic/CClientPed.cpp

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code changes here seem to be guarding against m_pModelInfo pointing to invalid data, but do we know why it's invalid in the first place?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Seems like a use after free or something?)

@Dutchman101 Dutchman101 Jul 2, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code changes here seem to be guarding against m_pModelInfo pointing to invalid data, but do we know why it's invalid in the first place?

@qaisjp
No, we don't know it.. the 'fix' acts on an emerging crash as per the crash stats. There's users complaining of this crash as well, but none of them could pinpoint the exact circumstances. After i couldn't find a 100% confirmed root cause, i just looked at, and prevented, additional paths i could find for it to occur.

Averting crashes based on stats and collected dumps alone is an established practice, we don't always get much to work with.

Original file line number Diff line number Diff line change
Expand Up @@ -3642,7 +3642,20 @@ void CClientPed::_CreateModel()
// Replace the loaded model info with the model we're going to load and
// add a reference to it.
m_pLoadedModelInfo = m_pModelInfo;
if (!m_pLoadedModelInfo)
{
NotifyUnableToCreate();
return;
}

m_pLoadedModelInfo->ModelAddRef(BLOCKING, "CClientPed::_CreateModel");
if (!m_pLoadedModelInfo->GetRwObject())
{
m_pLoadedModelInfo->RemoveRef();
m_pLoadedModelInfo = nullptr;
NotifyUnableToCreate();
return;
}

// Create the new ped
m_pPlayerPed = dynamic_cast<CPlayerPed*>(g_pGame->GetPools()->AddPed(this, m_ulModel));
Expand Down Expand Up @@ -3778,7 +3791,20 @@ void CClientPed::_CreateLocalModel()

// Add a reference to the model we're using
m_pLoadedModelInfo = m_pModelInfo;
if (!m_pLoadedModelInfo)
{
NotifyUnableToCreate();
return;
}

m_pLoadedModelInfo->ModelAddRef(BLOCKING, "CClientPed::_CreateLocalModel");
if (!m_pLoadedModelInfo->GetRwObject())
{
m_pLoadedModelInfo->RemoveRef();
m_pLoadedModelInfo = nullptr;
NotifyUnableToCreate();
return;
}

// Make sure we are CJ
if (m_pPlayerPed->GetModelIndex() != m_ulModel)
Expand Down Expand Up @@ -3912,12 +3938,20 @@ void CClientPed::_DestroyLocalModel()
// Make sure we are CJ again
if (m_pPlayerPed->GetModelIndex() != 0)
{
m_pPlayerPed->SetModelIndex(0);
auto* pDefaultModelInfo = g_pGame->GetModelInfo(0);
if (pDefaultModelInfo && pDefaultModelInfo->GetRwObject())
m_pPlayerPed->SetModelIndex(0);
}

// Remove reference to our previous model
m_pLoadedModelInfo->RemoveRef();
m_pLoadedModelInfo = NULL;
// Remove reference to our previous model.
// Always release regardless of whether the default model was restored;
// the ped is being abandoned by MTA here, and GTA's native ped ref still protects
// the model from streaming eviction if SetModelIndex(0) was not possible.
if (m_pLoadedModelInfo)
{
m_pLoadedModelInfo->RemoveRef();
m_pLoadedModelInfo = nullptr;
}

// NULL our pointers, we don't destroy the local player
m_pPlayerPed = NULL;
Expand All @@ -3929,6 +3963,32 @@ void CClientPed::_ChangeModel()
// Different model than before?
if (m_pPlayerPed->GetModelIndex() != m_ulModel)
{
CModelInfo* pLoadedModel = nullptr;
if (m_bIsLocalPlayer)
{
// Remember the model we had loaded and store the new model we're going to load
pLoadedModel = m_pLoadedModelInfo;
m_pLoadedModelInfo = m_pModelInfo;
if (!m_pLoadedModelInfo)
{
m_pLoadedModelInfo = pLoadedModel;
if (m_clientModel && m_clientModel->GetModelID() != m_ulModel)
m_clientModel = nullptr;
return;
}

// Add reference to the model
m_pLoadedModelInfo->ModelAddRef(BLOCKING, "CClientPed::_ChangeModel");
if (!m_pLoadedModelInfo->GetRwObject())
{
m_pLoadedModelInfo->RemoveRef();
m_pLoadedModelInfo = pLoadedModel;
if (m_clientModel && m_clientModel->GetModelID() != m_ulModel)
m_clientModel = nullptr;
return;
}
}

g_pMultiplayer->SetAutomaticVehicleStartupOnPedEnter(false);

// We need to reset visual stats when changing from CJ model
Expand Down Expand Up @@ -3976,13 +4036,6 @@ void CClientPed::_ChangeModel()
// Takes care of clothes/task issues
Respawn(NULL, true, false);

// Remember the model we had loaded and store the new model we're going to load
CModelInfo* pLoadedModel = m_pLoadedModelInfo;
m_pLoadedModelInfo = m_pModelInfo;

// Add reference to the model
m_pLoadedModelInfo->ModelAddRef(BLOCKING, "CClientPed::_ChangeModel");

// Set the new player model and restore the interior
m_pPlayerPed->SetModelIndex(m_ulModel);

Expand All @@ -3996,7 +4049,8 @@ void CClientPed::_ChangeModel()
}

// Remove reference to the old model we used (Flag extra GTA reference to be removed as well)
pLoadedModel->RemoveRef(true);
if (pLoadedModel)
pLoadedModel->RemoveRef(true);
pLoadedModel = NULL;

// Warp into it again
Expand Down
93 changes: 86 additions & 7 deletions Client/multiplayer_sa/CMultiplayerSA_CrashFixHacks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2832,29 +2832,32 @@ static void __declspec(naked) HOOK_CVolumetricShadowMgr_Update()
////////////////////////////////////////////////////////////////////////
// CAnimManager::CreateAnimAssocGroups
//
// CModelInfo::ms_modelInfoPtrs at the given index is a null pointer
// Missing model info or RwObject before the clump is used for associations
////////////////////////////////////////////////////////////////////////
void OnMY_CAnimManager_CreateAnimAssocGroups(uint uiModelId)
bool OnMY_CAnimManager_CreateAnimAssocGroups(uint uiModelId)
{
CModelInfo* pModelInfo = pGameInterface->GetModelInfo(uiModelId);
CBaseModelInfoSAInterface* pInterface = pModelInfo ? pModelInfo->GetInterface() : nullptr;
if (!pInterface || pInterface->pRwObject == nullptr)
if (!pInterface || !pInterface->pRwObject)
{
// Crash will occur at offset 00349b7b
OnCrashAverted(816);
LogEvent(816, "Model not loaded", "CAnimManager_CreateAnimAssocGroups", SString("No RwObject for model:%d", uiModelId), 5416);
CArgMap argMap;
argMap.Set("id", uiModelId);
argMap.Set("reason", "createanim");
SetApplicationSetting("diagnostics", "gta-model-fail", argMap.ToString());
return false;
}
return true;
}

// Hook info
#define HOOKPOS_CAnimManager_CreateAnimAssocGroups 0x4D3D52
#define HOOKSIZE_CAnimManager_CreateAnimAssocGroups 5
#define HOOKCHECK_CAnimManager_CreateAnimAssocGroups 0x8B
DWORD RETURN_CAnimManager_CreateAnimAssocGroups = 0x4D3D59;
static void __declspec(naked) HOOK_CAnimManager_CreateAnimAssocGroups()
DWORD RETURN_CAnimManager_CreateAnimAssocGroups = 0x4D3D59;
DWORD RETURN_CAnimManager_CreateAnimAssocGroups_Skip = 0x4D3D71;
void _declspec(naked) HOOK_CAnimManager_CreateAnimAssocGroups()
{
MTA_VERIFY_HOOK_LOCAL_SIZE;

Expand All @@ -2865,15 +2868,91 @@ static void __declspec(naked) HOOK_CAnimManager_CreateAnimAssocGroups()
push eax
call OnMY_CAnimManager_CreateAnimAssocGroups
add esp, 4*1
test al, al
popad
jz skipCreateInstance

// Replaced code
// Replaced code
push ecx
mov ecx, dword ptr[ARRAY_ModelInfo]
mov eax, dword ptr[ecx + eax*4]
pop ecx

jmp RETURN_CAnimManager_CreateAnimAssocGroups

skipCreateInstance:
xor ebx, ebx
jmp RETURN_CAnimManager_CreateAnimAssocGroups_Skip
}
// clang-format on
}

void OnMY_CAnimBlendAssocGroup_CreateAssociations(CBaseModelInfoSAInterface* pModelInfo)
{
OnCrashAverted(816);

int iModelId = -1;
CBaseModelInfoSAInterface** ppModelInfo = (CBaseModelInfoSAInterface**)ARRAY_ModelInfo;
const int maximumModelId = pGameInterface->GetBaseIDforTXD();
for (int i = 0; i < maximumModelId; i++)
{
if (ppModelInfo[i] == pModelInfo)
{
iModelId = i;
break;
}
}

LogEvent(816, "Model not loaded", "CAnimBlendAssocGroup_CreateAssociations", SString("No RwObject for model:%d", iModelId), 5416);
CArgMap argMap;
argMap.Set("id", iModelId);
argMap.Set("reason", "createassoc");
SetApplicationSetting("diagnostics", "gta-model-fail", argMap.ToString());
}

#define HOOKPOS_CAnimBlendAssocGroup_CreateAssociations 0x4CE2F7
#define HOOKSIZE_CAnimBlendAssocGroup_CreateAssociations 7
#define HOOKCHECK_CAnimBlendAssocGroup_CreateAssociations 0x8B
DWORD RETURN_CAnimBlendAssocGroup_CreateAssociations = 0x4CE2FE;
DWORD RETURN_CAnimBlendAssocGroup_CreateAssociations_Skip = 0x4CE36F;
void _declspec(naked) HOOK_CAnimBlendAssocGroup_CreateAssociations()
Comment on lines +2913 to +2918

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please could you write some comments explaining exactly what the hook does?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Dutchman101 please can you add the requested comments here and we'll get this merged

{
// clang-format off

MTA_VERIFY_HOOK_LOCAL_SIZE;
__asm
{
// CModelInfo::GetModelInfoFromHashKey returned the model info for
// this animation. SA already skips the entry when it returns null.
test eax, eax
jz skipCreateAssociation

// CBaseModelInfo::pRwObject is at +0x1C. The original instructions
// immediately call CBaseModelInfo::CreateInstance at vtable+0x2C.
cmp dword ptr[eax+1Ch], 0
jnz continueCreateAssociation

// CreateInstance requires the RenderWare object. Record its absence
// and follow SA's existing per-animation skip path.
pushad
push eax
call OnMY_CAnimBlendAssocGroup_CreateAssociations
add esp, 4*1
popad
jmp skipCreateAssociation

continueCreateAssociation:
// Restore the seven overwritten bytes: load the vtable, pass the
// model info as this, then call CreateInstance.
mov edx, [eax]
mov ecx, eax
call dword ptr[edx+2Ch]
jmp RETURN_CAnimBlendAssocGroup_CreateAssociations

skipCreateAssociation:
// 0x4CE36F increments the created-association count and advances
// the animation and static-association indices for the next entry.
jmp RETURN_CAnimBlendAssocGroup_CreateAssociations_Skip
}
// clang-format on
}
Expand Down
Loading