feat: added native scheduler

This commit is contained in:
2025-11-18 20:37:14 +01:00
parent b55bd2f783
commit e094c35437
2 changed files with 437 additions and 0 deletions

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);
};
}

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);
}
}