feat(native_hooks): add native hooks

This commit is contained in:
maybegreat48
2025-03-17 17:39:05 -04:00
parent 4e78d04d87
commit d1c1ff53ee
12 changed files with 336 additions and 48 deletions
+1
View File
@@ -17,6 +17,7 @@ namespace YimMenu
BaseHook::Add<Hooks::SwapChain::ResizeBuffers>(new DetourHook("ResizeBuffers", swapchain_vft[Hooks::SwapChain::VMTResizeBuffersIdx], Hooks::SwapChain::ResizeBuffers));
BaseHook::Add<Hooks::Script::RunScriptThreads>(new DetourHook("RunScriptThreads", Pointers.RunScriptThreads, Hooks::Script::RunScriptThreads));
BaseHook::Add<Hooks::Script::InitNativeTables>(new DetourHook("InitNativeTables", Pointers.InitNativeTables, Hooks::Script::InitNativeTables));
BaseHook::Add<Hooks::Info::AssignPhysicalIndex>(new DetourHook("AssignPhysicalIndex", Pointers.AssignPhysicalIndex, Hooks::Info::AssignPhysicalIndex));
}
+39 -38
View File
@@ -7,96 +7,97 @@
namespace YimMenu
{
template<std::size_t N>
class VMTHook : public BaseHook
class VMTHook
{
private:
std::string_view m_Name;
bool m_Enabled;
std::array<void*, N> m_NewVMT = {nullptr};
void** m_OriginalVMT;
int m_NumEntries;
void** m_NewVMT{};
void** m_OriginalVMT{};
void*** m_VMTAddress;
public:
template<typename T>
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<typename T>
inline T Original(const std::uint32_t idx) const;
template<typename T>
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<std::size_t N>
template<typename T>
inline VMTHook<N>::VMTHook(const std::string_view name, T* vmtAddress) :
BaseHook(name),
m_Enabled(false),
m_VMTAddress(reinterpret_cast<void***>(vmtAddress))
inline VMTHook::VMTHook(const std::string_view name, T* vmtAddress, int numEntries) :
m_Name(name),
m_VMTAddress(reinterpret_cast<void***>(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<std::size_t N>
inline VMTHook<N>::~VMTHook()
inline VMTHook::~VMTHook()
{
Disable();
delete[] m_NewVMT;
}
template<std::size_t N>
inline bool VMTHook<N>::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<std::size_t N>
inline bool VMTHook<N>::Disable()
inline void VMTHook::Disable()
{
if (!m_Enabled)
return false;
return;
*m_VMTAddress = m_OriginalVMT;
m_Enabled = false;
return true;
return;
}
template<std::size_t N>
template<typename T>
inline void VMTHook<N>::Hook(const std::uint32_t idx, T& detour)
inline void VMTHook::Hook(const std::uint32_t idx, void* detour)
{
if (std::is_pointer<T>())
m_NewVMT[idx] = reinterpret_cast<void*>(detour);
else
m_NewVMT[idx] = reinterpret_cast<void*>(&detour);
m_NewVMT[idx] = reinterpret_cast<void*>(detour);
}
template<std::size_t N>
inline void VMTHook<N>::UnHook(const std::uint32_t idx)
inline void VMTHook::UnHook(const std::uint32_t idx)
{
m_NewVMT[idx] = m_OriginalVMT[idx];
}
template<std::size_t N>
template<typename T>
inline T VMTHook<N>::Original(const std::uint32_t idx) const
inline T VMTHook::Original(const std::uint32_t idx) const
{
return reinterpret_cast<T>(m_OriginalVMT[idx]);
}
+130
View File
@@ -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<VMTHook>("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<decltype(&NativeHooks::Program::ScrProgram_Dtor)**>(_this))(_this, free_mem);
}
NativeHooks::NativeHooks() :
m_RegisteredPrograms(),
m_RegisteredHooks()
{
m_RegisteredHooks.emplace(ALL_SCRIPTS, std::vector<Hook>());
}
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<Hook> 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();
}
}
+80
View File
@@ -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<VMTHook> 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<rage::scrProgram*, std::unique_ptr<Program>> m_RegisteredPrograms;
std::unordered_map<joaat_t, std::vector<Hook>> 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();
};
}
+45
View File
@@ -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);
}
}
}
+4
View File
@@ -9,11 +9,15 @@ namespace YimMenu::Submenus
// TODO: this needs a rework
auto session = std::make_shared<Category>("Session");
auto toxicGroup = std::make_shared<Group>("Toxic");
auto enhancements = std::make_shared<Group>("Enhancements");
toxicGroup->AddItem(std::make_shared<CommandItem>("killall"_J, "Kill All"));
toxicGroup->AddItem(std::make_shared<CommandItem>("explodeall"_J, "Explode All"));
enhancements->AddItem(std::make_shared<BoolCommandItem>("fastjoin"_J));
session->AddItem(toxicGroup);
session->AddItem(enhancements);
AddCategory(std::move(session));
}
+3 -2
View File
@@ -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<rage::scrProgram*>(calloc(1, sizeof(rage::scrProgram)));
program->m_NativeCount = m_Handlers.size();
program->m_NativeEntrypoints = m_Handlers.data();
Pointers.PopulateNatives(program);
BaseHook::Get<Hooks::Script::InitNativeTables, DetourHook<decltype(&Hooks::Script::InitNativeTables)>>()->Original()(program);
free(program);
m_AreHandlersCached = true;
}
+2
View File
@@ -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
@@ -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<Script::InitNativeTables, DetourHook<decltype(&Script::InitNativeTables)>>()->Original()(program);
if (g_Running)
{
NativeHooks::RegisterProgram(program);
}
}
}
+7 -2
View File
@@ -47,9 +47,9 @@ namespace YimMenu
ScriptThreads = ptr.Add(3).Rip().As<rage::atArray<rage::scrThread*>*>();
});
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<Functions::PopulateNatives>();
InitNativeTables = ptr.Sub(0x2A).As<PVOID>();
});
constexpr auto runScriptThreadsPtrn = Pattern<"BE 40 5D C6 00">("RunScriptThreads");
@@ -93,6 +93,11 @@ namespace YimMenu
TriggerWeaponDamageEvent = ptr.Sub(0x51).As<Functions::TriggerWeaponDamageEvent>();
});
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<rage::scrProgram**>();
});
if (!scanner.Scan())
{
LOG(FATAL) << "Some patterns could not be found, unloading.";
+2 -2
View File
@@ -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<rage::scrThread*>* 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
+8 -4
View File
@@ -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<Script>(&NativeHooks::RunScript)); // runs once
ScriptMgr::AddScript(std::make_unique<Script>(&Self::RunScript));
FiberPool::Init(5);
ScriptMgr::AddScript(std::make_unique<Script>(&HotkeySystem::RunScript));
@@ -54,11 +56,13 @@ namespace YimMenu
std::this_thread::yield();
}
unload:
LOG(INFO) << "Unloading";
NativeHooks::Destroy();
FiberPool::Destroy();
ScriptMgr::Destroy();
Hooking::Destroy();
EARLY_UNLOAD:
Renderer::Destroy();
LogHelper::Destroy();