diff --git a/Client/mods/deathmatch/logic/CClientPed.cpp b/Client/mods/deathmatch/logic/CClientPed.cpp index 21dda2ee8b..b20f83074a 100644 --- a/Client/mods/deathmatch/logic/CClientPed.cpp +++ b/Client/mods/deathmatch/logic/CClientPed.cpp @@ -3631,7 +3631,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(g_pGame->GetPools()->AddPed(this, m_ulModel)); @@ -3767,7 +3780,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) @@ -3901,12 +3927,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; @@ -3918,6 +3952,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 @@ -3965,13 +4025,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); @@ -3985,7 +4038,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 diff --git a/Client/multiplayer_sa/CMultiplayerSA_CrashFixHacks.cpp b/Client/multiplayer_sa/CMultiplayerSA_CrashFixHacks.cpp index e2b3039fa5..5fb897ba2d 100644 --- a/Client/multiplayer_sa/CMultiplayerSA_CrashFixHacks.cpp +++ b/Client/multiplayer_sa/CMultiplayerSA_CrashFixHacks.cpp @@ -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; @@ -2865,15 +2868,80 @@ 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() +{ + // clang-format off + + MTA_VERIFY_HOOK_LOCAL_SIZE; + __asm + { + test eax, eax + jz skipCreateAssociation + cmp dword ptr[eax+1Ch], 0 + jnz continueCreateAssociation + + pushad + push eax + call OnMY_CAnimBlendAssocGroup_CreateAssociations + add esp, 4*1 + popad + jmp skipCreateAssociation + + continueCreateAssociation: + mov edx, [eax] + mov ecx, eax + call dword ptr[edx+2Ch] + jmp RETURN_CAnimBlendAssocGroup_CreateAssociations + + skipCreateAssociation: + jmp RETURN_CAnimBlendAssocGroup_CreateAssociations_Skip } // clang-format on }