diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp index 27652be6b2..64e9dbc165 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 166566ac9f..08fe4b4d07 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 0000000000..fd5c3cb8af --- /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 0000000000..c63d1cd442 --- /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 610f9a55f9..d9d410a714 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 28d2e07408..5403ad0800 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;