Compare commits
10 Commits
8fc83cde53
...
726c49b407
| Author | SHA1 | Date | |
|---|---|---|---|
|
726c49b407
|
|||
|
8a7cd27875
|
|||
|
d341524e66
|
|||
|
ce6c64d393
|
|||
|
da6614cb6f
|
|||
|
a7faf91b28
|
|||
|
9d36ce0f18
|
|||
|
e094c35437
|
|||
|
b55bd2f783
|
|||
|
989f7f4d73
|
@@ -20,4 +20,22 @@ target_include_directories(native_invoker_lib
|
|||||||
set(BLACKBASE_BUILD_TESTS OFF CACHE BOOL "Disable building tests for blackbase")
|
set(BLACKBASE_BUILD_TESTS OFF CACHE BOOL "Disable building tests for blackbase")
|
||||||
add_subdirectory(vendor/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()
|
||||||
80
examples/native_invoker.cpp
Normal file
80
examples/native_invoker.cpp
Normal 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
147
include/fxn/invoker.hpp
Normal 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
198
src/detail/event.hpp
Normal 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
13
src/detail/exports.hpp
Normal 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);
|
||||||
|
}
|
||||||
@@ -16,63 +16,25 @@ namespace fxn
|
|||||||
struct Impl;
|
struct Impl;
|
||||||
Impl* m_Impl;
|
Impl* m_Impl;
|
||||||
|
|
||||||
|
FxnManager();
|
||||||
|
~FxnManager();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static FxnManager& GetInstance();
|
static FxnManager& GetInstance();
|
||||||
static void Destroy();
|
static void Destroy();
|
||||||
|
|
||||||
static void DispatchNative(fxn::NativeContext* context, NativeHash hash);
|
static void DispatchNative(fxn::NativeContext* context, NativeHash hash);
|
||||||
|
|
||||||
public:
|
|
||||||
void Initialize();
|
|
||||||
void Shutdown();
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
const NativeHandler& GetNativeHandler(const NativeHash hash) const;
|
const NativeHandler& GetNativeHandler(const NativeHash hash) const;
|
||||||
void RegisterNativeHandler(const NativeHash hash, const NativeHandler handler);
|
void RegisterNativeHandler(const NativeHash hash, const NativeHandler handler);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void RegisterNativeHook(const NativeHash hash, const NativeHook& hook);
|
size_t RegisterNativeHook(const NativeHash hash, const NativeHook& hook);
|
||||||
void UnregisterNativeHook(const NativeHash hash);
|
void UnregisterNativeHook(const NativeHash hash, size_t index);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool Invoke(fxn::NativeContext* context);
|
bool Invoke(fxn::NativeContext* context);
|
||||||
bool Schedule(fxn::NativeContext* context, bool waitForCompletion = true);
|
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
22
src/detail/scheduler.hpp
Normal 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);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,11 +1,31 @@
|
|||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <detail/fxn.hpp>
|
#include <detail/fxn.hpp>
|
||||||
|
|
||||||
DWORD WINAPI ThreadProc(LPVOID lpParameter)
|
#include <blackbase/library/library.hpp>
|
||||||
|
|
||||||
|
void Initialize()
|
||||||
{
|
{
|
||||||
auto& fxnManager = fxn::FxnManager::GetInstance();
|
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)
|
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ulReason, LPVOID lpReserved)
|
||||||
|
|||||||
33
src/impl/exports.cpp
Normal file
33
src/impl/exports.cpp
Normal 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
264
src/impl/fxn.cpp
Normal 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
415
src/impl/scheduler.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user