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