From d1c1ff53eee20167f56960e987709abf678742e9 Mon Sep 17 00:00:00 2001 From: maybegreat48 <96936658+maybegreat48@users.noreply.github.com> Date: Mon, 17 Mar 2025 17:39:05 -0400 Subject: [PATCH] feat(native_hooks): add native hooks --- src/core/hooking/Hooking.cpp | 1 + src/core/hooking/VMTHook.hpp | 77 ++++++------ src/game/backend/NativeHooks.cpp | 130 +++++++++++++++++++++ src/game/backend/NativeHooks.hpp | 80 +++++++++++++ src/game/features/network/FastJoin.cpp | 45 +++++++ src/game/frontend/submenus/Network.cpp | 4 + src/game/gta/invoker/Invoker.cpp | 5 +- src/game/hooks/Hooks.hpp | 2 + src/game/hooks/Script/InitNativeTables.cpp | 15 +++ src/game/pointers/Pointers.cpp | 9 +- src/game/pointers/Pointers.hpp | 4 +- src/main.cpp | 12 +- 12 files changed, 336 insertions(+), 48 deletions(-) create mode 100644 src/game/backend/NativeHooks.cpp create mode 100644 src/game/backend/NativeHooks.hpp create mode 100644 src/game/features/network/FastJoin.cpp create mode 100644 src/game/hooks/Script/InitNativeTables.cpp diff --git a/src/core/hooking/Hooking.cpp b/src/core/hooking/Hooking.cpp index 98001cd..0930f41 100644 --- a/src/core/hooking/Hooking.cpp +++ b/src/core/hooking/Hooking.cpp @@ -17,6 +17,7 @@ namespace YimMenu BaseHook::Add(new DetourHook("ResizeBuffers", swapchain_vft[Hooks::SwapChain::VMTResizeBuffersIdx], Hooks::SwapChain::ResizeBuffers)); BaseHook::Add(new DetourHook("RunScriptThreads", Pointers.RunScriptThreads, Hooks::Script::RunScriptThreads)); + BaseHook::Add(new DetourHook("InitNativeTables", Pointers.InitNativeTables, Hooks::Script::InitNativeTables)); BaseHook::Add(new DetourHook("AssignPhysicalIndex", Pointers.AssignPhysicalIndex, Hooks::Info::AssignPhysicalIndex)); } diff --git a/src/core/hooking/VMTHook.hpp b/src/core/hooking/VMTHook.hpp index 8eadcd1..43cccd4 100644 --- a/src/core/hooking/VMTHook.hpp +++ b/src/core/hooking/VMTHook.hpp @@ -7,96 +7,97 @@ namespace YimMenu { - template - class VMTHook : public BaseHook + class VMTHook { private: std::string_view m_Name; bool m_Enabled; - std::array m_NewVMT = {nullptr}; - void** m_OriginalVMT; + int m_NumEntries; + void** m_NewVMT{}; + void** m_OriginalVMT{}; void*** m_VMTAddress; public: template - VMTHook(const std::string_view name, T* vmtAddress); + VMTHook(const std::string_view name, T* vmtAddress, int numEntries); ~VMTHook(); - virtual bool Enable() override; - virtual bool Disable() override; + VMTHook(VMTHook&& that) = delete; + VMTHook& operator=(VMTHook&& that) = delete; + VMTHook(VMTHook const&) = delete; + VMTHook& operator=(VMTHook const&) = delete; + + virtual void Enable(); + virtual void Disable(); template inline T Original(const std::uint32_t idx) const; - template - void Hook(const std::uint32_t idx, T& detour); + void Hook(const std::uint32_t idx, void* detour); void UnHook(const std::uint32_t idx); inline constexpr std::size_t VMTSize() const { - return N; + return m_NumEntries; } }; - template + template - inline VMTHook::VMTHook(const std::string_view name, T* vmtAddress) : - BaseHook(name), - m_Enabled(false), - m_VMTAddress(reinterpret_cast(vmtAddress)) + inline VMTHook::VMTHook(const std::string_view name, T* vmtAddress, int numEntries) : + m_Name(name), + m_VMTAddress(reinterpret_cast(vmtAddress)), + m_NumEntries(numEntries), + m_NewVMT(new void*[m_NumEntries]), + m_Enabled(false) { m_OriginalVMT = *m_VMTAddress; - std::copy_n(m_OriginalVMT, N, m_NewVMT.begin()); + memcpy(m_NewVMT, m_OriginalVMT, sizeof(void*) * m_NumEntries); } - template - inline VMTHook::~VMTHook() + inline VMTHook::~VMTHook() { Disable(); + delete[] m_NewVMT; } - template - inline bool VMTHook::Enable() + + inline void VMTHook::Enable() { if (m_Enabled) - return false; + return; m_Enabled = true; - *m_VMTAddress = m_NewVMT.data(); - return true; + *m_VMTAddress = m_NewVMT; + return; } - template - inline bool VMTHook::Disable() + + inline void VMTHook::Disable() { if (!m_Enabled) - return false; + return; *m_VMTAddress = m_OriginalVMT; m_Enabled = false; - return true; + return; } - template - template - inline void VMTHook::Hook(const std::uint32_t idx, T& detour) + inline void VMTHook::Hook(const std::uint32_t idx, void* detour) { - if (std::is_pointer()) - m_NewVMT[idx] = reinterpret_cast(detour); - else - m_NewVMT[idx] = reinterpret_cast(&detour); + m_NewVMT[idx] = reinterpret_cast(detour); } - template - inline void VMTHook::UnHook(const std::uint32_t idx) + + inline void VMTHook::UnHook(const std::uint32_t idx) { m_NewVMT[idx] = m_OriginalVMT[idx]; } - template + template - inline T VMTHook::Original(const std::uint32_t idx) const + inline T VMTHook::Original(const std::uint32_t idx) const { return reinterpret_cast(m_OriginalVMT[idx]); } diff --git a/src/game/backend/NativeHooks.cpp b/src/game/backend/NativeHooks.cpp new file mode 100644 index 0000000..9002202 --- /dev/null +++ b/src/game/backend/NativeHooks.cpp @@ -0,0 +1,130 @@ +#include "NativeHooks.hpp" + +#include "game/gta/invoker/Invoker.hpp" + +#include "types/script/scrProgram.hpp" +#include "types/script/scrThread.hpp" + + +namespace YimMenu +{ + NativeHooks::Program::Program(rage::scrProgram* program) : + m_Program(program), + m_Hook(std::make_unique("rage::scrProgram", program, 9)) + { + m_Hook->Hook(6, ScrProgram_Dtor); + m_Hook->Enable(); + m_OrigHandlers = new rage::scrNativeHandler[program->m_NativeCount]; + memcpy(m_OrigHandlers, program->m_NativeEntrypoints, program->m_NativeCount * sizeof(rage::scrNativeHandler)); + } + + void NativeHooks::Program::Apply(Hook hook) + { + auto old_native = NativeInvoker::GetNativeHandler(hook.m_Index); + auto old_native_addr = m_Program->GetAddressOfNativeEntrypoint(old_native); + if (old_native_addr) + *old_native_addr = hook.m_Replacement; + } + + void NativeHooks::Program::Cleanup() + { + memcpy(m_Program->m_NativeEntrypoints, m_OrigHandlers, m_Program->m_NativeCount * sizeof(rage::scrNativeHandler)); + delete[] m_OrigHandlers; + m_Hook->Disable(); + } + + void NativeHooks::Program::ScrProgram_Dtor(rage::scrProgram* _this, bool free_mem) + { + NativeHooks::UnregisterProgram(_this); + (**reinterpret_cast(_this))(_this, free_mem); + } + + NativeHooks::NativeHooks() : + m_RegisteredPrograms(), + m_RegisteredHooks() + { + m_RegisteredHooks.emplace(ALL_SCRIPTS, std::vector()); + } + + void NativeHooks::RunScriptImpl() + { + for (int i = 0; i < 176; i++) + { + if (Pointers.ScriptPrograms[i] != nullptr) + RegisterProgram(Pointers.ScriptPrograms[i]); + } + } + + void NativeHooks::AddHookImpl(joaat_t script, NativeIndex index, rage::scrNativeHandler hook) + { + if (!g_Running) + return; + + if (auto it = m_RegisteredHooks.find(script); it != m_RegisteredHooks.end()) + { + it->second.push_back(Hook(index, hook)); + } + else + { + std::vector hooks = {Hook(index, hook)}; + m_RegisteredHooks.emplace(script, hooks); + } + + for (auto& [scp, program] : m_RegisteredPrograms) + { + if (scp->m_NameHash == script || script == ALL_SCRIPTS) + { + program->Apply(Hook(index, hook)); + } + } + } + + void NativeHooks::RegisterProgramImpl(rage::scrProgram* program) + { + if (!g_Running) + return; + + if (m_RegisteredPrograms.contains(program)) + { + // LOG(FATAL) << "We've already registered " << program->m_Name << ". This is bad!"; + return; + } + + auto t = new Program(program); + m_RegisteredPrograms.emplace(program, std::move(t)); + + auto& pgrm = m_RegisteredPrograms[program]; + + if (auto it = m_RegisteredHooks.find(program->m_NameHash); it != m_RegisteredHooks.end()) + { + for (auto& hook : it->second) + { + pgrm->Apply(hook); + } + } + + for (auto& hook : m_RegisteredHooks.at(ALL_SCRIPTS)) + { + pgrm->Apply(hook); + } + } + + void NativeHooks::UnregisterProgramImpl(rage::scrProgram* program) + { + if (auto it = m_RegisteredPrograms.find(program); it != m_RegisteredPrograms.end()) + { + it->second->Cleanup(); + m_RegisteredPrograms.erase(it); + } + else + { + LOG(FATAL) << "Cannot find program " << program->m_Name << " in registry. This is bad!"; + } + } + + void NativeHooks::DestroyImpl() + { + for (auto& [_, program] : m_RegisteredPrograms) + program->Cleanup(); + } +} \ No newline at end of file diff --git a/src/game/backend/NativeHooks.hpp b/src/game/backend/NativeHooks.hpp new file mode 100644 index 0000000..2c5ae5a --- /dev/null +++ b/src/game/backend/NativeHooks.hpp @@ -0,0 +1,80 @@ +#pragma once +#include "core/hooking/VMTHook.hpp" +#include "core/util/Joaat.hpp" +#include "types/script/scrNativeHandler.hpp" + +enum class NativeIndex; + +namespace rage +{ + class scrProgram; +} + +namespace YimMenu +{ + class NativeHooks + { + struct Hook + { + NativeIndex m_Index; + rage::scrNativeHandler m_Replacement; + }; + + class Program final + { + rage::scrProgram* m_Program = nullptr; + std::unique_ptr m_Hook = nullptr; + rage::scrNativeHandler* m_OrigHandlers = nullptr; + + public: + Program(rage::scrProgram* program); + void Apply(Hook hook); + void Cleanup(); + static void ScrProgram_Dtor(rage::scrProgram* _this, bool free_mem); + }; + + std::unordered_map> m_RegisteredPrograms; + std::unordered_map> m_RegisteredHooks; + + public: + static void RunScript() + { + GetInstance().RunScriptImpl(); + } + + static constexpr auto ALL_SCRIPTS = "ALL_SCRIPTS"_J; + static void AddHook(joaat_t script, NativeIndex index, rage::scrNativeHandler hook) + { + GetInstance().AddHookImpl(script, index, hook); + } + + static void RegisterProgram(rage::scrProgram* program) + { + GetInstance().RegisterProgramImpl(program); + } + + static void UnregisterProgram(rage::scrProgram* program) + { + GetInstance().UnregisterProgramImpl(program); + } + + static void Destroy() + { + GetInstance().DestroyImpl(); + } + + private: + NativeHooks(); + static NativeHooks& GetInstance() + { + static NativeHooks Instance; + return Instance; + } + + void RunScriptImpl(); + void AddHookImpl(joaat_t script, NativeIndex index, rage::scrNativeHandler hook); + void RegisterProgramImpl(rage::scrProgram* program); + void UnregisterProgramImpl(rage::scrProgram* program); + void DestroyImpl(); + }; +} \ No newline at end of file diff --git a/src/game/features/network/FastJoin.cpp b/src/game/features/network/FastJoin.cpp new file mode 100644 index 0000000..02c6ed4 --- /dev/null +++ b/src/game/features/network/FastJoin.cpp @@ -0,0 +1,45 @@ +#include "core/commands/BoolCommand.hpp" +#include "game/backend/NativeHooks.hpp" +#include "game/gta/Natives.hpp" +#include "types/script/globals/GSBD.hpp" + +namespace YimMenu::Features +{ + static void BroadcastHook(rage::scrNativeCallContext* ctx); + + class FastJoin : public BoolCommand + { + using BoolCommand::BoolCommand; + + virtual void OnEnable() override + { + NativeHooks::AddHook("freemode"_J, NativeIndex::NETWORK_HAS_RECEIVED_HOST_BROADCAST_DATA, &BroadcastHook); + NativeHooks::AddHook("fmmc_launcher"_J, NativeIndex::NETWORK_HAS_RECEIVED_HOST_BROADCAST_DATA, &BroadcastHook); + NativeHooks::AddHook("am_launcher"_J, NativeIndex::NETWORK_HAS_RECEIVED_HOST_BROADCAST_DATA, &BroadcastHook); + } + }; + + static FastJoin _FastJoin{"fastjoin", "Fast Join", "Attempts to improve join speed by loading host broadcast variables asynchronously. This may cause certain jobs to break, use with caution"}; + + static void BroadcastHook(rage::scrNativeCallContext* ctx) + { + if (!_FastJoin.GetState() + || NETWORK::NETWORK_IS_ACTIVITY_SESSION() + || NETWORK::NETWORK_IS_IN_TRANSITION() + || NETWORK::NETWORK_IS_TRANSITION_TO_GAME() + || NETWORK::NETWORK_IS_HOST_OF_THIS_SCRIPT() + || !STREAMING::IS_PLAYER_SWITCH_IN_PROGRESS()) // if there's no player switch, we're most likely loading into a job + { + return ctx->SetReturnValue(NETWORK::NETWORK_HAS_RECEIVED_HOST_BROADCAST_DATA()); + } + else + { + if (SCRIPT::GET_HASH_OF_THIS_SCRIPT_NAME() == "freemode"_J) + { + GSBD::Get()->FreemodeState = eFreemodeState::RUNNING; + GSBD::Get()->CayoPericoStrandedAnimalChoice = 0; // unbrick cayo just in case if the script host never responds + } + return ctx->SetReturnValue(TRUE); + } + } +} \ No newline at end of file diff --git a/src/game/frontend/submenus/Network.cpp b/src/game/frontend/submenus/Network.cpp index b8b142f..c14061c 100644 --- a/src/game/frontend/submenus/Network.cpp +++ b/src/game/frontend/submenus/Network.cpp @@ -9,11 +9,15 @@ namespace YimMenu::Submenus // TODO: this needs a rework auto session = std::make_shared("Session"); auto toxicGroup = std::make_shared("Toxic"); + auto enhancements = std::make_shared("Enhancements"); toxicGroup->AddItem(std::make_shared("killall"_J, "Kill All")); toxicGroup->AddItem(std::make_shared("explodeall"_J, "Explode All")); + enhancements->AddItem(std::make_shared("fastjoin"_J)); + session->AddItem(toxicGroup); + session->AddItem(enhancements); AddCategory(std::move(session)); } diff --git a/src/game/gta/invoker/Invoker.cpp b/src/game/gta/invoker/Invoker.cpp index 85cc26c..f509ead 100644 --- a/src/game/gta/invoker/Invoker.cpp +++ b/src/game/gta/invoker/Invoker.cpp @@ -1,7 +1,8 @@ #include "Invoker.hpp" #include "Crossmap.hpp" -#include "game/pointers/Pointers.hpp" +#include "core/hooking/DetourHook.hpp" +#include "game/hooks/Hooks.hpp" #include "types/script/scrProgram.hpp" @@ -22,7 +23,7 @@ namespace YimMenu auto program = reinterpret_cast(calloc(1, sizeof(rage::scrProgram))); program->m_NativeCount = m_Handlers.size(); program->m_NativeEntrypoints = m_Handlers.data(); - Pointers.PopulateNatives(program); + BaseHook::Get>()->Original()(program); free(program); m_AreHandlersCached = true; } diff --git a/src/game/hooks/Hooks.hpp b/src/game/hooks/Hooks.hpp index 9fb68ec..9fe2b6c 100644 --- a/src/game/hooks/Hooks.hpp +++ b/src/game/hooks/Hooks.hpp @@ -7,6 +7,7 @@ namespace rage { class netConnectionManager; class netEvent; + class scrProgram; } namespace YimMenu::Hooks @@ -27,6 +28,7 @@ namespace YimMenu::Hooks namespace Script { extern bool RunScriptThreads(int ops_to_execute); + extern void InitNativeTables(rage::scrProgram* program); } namespace Window diff --git a/src/game/hooks/Script/InitNativeTables.cpp b/src/game/hooks/Script/InitNativeTables.cpp new file mode 100644 index 0000000..007b1df --- /dev/null +++ b/src/game/hooks/Script/InitNativeTables.cpp @@ -0,0 +1,15 @@ +#include "core/hooking/DetourHook.hpp" +#include "game/backend/NativeHooks.hpp" +#include "game/hooks/Hooks.hpp" + +namespace YimMenu::Hooks +{ + void Script::InitNativeTables(rage::scrProgram* program) + { + BaseHook::Get>()->Original()(program); + if (g_Running) + { + NativeHooks::RegisterProgram(program); + } + } +} \ No newline at end of file diff --git a/src/game/pointers/Pointers.cpp b/src/game/pointers/Pointers.cpp index 8e325e6..dba95bb 100644 --- a/src/game/pointers/Pointers.cpp +++ b/src/game/pointers/Pointers.cpp @@ -47,9 +47,9 @@ namespace YimMenu ScriptThreads = ptr.Add(3).Rip().As*>(); }); - constexpr auto populateNativesPtrn = Pattern<"EB 2A 0F 1F 40 00 48 8B 54 17 10">("PopulateNatives"); + constexpr auto populateNativesPtrn = Pattern<"EB 2A 0F 1F 40 00 48 8B 54 17 10">("InitNativeTables"); scanner.Add(populateNativesPtrn, [this](PointerCalculator ptr) { - PopulateNatives = ptr.Sub(0x2A).As(); + InitNativeTables = ptr.Sub(0x2A).As(); }); constexpr auto runScriptThreadsPtrn = Pattern<"BE 40 5D C6 00">("RunScriptThreads"); @@ -93,6 +93,11 @@ namespace YimMenu TriggerWeaponDamageEvent = ptr.Sub(0x51).As(); }); + constexpr auto scriptProgramsPtrn = Pattern<"48 C7 84 C8 D8 00 00 00 00 00 00 00">("ScriptPrograms"); + scanner.Add(scriptProgramsPtrn, [this](PointerCalculator ptr) { + ScriptPrograms = ptr.Add(0x13).Add(3).Rip().Add(0xD8).As(); + }); + if (!scanner.Scan()) { LOG(FATAL) << "Some patterns could not be found, unloading."; diff --git a/src/game/pointers/Pointers.hpp b/src/game/pointers/Pointers.hpp index 53e8471..e93e2ec 100644 --- a/src/game/pointers/Pointers.hpp +++ b/src/game/pointers/Pointers.hpp @@ -19,7 +19,6 @@ namespace YimMenu { namespace Functions { - using PopulateNatives = void (*)(rage::scrProgram* program); using HandleToPtr = void* (*)(int handle); using PtrToHandle = int (*)(void* pointer); using GetNetPlayerFromPid = CNetGamePlayer* (*)(int id); @@ -35,7 +34,7 @@ namespace YimMenu std::uint32_t* ScreenResX; std::uint32_t* ScreenResY; rage::atArray* ScriptThreads; - Functions::PopulateNatives PopulateNatives; + PVOID InitNativeTables; std::int64_t** ScriptGlobals; PVOID RunScriptThreads; Functions::HandleToPtr HandleToPtr; @@ -45,6 +44,7 @@ namespace YimMenu bool* IsSessionStarted; PVOID AssignPhysicalIndex; Functions::TriggerWeaponDamageEvent TriggerWeaponDamageEvent; + rage::scrProgram** ScriptPrograms; }; struct Pointers : PointerData diff --git a/src/main.cpp b/src/main.cpp index 0f84a12..fdc0c36 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,6 +10,7 @@ #include "core/memory/ModuleMgr.hpp" #include "core/renderer/Renderer.hpp" #include "game/backend/Self.hpp" +#include "game/backend/NativeHooks.hpp" #include "game/frontend/GUI.hpp" #include "game/pointers/Pointers.hpp" @@ -26,13 +27,13 @@ namespace YimMenu Settings::Initialize(FileMgr::GetProjectFile("./settings.json")); if (!ModuleMgr.LoadModules()) - goto unload; + goto EARLY_UNLOAD; if (!Pointers.Init()) - goto unload; + goto EARLY_UNLOAD; if (!Renderer::Init()) - goto unload; + goto EARLY_UNLOAD; Hooking::Init(); @@ -41,6 +42,7 @@ namespace YimMenu GUI::Init(); + ScriptMgr::AddScript(std::make_unique