Compare commits

...

10 Commits

Author SHA1 Message Date
726c49b407 fix: add examples to cmake 2025-11-18 22:03:04 +01:00
8a7cd27875 fix: hook registration fixes 2025-11-18 22:02:44 +01:00
d341524e66 feat: added hooks to the api 2025-11-18 22:02:03 +01:00
ce6c64d393 feat: added examples 2025-11-18 22:01:09 +01:00
da6614cb6f feat: added exports 2025-11-18 21:20:04 +01:00
a7faf91b28 feat: added scheduling 2025-11-18 21:19:07 +01:00
9d36ce0f18 fix: added missing ctor declarations 2025-11-18 20:38:51 +01:00
e094c35437 feat: added native scheduler 2025-11-18 20:37:14 +01:00
b55bd2f783 feat: added event class 2025-11-18 20:30:44 +01:00
989f7f4d73 feat: added native manager impl 2025-11-18 20:21:10 +01:00
11 changed files with 1218 additions and 46 deletions

View File

@@ -20,4 +20,22 @@ target_include_directories(native_invoker_lib
set(BLACKBASE_BUILD_TESTS OFF CACHE BOOL "Disable building tests for blackbase")
add_subdirectory(vendor/blackbase)
target_link_libraries(native_invoker_lib PUBLIC blackbase)
target_link_libraries(native_invoker_lib PUBLIC blackbase)
file(GLOB NATIVE_INVOKER_EXAMPLES
examples/*.cpp
)
foreach(EXAMPLE_SOURCE ${NATIVE_INVOKER_EXAMPLES})
get_filename_component(EXAMPLE_NAME ${EXAMPLE_SOURCE} NAME_WE)
add_library(${EXAMPLE_NAME} SHARED ${EXAMPLE_SOURCE})
target_include_directories(${EXAMPLE_NAME}
PUBLIC
include
)
target_compile_definitions(${EXAMPLE_NAME}
PRIVATE
FXN_IMPORTS
)
endforeach()

View File

@@ -0,0 +1,80 @@
#include <windows.h>
#include <fxn/invoker.hpp>
#include <fxn/utility.hpp>
void MainFunc()
{
fxn::invoker_base::initialize("vcruntime140_1.dll");
constexpr auto GIVE_WEAPON_TO_PED = 0xB41DEC3AAC1AA107;
fxn::native_hooker<0xB41DEC3AAC1AA107> giveWeaponHook([](fxn::NativeContext* context, bool& callOriginal) -> bool
{
auto pedId = context->GetArgument<int>(0);
auto weaponHash = context->GetArgument<unsigned int>(1);
auto ammoCount = context->GetArgument<int>(2);
auto isHidden = context->GetArgument<bool>(3);
auto bForceInHand = context->GetArgument<bool>(4);
printf("GiveWeaponToPed Hooked! PedID: %d, WeaponHash: 0x%X, AmmoCount: %d, IsHidden: %d, ForceInHand: %d\n",
pedId, weaponHash, ammoCount, isHidden, bForceInHand);
// Allow original function to be called
callOriginal = true;
return true;
});
while (!GetAsyncKeyState(VK_END))
{
if (GetAsyncKeyState(VK_F1))
{
auto playerPedId = fxn::invoker<0x4A8C381C258A124D, int>::invoke();
printf("Player Ped ID: %d\n", playerPedId);
auto weaponHash = fxn::HashString("WEAPON_PISTOL");
fxn::invoker<0xB41DEC3AAC1AA107, void, int, unsigned int, int, bool, bool>::invoke(
playerPedId,
weaponHash,
250,
true, true
);
Sleep(500); // Debounce
}
Sleep(10);
}
}
DWORD ThreadMain(LPVOID lpParam)
{
FILE* fp;
AllocConsole();
freopen_s(&fp, "CONOUT$", "w", stdout);
HMODULE hModule = (HMODULE)lpParam;
MainFunc();
FreeConsole();
if (fp)
{
fclose(fp);
}
FreeLibraryAndExitThread(hModule, 0);
return 0;
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
if (ul_reason_for_call == DLL_PROCESS_ATTACH)
{
HANDLE hThread = CreateThread(NULL, NULL, ThreadMain, hModule, NULL, NULL);
if (hThread)
{
CloseHandle(hThread);
}
}
return TRUE;
}

147
include/fxn/invoker.hpp Normal file
View File

@@ -0,0 +1,147 @@
#pragma once
#include <cstdint>
#include <string_view>
#include <fxn/context.hpp>
namespace fxn
{
struct invoker_base
{
typedef void*(*get_native_handler_t)(std::uint64_t hash);
static get_native_handler_t _get_native_handler;
typedef void(*schedule_native_t)(fxn::NativeContext* context, bool wait);
static schedule_native_t _schedule_native;
typedef bool(*native_hook_t)(fxn::NativeContext* context, bool& call_original);
typedef size_t(*register_native_hook_t)(std::uint64_t hash, native_hook_t hook);
static register_native_hook_t _register_native_hook;
typedef void(*unregister_native_hook_t)(std::uint64_t hash, size_t index);
static unregister_native_hook_t _unregister_native_hook;
static void initialize(std::string_view module_name);
static void get_native_handler(std::uint64_t hash);
static void schedule_native(fxn::NativeContext* context, bool wait);
static size_t register_native_hook(std::uint64_t hash, native_hook_t hook);
static void unregister_native_hook(std::uint64_t hash, size_t index);
};
template <std::uint64_t Hash, typename Ret, typename... Args>
struct invoker
{
using return_type = Ret;
using native_hash = std::integral_constant<std::uint64_t, Hash>;
return_type operator()(Args... args);
static return_type invoke(Args... args)
{
invoker<Hash, Ret, Args...> inv;
return inv(args...);
}
};
template<std::uint64_t Hash>
struct native_hooker
{
private:
std::size_t _hook;
public:
using native_hash = std::integral_constant<std::uint64_t, Hash>;
native_hooker(fxn::invoker_base::native_hook_t hook)
{
_hook = fxn::invoker_base::register_native_hook(native_hash::value, hook);
}
~native_hooker()
{
fxn::invoker_base::unregister_native_hook(native_hash::value, _hook);
}
};
}
#ifdef FXN_IMPORTS
#include <Windows.h>
namespace fxn
{
inline invoker_base::get_native_handler_t invoker_base::_get_native_handler = nullptr;
inline invoker_base::schedule_native_t invoker_base::_schedule_native = nullptr;
inline invoker_base::register_native_hook_t invoker_base::_register_native_hook = nullptr;
inline invoker_base::unregister_native_hook_t invoker_base::_unregister_native_hook = nullptr;
inline void invoker_base::initialize(std::string_view module_name)
{
HMODULE mod = GetModuleHandleA(module_name.data());
if (!mod)
{
exit(EXIT_FAILURE);
}
invoker_base::_get_native_handler = reinterpret_cast<get_native_handler_t>(
GetProcAddress(mod, "fxn_native_invoker_get_handler")
);
invoker_base::_schedule_native = reinterpret_cast<schedule_native_t>(
GetProcAddress(mod, "fxn_schedule_native")
);
invoker_base::_register_native_hook = reinterpret_cast<register_native_hook_t>(
GetProcAddress(mod, "fxn_register_native_hook")
);
invoker_base::_unregister_native_hook = reinterpret_cast<unregister_native_hook_t>(
GetProcAddress(mod, "fxn_unregister_native_hook")
);
if (!invoker_base::_get_native_handler ||
!invoker_base::_schedule_native ||
!invoker_base::_register_native_hook ||
!invoker_base::_unregister_native_hook)
{
exit(EXIT_FAILURE);
}
}
inline void invoker_base::get_native_handler(std::uint64_t hash)
{
invoker_base::_get_native_handler(hash);
}
inline void invoker_base::schedule_native(fxn::NativeContext* context, bool wait)
{
invoker_base::_schedule_native(context, wait);
}
inline size_t invoker_base::register_native_hook(std::uint64_t hash, native_hook_t hook)
{
return invoker_base::_register_native_hook(hash, hook);
}
inline void invoker_base::unregister_native_hook(std::uint64_t hash, size_t index)
{
invoker_base::_unregister_native_hook(hash, index);
}
template <std::uint64_t Hash, typename Ret, typename... Args>
inline typename invoker<Hash, Ret, Args...>::return_type invoker<Hash, Ret, Args...>::operator()(Args... args)
{
fxn::NativeContext context(Hash);
// Push arguments
(context.PushArgument<Args>(args), ...);
// Schedule native call
invoker_base::schedule_native(&context, true);
if constexpr (!std::is_same_v<Ret, void>)
{
return context.GetResult<Ret>();
}
}
}
#endif

198
src/detail/event.hpp Normal file
View File

@@ -0,0 +1,198 @@
#pragma once
#include <memory>
#include <functional>
#include <atomic>
#include <cstdint>
namespace fx
{
template<typename... Args>
class fwEvent
{
public:
using TFunc = std::function<bool(Args...)>;
public:
struct callback
{
TFunc function;
std::unique_ptr<callback> next = nullptr;
int order = 0;
size_t cookie = -1;
callback(TFunc func)
: function(func)
{
}
~callback()
{
while (next)
{
next = std::move(next->next);
}
}
};
std::unique_ptr<callback> m_callbacks;
std::atomic<size_t> m_connectCookie = 0;
public:
fwEvent()
: m_callbacks(nullptr)
{
}
~fwEvent()
{
Reset();
}
template<typename T>
auto Connect(T func)
{
return ConnectInternal(func, 0);
}
template<typename T>
auto Connect(T func, int order)
{
if constexpr (std::is_same_v<std::invoke_result_t<T, Args...>, bool>)
{
return ConnectInternal(func, order);
}
else
{
return ConnectInternal([=](Args... args) -> bool
{
std::invoke(func, args...);
return true;
}, order);
}
}
void Reset()
{
m_callbacks.reset();
}
private:
size_t ConnectInternal(TFunc func, int order)
{
if (!func)
{
return -1;
}
auto cookie = m_connectCookie++;
auto cb = std::unique_ptr<callback>(new callback(func));
cb->order = order;
cb->cookie = cookie;
if (!m_callbacks)
{
m_callbacks = std::move(cb);
}
else
{
auto cur = &m_callbacks;
callback* last = nullptr;
while (*cur && order >= (*cur)->order)
{
last = cur->get();
cur = &(*cur)->next;
}
cb->next = std::move(*cur);
(!last ? m_callbacks : last->next) = std::move(cb);
}
return cookie;
}
public:
void Disconnect(size_t cookie)
{
if (cookie == -1)
{
return;
}
callback* prev = nullptr;
for (auto cb = m_callbacks.get(); cb; cb = cb->next.get())
{
if (cb->cookie == cookie)
{
if (prev)
{
prev->next = std::move(cb->next);
}
else
{
m_callbacks = std::move(cb->next);
}
break;
}
prev = cb;
}
}
auto ConnectInternal(TFunc func)
{
return ConnectInternal(func, 0);
}
public:
operator bool() const
{
return m_callbacks.get() != nullptr;
}
bool operator()(Args... args) const
{
if (!m_callbacks)
{
return false;
}
decltype(m_callbacks.get()) next = {};
for (auto cb = m_callbacks.get(); cb; cb = next)
{
next = cb->next.get();
if(!std::invoke(cb->function, args...))
{
return false;
}
}
return true;
}
void ForeachCallback(const std::function<bool(callback&)>& func)
{
callback* prev = nullptr;
std::unique_ptr<callback>* curPtr = &m_callbacks;
while (*curPtr)
{
callback* cur = curPtr->get();
bool keep = func(*cur);
if (!keep)
{
*curPtr = std::move(cur->next);
}
else
{
prev = cur;
curPtr = &cur->next;
}
}
}
};
}

13
src/detail/exports.hpp Normal file
View File

@@ -0,0 +1,13 @@
#pragma once
#include <cstdint>
#include <fxn/context.hpp>
extern "C"
{
typedef bool (*native_hook_t)(fxn::NativeContext* context, bool& call_original);
__declspec(dllexport) void* fxn_native_invoker_get_handler(std::uint64_t hash);
__declspec(dllexport) void fxn_schedule_native(fxn::NativeContext* context, bool wait);
__declspec(dllexport) size_t fxn_register_native_hook(std::uint64_t hash, native_hook_t hook);
__declspec(dllexport) void fxn_unregister_native_hook(std::uint64_t hash, size_t index);
}

View File

@@ -16,63 +16,25 @@ namespace fxn
struct Impl;
Impl* m_Impl;
FxnManager();
~FxnManager();
public:
static FxnManager& GetInstance();
static void Destroy();
static void DispatchNative(fxn::NativeContext* context, NativeHash hash);
public:
void Initialize();
void Shutdown();
public:
const NativeHandler& GetNativeHandler(const NativeHash hash) const;
void RegisterNativeHandler(const NativeHash hash, const NativeHandler handler);
public:
void RegisterNativeHook(const NativeHash hash, const NativeHook& hook);
void UnregisterNativeHook(const NativeHash hash);
size_t RegisterNativeHook(const NativeHash hash, const NativeHook& hook);
void UnregisterNativeHook(const NativeHash hash, size_t index);
public:
bool Invoke(fxn::NativeContext* context);
bool Schedule(fxn::NativeContext* context, bool waitForCompletion = true);
public:
template<typename R, typename... Args>
R Invoke(const NativeHash hash, Args&&... args)
{
fxn::NativeContext context(hash);
(context.PushArgument(std::forward<Args>(args)), ...);
if (!this->Invoke(&context))
{
throw std::runtime_error("Failed to invoke native with hash: " + std::to_string(hash));
}
if constexpr (!std::is_same_v<R, void>)
{
return context.GetResult<R>();
}
}
template<typename R, typename... Args>
R Schedule(const NativeHash hash, Args&&... args)
{
fxn::NativeContext context(hash);
(context.PushArgument(std::forward<Args>(args)), ...);
if (!this->Schedule(&context, true))
{
throw std::runtime_error("Failed to schedule native with hash: " + std::to_string(hash));
}
if constexpr (!std::is_same_v<R, void>)
{
return context.GetResult<R>();
}
}
};
}

22
src/detail/scheduler.hpp Normal file
View File

@@ -0,0 +1,22 @@
#pragma once
#include <functional>
namespace fxn
{
class NativeScheduler
{
private:
struct Impl;
Impl* m_Impl;
private:
NativeScheduler();
~NativeScheduler();
public:
static NativeScheduler& GetInstance();
public:
void Schedule(const std::function<void()>& task);
};
}

View File

@@ -1,11 +1,31 @@
#include <windows.h>
#include <detail/fxn.hpp>
DWORD WINAPI ThreadProc(LPVOID lpParameter)
#include <blackbase/library/library.hpp>
void Initialize()
{
auto& fxnManager = fxn::FxnManager::GetInstance();
}
fxnManager.Initialize();
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
auto currentLibrary = blackbase::Library::GetCurrent();
if (!currentLibrary.has_value())
{
return FALSE;
}
auto name = currentLibrary->GetName();
if (!name.contains("GameProcess") && !name.contains("GTAProcess"))
{
return FALSE;
}
Sleep(5000);
Initialize();
return TRUE;
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ulReason, LPVOID lpReserved)

33
src/impl/exports.cpp Normal file
View File

@@ -0,0 +1,33 @@
#include <detail/exports.hpp>
#include <detail/fxn.hpp>
extern "C"
{
__declspec(dllexport) void* fxn_native_invoker_get_handler(std::uint64_t hash)
{
static auto& manager = fxn::FxnManager::GetInstance();
return manager.GetNativeHandler(hash);
}
__declspec(dllexport) void fxn_schedule_native(fxn::NativeContext* context, bool wait)
{
static auto& manager = fxn::FxnManager::GetInstance();
manager.Schedule(context, wait);
}
__declspec(dllexport) size_t fxn_register_native_hook(std::uint64_t hash, native_hook_t hook)
{
static auto& manager = fxn::FxnManager::GetInstance();
return manager.RegisterNativeHook(hash, hook);
}
__declspec(dllexport) void fxn_unregister_native_hook(std::uint64_t hash, size_t index)
{
static auto& manager = fxn::FxnManager::GetInstance();
manager.UnregisterNativeHook(hash, index);
}
}

264
src/impl/fxn.cpp Normal file
View File

@@ -0,0 +1,264 @@
#include <stdexcept>
#include <unordered_set>
#include <vector>
#include <thread>
#include <detail/fxn.hpp>
#include <detail/rage.hpp>
#include <detail/dispatcher.hpp>
#include <detail/hooking.hpp>
#include <detail/scheduler.hpp>
#include <blackbase/xorstr.hpp>
#include <blackbase/library/library.hpp>
#include <blackbase/pattern/matcher.hpp>
#define HOOKED_INSERT_PATTERN xorstr_("89 05 ? ? ? ? 89 42 ? 8D 42 ? 33 05 ? ? ? ? 89 42 ? 48 89 13 8B 05 ? ? ? ? 4C 8D 42 ?")
#define HOOK_INSERT_OFFSET -0xA0
namespace fxn
{
namespace
{
FxnManager* g_FxnManager = nullptr;
void HookedInsert(rage::scrCommandHash<void*> _this, std::uint64_t hash, void* handler)
{
static auto& fxnManager = FxnManager::GetInstance();
auto dispatcherStub = fxn::CreateNativeDispatcher(hash);
if (dispatcherStub)
{
_this.Insert(hash, dispatcherStub);
fxnManager.RegisterNativeHandler(hash, reinterpret_cast<NativeHandler>(handler));
}
else
{
// If we couldn't create a dispatcher, fall back to the original behavior
// and don't register the handler.
_this.Insert(hash, handler);
}
}
}
struct FxnManager::Impl
{
private:
void* m_HookedInsert = nullptr;
std::unordered_map<NativeHash, NativeHandler> m_NativeHandlers;
std::unordered_map<NativeHash, std::vector<NativeHook>> m_NativeHooks;
public:
Impl()
{
auto library = blackbase::Library::GetCurrent();
if (!library.has_value())
{
throw std::runtime_error("Failed to get current library for hooking.");
}
blackbase::Pattern p(HOOKED_INSERT_PATTERN);
blackbase::Matcher m(library->GetBaseAddress(), library->GetEndAddress());
auto match = m.FindFirst(p);
if (!match.has_value())
{
throw std::runtime_error("Failed to find pattern for hooking Insert function.");
}
auto targetAddress = match->Move(HOOK_INSERT_OFFSET).As<void*>();
auto& hookManager = fxn::HookingManager::GetInstance();
if (!hookManager.InstallHook(targetAddress, reinterpret_cast<void*>(&HookedInsert)))
{
throw std::runtime_error("Failed to install hook on Insert function.");
}
m_HookedInsert = targetAddress;
}
~Impl()
{
if (m_HookedInsert)
{
auto& hookManager = fxn::HookingManager::GetInstance();
hookManager.RemoveHook(m_HookedInsert);
}
}
public:
const NativeHandler& GetNativeHandler(const NativeHash hash) const
{
static NativeHandler nullHandler = nullptr;
auto it = m_NativeHandlers.find(hash);
if (it != m_NativeHandlers.end())
{
return it->second;
}
return nullHandler;
}
void RegisterNativeHandler(const NativeHash hash, const NativeHandler handler)
{
m_NativeHandlers[hash] = handler;
}
size_t RegisterNativeHook(const NativeHash hash, const NativeHook& hook)
{
m_NativeHooks[hash].push_back(hook);
return m_NativeHooks[hash].size() - 1;
}
void UnregisterNativeHook(const NativeHash hash, std::size_t index)
{
auto it = m_NativeHooks.find(hash);
if (it != m_NativeHooks.end())
{
auto& hooks = it->second;
if (index < hooks.size())
{
hooks.erase(hooks.begin() + index);
}
}
}
void DispatchNative(fxn::NativeContext* context, NativeHash hash)
{
auto hooks = m_NativeHooks.find(hash);
if (hooks != m_NativeHooks.end())
{
bool callOriginal = true;
for (const auto& hook : hooks->second)
{
if (hook(context, callOriginal) == false)
{
return; // Hook has decided to skip further processing
}
}
if (!callOriginal)
{
return; // Skip original handler
}
}
auto handlerIt = m_NativeHandlers.find(hash);
if (handlerIt != m_NativeHandlers.end() && handlerIt->second)
{
handlerIt->second(context);
}
}
void InvokeNative(fxn::NativeContext* context)
{
DispatchNative(context, context->GetNativeHash());
}
void ScheduleNative(fxn::NativeContext* context, bool waitForCompletion)
{
auto job = [this, context]()
{
this->InvokeNative(context);
context->SetExecuted(true);
};
static auto& scheduler = fxn::NativeScheduler::GetInstance();
scheduler.Schedule(job);
if (waitForCompletion)
{
while (!context->IsExecuted())
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
}
};
FxnManager& FxnManager::GetInstance()
{
if (!g_FxnManager)
{
g_FxnManager = new FxnManager();
}
return *g_FxnManager;
}
void FxnManager::Destroy()
{
if (g_FxnManager)
{
delete g_FxnManager;
}
g_FxnManager = nullptr;
}
FxnManager::FxnManager()
: m_Impl(new Impl())
{
}
FxnManager::~FxnManager()
{
delete m_Impl;
}
void FxnManager::DispatchNative(fxn::NativeContext* context, NativeHash hash)
{
static auto& instance = FxnManager::GetInstance();
instance.m_Impl->DispatchNative(context, hash);
}
const NativeHandler& FxnManager::GetNativeHandler(const NativeHash hash) const
{
return m_Impl->GetNativeHandler(hash);
}
void FxnManager::RegisterNativeHandler(const NativeHash hash, const NativeHandler handler)
{
m_Impl->RegisterNativeHandler(hash, handler);
}
size_t FxnManager::RegisterNativeHook(const NativeHash hash, const NativeHook& hook)
{
return m_Impl->RegisterNativeHook(hash, hook);
}
void FxnManager::UnregisterNativeHook(const NativeHash hash, size_t index)
{
m_Impl->UnregisterNativeHook(hash, index);
}
bool FxnManager::Invoke(fxn::NativeContext* context)
{
try
{
m_Impl->InvokeNative(context);
return true;
}
catch (...)
{
return false;
}
}
bool FxnManager::Schedule(fxn::NativeContext* context, bool waitForCompletion)
{
try
{
m_Impl->ScheduleNative(context, waitForCompletion);
return true;
}
catch (...)
{
return false;
}
}
}

415
src/impl/scheduler.cpp Normal file
View File

@@ -0,0 +1,415 @@
#include <mutex>
#include <detail/scheduler.hpp>
#include <detail/event.hpp>
#include <detail/fxn.hpp>
#include <fxn/utility.hpp>
#include <blackbase/xorstr.hpp>
#include <blackbase/library/library.hpp>
#include <blackbase/pattern/matcher.hpp>
#define RESOURCE_MANAGER_PATTERN xorstr_("48 8B 0D ? ? ? ? 48 85 C9 74 06 48 8B 01 FF")
#define RESOURCE_MANAGER_MODULE xorstr_("citizen-resources-gta.dll")
namespace fxn
{
namespace
{
struct scrThreadContext
{
unsigned int ThreadId;
unsigned int ThreadHash;
};
typedef void* GtaThread;
struct CfxThread
{
private:
GtaThread m_GtaThread;
public:
GtaThread GetGtaThread()
{
return m_GtaThread;
}
public:
virtual ~CfxThread() = default;
virtual void Reset() = 0;
virtual void DoRun() = 0;
virtual void Kill() = 0;
public:
CfxThread()
{
auto library = blackbase::Library::FindByName(xorstr_("rage-scripting-five.dll"));
if (!library.has_value())
{
throw std::runtime_error(xorstr_("rage-scripting-five.dll not found"));
}
auto constructor = library->GetExport(xorstr_("??0CfxThread@@QEAA@XZ"));
if (!constructor.has_value())
{
throw std::runtime_error(xorstr_("CfxThread constructor not found"));
}
using ctor_t = void(*)(CfxThread*);
ctor_t ctor = reinterpret_cast<ctor_t>(constructor->GetAddress());
ctor(this);
}
public:
void Initialize()
{
using attach_t = void(*)(CfxThread*);
static auto attach = []() -> attach_t
{
auto library = blackbase::Library::FindByName(xorstr_("rage-scripting-five.dll"));
if (!library.has_value())
{
throw std::runtime_error(xorstr_("rage-scripting-five.dll not found"));
}
auto attachExport = library->GetExport(xorstr_("?AttachScriptHandler@CfxThread@@QEAAXXZ"));
if (!attachExport.has_value())
{
throw std::runtime_error(xorstr_("CfxThread::AttachScriptHandler not found"));
}
return reinterpret_cast<attach_t>(attachExport->GetAddress());
}();
if (attach)
{
attach(this);
}
}
void Shutdown()
{
using detach_t = void(*)(CfxThread*);
static auto detach = []() -> detach_t
{
auto library = blackbase::Library::FindByName(xorstr_("rage-scripting-five.dll"));
if (!library.has_value())
{
throw std::runtime_error(xorstr_("rage-scripting-five.dll not found"));
}
auto detachExport = library->GetExport(xorstr_("?DetachScriptHandler@CfxThread@@QEAAXXZ"));
if (!detachExport.has_value())
{
throw std::runtime_error(xorstr_("CfxThread::DetachScriptHandler not found"));
}
return reinterpret_cast<detach_t>(detachExport->GetAddress());
}();
if (detach)
{
detach(this);
}
}
scrThreadContext* GetContext()
{
using get_context_t = scrThreadContext* (*)(GtaThread);
static auto get_context = []() -> get_context_t
{
auto library = blackbase::Library::FindByName(xorstr_("rage-scripting-five.dll"));
if (!library.has_value())
{
throw std::runtime_error(xorstr_("rage-scripting-five.dll not found"));
}
auto getContextExport = library->GetExport(xorstr_("?GetContext@scrThread@rage@@QEAAPEAUscrThreadContext@2@XZ"));
if (!getContextExport.has_value())
{
throw std::runtime_error(xorstr_("GtaThread::GetThreadContext not found"));
}
return reinterpret_cast<get_context_t>(getContextExport->GetAddress());
}();
if (get_context)
{
return get_context(m_GtaThread);
}
return nullptr;
}
void SetScriptName(const char* name)
{
using set_script_name_t = void(*)(GtaThread, const char*);
static auto set_script_name = []() -> set_script_name_t
{
auto library = blackbase::Library::FindByName(xorstr_("rage-scripting-five.dll"));
if (!library.has_value())
{
throw std::runtime_error(xorstr_("rage-scripting-five.dll not found"));
}
auto setScriptNameExport = library->GetExport(xorstr_("?SetScriptName@GtaThread@@QEAAXPEBD@Z"));
if (!setScriptNameExport.has_value())
{
throw std::runtime_error(xorstr_("GtaThread::SetScriptName not found"));
}
return reinterpret_cast<set_script_name_t>(setScriptNameExport->GetAddress());
}();
if (set_script_name)
{
set_script_name(m_GtaThread, name);
}
}
};
struct FxnThread : public CfxThread
{
public:
virtual void Reset() override {}
virtual void DoRun() override {}
virtual void Kill() override {}
public:
FxnThread() : CfxThread()
{
auto context = GetContext();
context->ThreadId = HashString(xorstr_("FxnThread"));
context->ThreadHash = HashString(xorstr_("FxnThread"));
SetScriptName(xorstr_("FxnThread"));
Initialize();
}
~FxnThread() override
{
Shutdown();
}
};
using get_active_thread_t = GtaThread (*)();
static get_active_thread_t g_GetActiveThread = nullptr;
using set_active_thread_t = void (*)(GtaThread);
static set_active_thread_t g_SetActiveThread = nullptr;
struct NativeScope
{
private:
GtaThread m_PreviousThread;
public:
void InitializeFunctions()
{
if (g_GetActiveThread && g_SetActiveThread)
{
return;
}
auto library = blackbase::Library::FindByName(xorstr_("rage-scripting-five.dll"));
if (!library.has_value())
{
throw std::runtime_error(xorstr_("rage-scripting-five.dll not found"));
}
auto getActiveThreadExport = library->GetExport(xorstr_("?GetActiveThread@scrEngine@rage@@SAPEAVscrThread@2@XZ"));
if (!getActiveThreadExport.has_value())
{
throw std::runtime_error(xorstr_("GtaThread::GetActiveThread not found"));
}
auto setActiveThreadExport = library->GetExport(xorstr_("?SetActiveThread@scrEngine@rage@@SAXPEAVscrThread@2@@Z"));
if (!setActiveThreadExport.has_value())
{
throw std::runtime_error(xorstr_("GtaThread::SetActiveThread not found"));
}
g_GetActiveThread = reinterpret_cast<get_active_thread_t>(getActiveThreadExport->GetAddress());
g_SetActiveThread = reinterpret_cast<set_active_thread_t>(setActiveThreadExport->GetAddress());
}
public:
NativeScope(FxnThread* fxnThread)
{
InitializeFunctions();
m_PreviousThread = g_GetActiveThread();
g_SetActiveThread(fxnThread->GetGtaThread());
}
~NativeScope()
{
g_SetActiveThread(m_PreviousThread);
}
};
void* GetResourceManager()
{
blackbase::Pattern p(RESOURCE_MANAGER_PATTERN);
blackbase::Matcher m(RESOURCE_MANAGER_MODULE);
auto match = m.FindFirst(p);
if (!match.has_value())
{
throw std::runtime_error(xorstr_("Resource Manager pattern not found"));
}
struct RefContainer
{
void* ptr;
};
auto ref = match->ResolveRelative().As<RefContainer*>();
return ref->ptr;
}
fx::fwEvent<>* GetOnTickEvent()
{
auto resourceManager = GetResourceManager();
if (!resourceManager)
{
throw std::runtime_error(xorstr_("Resource Manager is null"));
}
return reinterpret_cast<fx::fwEvent<>*>(
reinterpret_cast<std::uintptr_t>(resourceManager) + 0x20
);
}
}
struct NativeScheduler::Impl
{
std::size_t m_EventCookie = -1;
FxnThread* m_FxnThread = nullptr;
std::vector<std::function<void()>> m_Tasks;
std::mutex m_Mutex;
Impl()
{
auto event = GetOnTickEvent();
if (!event)
{
throw std::runtime_error(xorstr_("OnTick event not found"));
}
m_EventCookie = event->Connect([this]()
{
this->Initialize();
if (!m_FxnThread)
{
return true;
}
std::vector<std::function<void()>> tasksCopy;
{
std::lock_guard<std::mutex> lock(m_Mutex);
tasksCopy = std::move(m_Tasks);
m_Tasks.clear();
}
NativeScope scope(m_FxnThread);
for (const auto& task : tasksCopy)
{
try
{
task();
}
catch (const std::exception& e)
{
// Handle exceptions from tasks
}
}
return true;
});
}
~Impl()
{
auto event = GetOnTickEvent();
if (event)
{
event->Disconnect(m_EventCookie);
}
Shutdown();
}
void Schedule(const std::function<void()>& task)
{
std::lock_guard<std::mutex> lock(m_Mutex);
m_Tasks.push_back(task);
}
private:
void Initialize()
{
if (m_FxnThread)
{
return;
}
m_FxnThread = new FxnThread();
NativeScope scope(m_FxnThread);
auto& manager = FxnManager::GetInstance();
auto handler = manager.GetNativeHandler(0xDB2434E51017FFCC);
if (!handler)
{
throw std::runtime_error(xorstr_("Native handler for 0xDB2434E51017FFCC not found"));
}
fxn::NativeContext context { 0xDB2434E51017FFCC };
context.PushArgument<int>(32);
context.PushArgument<bool>(false);
context.PushArgument<int>(-1);
handler(&context);
}
void Shutdown()
{
if (m_FxnThread)
{
delete m_FxnThread;
m_FxnThread = nullptr;
}
}
};
NativeScheduler& NativeScheduler::GetInstance()
{
static NativeScheduler instance;
return instance;
}
NativeScheduler::NativeScheduler()
: m_Impl(new Impl())
{
}
NativeScheduler::~NativeScheduler()
{
delete m_Impl;
}
void NativeScheduler::Schedule(const std::function<void()>& task)
{
m_Impl->Schedule(task);
}
}