From e094c35437273da009cf56c1e7a06a18daeaa748 Mon Sep 17 00:00:00 2001 From: slayercio Date: Tue, 18 Nov 2025 20:37:14 +0100 Subject: [PATCH] feat: added native scheduler --- src/detail/scheduler.hpp | 22 +++ src/impl/scheduler.cpp | 415 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 437 insertions(+) create mode 100644 src/detail/scheduler.hpp create mode 100644 src/impl/scheduler.cpp diff --git a/src/detail/scheduler.hpp b/src/detail/scheduler.hpp new file mode 100644 index 0000000..6cce440 --- /dev/null +++ b/src/detail/scheduler.hpp @@ -0,0 +1,22 @@ +#pragma once +#include + +namespace fxn +{ + class NativeScheduler + { + private: + struct Impl; + Impl* m_Impl; + + private: + NativeScheduler(); + ~NativeScheduler(); + + public: + static NativeScheduler& GetInstance(); + + public: + void Schedule(const std::function& task); + }; +} \ No newline at end of file diff --git a/src/impl/scheduler.cpp b/src/impl/scheduler.cpp new file mode 100644 index 0000000..f13374d --- /dev/null +++ b/src/impl/scheduler.cpp @@ -0,0 +1,415 @@ +#include + +#include +#include +#include + +#include + +#include +#include +#include + +#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(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(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(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(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(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(getActiveThreadExport->GetAddress()); + g_SetActiveThread = reinterpret_cast(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(); + return ref->ptr; + } + + fx::fwEvent<>* GetOnTickEvent() + { + auto resourceManager = GetResourceManager(); + if (!resourceManager) + { + throw std::runtime_error(xorstr_("Resource Manager is null")); + } + + return reinterpret_cast*>( + reinterpret_cast(resourceManager) + 0x20 + ); + } + } + + struct NativeScheduler::Impl + { + std::size_t m_EventCookie = -1; + FxnThread* m_FxnThread = nullptr; + std::vector> 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> tasksCopy; + + { + std::lock_guard 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& task) + { + std::lock_guard 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(32); + context.PushArgument(false); + context.PushArgument(-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& task) + { + m_Impl->Schedule(task); + } +} \ No newline at end of file