Stand/Stand/ComponentCrashPatch.cpp
2024-10-16 11:20:42 +08:00

902 lines
36 KiB
C++

#include "ComponentCrashPatch.hpp"
#include "AbstractEntity.hpp"
#include "CLightEntity.hpp"
#include "cloth.hpp"
#include "ComponentNetcode.hpp" // packet_src
#include "CPedDrawHandler.hpp"
#include "CPickup.hpp"
#include "crSkeleton.hpp"
#include "eEntityType.hpp"
#include "FlowEvent.hpp"
#include "grmShaderGroup.hpp"
#include "gta_custom_shader_effect.hpp"
#include "gta_ped.hpp"
#include "gta_vehicle.hpp"
#include "Gui.hpp"
#include "Hooking.hpp"
#include "Label.hpp"
#include "phBoundComposite.hpp"
#include "phConstraint.hpp"
#include "pointers.hpp"
#include "Util.hpp"
#include "VehicleGadgets.hpp"
#include "VehicleType.hpp"
#include "ComponentImpl.hpp"
namespace Stand
{
static int32_t* CPopCycle_sm_nCurrentDaySubdivision;
static DetourHook report_error_wrap_hook{ "X3" };
static DetourHook CPopCycle_UpdateCurrZone_hook{ "X4" };
static DetourHook unk_cherax_crash_hook{ "X5" };
static DetourHook unk_cherax_crash_2_hook{ "X6" };
static DetourHook CVehicle_SlideMechanicalPart_hook{ "X7" };
static DetourHook CPed_SwitchToRagdollInternal_hook{ "X8" };
static DetourHook rage_crSkeleton_GetGlobalMtx_hook{ "X9" };
static DetourHook CTorsoIkSolver_ApplyPointTorsoInDirection_hook{ "XA" };
static DetourHook CLightEntity_GetWorldMatrix_hook{ "XB" };
static DetourHook unk_izuku_crash_hook{ "XC" };
static DetourHook rage_grcProgram_SetTextureResourcesUsingVectorDXAPICall_hook{ "XD" };
static DetourHook ped_component_draw_thingy_hook{ "XE" };
static DetourHook draw_object_hook{ "XF" };
static DetourHook CCustomShaderEffectVehicleType_RestoreModelInfoDrawable_hook{ "XG" };
static DetourHook update_ped_hair_texture_hook{ "XH" };
static DetourHook CTaskHeliPassengerRappel_do_something_hook{ "XI" };
static DetourHook rage_ComponentInfoManager_CreateComponentInfo_hook{ "XJ" };
static DetourHook unk_related_to_object_despawning_hook{ "XK" };
static DetourHook something_related_to_vehicle_parachutes_hook{ "XL" };
static DetourHook CObjectFragmentDrawHandler_do_something_hook{ "XM" };
static DetourHook CTrain_ValidateLinkedLists_hook{ "XN" };
static DetourHook rage_characterClothController_SkinMesh_hook{ "XO" };
static DetourHook rage_characterClothController_Update_hook{ "XP" };
static DetourHook CPhysical_AttachToPhysicalBasic_hook{ "XQ" };
static DetourHook CPhysical_AttachToPhysicalUsingPhysics_hook{ "XR" };
static DetourHook CPickup_AssignCustomArchetype_hook{ "XS" };
//static DetourHook rage_phConstraintMgr_AllocateAndConstruct_hook{ "XT" };
static DetourHook rage_phConstraintBase_ctor_hook{ "XT" };
static DetourHook CGameScriptHandlerMgr_AddToReservationCount_hook{ "XU" };
static DetourHook CDynamicEntity_GetBoneIndexFromBoneTag_hook{ "XV" };
static DetourHook CBodyRecoilIkSolver_PreIkUpdate_hook{ "XW" };
static DetourHook ARTFeedbackInterfaceGta_onBehaviourFailure_hook{ "XX" };
static DetourHook CTaskVehicleMountedWeapon_ComputeSearchTarget_hook{ "XY" };
static bool reported_fatal_error = false;
static void __fastcall report_error_wrap(uint32_t a1)
{
SOUP_IF_UNLIKELY (!g_gui.doesRootStateAllowCrashPatches())
{
return COMP_OG(report_error_wrap)(a1);
}
if (packet_src.isValid())
{
//packet_src.getAndApplyReactions(FlowEvent::SE_CRASH, "E0");
Util::onPreventedCrash("E0", Util::to_padded_hex_string(a1), packet_src);
}
else if (!reported_fatal_error)
{
Exceptional::report(fmt::format("Fatal Game Error (Code {})", Util::to_padded_hex_string(a1)), LANG_GET("ERR_T_GME"));
reported_fatal_error = true;
}
}
static void __fastcall CPopCycle_UpdateCurrZone()
{
__try
{
if (*CPopCycle_sm_nCurrentDaySubdivision >= 12)
{
*CPopCycle_sm_nCurrentDaySubdivision %= 12;
Util::onPreventedCrash(CODENAME(CPopCycle_UpdateCurrZone));
}
}
__EXCEPTIONAL()
{
}
__try
{
COMP_OG(CPopCycle_UpdateCurrZone)();
}
__EXCEPTIONAL_IF(g_gui.doesRootStateAllowCrashPatches())
{
}
}
// void __fastcall CPhysical::DetachFromParentAndChildren(CPhysical *this, unsigned __int16 nDetachFlags)
static void __fastcall unk_cherax_crash(int64_t* a1, unsigned __int16 a2)
{
__try
{
COMP_OG(unk_cherax_crash)(a1, a2);
}
__EXCEPTIONAL_IF(g_gui.doesRootStateAllowCrashPatches())
{
}
}
// void __fastcall CPopSchedule::SetOverridePedGroup(CPopSchedule* this, unsigned int overridePedGroup, unsigned int overridePercentage)
static void __fastcall unk_cherax_crash_2(__int64 a1, int a2, int a3)
{
__try
{
return COMP_OG(unk_cherax_crash_2)(a1, a2, a3);
}
__except (g_gui.doesRootStateAllowCrashPatches() ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
{
Util::onPreventedCrash(CODENAME(unk_cherax_crash_2));
}
}
// Invalid bone index when doing deluxo flight with some vehicles, like kuruma.
// I'm not aware of this being networkable.
static void __fastcall CVehicle_SlideMechanicalPart(CVehicle* _this, int iBoneIndex, float fDesiredOffset, float* rfCurrentOffset, v3* vSlideDirection, float fMovementSpeed)
{
SOUP_IF_UNLIKELY (iBoneIndex < 0)
{
SOUP_IF_LIKELY (g_gui.doesRootStateAllowCrashPatches())
{
if (!g_comp_crashpatch.silently_block_invalid_mechanical_part)
{
Util::onPreventedCrash(CODENAME(CVehicle_SlideMechanicalPart));
}
return;
}
}
return COMP_OG(CVehicle_SlideMechanicalPart)(_this, iBoneIndex, fDesiredOffset, rfCurrentOffset, vSlideDirection, fMovementSpeed);
}
// Out-of-bounds boneIdx e.g. when a_c_poodle dies or starts to ragdoll, because it's missing a weapon bone. See scripts/no-weapon-bone.pluto.
// That ends up throwing in CPedWeaponManager::SwitchToRagdoll because of ->GetUndamagedEntity()->GetBoundMatrix() on nullptr.
// We catch it here.
static void __fastcall CPed_SwitchToRagdollInternal(CPed* a1)
{
__try
{
COMP_OG(CPed_SwitchToRagdollInternal)(a1);
}
__except (g_gui.doesRootStateAllowCrashPatches() ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
{
Util::onPreventedCrash(CODENAME(CPed_SwitchToRagdollInternal));
}
}
static bool can_catch_out_of_bounds_boneIdx = false;
struct OutOfBoundsBoneIdx {};
static void __fastcall rage_crSkeleton_GetGlobalMtx(rage::crSkeleton* a1, unsigned int boneIdx, rage::Mat34V* outMtx)
{
SOUP_IF_UNLIKELY (boneIdx >= a1->num_bones)
{
SOUP_IF_LIKELY (g_gui.doesRootStateAllowCrashPatches())
{
if (can_catch_out_of_bounds_boneIdx)
{
throw OutOfBoundsBoneIdx();
}
boneIdx = (a1->num_bones - 1);
Util::onPreventedCrash(CODENAME(rage_crSkeleton_GetGlobalMtx));
}
}
__try
{
COMP_OG(rage_crSkeleton_GetGlobalMtx)(a1, boneIdx, outMtx);
}
__EXCEPTIONAL_IF(g_gui.doesRootStateAllowCrashPatches())
{
}
}
// Catch out-of-bounds boneIdx here, can occur e.g. when shooting weapons as a fish.
// Also seems to be a part of Nightfall's "Luna" crash.
static void __fastcall CTorsoIkSolver_ApplyPointTorsoInDirection(__int64 a1)
{
can_catch_out_of_bounds_boneIdx = true;
try
{
COMP_OG(CTorsoIkSolver_ApplyPointTorsoInDirection)(a1);
}
catch (OutOfBoundsBoneIdx&)
{
Util::onPreventedCrash(CODENAME(CTorsoIkSolver_ApplyPointTorsoInDirection));
}
can_catch_out_of_bounds_boneIdx = false;
}
static bool CLightEntity_GetWorldMatrix(CLightEntity* _this, rage::Matrix34& mat, /*C2dEffect*/ void* pEffect, /*CLightAttr*/ void* lightData)
{
__try
{
if (!pEffect
|| (_this->Get2dEffectType() == ET_LIGHT
&& !lightData
)
)
{
SOUP_IF_LIKELY (g_gui.doesRootStateAllowCrashPatches())
{
Util::onPreventedCrash(CODENAME(CLightEntity_GetWorldMatrix));
// LightEntityMgr::Remove
{
pointers::CGameWorld_Remove(_this, false, false);
delete _this;
}
}
return false;
}
return COMP_OG(CLightEntity_GetWorldMatrix)(_this, mat, pEffect, lightData);
}
__EXCEPTIONAL_IF(g_gui.doesRootStateAllowCrashPatches())
{
}
return false;
}
// void __fastcall rage::netObjectMgrBase::Update(rage::netObjectMgrBase *this, bool bUpdateNetworkObjects)
//
// This catches an exception when trying to apply a force to an object where HAS_OBJECT_BEEN_BROKEN is true
// Possibly also when trying to apply a force on an invalid entity handle
// Not sure what "Izuku" does
//
// We also catch an exception here in rage::netSyncTree::Update when GetSyncData returned a nullptr.
static void __fastcall unk_izuku_crash(rage::netObjectMgrBase* a1, bool bUpdateNetworkObjects)
{
__try
{
COMP_OG(unk_izuku_crash)(a1, bUpdateNetworkObjects);
}
__EXCEPTIONAL_IF(g_gui.doesRootStateAllowCrashPatches())
{
}
}
// This likes to crash sometimes in D3D11.dll, unsure why.
static void __fastcall rage_grcProgram_SetTextureResourcesUsingVectorDXAPICall(void* _this, int eType)
{
__try
{
COMP_OG(rage_grcProgram_SetTextureResourcesUsingVectorDXAPICall)(_this, eType);
}
__EXCEPTIONAL()
{
}
}
// void __fastcall rage::grmShaderGroup::SetVar(rage::grmShaderGroup* this, rage::__grmShaderGroupVar var, const rage::grcTexture* tex)
static void __fastcall ped_component_draw_thingy(rage::grmShaderGroup* a1, int a2, __int64 a3)
{
__try
{
if (a1->var_array)
{
COMP_OG(ped_component_draw_thingy)(a1, a2, a3);
}
else
{
Util::onPreventedCrash(CODENAME(ped_component_draw_thingy));
}
}
__except (g_gui.doesRootStateAllowCrashPatches() ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
{
Util::onPreventedCrash(CODENAME(ped_component_draw_thingy));
}
}
// void __fastcall rage::grcVertexProgram::SetStructuredBuffer(unsigned int address, const rage::grcBufferD3D11Resource* pBuffer)
static void __fastcall draw_object(unsigned int a1, __int64 a2)
{
__try
{
COMP_OG(draw_object)(a1, a2);
}
__except (g_gui.doesRootStateAllowCrashPatches() ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
{
Util::onPreventedCrash(CODENAME(draw_object));
}
}
// Related to CVehicleStreamRequestGfx/CVehicleStreamRenderGfx.
static bool __fastcall CCustomShaderEffectVehicleType_RestoreModelInfoDrawable(CCustomShaderEffectVehicleType* _this, rage::rmcDrawable* pDrawable)
{
__try
{
return COMP_OG(CCustomShaderEffectVehicleType_RestoreModelInfoDrawable)(_this, pDrawable);
}
__EXCEPTIONAL_IF(g_gui.doesRootStateAllowCrashPatches())
{
}
return false;
}
// void __fastcall CPedVariationStream::SetPaletteTexture(CPed *pPed, int comp)
static void __fastcall update_ped_hair_texture(CPed* a1, int a2)
{
__try
{
auto gfx = reinterpret_cast<CPedDrawHandler*>(a1->draw_handler)->stream_render_gfx;
SOUP_IF_UNLIKELY (gfx != nullptr // game can handle gfx being nullptr
&& gfx->custom_shader_effect == nullptr // but not the custom shader effect being nullptr
)
{
SOUP_IF_LIKELY (g_gui.doesRootStateAllowCrashPatches())
{
return;
}
}
COMP_OG(update_ped_hair_texture)(a1, a2);
}
__EXCEPTIONAL_IF(g_gui.doesRootStateAllowCrashPatches())
{
}
}
// CTaskHeliPassengerRappel::ProcessPostMovement
static bool __fastcall CTaskHeliPassengerRappel_do_something(__int64 a1)
{
__try
{
return COMP_OG(CTaskHeliPassengerRappel_do_something)(a1);
}
__EXCEPTIONAL_IF(g_gui.doesRootStateAllowCrashPatches())
{
}
return false;
}
// Related to 2take1's "Fragment" crash, which spawns prop_fragtest_cnst_04 and breaks it.
// That causes this function to be called within phMidphase::ProcessSelfCollisionNew with an invalid component index.
// We force the component in-bound. It can't be nullptr because of the subsequent call to OBBTest.
static rage::ComponentInfo* __fastcall rage_ComponentInfoManager_CreateComponentInfo(rage::ComponentInfoManager* _this, int component, const rage::Mat34V* curInstanceMatrix, const rage::Mat34V* lastInstanceMatrix, const rage::phBoundComposite* boundComposite)
{
SOUP_IF_UNLIKELY (boundComposite->bounds[component] == nullptr)
{
SOUP_IF_LIKELY (g_gui.doesRootStateAllowCrashPatches())
{
component = 0;
Util::onPreventedCrash(CODENAME(rage_ComponentInfoManager_CreateComponentInfo));
}
}
return COMP_OG(rage_ComponentInfoManager_CreateComponentInfo)(_this, component, curInstanceMatrix, lastInstanceMatrix, boundComposite);
}
// __int64 __fastcall CObject::ProcessControl(CObject* this)
static __int64 __fastcall unk_related_to_object_despawning(CObject* a1, __int64 a2, __int64 a3)
{
__try
{
return COMP_OG(unk_related_to_object_despawning)(a1, a2, a3);
}
__except (g_gui.doesRootStateAllowCrashPatches() ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
{
}
Util::onPreventedCrash(CODENAME(unk_related_to_object_despawning));
__try
{
AbstractEntity::get(reinterpret_cast<CPhysical*>(a1)).removeFromPlaneOfExistence();
}
__EXCEPTIONAL()
{
}
return 0;
}
// CAutomobile::ProcessCarParachute
static void __fastcall something_related_to_vehicle_parachutes(void* a1)
{
__try
{
return COMP_OG(something_related_to_vehicle_parachutes)(a1);
}
__except (g_gui.doesRootStateAllowCrashPatches() ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
{
Util::onPreventedCrash(CODENAME(something_related_to_vehicle_parachutes));
}
}
// rage::dlCmdBase *__fastcall CObjectFragmentDrawHandler::AddToDrawList(CObjectFragmentDrawHandler* this, CEntity* pBaseEntity, rage::fwDrawDataAddParams* pBaseParams)
static __int64 __fastcall CObjectFragmentDrawHandler_do_something(__int64 CObjectFragmentDrawHandler, CEntity* a2, __int64 a3)
{
SOUP_IF_LIKELY (auto mi = g_hooking.getModelInfo(a2->archetype->hash);
mi != nullptr
&& pointers::rage_fwArchetype_GetFragType(mi) != nullptr
)
{
__try
{
return COMP_OG(CObjectFragmentDrawHandler_do_something)(CObjectFragmentDrawHandler, a2, a3);
}
__EXCEPTIONAL()
{
}
}
else
{
// Not a valid model, remove this entity.
__try
{
AbstractEntity::get(reinterpret_cast<CPhysical*>(a2)).removeFromPlaneOfExistenceForce();
Util::onPreventedCrash(CODENAME(CObjectFragmentDrawHandler_do_something));
}
__EXCEPTIONAL()
{
}
}
return 0;
}
static void __fastcall CTrain_ValidateLinkedLists(CTrain* carriage)
{
__try
{
if (!carriage)
{
return;
}
// `carriage` has just been set to a train's linked_backward or linked_forward
SOUP_IF_UNLIKELY (carriage->isBackwardLinkedTo(carriage))
{
SOUP_IF_LIKELY (g_gui.doesRootStateAllowCrashPatches())
{
//Util::toast("Recursive backward linkage detected, trying to fix it.", TC_OTHER);
Util::onPreventedCrash(LOC("CRSH_TRNLOOP"), g_hooking.train_sync_blamer.getBlame());
carriage->linked_backward.reset();
}
}
SOUP_IF_UNLIKELY (carriage->isForwardLinkedTo(carriage))
{
SOUP_IF_LIKELY (g_gui.doesRootStateAllowCrashPatches())
{
//Util::toast("Recursive forward linkage detected, trying to fix it.", TC_OTHER);
Util::onPreventedCrash(LOC("CRSH_TRNLOOP"), g_hooking.train_sync_blamer.getBlame());
carriage->linked_forward.reset();
}
}
return COMP_OG(CTrain_ValidateLinkedLists)(carriage);
}
__EXCEPTIONAL()
{
}
}
// Part of Nightfall's "Schizophrenic crash", transforms the player into cs_solomon, cs_josh, or cs_lazlow.
static void rage_characterClothController_SkinMesh(rage::characterClothController* _this, const rage::crSkeleton* skeleton)
{
__try
{
for (auto& boneIdx : _this->m_BoneIndexMap)
{
SOUP_IF_UNLIKELY (boneIdx >= skeleton->num_bones)
{
//boneIdx = (skel->num_bones - 1);
Util::onPreventedCrash(CODENAME(rage_characterClothController_SkinMesh), g_hooking.schizo_sync_blamer.getBlame());
return;
}
}
COMP_OG(rage_characterClothController_SkinMesh)(_this, skeleton);
}
__EXCEPTIONAL_IF(g_gui.doesRootStateAllowCrashPatches())
{
}
}
// Same as above.
static void rage_characterClothController_Update(rage::characterClothController* _this, const rage::Vec3V* gravityV, float timeScale, const rage::crSkeleton* skeleton, float fTime, const rage::phBoundComposite* customBound, int* bonesIndices)
{
__try
{
for (auto& boneIdx : _this->m_BoneIndexMap)
{
SOUP_IF_UNLIKELY (boneIdx >= skeleton->num_bones)
{
SOUP_IF_LIKELY (g_gui.doesRootStateAllowCrashPatches())
{
//boneIdx = (skel->num_bones - 1);
Util::onPreventedCrash(CODENAME(rage_characterClothController_Update), g_hooking.schizo_sync_blamer.getBlame());
return;
}
}
}
COMP_OG(rage_characterClothController_Update)(_this, gravityV, timeScale, skeleton, fTime, customBound, bonesIndices);
}
__EXCEPTIONAL_IF(g_gui.doesRootStateAllowCrashPatches())
{
}
}
[[nodiscard]] static bool validateAttachment(CPhysical* attacher, CPhysical* attachee)
{
SOUP_IF_UNLIKELY (!g_gui.doesRootStateAllowCrashPatches())
{
return true;
}
//Util::toast(fmt::format("Attaching {} to {}", attacher->toString(), attachee->toString()), TOAST_ALL);
// This goes wrong because CTrailer often assumes its attach parent is CVehicle and tries to access its data,
// e.g. CTrailer::DetachFromParent will try to access the vehicle gadgets of its attach parent.
if (attacher->type == ENTITY_TYPE_VEHICLE
&& static_cast<CVehicle*>(attacher)->vehicle_type == VEHICLE_TYPE_TRAILER
&& attachee->type != ENTITY_TYPE_VEHICLE
)
{
//Util::toast(PHSTR("Attaching a trailer to a non-vehicle? Nonsense."));
Util::onPreventedCrash(CODENAME(CPhysical_AttachToPhysicalBasic));
return false;
}
for (auto node = attacher; node; node = static_cast<CPhysical*>(node->GetChildAttachment()))
{
if (node == attachee)
{
//Util::toast(PHSTR("My new parent is already my child? Nonsense."), TOAST_ALL);
Util::onPreventedCrash(LOC("CRSH_ATTLOOP"));
return false;
}
}
for (auto node = attacher; node; node = static_cast<CPhysical*>(node->GetSiblingAttachment()))
{
if (node == attachee)
{
//Util::toast(PHSTR("My new parent is already my sibling? Nonsense."), TOAST_ALL);
Util::onPreventedCrash(LOC("CRSH_ATTLOOP"));
return false;
}
}
for (auto node = attachee; node; node = static_cast<CPhysical*>(node->GetAttachParentForced()))
{
if (node == attacher)
{
//Util::toast(PHSTR("My new child is already my parent? Nonsense."), TOAST_ALL);
Util::onPreventedCrash(LOC("CRSH_ATTLOOP"));
return false;
}
}
return true;
}
static void __fastcall CPhysical_AttachToPhysicalBasic(CPhysical* _this, CPhysical* pPhysical, int16_t nEntBone, uint32_t nAttachFlags, const v3* pVecOffset, const void* pQuatOrientation, int16_t nMyBone)
{
SOUP_IF_LIKELY (validateAttachment(_this, pPhysical))
{
COMP_OG(CPhysical_AttachToPhysicalBasic)(_this, pPhysical, nEntBone, nAttachFlags, pVecOffset, pQuatOrientation, nMyBone);
}
}
static bool __fastcall CPhysical_AttachToPhysicalUsingPhysics(CPhysical* _this, CPhysical* pPhysical, int16_t nParentBone, int16_t nMyBone, uint32_t nAttachFlags, const v3* pParentAnchorLocalSpace, const void* pQuatOrientation, const v3* pMyAnchorLocalSpace, float fStrength, bool bAllowInitialSeparation, float massInvScaleA, float massInvScaleB)
{
SOUP_IF_UNLIKELY (!validateAttachment(_this, pPhysical))
{
return false;
}
return COMP_OG(CPhysical_AttachToPhysicalUsingPhysics)(_this, pPhysical, nParentBone, nMyBone, nAttachFlags, pParentAnchorLocalSpace, pQuatOrientation, pMyAnchorLocalSpace, fStrength, bAllowInitialSeparation, massInvScaleA, massInvScaleB);
}
// Sometimes the game may try to apply a starting velocity on a pickup without a collider.
// Not sure what exactly triggers this, something about killing/exploding peds who are holding RPGs while the renderer is overworked.
// I tried detecting the exact conditions under which it would occur and then zero the velocity, but it would false-positive, so try-except it is.
static void CPickup_AssignCustomArchetype(CPickup* _this)
{
__try
{
COMP_OG(CPickup_AssignCustomArchetype)(_this);
}
__except (g_gui.doesRootStateAllowCrashPatches() ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
{
Util::onPreventedCrash(CODENAME(CPickup_AssignCustomArchetype));
}
}
// rage::phConstraintMgr::InsertAndReturnTemporaryPointer will not free its lock if this throws.
/*static void* __fastcall rage_phConstraintMgr_AllocateAndConstruct(void* _this, const rage::phConstraintBase::Params& params)
{
__try
{
return COMP_OG(rage_phConstraintMgr_AllocateAndConstruct)(_this, params);
}
__EXCEPTIONAL()
{
//Util::onPreventedCrash(CODENAME(rage_phConstraintMgr_AllocateAndConstruct));
}
return nullptr;
}*/
// In case ctor fails, we should initialise pointers to nullptr so DisableManifolds and possibly other functions don't go insane.
static void rage_phConstraintBase_ctor(rage::phConstraintBase* _this, const rage::phConstraintBase::Params& params)
{
switch (params.type)
{
case rage::phConstraintBase::ATTACHMENT:
static_cast<rage::phConstraintAttachment*>(_this)->m_RotationConstraint = nullptr;
break;
case rage::phConstraintBase::FIXED:
static_cast<rage::phConstraintFixed*>(_this)->m_TranslationManifold = nullptr;
static_cast<rage::phConstraintFixed*>(_this)->m_RotationManifold = nullptr;
break;
case rage::phConstraintBase::FIXEDROTATION:
static_cast<rage::phConstraintFixedRotation*>(_this)->m_RotationManifold = nullptr;
break;
case rage::phConstraintBase::PRISMATIC:
for (auto*& pManifold : static_cast<rage::phConstraintPrismatic*>(_this)->m_Manifolds)
{
pManifold = nullptr;
}
break;
}
COMP_OG(rage_phConstraintBase_ctor)(_this, params);
}
// This is caught by rage::netObjectMgrBase::Update, but I don't like that hook.
static void __fastcall CGameScriptHandlerMgr_AddToReservationCount(CGameScriptHandlerMgr* _this, /* CNetObjGame* */ void* scriptEntityObj)
{
__try
{
COMP_OG(CGameScriptHandlerMgr_AddToReservationCount)(_this, scriptEntityObj);
}
__EXCEPTIONAL()
{
}
}
static bool bone_index_must_be_in_bounds = false;
// CBodyRecoilIkSolver::PreIkUpdate uses this function's result directly as an array index.
// scripts/body-recoil-ik.lua
static int32_t __fastcall CDynamicEntity_GetBoneIndexFromBoneTag(CDynamicEntity* _this, const /* eAnimBoneTag */ int32_t boneTag)
{
const auto boneIdx = COMP_OG(CDynamicEntity_GetBoneIndexFromBoneTag)(_this, boneTag);
SOUP_IF_UNLIKELY (bone_index_must_be_in_bounds
&& boneIdx == -1
)
{
SOUP_IF_LIKELY (g_gui.doesRootStateAllowCrashPatches())
{
Util::onPreventedCrash(CODENAME(CDynamicEntity_GetBoneIndexFromBoneTag));
return 0;
}
}
return boneIdx;
}
static void __fastcall CBodyRecoilIkSolver_PreIkUpdate(void /*CBodyRecoilIkSolver*/* _this, float deltaTime)
{
bone_index_must_be_in_bounds = true;
__try
{
COMP_OG(CBodyRecoilIkSolver_PreIkUpdate)(_this, deltaTime);
}
__EXCEPTIONAL_IF(g_gui.doesRootStateAllowCrashPatches())
{
}
bone_index_must_be_in_bounds = false;
}
// There is a weird interaction here where CTaskNMShot::m_pShotBy is a ped without a CPedIntelligence, causing a nullptr dereference in CTaskNMShot::BehaviourFailure.
// The same may also happen in CTaskNMFlinch.
// We can catch this via our rage::aiTaskTree::UpdateTask hook, but this causes memory corruption, which will crash if used often enough.
// See scripts/CTaskNMShot.pluto.
static int __fastcall ARTFeedbackInterfaceGta_onBehaviourFailure(void* _this)
{
__try
{
return COMP_OG(ARTFeedbackInterfaceGta_onBehaviourFailure)(_this);
}
__except (g_gui.doesRootStateAllowCrashPatches() ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
{
Util::onPreventedCrash(CODENAME(ARTFeedbackInterfaceGta_onBehaviourFailure));
}
return 0;
}
// This checks for nullptr only after it's been dereferenced.
// We can't catch this in rage::aiTaskTree::UpdateTask because we could also get here from CPed::ProcessPostCamera.
static void __fastcall CTaskVehicleMountedWeapon_ComputeSearchTarget(rage::Vector3* o_Target, CPed* in_Ped)
{
bool searchlight_is_not_nullptr = false;
if (in_Ped->m_pMyVehicle.m_p)
{
if (auto vwm = in_Ped->m_pMyVehicle.m_p->m_pVehicleWeaponMgr)
{
for (int i = 0; i != vwm->m_iNumVehicleWeapons; ++i)
{
if (vwm->m_pVehicleWeapons[i]->GetType() == VGT_SEARCHLIGHT)
{
searchlight_is_not_nullptr = true;
break;
}
}
}
}
SOUP_IF_LIKELY (searchlight_is_not_nullptr)
{
return COMP_OG(CTaskVehicleMountedWeapon_ComputeSearchTarget)(o_Target, in_Ped);
}
Util::onPreventedCrash(CODENAME(CTaskVehicleMountedWeapon_ComputeSearchTarget));
}
void ComponentCrashPatch::addPatterns(PatternBatch& batch)
{
BATCH_ADD_OPTIONAL(CODENAME(report_error_wrap), "B9 39 EC 8B 43 E8", [](soup::Pointer p)
{
p = p.add(6).rip();
CHECK_EXISTING_HOOK(CODENAME(report_error_wrap), "48 83 EC 28 33");
INIT_HOOK_OPTIONAL(report_error_wrap);
});
BATCH_ADD_OPTIONAL(CODENAME(CPopCycle_UpdateCurrZone), "2B C2 D1 F8 89 05 ? ? ? ? E8", [](soup::Pointer p)
{
CPopCycle_sm_nCurrentDaySubdivision = p.add(6).rip().as<int32_t*>();
p = p.add(11).rip();
CHECK_EXISTING_HOOK(CODENAME(CPopCycle_UpdateCurrZone), "48 83 EC 48 80");
INIT_HOOK_OPTIONAL(CPopCycle_UpdateCurrZone);
});
BATCH_ADD_OPTIONAL_LONGJUMP_HOOK(unk_cherax_crash, "48 89 5C 24 08", "48 89 74 24 10 57 48 83 EC 20 48 8B 41 50 0F B7 F2 48 85 C0 74 04 48 8B 40 48");
BATCH_ADD_OPTIONAL_LONGJUMP_HOOK(unk_cherax_crash_2, "44 89 81 4C 02", "00 00 89 91 48 02 00 00 C6 05 ? ? ? ? 01 C3");
BATCH_ADD_OPTIONAL(CODENAME(CVehicle_SlideMechanicalPart), "34 00 00 80 BF 48 89 44 24 20 E8", [](soup::Pointer p)
{
p = p.add(11).rip();
CHECK_EXISTING_HOOK(CODENAME(CVehicle_SlideMechanicalPart), "48 8B C4 48 89");
INIT_HOOK_OPTIONAL(CVehicle_SlideMechanicalPart);
});
BATCH_ADD_OPTIONAL(CODENAME(CPed_SwitchToRagdollInternal), "0F 84 31 01 00 00 48 8B CE E8", [](soup::Pointer p)
{
// p is now inside of CEvent *__fastcall CPed::SwitchToRagdoll(CPed *this, CEvent *event)
p = p.add(10).rip();
CHECK_EXISTING_HOOK(CODENAME(CPed_SwitchToRagdollInternal), "48 8B C4 48 89");
INIT_HOOK_OPTIONAL_LONGJUMP(CPed_SwitchToRagdollInternal);
});
BATCH_ADD_OPTIONAL_HOOK(rage_crSkeleton_GetGlobalMtx, "40 55 48 8D 6C", "24 A9 48 81 EC D0 00 00 00 4D 8B D0");
BATCH_ADD_OPTIONAL_HOOK(CTorsoIkSolver_ApplyPointTorsoInDirection, "48 8B C4 55 53", "56 57 41 54 41 55 41 56 41 57 48 8D A8 78 FE FF FF 48 81 EC 48 02 00 00 48 8B 79 30");
BATCH_ADD_OPTIONAL_HOOK(CLightEntity_GetWorldMatrix, "40 53 48 83 EC", "60 0F 29 74 24 50 49 8B C0");
BATCH_ADD_OPTIONAL_LONGJUMP_HOOK(unk_izuku_crash, "48 89 5C 24 08", "48 89 74 24 10 57 48 83 EC 20 48 8B D9 84 D2 74 47");
BATCH_ADD_OPTIONAL_LONGJUMP_HOOK(rage_grcProgram_SetTextureResourcesUsingVectorDXAPICall, "48 89 5C 24 10", "48 89 6C 24 18 48 89 74 24 20 57 41 54 41 55 41 56 41 57 48 83 EC 20 8B 81 A8 00 00 00 48 63 DA");
BATCH_ADD_OPTIONAL(CODENAME(ped_component_draw_thingy), "4D 8B 45 70 48 8B 4B 10 8B D0 E8", [](soup::Pointer p)
{
p = p.add(11).rip();
CHECK_EXISTING_HOOK(CODENAME(ped_component_draw_thingy), "85 D2 0F 84 83");
INIT_HOOK_OPTIONAL(ped_component_draw_thingy);
});
BATCH_ADD_OPTIONAL_HOOK(draw_object, "48 83 EC 28 45", "33 C0 4C 63 D1 48 85 D2");
BATCH_ADD_OPTIONAL_HOOK(CCustomShaderEffectVehicleType_RestoreModelInfoDrawable, "48 8B C4 48 89", "58 08 48 89 70 18 48 89 78 20 55 41 54 41 55 41 56 41 57 48 8D 68 A1 48 81 EC C0 00 00 00 33 DB")
BATCH_ADD_OPTIONAL(CODENAME(update_ped_hair_texture), "48 89 AC 03 30 FF FF FF E8", [](soup::Pointer p)
{
p = p.add(9).rip();
CHECK_EXISTING_HOOK(CODENAME(update_ped_hair_texture), "40 55 53 56 57");
INIT_HOOK_OPTIONAL(update_ped_hair_texture);
});
BATCH_ADD_OPTIONAL_HOOK(CTaskHeliPassengerRappel_do_something, "48 8B C4 48 89", "58 18 55 56 57 41 54 41 55 41 56 41 57 48 8D A8 D8 FE FF FF");
BATCH_ADD_OPTIONAL(CODENAME(rage_ComponentInfoManager_CreateComponentInfo), "8B D5 0F 29 4C 24 60 E8", [](soup::Pointer p)
{
p = p.add(8).rip();
CHECK_EXISTING_HOOK(CODENAME(rage_ComponentInfoManager_CreateComponentInfo), "40 53 48 83 EC");
INIT_HOOK_OPTIONAL(rage_ComponentInfoManager_CreateComponentInfo);
});
BATCH_ADD_OPTIONAL_HOOK(unk_related_to_object_despawning, "48 8B C4 48 89", "58 10 48 89 70 18 48 89 78 20 55 41 54 41 55 41 56 41 57 48 8D A8 48 FF FF FF 48 81 EC 90 01 00 00 0F 29 70 C8");
BATCH_ADD_OPTIONAL_HOOK(something_related_to_vehicle_parachutes, "48 8B C4 48 89", "58 08 48 89 68 10 48 89 70 18 48 89 78 20 41 54 48 81 EC E0 00 00 00 0F 29 70 E8");
BATCH_ADD_OPTIONAL_HOOK(CObjectFragmentDrawHandler_do_something, "48 89 5C 24 08", "48 89 6C 24 18 56 57 41 54 41 56 41 57 48 81 EC 80 00 00 00 48 8B E9");
BATCH_ADD_OPTIONAL(CODENAME(CTrain_ValidateLinkedLists), "49 8B 0E E8 ? ? ? ? 48 8D 9F ? ? 00 00 48 8B 0B", [](soup::Pointer p)
{
p = p.add(4).rip();
CHECK_EXISTING_HOOK(CODENAME(CTrain_ValidateLinkedLists), "48 85 C9 0F");
INIT_HOOK_OPTIONAL(CTrain_ValidateLinkedLists);
});
BATCH_ADD_OPTIONAL_HOOK(rage_characterClothController_SkinMesh, "40 55 53 56 57", "41 54 41 55 41 56 41 57 48 81 EC 48 01 00 00 48 8D 6C 24 50 48 0F BE 41 56");
BATCH_ADD_OPTIONAL_HOOK(rage_characterClothController_Update, "48 8B C4 4C 89", "48 20 F3 0F 11 50 18 48 89 50 10");
BATCH_ADD_OPTIONAL(CODENAME(CPhysical_AttachToPhysicalBasic), "48 89 44 24 20 E8 ? ? ? ? 80 7B 28 04", [](soup::Pointer p)
{
p = p.add(6).rip();
CHECK_EXISTING_HOOK(CODENAME(CPhysical_AttachToPhysicalBasic), "48 85 D2 0F 84");
INIT_HOOK_OPTIONAL(CPhysical_AttachToPhysicalBasic);
});
BATCH_ADD_OPTIONAL(CODENAME(CPhysical_AttachToPhysicalUsingPhysics), "8B 85 40 01 00 00 89 44 24 20 E8", [](soup::Pointer p)
{
p = p.add(11).rip();
CHECK_EXISTING_HOOK(CODENAME(CPhysical_AttachToPhysicalUsingPhysics), "48 8B C4 48 89");
INIT_HOOK_OPTIONAL(CPhysical_AttachToPhysicalUsingPhysics);
});
BATCH_ADD_OPTIONAL(CODENAME(CPickup_AssignCustomArchetype), "FF 90 90 01 00 00 48 8B CB E8 ? ? ? ? 48 8B 83 D0 00 00 00", [](soup::Pointer p)
{
p = p.add(10).rip();
CHECK_EXISTING_HOOK(CODENAME(CPickup_AssignCustomArchetype), "48 8B C4 48 89");
INIT_HOOK_OPTIONAL_LONGJUMP(CPickup_AssignCustomArchetype);
});
/*BATCH_ADD_OPTIONAL(CODENAME(rage_phConstraintMgr_AllocateAndConstruct), "48 8B CB E8 ? ? ? ? 49 89 06", [](soup::Pointer p)
{
p = p.add(4).rip();
CHECK_EXISTING_HOOK(CODENAME(rage_phConstraintMgr_AllocateAndConstruct), "48 89 5C 24 08");
INIT_HOOK_OPTIONAL(rage_phConstraintMgr_AllocateAndConstruct);
});*/
BATCH_ADD_OPTIONAL_HOOK(rage_phConstraintBase_ctor, "48 89 5C 24 08", "48 89 7C 24 10 4C 8B CA 4C 8B C1 48 8D 05");
BATCH_ADD_OPTIONAL_LONGJUMP_HOOK(CGameScriptHandlerMgr_AddToReservationCount, "48 89 5C 24 08", "48 89 6C 24 10 48 89 74 24 18 57 41 54 41 55 41 56 41 57 48 83 EC 30 48 8B DA 4C 8B F1 BA 00 40 00 00");
BATCH_ADD_OPTIONAL_HOOK(CDynamicEntity_GetBoneIndexFromBoneTag, "48 89 5C 24 10", "48 89 74 24 18 57 48 83 EC 20 48 8B 01 83 4C 24 30 FF");
BATCH_ADD_OPTIONAL_HOOK(CBodyRecoilIkSolver_PreIkUpdate, "48 8B C4 48 89", "58 08 48 89 70 10 57 48 81 EC ? 00 00 00 F6 41 78 04");
BATCH_ADD_OPTIONAL(CODENAME(ARTFeedbackInterfaceGta_onBehaviourFailure), "48 8D 4C 24 20 E8 ? ? ? ? 48 8D 4C 24 20 E8 ? ? ? ? 41 B0 01", [](soup::Pointer p)
{
p = p.add(6).rip();
CHECK_EXISTING_HOOK(CODENAME(ARTFeedbackInterfaceGta_onBehaviourFailure), "48 89 5C 24 08");
INIT_HOOK_OPTIONAL_LONGJUMP(ARTFeedbackInterfaceGta_onBehaviourFailure);
});
BATCH_ADD_OPTIONAL_HOOK(CTaskVehicleMountedWeapon_ComputeSearchTarget, "48 8B C4 48 89", "58 08 48 89 68 10 48 89 70 18 57 48 81 EC A0 00 00 00 48 8B B2");
}
std::vector<DetourHook*> ComponentCrashPatch::getHooks()
{
return {
&report_error_wrap_hook,
&CPopCycle_UpdateCurrZone_hook,
&unk_cherax_crash_hook,
&unk_cherax_crash_2_hook,
&CVehicle_SlideMechanicalPart_hook,
&CPed_SwitchToRagdollInternal_hook,
&rage_crSkeleton_GetGlobalMtx_hook,
&CTorsoIkSolver_ApplyPointTorsoInDirection_hook,
&CLightEntity_GetWorldMatrix_hook,
&unk_izuku_crash_hook,
&rage_grcProgram_SetTextureResourcesUsingVectorDXAPICall_hook,
&ped_component_draw_thingy_hook,
&draw_object_hook,
&CCustomShaderEffectVehicleType_RestoreModelInfoDrawable_hook,
&update_ped_hair_texture_hook,
&CTaskHeliPassengerRappel_do_something_hook,
&rage_ComponentInfoManager_CreateComponentInfo_hook,
&unk_related_to_object_despawning_hook,
&something_related_to_vehicle_parachutes_hook,
&CObjectFragmentDrawHandler_do_something_hook,
&CTrain_ValidateLinkedLists_hook,
&rage_characterClothController_SkinMesh_hook,
&rage_characterClothController_Update_hook,
&CPhysical_AttachToPhysicalBasic_hook,
&CPhysical_AttachToPhysicalUsingPhysics_hook,
&CPickup_AssignCustomArchetype_hook,
&rage_phConstraintBase_ctor_hook,
&CGameScriptHandlerMgr_AddToReservationCount_hook,
&CDynamicEntity_GetBoneIndexFromBoneTag_hook,
&CBodyRecoilIkSolver_PreIkUpdate_hook,
&ARTFeedbackInterfaceGta_onBehaviourFailure_hook,
&CTaskVehicleMountedWeapon_ComputeSearchTarget_hook,
};
}
// A collection of unpatched flaws in GTA that are networkable.
// Buffer overruns:
// - CTrain::SetTrackActive may be called with track index <0 which overwrites data in CPropellerCollisionProcessor which can be exploited by then making a plane have >16 propeller collisions
// - via CNetworkTrainReportEvent::Decide
// - via CNetworkTrainRequestEvent::Decide (indirect via CTrain::DetermineHostApproval)
// - via CNetObjTrain::Update due to CTrainGameStateDataNode with isEngine set to true (as simple as using CREATE_VEHICLE with a train model)
// - CTaskVehicleGoToPlane, CTaskVehicleLandPlane, & CTaskVehicleFlyDirection assume entity is a CPlane without checking, corrupting data of other entities in pool
// - CTaskVehicleGoToHelicopter & CTaskVehicleLand assume entity is a CHeli (or derived) without checking, corrupting data of other entities in pool
// Nullptr dereferences:
// - CPedVariationStream::SetPaletteTexture assumes that gfx's custom shader effect is not nullptr
// Some others we don't bother catching because of generic handlers:
// - CNetObjVehicle::SetVehicleControlData assumes the vehicle is a CSubmarineCar and sub handling is not nullptr if m_isSubCar is true
// - CTaskExitVehicle::Start_OnEnter assumes ped's helmet component is not nullptr (which does not hold for a_c_rat)
// - CTaskParachute::DetachParachuteFromPed assumes parachute object's physics inst is not nullptr
// - CTaskGeneralSweep::StateInitial_OnUpdate assumes clip is not nullptr (GetYawMinMaxAndPitch ends up dereferencing it with no check either)
// - CTaskParachuteObject::Stream_OnUpdate may use nullptr as rage::fwAnimDirector* to call rage::fwAnimDirector::GetMove
// - CTaskJump::StateInit_OnUpdate assumes rage::fwClipSetManager::GetClipSet does not return nullptr (may happen if syncing a_c_rat with this task)
// - CTaskClimbLadder::IsMovementBlocked assumes GetBipedCapsuleInfo does not return nullptr (may happen if syncing a_c_rat with this task)
// Unitialised memory:
// - CTrainGameStateDataNode may have track index >11 causing train entity to use unitialised track data or even go out of bounds
// - rage::phConstraintAttachment constructor does not initialise m_RotationConstraint to nullptr in case translation constraint fails to allocate, which would cause DisableManifolds (which is called in FlagForDestruction) to use the uninitialised m_RotationConstraint as a pointer
// - rage::phConstraintFixed constructor does not initialise m_TranslationManifold & m_RotationManifold to nullptr in case AllocateManifold fails, causing DisableManifolds (which is called in FlagForDestruction) to use garbage memory as pointers
// - rage::phConstraintFixedRotation constructor does not initialise m_RotationManifold to nullptr in case AllocateManifold fails, causing DisableManifolds (which is called in FlagForDestruction) to use garbage memory as a pointer
// - rage::phConstraintPrismatic constructor does not initialise m_Manifolds items to nullptr in case AllocateManifold, causing DisableManifolds (which is called in FlagForDestruction) to use garbage memory as a pointer (also m_RotationConstraint->DisableManifolds will be a nullptr dereference if we get that far)
}