From afef0c600f13b5d3832aef7c11728e8017bd8b1a Mon Sep 17 00:00:00 2001 From: SpeedyFolf Date: Tue, 12 May 2026 09:54:35 -0500 Subject: [PATCH 1/2] Add ability to enable/disable vehicle model hierarchy typo fixes Introduce an optional SilentPatch-style fix that matches canonical vehicle component names to typo'd hierarchy nodes in stock DFF files. Adds CMultiplayer API methods (Set/GetVehicleHierarchyTypoFixesEnabled) and CMultiplayerSA overrides, a new implementation file that hooks the stricmp call used during CVehicleModelInfo setup with a small mapping table, and initializes the hook from the vehicle init. Exposes Lua bindings (engineSet/GetVehicleHierarchyTypoFixesEnabled and Engine.set/getVehicleHierarchyTypoFixesEnabled) and ensures the feature is disabled by default during ResetWorldProperties to preserve vanilla behaviour. --- Client/mods/deathmatch/logic/CClientGame.cpp | 1 + .../logic/luadefs/CLuaEngineDefs.cpp | 17 ++++ Client/multiplayer_sa/CMultiplayerSA.h | 3 + ...ultiplayerSA_VehicleHierarchyTypoFixes.cpp | 82 +++++++++++++++++++ ...CMultiplayerSA_VehicleHierarchyTypoFixes.h | 12 +++ .../CMultiplayerSA_Vehicles.cpp | 4 + Client/sdk/multiplayer/CMultiplayer.h | 5 ++ 7 files changed, 124 insertions(+) create mode 100644 Client/multiplayer_sa/CMultiplayerSA_VehicleHierarchyTypoFixes.cpp create mode 100644 Client/multiplayer_sa/CMultiplayerSA_VehicleHierarchyTypoFixes.h diff --git a/Client/mods/deathmatch/logic/CClientGame.cpp b/Client/mods/deathmatch/logic/CClientGame.cpp index 19a4e92d911..ecb42524e26 100644 --- a/Client/mods/deathmatch/logic/CClientGame.cpp +++ b/Client/mods/deathmatch/logic/CClientGame.cpp @@ -7008,6 +7008,7 @@ void CClientGame::ResetWorldProperties(const ResetWorldPropsInfo& resetPropsInfo m_pVehicleManager->SetSpawnFlyingComponentEnabled(true); g_pGame->SetVehicleBurnExplosionsEnabled(true); SetVehicleEngineAutoStartEnabled(true); + g_pMultiplayer->SetVehicleHierarchyTypoFixesEnabled(false); } // Reset all setWorldProperty to default diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp index 27652be6b2c..64e9dbc165c 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp +++ b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp @@ -10,6 +10,7 @@ *****************************************************************************/ #include "StdInc.h" +#include #include #include #include @@ -83,6 +84,17 @@ size_t EngineStreamingGetBufferSize() return g_pGame->GetStreaming()->GetStreamingBufferSize(); } +// Opt-in SilentPatch-style fixes for stock vehicle.dff hierarchy typos (wheel_lm_dummy vs wheel_lm, etc.). +void EngineSetVehicleHierarchyTypoFixesEnabled(bool enabled) +{ + g_pCore->GetMultiplayer()->SetVehicleHierarchyTypoFixesEnabled(enabled); +} + +bool EngineGetVehicleHierarchyTypoFixesEnabled() +{ + return g_pCore->GetMultiplayer()->GetVehicleHierarchyTypoFixesEnabled(); +} + void CLuaEngineDefs::LoadFunctions() { constexpr static const std::pair functions[]{ @@ -163,6 +175,8 @@ void CLuaEngineDefs::LoadFunctions() {"enginePreloadWorldArea", ArgumentParser}, {"engineRestreamModel", ArgumentParser}, {"engineRestream", ArgumentParser}, + {"engineSetVehicleHierarchyTypoFixesEnabled", ArgumentParser}, + {"engineGetVehicleHierarchyTypoFixesEnabled", ArgumentParser}, // CLuaCFunctions::AddFunction ( "engineReplaceMatchingAtomics", EngineReplaceMatchingAtomics ); // CLuaCFunctions::AddFunction ( "engineReplaceWheelAtomics", EngineReplaceWheelAtomics ); @@ -212,6 +226,9 @@ void CLuaEngineDefs::AddClass(lua_State* luaVM) lua_classfunction(luaVM, "setModelTXDID", "engineSetModelTXDID"); lua_classfunction(luaVM, "resetModelTXDID", "engineResetModelTXDID"); + lua_classfunction(luaVM, "setVehicleHierarchyTypoFixesEnabled", "engineSetVehicleHierarchyTypoFixesEnabled"); + lua_classfunction(luaVM, "getVehicleHierarchyTypoFixesEnabled", "engineGetVehicleHierarchyTypoFixesEnabled"); + lua_registerstaticclass(luaVM, "Engine"); // `EngineStreaming` class diff --git a/Client/multiplayer_sa/CMultiplayerSA.h b/Client/multiplayer_sa/CMultiplayerSA.h index 166566ac9f9..08fe4b4d071 100644 --- a/Client/multiplayer_sa/CMultiplayerSA.h +++ b/Client/multiplayer_sa/CMultiplayerSA.h @@ -356,6 +356,9 @@ class CMultiplayerSA : public CMultiplayer void SetBoatWaterSplashEnabled(bool bEnabled); void SetTyreSmokeEnabled(bool bEnabled); + void SetVehicleHierarchyTypoFixesEnabled(bool bEnabled) override; + bool GetVehicleHierarchyTypoFixesEnabled() const override; + void SetLastStaticAnimationPlayed(eAnimGroup dwGroupID, eAnimID dwAnimID, DWORD dwAnimArrayAddress) { m_dwLastStaticAnimGroupID = dwGroupID; diff --git a/Client/multiplayer_sa/CMultiplayerSA_VehicleHierarchyTypoFixes.cpp b/Client/multiplayer_sa/CMultiplayerSA_VehicleHierarchyTypoFixes.cpp new file mode 100644 index 00000000000..fd5c3cb8af4 --- /dev/null +++ b/Client/multiplayer_sa/CMultiplayerSA_VehicleHierarchyTypoFixes.cpp @@ -0,0 +1,82 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: multiplayer_sa/CMultiplayerSA_VehicleHierarchyTypoFixes.cpp + * PURPOSE: Optional CVehicleModelInfo hierarchy compare hook (matches SilentPatch SA) + * + * GTA matches IDE/handling component names to frame names via _stricmp inside + * CVehicleModelInfo setup. Stock models use inconsistent spellings; SilentPatch + * wraps that call to accept both canonical and typo names. We expose the same + * behaviour only when enabled so servers can keep fully vanilla visuals. + * + *****************************************************************************/ + +#include "StdInc.h" +#include "CMultiplayerSA.h" + +#include + +namespace +{ + // Off by default (matches stock GTA SA without SilentPatch). + bool g_bVehicleHierarchyTypoFixesEnabled = false; + + int(__cdecl* g_pfnOrgStricmp)(const char*, const char*) = nullptr; + + // Pairs are { correct name, name as in shipping .dff }. Must be sorted by the second string. + static constexpr std::pair typosAndFixes[] = { + {"boat_moving_hi", "boat_moving"}, + {"elevator_r", "elevator"}, + {"misc_a", "misca"}, + {"misc_b", "miscb"}, + {"taillights", "tailights"}, + {"taillights2", "tailights2"}, + {"transmission_f", "transmision_f"}, + {"transmission_r", "transmision_r"}, + {"wheel_lm_dummy", "wheel_lm"}, + }; + + int __cdecl Hooked_VehicleHierarchy_Stricmp(const char* dataName, const char* nodeName) + { + if (!g_bVehicleHierarchyTypoFixesEnabled) + return g_pfnOrgStricmp(dataName, nodeName); + + const int origComp = g_pfnOrgStricmp(dataName, nodeName); + if (origComp == 0) + return 0; + + for (const auto& typo : typosAndFixes) + { + const int nodeComp = _stricmp(typo.second, nodeName); + if (nodeComp > 0) + break; + + if (nodeComp == 0 && _stricmp(typo.first, dataName) == 0) + return 0; + } + + return origComp; + } +} // namespace + +void InitVehicleHierarchyTypoFixesHook() +{ + // SA 1.0 US: indirect call to _stricmp while CVehicleModelInfo binds handling/IDE component names to clump frames. + // Same site hooked by SilentPatch (`InterceptCall(0x4C5311, ...)`). + constexpr DWORD CALLSITE = 0x4C5311; + HookCheckOriginalByte(CALLSITE, 0xE8); + const DWORD rel = *reinterpret_cast(CALLSITE + 1); + g_pfnOrgStricmp = reinterpret_cast(CALLSITE + 5 + rel); + HookInstallCall(CALLSITE, reinterpret_cast(&Hooked_VehicleHierarchy_Stricmp)); +} + +void CMultiplayerSA::SetVehicleHierarchyTypoFixesEnabled(bool bEnabled) +{ + g_bVehicleHierarchyTypoFixesEnabled = bEnabled; +} + +bool CMultiplayerSA::GetVehicleHierarchyTypoFixesEnabled() const +{ + return g_bVehicleHierarchyTypoFixesEnabled; +} diff --git a/Client/multiplayer_sa/CMultiplayerSA_VehicleHierarchyTypoFixes.h b/Client/multiplayer_sa/CMultiplayerSA_VehicleHierarchyTypoFixes.h new file mode 100644 index 00000000000..c63d1cd442b --- /dev/null +++ b/Client/multiplayer_sa/CMultiplayerSA_VehicleHierarchyTypoFixes.h @@ -0,0 +1,12 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: multiplayer_sa/CMultiplayerSA_VehicleHierarchyTypoFixes.h + * PURPOSE: Init for optional vehicle.dff hierarchy typo matching (SilentPatch-style) + * + *****************************************************************************/ + +#pragma once + +void InitVehicleHierarchyTypoFixesHook(); diff --git a/Client/multiplayer_sa/CMultiplayerSA_Vehicles.cpp b/Client/multiplayer_sa/CMultiplayerSA_Vehicles.cpp index 610f9a55f9d..d9d410a714d 100644 --- a/Client/multiplayer_sa/CMultiplayerSA_Vehicles.cpp +++ b/Client/multiplayer_sa/CMultiplayerSA_Vehicles.cpp @@ -9,6 +9,7 @@ *****************************************************************************/ #include "StdInc.h" +#include "CMultiplayerSA_VehicleHierarchyTypoFixes.h" static bool __fastcall AreVehicleDoorsUndamageable(CVehicleSAInterface* vehicle) { @@ -72,4 +73,7 @@ static void __declspec(naked) HOOK_CDamageManager__ProgressDoorDamage() void CMultiplayerSA::InitHooks_Vehicles() { EZHookInstall(CDamageManager__ProgressDoorDamage); + + // SilentPatch-style optional matching for typo'd vehicle component frame names (see CMultiplayerSA_VehicleHierarchyTypoFixes.cpp). + InitVehicleHierarchyTypoFixesHook(); } diff --git a/Client/sdk/multiplayer/CMultiplayer.h b/Client/sdk/multiplayer/CMultiplayer.h index 28d2e074088..5403ad08005 100644 --- a/Client/sdk/multiplayer/CMultiplayer.h +++ b/Client/sdk/multiplayer/CMultiplayer.h @@ -463,6 +463,11 @@ class CMultiplayer virtual void SetBoatWaterSplashEnabled(bool bEnabled) = 0; virtual void SetTyreSmokeEnabled(bool bEnabled) = 0; + // When enabled, matches canonical vehicle component names to the typo'd hierarchy nodes in stock GTA SA models + // (SilentPatch-style fix). Off by default so behaviour stays vanilla unless a script opts in. + virtual void SetVehicleHierarchyTypoFixesEnabled(bool bEnabled) = 0; + virtual bool GetVehicleHierarchyTypoFixesEnabled() const = 0; + virtual eAnimGroup GetLastStaticAnimationGroupID() = 0; virtual eAnimID GetLastStaticAnimationID() = 0; virtual DWORD GetLastAnimArrayAddress() = 0; From c6d0ed85e023c0f8a532a1f7a09fb91b7911687d Mon Sep 17 00:00:00 2001 From: SpeedyFolf Date: Sat, 16 May 2026 08:30:27 -0500 Subject: [PATCH 2/2] Remove vehicle model hierarchy typo fixes toggle in ResetWorldProperties People can just be responsible and disable this themselves when their map ends like the fastsprint "glitch". --- Client/mods/deathmatch/logic/CClientGame.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Client/mods/deathmatch/logic/CClientGame.cpp b/Client/mods/deathmatch/logic/CClientGame.cpp index ecb42524e26..19a4e92d911 100644 --- a/Client/mods/deathmatch/logic/CClientGame.cpp +++ b/Client/mods/deathmatch/logic/CClientGame.cpp @@ -7008,7 +7008,6 @@ void CClientGame::ResetWorldProperties(const ResetWorldPropsInfo& resetPropsInfo m_pVehicleManager->SetSpawnFlyingComponentEnabled(true); g_pGame->SetVehicleBurnExplosionsEnabled(true); SetVehicleEngineAutoStartEnabled(true); - g_pMultiplayer->SetVehicleHierarchyTypoFixesEnabled(false); } // Reset all setWorldProperty to default