From 8d91379c4b2a2471fcc738f82d8c803a3c0c4d81 Mon Sep 17 00:00:00 2001 From: Ehbw Date: Thu, 5 Dec 2024 13:02:39 +0000 Subject: [PATCH] tweak(extra-natives/five): sanitize mission train creation --- .../extra-natives-five/include/Train.h | 59 ++++++++++++++ .../extra-natives-five/src/NativeFixes.cpp | 60 ++++++++++++++ .../extra-natives-five/src/TrackNatives.cpp | 78 ++++++++----------- 3 files changed, 150 insertions(+), 47 deletions(-) create mode 100644 code/components/extra-natives-five/include/Train.h diff --git a/code/components/extra-natives-five/include/Train.h b/code/components/extra-natives-five/include/Train.h new file mode 100644 index 0000000000..c4efc87056 --- /dev/null +++ b/code/components/extra-natives-five/include/Train.h @@ -0,0 +1,59 @@ +#pragma once + +#include + +namespace rage +{ +struct CTrackNode +{ +public: + float m_x, m_y, m_z; + float m_unk; + uint8_t m_unk1; + int m_station; +}; + +struct CTrainTrack +{ +public: + // This has never changed. + static const int kMaxTracks = 27; + + uint32_t m_hash; + bool m_enabled; + bool m_isLooped; + bool m_stopsAtStation; + bool m_MPStopsAtStation; + uint32_t m_speed; + uint32_t m_brakeDistance; + + int m_nodeCount; + CTrackNode* m_nodes; + + uint8_t m_pad[8]; + + bool m_disableAmbientTrains; + uint8_t m_pad2[0x220]; + + // Helper functions + static bool AreAllTracksDisabled(); +}; + +struct CTrainConfig +{ + struct CarriageData + { + uint32_t m_hash; + uint8_t m_pad[0xE]; + }; + + uint32_t m_hash; + uint8_t m_pad[0x1A]; + atArray m_carriages; +}; + +struct CTrainConfigData +{ + atArray m_trainConfigs; +}; +} diff --git a/code/components/extra-natives-five/src/NativeFixes.cpp b/code/components/extra-natives-five/src/NativeFixes.cpp index 2f52e7a557..19296689d7 100644 --- a/code/components/extra-natives-five/src/NativeFixes.cpp +++ b/code/components/extra-natives-five/src/NativeFixes.cpp @@ -17,6 +17,7 @@ #include "RageParser.h" #include "Resource.h" #include "ScriptWarnings.h" +#include static void FixVehicleWindowNatives() { @@ -453,11 +454,68 @@ static void FixApplyForceToEntity() }); } +static hook::cdecl_stub hasModelLoaded([]() +{ + return hook::get_call(hook::get_pattern("25 FF FF FF 3F 89 45 6F E8 ? ? ? ? 84 C0", 8)); +}); + +static rage::CTrainConfigData* g_trainConfigData; + +static void FixMissionTrain() +{ + constexpr uint64_t CREATE_MISSION_TRAIN = 0x63C6CCA8E68AE8C8; + + auto originalHandler = fx::ScriptEngine::GetNativeHandler(CREATE_MISSION_TRAIN); + if (!originalHandler) + { + return; + } + + auto handler = *originalHandler; + fx::ScriptEngine::RegisterNativeHandler(CREATE_MISSION_TRAIN, [handler](fx::ScriptContext& ctx) + { + auto variation = ctx.GetArgument(0); + + if (variation < 0 || variation >= g_trainConfigData->m_trainConfigs.GetCount()) + { + fx::scripting::Warningf("natives", "Invalid train variation index was passed to CREATE_MISSION_TRAIN (%i), should be from 0 to %i\n", variation, g_trainConfigData->m_trainConfigs.GetCount() - 1); + ctx.SetResult(0); + return; + } + + rage::CTrainConfig config = g_trainConfigData->m_trainConfigs.Get(variation); + + // Prevent the native from executing if one any of the required models are not in memory + for (auto& carriage : config.m_carriages) + { + rage::fwModelId idx{ 0 }; + rage::fwArchetypeManager::GetArchetypeFromHashKey(carriage.m_hash, idx); + if (!hasModelLoaded(&idx.value)) + { + fx::scripting::Warningf("natives", "Failed to spawn mission train as carriage hash '%i' is not loaded\n", carriage.m_hash); + ctx.SetResult(0); + return; + } + } + + // Prevent the native from executing if there are no tracks available. This won't crash the game but can give a confusing error. + if (rage::CTrainTrack::AreAllTracksDisabled()) + { + fx::scripting::Warningf("natives", "Failed to spawn mission train as there are no tracks enabled\n"); + ctx.SetResult(0); + return; + } + + handler(ctx); + }); +} + static HookFunction hookFunction([]() { g_fireInstances = (std::array*)(hook::get_address(hook::get_pattern("74 47 48 8D 0D ? ? ? ? 48 8B D3", 2), 3, 7) + 0x10); g_maxHudColours = *hook::get_pattern("81 F9 ? ? ? ? 77 5A 48 89 5C 24", 2); g_numMarkerTypes = *hook::get_pattern("BE FF FF FF DF 41 BF 00 00 FF 0F 41 BC FF FF FF BF", -4); + g_trainConfigData = hook::get_address(hook::get_pattern("48 8B 05 ? ? ? ? 4D 03 C0 4A 8B 4C C0 ? 80 7C 91 ? ? 74", 3)); rage::scrEngine::OnScriptInit.Connect([]() { @@ -489,5 +547,7 @@ static HookFunction hookFunction([]() FixDrawMarker(); FixApplyForceToEntity(); + + FixMissionTrain(); }); }); diff --git a/code/components/extra-natives-five/src/TrackNatives.cpp b/code/components/extra-natives-five/src/TrackNatives.cpp index ac250ce034..30493cbd0d 100644 --- a/code/components/extra-natives-five/src/TrackNatives.cpp +++ b/code/components/extra-natives-five/src/TrackNatives.cpp @@ -11,37 +11,10 @@ #include #include #include +#include "ScriptWarnings.h" +#include -struct TrackNode -{ -public: - float m_x, m_y, m_z; - float m_unk; - uint8_t m_unk1; - int m_station; -}; - -struct TrackData -{ -public: - uint32_t m_hash; - bool m_enabled; - bool m_isLooped; - bool m_stopsAtStation; - bool m_MPStopsAtStation; - uint32_t m_speed; - uint32_t m_brakeDistance; - - int m_nodeCount; - TrackNode* m_nodes; - - uint8_t m_pad[8]; - - bool m_disableAmbientTrains; - // and a bunch of other fields... -}; - -static hook::cdecl_stub getTrainTrack([] +static hook::cdecl_stub getTrainTrack([] { return hook::get_call(hook::get_pattern("E8 ? ? ? ? 33 DB 45 0F 57 DB")); }); @@ -51,18 +24,15 @@ static float calculateDistance(const rage::Vector3& point1, const float& x, cons return (point1.x - x) * (point1.x - x) + (point1.y - y) * (point1.y - y) + (point1.z - z) * (point1.z - z); } -// This has never changed. -static constexpr int kMaxTrainTracks = 27; - static int32_t FindClosestTrack(rage::Vector3& position, int8_t* outTrack) { *outTrack = -1; float closestDistance = std::numeric_limits::infinity(); int closestNode = -1; - for (int i = 0; i < kMaxTrainTracks; i++) + for (int i = 0; i < rage::CTrainTrack::kMaxTracks; i++) { - TrackData* track = getTrainTrack(i); + rage::CTrainTrack* track = getTrainTrack(i); // Skip if this track is a nullptr or is currently disabled. The game doesn't check for this. if (!track || !track->m_enabled) @@ -72,7 +42,7 @@ static int32_t FindClosestTrack(rage::Vector3& position, int8_t* outTrack) for (int n = 0; n < track->m_nodeCount; n++) { - TrackNode node = track->m_nodes[n]; + rage::CTrackNode node = track->m_nodes[n]; float Distance = calculateDistance(position, node.m_x, node.m_y, node.m_z); @@ -88,7 +58,6 @@ static int32_t FindClosestTrack(rage::Vector3& position, int8_t* outTrack) return closestNode; } - static HookFunction hookFunction([]() { MH_Initialize(); @@ -100,17 +69,17 @@ static HookFunction hookFunction([]() hook::nop(hook::get_pattern("F3 0F 10 75 ? 8B 55"), 5); }); -static TrackData* getAndCheckTrack(fx::ScriptContext& context, std::string_view nn) +static rage::CTrainTrack* getAndCheckTrack(fx::ScriptContext& context, std::string_view nn) { int trackIndex = context.GetArgument(0); - if (trackIndex < 0 || trackIndex > kMaxTrainTracks) + if (trackIndex < 0 || trackIndex > rage::CTrainTrack::kMaxTracks) { trace("Invalid track index %i passed to %s\n", trackIndex, nn); context.SetResult(0); return NULL; } - TrackData* track = getTrainTrack(trackIndex); + rage::CTrainTrack* track = getTrainTrack(trackIndex); if (!track || track->m_hash == 0) { @@ -122,13 +91,28 @@ static TrackData* getAndCheckTrack(fx::ScriptContext& context, std::string_view return track; } +bool rage::CTrainTrack::AreAllTracksDisabled() +{ + for (int i = 0; i < rage::CTrainTrack::kMaxTracks; i++) + { + CTrainTrack* track = getTrainTrack(i); + + if (track && track->m_enabled) + { + return false; + } + } + + return true; +} + static InitFunction initFunction([]() { fx::ScriptEngine::RegisterNativeHandler("SET_TRACK_MAX_SPEED", [](fx::ScriptContext& context) { int maxSpeed = context.CheckArgument(1); - if (TrackData* track = getAndCheckTrack(context, "SET_TRACK_MAX_SPEED")) + if (rage::CTrainTrack* track = getAndCheckTrack(context, "SET_TRACK_MAX_SPEED")) { track->m_speed = maxSpeed; } @@ -136,7 +120,7 @@ static InitFunction initFunction([]() fx::ScriptEngine::RegisterNativeHandler("GET_TRACK_MAX_SPEED", [](fx::ScriptContext& context) { - if (TrackData* track = getAndCheckTrack(context, "GET_TRACK_MAX_SPEED")) + if (rage::CTrainTrack* track = getAndCheckTrack(context, "GET_TRACK_MAX_SPEED")) { context.SetResult(track->m_speed); } @@ -144,7 +128,7 @@ static InitFunction initFunction([]() fx::ScriptEngine::RegisterNativeHandler("GET_TRACK_BRAKING_DISTANCE", [](fx::ScriptContext& context) { - if (TrackData* track = getAndCheckTrack(context, "GET_TRACK_BRAKING_DISTANCE")) + if (rage::CTrainTrack* track = getAndCheckTrack(context, "GET_TRACK_BRAKING_DISTANCE")) { context.SetResult(track->m_brakeDistance); } @@ -153,7 +137,7 @@ static InitFunction initFunction([]() fx::ScriptEngine::RegisterNativeHandler("SET_TRACK_BRAKING_DISTANCE", [](fx::ScriptContext& context) { int brakeDistance = context.CheckArgument(1); - if (TrackData* track = getAndCheckTrack(context, "SET_TRACK_BRAKING_DISTANCE")) + if (rage::CTrainTrack* track = getAndCheckTrack(context, "SET_TRACK_BRAKING_DISTANCE")) { track->m_brakeDistance = brakeDistance; } @@ -162,7 +146,7 @@ static InitFunction initFunction([]() fx::ScriptEngine::RegisterNativeHandler("SET_TRACK_ENABLED", [](fx::ScriptContext& context) { bool state = context.GetArgument(1); - if (TrackData* track = getAndCheckTrack(context, "SET_TRACK_ENABLED")) + if (rage::CTrainTrack* track = getAndCheckTrack(context, "SET_TRACK_ENABLED")) { track->m_enabled = state; } @@ -170,7 +154,7 @@ static InitFunction initFunction([]() fx::ScriptEngine::RegisterNativeHandler("IS_TRACK_ENABLED", [](fx::ScriptContext& context) { - if (TrackData* track = getAndCheckTrack(context, "IS_TRACK_ENABLED")) + if (rage::CTrainTrack* track = getAndCheckTrack(context, "IS_TRACK_ENABLED")) { context.SetResult(track->m_enabled); } @@ -178,7 +162,7 @@ static InitFunction initFunction([]() fx::ScriptEngine::RegisterNativeHandler("IS_TRACK_SWITCHED_OFF", [](fx::ScriptContext& context) { - if (TrackData* track = getAndCheckTrack(context, "IS_TRACK_SWITCHED_OFF")) + if (rage::CTrainTrack* track = getAndCheckTrack(context, "IS_TRACK_SWITCHED_OFF")) { context.SetResult(track->m_disableAmbientTrains); }