init
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
build/
|
||||
.vscode/
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "vendor/blackbase"]
|
||||
path = vendor/blackbase
|
||||
url = https://git.slayercio.space/slayercio/blackbase
|
||||
28
CMakeLists.txt
Normal file
28
CMakeLists.txt
Normal file
@@ -0,0 +1,28 @@
|
||||
cmake_minimum_required(VERSION 4.0)
|
||||
project(native_invoker VERSION 1.0.0 LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
file(GLOB_RECURSE FXN_SOURCES
|
||||
"src/*.cpp"
|
||||
"src/**/*.cpp"
|
||||
)
|
||||
|
||||
add_library(native_invoker STATIC ${FXN_SOURCES})
|
||||
target_include_directories(native_invoker
|
||||
PUBLIC
|
||||
include
|
||||
PRIVATE
|
||||
src
|
||||
)
|
||||
|
||||
set(BLACKBASE_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
add_subdirectory(vendor/blackbase)
|
||||
target_link_libraries(native_invoker PRIVATE blackbase Dbghelp)
|
||||
|
||||
option(NATIVE_INVOKER_BUILD_EXAMPLES "Build Native Invoker examples" ON)
|
||||
if(NATIVE_INVOKER_BUILD_EXAMPLES)
|
||||
add_subdirectory(examples)
|
||||
endif()
|
||||
17
examples/CMakeLists.txt
Normal file
17
examples/CMakeLists.txt
Normal file
@@ -0,0 +1,17 @@
|
||||
file(GLOB_RECURSE EXAMPLE_SOURCES
|
||||
"*.cpp"
|
||||
)
|
||||
|
||||
foreach(EXAMPLE_SOURCE ${EXAMPLE_SOURCES})
|
||||
get_filename_component(EXAMPLE_NAME ${EXAMPLE_SOURCE} NAME_WE)
|
||||
|
||||
if(EXAMPLE_NAME MATCHES "_dll$")
|
||||
add_library(${EXAMPLE_NAME} SHARED ${EXAMPLE_SOURCE})
|
||||
target_link_libraries(${EXAMPLE_NAME} PRIVATE native_invoker)
|
||||
elseif(EXAMPLE_NAME MATCHES "_exe$")
|
||||
add_executable(${EXAMPLE_NAME} ${EXAMPLE_SOURCE})
|
||||
target_link_libraries(${EXAMPLE_NAME} PRIVATE native_invoker)
|
||||
endif()
|
||||
|
||||
target_include_directories(${EXAMPLE_NAME} PRIVATE include src)
|
||||
endforeach()
|
||||
249
examples/natives_test_dll.cpp
Normal file
249
examples/natives_test_dll.cpp
Normal file
@@ -0,0 +1,249 @@
|
||||
#include <iostream>
|
||||
#include <windows.h>
|
||||
#include <thread>
|
||||
|
||||
#include <fxn/invoker.hpp>
|
||||
|
||||
void spawn_car()
|
||||
{
|
||||
auto model = fxn::HashString("adder");
|
||||
constexpr auto REQUEST_MODEL = 0xEC9DAA34BBB4658C; // REQUEST_MODEL
|
||||
constexpr auto HAS_MODEL_LOADED = 0x6252BC0DD8A320DB; // HAS_MODEL_LOADED
|
||||
constexpr auto CREATE_VEHICLE = 0x5779387E956077A6; // CREATE_VEHICLE
|
||||
constexpr auto SET_PED_INTO_VEHICLE = 0x73CAFD2038E812B3; // SET_PED_INTO_VEHICLE
|
||||
constexpr auto PLAYER_PED_ID = 0x4A8C381C258A124D; // PLAYER_PED_ID
|
||||
constexpr auto GET_ENTITY_COORDS = 0xD1A6A821F5AC81DB; // GET_ENTITY_COORDS
|
||||
constexpr auto SET_MODEL_AS_NO_LONGER_NEEDED = 0x55098D9E9AD58806; // SET_MODEL_AS_NO_LONGER_NEEDED
|
||||
|
||||
auto& invoker = fxn::NativeInvoker::GetInstance();
|
||||
invoker.Schedule<void>(REQUEST_MODEL, model);
|
||||
while (!invoker.Schedule<bool>(HAS_MODEL_LOADED, model))
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
|
||||
int playerPedId = invoker.Schedule<int>(PLAYER_PED_ID);
|
||||
auto playerCoords = invoker.Schedule<fxn::Vector3>(GET_ENTITY_COORDS, playerPedId, true);
|
||||
int vehicle = invoker.Schedule<int>(CREATE_VEHICLE, model, playerCoords.x + 5.0f, playerCoords.y, playerCoords.z, 0.0f, true, false, false);
|
||||
invoker.Schedule<void>(SET_PED_INTO_VEHICLE, playerPedId, vehicle, -1);
|
||||
invoker.Schedule<void>(SET_MODEL_AS_NO_LONGER_NEEDED, model);
|
||||
}
|
||||
|
||||
static void nullsub()
|
||||
{
|
||||
std::printf("[natives_test_dll] Nullsub thread executed.\n");
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds(40));
|
||||
}
|
||||
|
||||
static void* GetInitThreadStub()
|
||||
{
|
||||
static void* addr = []() -> void*
|
||||
{
|
||||
void* mem = VirtualAllocEx(GetCurrentProcess(), nullptr, 0x100, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
|
||||
if (!mem)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
unsigned char stub[] =
|
||||
{
|
||||
0x40, 0x53,
|
||||
0x48, 0x83, 0xec, 0x20,
|
||||
0x48, 0xB8,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xFF, 0xE0, // jmp rax
|
||||
};
|
||||
|
||||
auto kernel32 = GetModuleHandleA("kernel32.dll");
|
||||
auto baseThreadInitThunk = GetProcAddress(kernel32, "BaseThreadInitThunk");
|
||||
if (!baseThreadInitThunk)
|
||||
{
|
||||
VirtualFreeEx(GetCurrentProcess(), mem, 0, MEM_RELEASE);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
*reinterpret_cast<void**>(stub + 8) = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(baseThreadInitThunk) + 0x6);
|
||||
std::memcpy(mem, stub, sizeof(stub));
|
||||
|
||||
return mem;
|
||||
}();
|
||||
|
||||
return addr;
|
||||
}
|
||||
|
||||
void CreateThreadBypass()
|
||||
{
|
||||
HANDLE hThread = CreateThread(nullptr, 0, reinterpret_cast<LPTHREAD_START_ROUTINE>(nullsub), nullptr, CREATE_SUSPENDED, nullptr);
|
||||
if (!hThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CONTEXT ctx = { NULL };
|
||||
ctx.ContextFlags = CONTEXT_INTEGER;
|
||||
if (!GetThreadContext(hThread, &ctx))
|
||||
{
|
||||
CloseHandle(hThread);
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.Rip = reinterpret_cast<DWORD64>(nullsub);
|
||||
|
||||
if (!SetThreadContext(hThread, &ctx))
|
||||
{
|
||||
CloseHandle(hThread);
|
||||
return;
|
||||
}
|
||||
|
||||
ResumeThread(hThread);
|
||||
CloseHandle(hThread);
|
||||
}
|
||||
|
||||
void HookThreadCreation()
|
||||
{
|
||||
static std::once_flag flag;
|
||||
std::call_once(flag, []
|
||||
{
|
||||
auto ntdll = GetModuleHandleA("ntdll.dll");
|
||||
if (!ntdll)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto startUserThreadAddr = GetProcAddress(ntdll, "RtlUserThreadStart");
|
||||
if (!startUserThreadAddr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr auto offset = 0x7;
|
||||
int32_t rvaOffset = *reinterpret_cast<int32_t*>(reinterpret_cast<uintptr_t>(startUserThreadAddr) + offset + 3);
|
||||
std::printf("[natives_test_dll] RtlUserThreadStart RVA offset: 0x%X\n", rvaOffset);
|
||||
|
||||
void** targetAddr = reinterpret_cast<void**>(reinterpret_cast<uintptr_t>(startUserThreadAddr) + offset + 7 + rvaOffset);
|
||||
std::printf("[natives_test_dll] RtlUserThreadStart target address: 0x%p\n", targetAddr);
|
||||
|
||||
DWORD oldProtect;
|
||||
if (VirtualProtect(targetAddr, sizeof(void*), PAGE_EXECUTE_READWRITE, &oldProtect))
|
||||
{
|
||||
*targetAddr = GetInitThreadStub();
|
||||
VirtualProtect(targetAddr, sizeof(void*), oldProtect, &oldProtect);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void mani_()
|
||||
{
|
||||
static auto& invoker = fxn::NativeInvoker::GetInstance();
|
||||
|
||||
// GIVE_WEAPON_TO_PED
|
||||
invoker.HookNative(0xB41DEC3AAC1AA107, [](fxn::NativeContext* context, bool& shouldCallOriginal) -> bool
|
||||
{
|
||||
int32_t pedId = context->GetArgument<int32_t>(0);
|
||||
uint32_t weaponHash = context->GetArgument<uint32_t>(1);
|
||||
int32_t ammoCount = context->GetArgument<int32_t>(2);
|
||||
bool isHidden = context->GetArgument<bool>(3);
|
||||
bool forceInHand = context->GetArgument<bool>(4);
|
||||
|
||||
std::cout << "[natives_test_dll] GIVE_WEAPON_TO_PED called with pedId: " << pedId
|
||||
<< ", weaponHash: " << std::hex << weaponHash << std::dec
|
||||
<< ", ammoCount: " << ammoCount
|
||||
<< ", isHidden: " << isHidden
|
||||
<< ", forceInHand: " << forceInHand << std::endl;
|
||||
|
||||
shouldCallOriginal = true;
|
||||
return true;
|
||||
});
|
||||
|
||||
invoker.Initialize();
|
||||
|
||||
std::cout << "[natives_test_dll] mani_ thread started. Press END to exit." << std::endl;
|
||||
|
||||
while (!GetAsyncKeyState(VK_END))
|
||||
{
|
||||
if (GetAsyncKeyState(VK_F1))
|
||||
{
|
||||
fxn::NativeContext context { 0x4A8C381C258A124D }; // PLAYER_PED_ID
|
||||
invoker.Schedule(&context);
|
||||
|
||||
int playerPedId = context.GetResult<int>();
|
||||
std::cout << "[natives_test_dll] PLAYER_PED_ID: " << playerPedId << std::endl;
|
||||
}
|
||||
|
||||
if (GetAsyncKeyState(VK_F2))
|
||||
{
|
||||
std::cout << "[natives_test_dll] Spawning car..." << std::endl;
|
||||
spawn_car();
|
||||
std::cout << "[natives_test_dll] Car spawned." << std::endl;
|
||||
}
|
||||
|
||||
if (GetAsyncKeyState(VK_F3))
|
||||
{
|
||||
std::once_flag flag;
|
||||
std::call_once(flag, []
|
||||
{
|
||||
HookThreadCreation();
|
||||
});
|
||||
|
||||
CreateThreadBypass();
|
||||
std::cout << "[natives_test_dll] Created thread with bypass." << std::endl;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
|
||||
std::cout << "[natives_test_dll] Exiting mani_ thread." << std::endl;
|
||||
}
|
||||
|
||||
|
||||
DWORD WINAPI ThreadProc(LPVOID lpParameter)
|
||||
{
|
||||
Sleep(1000);
|
||||
|
||||
AllocConsole();
|
||||
|
||||
FILE* fDummy;
|
||||
freopen_s(&fDummy, "CONOUT$", "w", stdout);
|
||||
|
||||
mani_();
|
||||
|
||||
fclose(fDummy);
|
||||
FreeConsole();
|
||||
|
||||
FreeLibraryAndExitThread(static_cast<HMODULE>(lpParameter), 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Initialize(LPVOID lpParameter)
|
||||
{
|
||||
AllocConsole();
|
||||
FILE* fDummy;
|
||||
|
||||
freopen_s(&fDummy, "CONOUT$", "w", stdout);
|
||||
std::cout << "[natives_test_dll] DLL injected successfully." << std::endl;
|
||||
}
|
||||
|
||||
HMODULE g_module = nullptr;
|
||||
|
||||
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ulReason, LPVOID lpReserved)
|
||||
{
|
||||
g_module = hModule;
|
||||
|
||||
if (ulReason == DLL_PROCESS_ATTACH)
|
||||
{
|
||||
DisableThreadLibraryCalls(hModule);
|
||||
|
||||
// fxn::NativeInvoker::GetInstance().Initialize();
|
||||
|
||||
HookThreadCreation();
|
||||
|
||||
HANDLE hThread = CreateThread(nullptr, 0, ThreadProc, hModule, 0, nullptr);
|
||||
if (hThread)
|
||||
{
|
||||
CloseHandle(hThread);
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
173
include/fxn/context.hpp
Normal file
173
include/fxn/context.hpp
Normal file
@@ -0,0 +1,173 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <stdexcept>
|
||||
#include <atomic>
|
||||
|
||||
namespace fxn
|
||||
{
|
||||
struct Vector4
|
||||
{
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
float w;
|
||||
};
|
||||
|
||||
struct alignas(16) Vector3 // input
|
||||
{
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
};
|
||||
|
||||
struct scrVector3 // output
|
||||
{
|
||||
alignas(8) float x;
|
||||
alignas(8) float y;
|
||||
alignas(8) float z;
|
||||
};
|
||||
|
||||
struct Vector2
|
||||
{
|
||||
float x;
|
||||
float y;
|
||||
};
|
||||
|
||||
struct alignas(16) scrVector3N
|
||||
{
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
};
|
||||
|
||||
struct VectorSpace
|
||||
{
|
||||
scrVector3* outVectors[4];
|
||||
scrVector3N inVectors[4];
|
||||
};
|
||||
|
||||
struct NativeContext
|
||||
{
|
||||
private:
|
||||
void* m_ReturnBuffer;
|
||||
std::uint32_t m_ArgumentCount;
|
||||
void* m_Arguments;
|
||||
std::uint32_t m_DataCount;
|
||||
|
||||
VectorSpace m_VectorSpace;
|
||||
scrVector3 m_Vectors[4];
|
||||
|
||||
enum
|
||||
{
|
||||
MAX_ARGUMENTS = 32,
|
||||
ARGUMENT_SIZE = sizeof(void*)
|
||||
};
|
||||
|
||||
std::uint8_t m_Stack[MAX_ARGUMENTS * ARGUMENT_SIZE];
|
||||
std::uint64_t m_NativeHash;
|
||||
std::atomic_bool m_Executed;
|
||||
|
||||
public:
|
||||
inline NativeContext()
|
||||
: m_ReturnBuffer(nullptr), m_ArgumentCount(0), m_Arguments(nullptr), m_DataCount(0), m_NativeHash(0), m_VectorSpace{}, m_Vectors{}, m_Stack{}, m_Executed(false)
|
||||
{
|
||||
for (std::size_t i = 0; i < sizeof(m_Stack); i++)
|
||||
{
|
||||
m_Stack[i] = 0;
|
||||
}
|
||||
|
||||
m_Arguments = &m_Stack;
|
||||
m_ReturnBuffer = &m_Stack;
|
||||
|
||||
m_ArgumentCount = 0;
|
||||
m_DataCount = 0;
|
||||
}
|
||||
|
||||
inline NativeContext(std::uint64_t hash)
|
||||
: NativeContext()
|
||||
{
|
||||
m_NativeHash = hash;
|
||||
}
|
||||
|
||||
public:
|
||||
template<typename T>
|
||||
inline void PushArgument(const T& arg)
|
||||
{
|
||||
if (m_ArgumentCount >= MAX_ARGUMENTS)
|
||||
{
|
||||
throw std::runtime_error("Exceeded maximum number of arguments.");
|
||||
}
|
||||
|
||||
if constexpr (sizeof(T) < ARGUMENT_SIZE)
|
||||
{
|
||||
// Zero out the memory to avoid garbage values
|
||||
*reinterpret_cast<std::uintptr_t*>(m_Stack + (m_ArgumentCount * ARGUMENT_SIZE)) = 0;
|
||||
}
|
||||
|
||||
*reinterpret_cast<T*>(m_Stack + (m_ArgumentCount * ARGUMENT_SIZE)) = arg;
|
||||
m_ArgumentCount++;
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void PushArgument<Vector3>(const Vector3& arg)
|
||||
{
|
||||
PushArgument(arg.x);
|
||||
PushArgument(arg.y);
|
||||
PushArgument(arg.z);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline T GetResult()
|
||||
{
|
||||
return *reinterpret_cast<T*>(m_ReturnBuffer);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline Vector3 GetResult<Vector3>()
|
||||
{
|
||||
auto vec = *reinterpret_cast<scrVector3*>(m_ReturnBuffer);
|
||||
return Vector3{ vec.x, vec.y, vec.z }; // fix for alignment issues
|
||||
}
|
||||
|
||||
inline void SetExecuted(bool executed)
|
||||
{
|
||||
m_Executed.store(executed, std::memory_order_release);
|
||||
}
|
||||
|
||||
inline bool IsExecuted() const
|
||||
{
|
||||
return m_Executed.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
inline void SetVectorResults()
|
||||
{
|
||||
for (size_t i = 0; i < m_DataCount; i++)
|
||||
{
|
||||
auto outVector = m_VectorSpace.outVectors[i];
|
||||
const auto& inVector = m_VectorSpace.inVectors[i];
|
||||
|
||||
outVector->x = inVector.x;
|
||||
outVector->y = inVector.y;
|
||||
outVector->z = inVector.z;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline const T& GetArgument(std::size_t index) const
|
||||
{
|
||||
if (index >= m_ArgumentCount)
|
||||
{
|
||||
throw std::out_of_range("Argument index out of range.");
|
||||
}
|
||||
|
||||
auto functionData = reinterpret_cast<uintptr_t*>(m_Arguments);
|
||||
return *reinterpret_cast<T*>(&functionData[index]);
|
||||
}
|
||||
|
||||
inline std::uint64_t GetNativeHash() const
|
||||
{
|
||||
return m_NativeHash;
|
||||
}
|
||||
};
|
||||
}
|
||||
92
include/fxn/invoker.hpp
Normal file
92
include/fxn/invoker.hpp
Normal file
@@ -0,0 +1,92 @@
|
||||
#pragma once
|
||||
#include <string_view>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
|
||||
#include <fxn/context.hpp>
|
||||
|
||||
namespace fxn
|
||||
{
|
||||
inline constexpr char ToLower(const char c) noexcept
|
||||
{
|
||||
return (c >= 'A' && c <= 'Z') ? (c - 'A' + 'a') : c;
|
||||
}
|
||||
|
||||
inline constexpr unsigned int HashString(std::string_view str) noexcept
|
||||
{
|
||||
uint32_t hash = 0;
|
||||
|
||||
for (char ch : str)
|
||||
{
|
||||
hash += ToLower(ch);
|
||||
hash += (hash << 10);
|
||||
hash ^= (hash >> 6);
|
||||
}
|
||||
|
||||
hash += (hash << 3);
|
||||
hash ^= (hash >> 11);
|
||||
hash += (hash << 15);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
class NativeInvoker
|
||||
{
|
||||
public:
|
||||
static NativeInvoker& GetInstance();
|
||||
static void AddTickEvent(const std::function<void()>& callback);
|
||||
|
||||
public:
|
||||
void Initialize();
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Invokes the native on current thread
|
||||
* @note MAYBE UNSAFE IF NOT ON GAME THREAD
|
||||
* @param hash native hash to invoke
|
||||
* @param context native context to use
|
||||
* @return whether the native could be found and executed
|
||||
*/
|
||||
bool Invoke(fxn::NativeContext* context);
|
||||
|
||||
/**
|
||||
* @brief Invokes the native on a special game thread
|
||||
* @param hash native hash to invoke
|
||||
* @param context native context to use
|
||||
* @return whether the native could be found and executed
|
||||
*/
|
||||
bool Schedule(fxn::NativeContext* context, bool waitForCompletion = true);
|
||||
|
||||
public:
|
||||
template<typename R, typename... Args>
|
||||
R Invoke(std::uint64_t hash, Args&&... args)
|
||||
{
|
||||
fxn::NativeContext context { hash };
|
||||
|
||||
(context.PushArgument(std::forward<Args>(args)), ...);
|
||||
Invoke(&context);
|
||||
|
||||
if constexpr (!std::is_same_v<R, void>)
|
||||
{
|
||||
return context.GetResult<R>();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename R, typename... Args>
|
||||
R Schedule(std::uint64_t hash, Args&&... args)
|
||||
{
|
||||
fxn::NativeContext context { hash };
|
||||
|
||||
(context.PushArgument(std::forward<Args>(args)), ...);
|
||||
Schedule(&context, true);
|
||||
|
||||
if constexpr (!std::is_same_v<R, void>)
|
||||
{
|
||||
return context.GetResult<R>();
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void HookNative(std::uint64_t hash, std::function<bool(fxn::NativeContext* context, bool& shouldCallOriginal)> hook);
|
||||
};
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
30
src/detail/hooking.hpp
Normal file
30
src/detail/hooking.hpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <cstdint>
|
||||
|
||||
namespace fxn::detail
|
||||
{
|
||||
struct InlineHookInfo
|
||||
{
|
||||
std::uintptr_t target;
|
||||
std::uintptr_t detour;
|
||||
std::vector<std::uint8_t> originalBytes;
|
||||
};
|
||||
|
||||
class HookManager
|
||||
{
|
||||
protected:
|
||||
std::vector<InlineHookInfo> m_InlineHooks;
|
||||
|
||||
protected:
|
||||
~HookManager();
|
||||
|
||||
public:
|
||||
static HookManager& GetInstance();
|
||||
|
||||
public:
|
||||
bool InstallInlineHook(std::uintptr_t target, std::uintptr_t detour);
|
||||
bool RemoveInlineHook(std::uintptr_t target);
|
||||
};
|
||||
}
|
||||
43
src/detail/native_manager.hpp
Normal file
43
src/detail/native_manager.hpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <unordered_map>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#include <fxn/context.hpp>
|
||||
|
||||
namespace fxn::detail
|
||||
{
|
||||
using NativeHandler = void(*)(fxn::NativeContext* context);
|
||||
|
||||
/**
|
||||
* @brief A function type for native hooks.
|
||||
* @param context Pointer to the NativeContext containing the native call information.
|
||||
* @param shouldCallOriginal Reference to a boolean that indicates whether the original native function should be called. Default is true.
|
||||
* @return A boolean indicating whether the next hook should be called.
|
||||
*/
|
||||
using NativeHook = std::function<bool(fxn::NativeContext* context, bool& shouldCallOriginal)>;
|
||||
|
||||
class NativeManager
|
||||
{
|
||||
private:
|
||||
std::unordered_map<std::uint64_t, NativeHandler> m_NativeHandlers;
|
||||
std::unordered_map<std::uint64_t, std::vector<NativeHook>> m_NativeHooks;
|
||||
|
||||
public:
|
||||
static NativeManager& GetInstance();
|
||||
|
||||
public:
|
||||
void HookNative(std::uint64_t nativeHash, NativeHook hook);
|
||||
void UnhookNative(std::uint64_t nativeHash);
|
||||
|
||||
public:
|
||||
NativeHandler GetNativeHandler(std::uint64_t nativeHash);
|
||||
|
||||
public:
|
||||
void RegisterNativeHandler(std::uint64_t nativeHash, NativeHandler handler);
|
||||
|
||||
public:
|
||||
static void DispatchNative(fxn::NativeContext* context, std::uint64_t nativeHash);
|
||||
};
|
||||
}
|
||||
33
src/detail/native_thread.hpp
Normal file
33
src/detail/native_thread.hpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <set>
|
||||
|
||||
namespace fxn::detail
|
||||
{
|
||||
class NativeThread
|
||||
{
|
||||
private:
|
||||
std::vector<std::function<void()>> m_Tasks;
|
||||
std::set<std::size_t> m_TickEvents;
|
||||
std::mutex m_TasksMutex;
|
||||
std::size_t m_EventCookie;
|
||||
|
||||
protected:
|
||||
NativeThread();
|
||||
~NativeThread();
|
||||
|
||||
protected:
|
||||
void Initialize();
|
||||
void Shutdown();
|
||||
|
||||
public:
|
||||
static NativeThread& GetInstance();
|
||||
|
||||
public:
|
||||
void QueueTask(const std::function<void()>& task);
|
||||
void AddTickEvent(const std::function<void()>& callback);
|
||||
};
|
||||
}
|
||||
218
src/detail/rage.hpp
Normal file
218
src/detail/rage.hpp
Normal file
@@ -0,0 +1,218 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
using u32 = std::uint32_t;
|
||||
using u64 = std::uint64_t;
|
||||
|
||||
namespace fxn::detail::rage
|
||||
{
|
||||
namespace sysObfuscatedTypes
|
||||
{
|
||||
__forceinline u32 obfRand()
|
||||
{
|
||||
static u32 next = 0xCB536E6A;
|
||||
next = next * 214013 + 2531011;
|
||||
return next;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, bool TMutate = true>
|
||||
class sysObfuscated
|
||||
{
|
||||
public:
|
||||
sysObfuscated() { CompileTimeAssert((sizeof(T) & 3) == 0); Init(); }
|
||||
sysObfuscated(const sysObfuscated<T, TMutate>& rhs) { Init(); Set(rhs.Get()); }
|
||||
template<bool TMutateOther> sysObfuscated(const sysObfuscated<T, TMutateOther>& rhs) { Init(); Set(rhs.Get()); }
|
||||
explicit sysObfuscated(const T& data) { Init(); Set(data); }
|
||||
|
||||
~sysObfuscated() {}
|
||||
|
||||
public:
|
||||
__forceinline T Get() const;
|
||||
__forceinline void Set(const T& data);
|
||||
__forceinline operator T() const { return Get(); }
|
||||
|
||||
public:
|
||||
template<bool TMutateOther> bool operator==(const sysObfuscated<T, TMutateOther>& rhs) const { return Get() == rhs.Get(); }
|
||||
template<bool TMutateOther> bool operator!=(const sysObfuscated<T, TMutateOther>& rhs) const { return Get() != rhs.Get(); }
|
||||
template<bool TMutateOther> bool operator<(const sysObfuscated<T, TMutateOther>& rhs) const { return Get() < rhs.Get(); }
|
||||
template<bool TMutateOther> bool operator<=(const sysObfuscated<T, TMutateOther>& rhs) const { return Get() <= rhs.Get(); }
|
||||
template<bool TMutateOther> bool operator>(const sysObfuscated<T, TMutateOther>& rhs) const { return Get() > rhs.Get(); }
|
||||
template<bool TMutateOther> bool operator>=(const sysObfuscated<T, TMutateOther>& rhs) const { return Get() >= rhs.Get(); }
|
||||
|
||||
public:
|
||||
template<bool TMutateOther> sysObfuscated<T, TMutate>& operator=(const sysObfuscated<T, TMutateOther>& rhs) { Set(rhs.Get()); return *this; }
|
||||
sysObfuscated<T, TMutate>& operator=(const T& data) { Set(data); return *this; }
|
||||
|
||||
public:
|
||||
template<bool TMutateOther> sysObfuscated<T, TMutate>& operator+=(const sysObfuscated<T, TMutateOther>& rhs) { Set(Get()+rhs.Get()); return *this; }
|
||||
sysObfuscated<T, TMutate>& operator+=(const T& data) { Set(Get()+data); return *this; }
|
||||
template<bool TMutateOther> sysObfuscated<T, TMutate>& operator-=(const sysObfuscated<T, TMutateOther>& rhs) { Set(Get()-rhs.Get()); return *this; }
|
||||
sysObfuscated<T, TMutate>& operator-=(const T& data) { Set(Get()-data); return *this; }
|
||||
template<bool TMutateOther> sysObfuscated<T, TMutate>& operator*=(const sysObfuscated<T, TMutateOther>& rhs) { Set(Get()*rhs.Get()); return *this; }
|
||||
sysObfuscated<T, TMutate>& operator*=(const T& data) { Set(Get()*data); return *this; }
|
||||
template<bool TMutateOther> sysObfuscated<T, TMutate>& operator/=(const sysObfuscated<T, TMutateOther>& rhs) { Set(Get()/rhs.Get()); return *this; }
|
||||
sysObfuscated<T, TMutate>& operator/=(const T& data) { Set(Get()/data); return *this; }
|
||||
|
||||
private:
|
||||
void Init();
|
||||
|
||||
mutable u32 m_data[(TMutate ? sizeof(T)*2 : sizeof(T)) / sizeof(u32)];
|
||||
mutable u32 m_xor;
|
||||
mutable u32 m_mutate;
|
||||
};
|
||||
|
||||
template<class T, bool TMutate> __forceinline void sysObfuscated<T, TMutate>::Init()
|
||||
{
|
||||
m_xor = sysObfuscatedTypes::obfRand();
|
||||
if(TMutate)
|
||||
{
|
||||
m_mutate = sysObfuscatedTypes::obfRand();
|
||||
}
|
||||
}
|
||||
|
||||
template<class T, bool TMutate> __forceinline T sysObfuscated<T, TMutate>::Get() const
|
||||
{
|
||||
u32 xorVal = m_xor ^ (u32)(size_t)this;
|
||||
u32 ret[sizeof(T)/sizeof(u32)];
|
||||
u32* src = const_cast<u32*>(&m_data[0]);
|
||||
u32* dest = (u32*)&ret;
|
||||
for(size_t i=0; i<sizeof(T)/4; ++i)
|
||||
{
|
||||
if(TMutate)
|
||||
{
|
||||
// Extract valid data from two words of storage
|
||||
u32 a = *src & m_mutate;
|
||||
u32 b = src[sizeof(T)/4] & (~m_mutate);
|
||||
|
||||
// Apply entropy in the unused bits: Just flip the two u16's in the u32. We can't do a
|
||||
// huge amount more without knowledge of the mutation mask.
|
||||
u32 entropyA = ((*src & (~m_mutate)) << 16) | ((*src & (~m_mutate)) >> 16);
|
||||
u32 entropyB = ((src[sizeof(T)/4] & m_mutate) << 16) | ((src[sizeof(T)/4] & m_mutate) >> 16);
|
||||
*src = (*src & m_mutate) | entropyA;
|
||||
src[sizeof(T)/4] = (src[sizeof(T)/4] & (~m_mutate)) | entropyB;
|
||||
|
||||
*dest++ = a | b;
|
||||
++src;
|
||||
}
|
||||
else
|
||||
{
|
||||
*dest++ = *src++ ^ xorVal;
|
||||
}
|
||||
}
|
||||
|
||||
// Call Set() to reset the xor and mutate keys on every call to Get()
|
||||
if(TMutate)
|
||||
{
|
||||
const_cast<sysObfuscated<T, TMutate>*>(this)->Set(*(T*)&ret);
|
||||
}
|
||||
|
||||
return *(T*)&ret;
|
||||
}
|
||||
|
||||
template<class T, bool TMutate> __forceinline void sysObfuscated<T, TMutate>::Set(const T& data)
|
||||
{
|
||||
// Reset xor and mutate keys
|
||||
Init();
|
||||
|
||||
u32 xorVal = m_xor ^ (u32)(size_t)this;
|
||||
u32* src = (u32*)&data;
|
||||
u32* dest = &m_data[0];
|
||||
for(size_t i=0; i<sizeof(T)/4; ++i)
|
||||
{
|
||||
if(TMutate)
|
||||
{
|
||||
u32 a = *src & m_mutate;
|
||||
u32 b = *src & (~m_mutate);
|
||||
++src;
|
||||
|
||||
*dest = a;
|
||||
dest[sizeof(T)/4] = b;
|
||||
++dest;
|
||||
}
|
||||
else
|
||||
{
|
||||
*dest++ = *src++ ^ xorVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename _T>
|
||||
class scrCommandHash
|
||||
{
|
||||
private:
|
||||
static const int ToplevelSize = 256;
|
||||
static const int PerBucket = 7;
|
||||
|
||||
struct Bucket
|
||||
{
|
||||
sysObfuscated<Bucket *, false> obf_Next;
|
||||
_T Data[PerBucket];
|
||||
sysObfuscated<u32, false> obf_Count;
|
||||
sysObfuscated<u64, false> obf_Hashes[PerBucket];
|
||||
u64 plainText_Hashes[PerBucket];
|
||||
};
|
||||
|
||||
public:
|
||||
void RegistrationComplete(bool val)
|
||||
{
|
||||
m_bRegistrationComplete = val;
|
||||
}
|
||||
|
||||
void Init()
|
||||
{
|
||||
m_Occupancy = 0;
|
||||
m_bRegistrationComplete = false;
|
||||
for (int i=0; i<ToplevelSize; i++)
|
||||
m_Buckets[i] = NULL;
|
||||
}
|
||||
|
||||
void Kill()
|
||||
{
|
||||
for (int i=0; i<ToplevelSize; i++) {
|
||||
Bucket *b = m_Buckets[i];
|
||||
while (b) {
|
||||
char *old = (char*) b;
|
||||
b = b->obf_Next.Get();
|
||||
delete[] old;
|
||||
}
|
||||
m_Buckets[i] = NULL;
|
||||
}
|
||||
m_Occupancy = 0;
|
||||
}
|
||||
|
||||
void Insert(u64 hashcode,_T cmd)
|
||||
{
|
||||
Bucket * b = m_Buckets[hashcode & (ToplevelSize-1)];
|
||||
// If this chain is empty, or the first bucket is full, allocate and patch in a new bucket
|
||||
if (!b || b->obf_Count.Get() == PerBucket) {
|
||||
Bucket *nb = (Bucket*) new char[sizeof(Bucket)];
|
||||
nb->obf_Next.Set(m_Buckets[hashcode & (ToplevelSize-1)]);
|
||||
nb->obf_Count.Set(0);
|
||||
b = m_Buckets[hashcode & (ToplevelSize-1)] = nb;
|
||||
}
|
||||
b->obf_Hashes[b->obf_Count.Get()].Set(hashcode);
|
||||
b->plainText_Hashes[b->obf_Count.Get()] = 0; //hashcode;
|
||||
b->Data[b->obf_Count] = cmd;
|
||||
b->obf_Count.Set(b->obf_Count.Get()+1); //inc count
|
||||
}
|
||||
|
||||
_T Lookup(u64 hashcode)
|
||||
{
|
||||
Bucket *b = m_Buckets[hashcode & (ToplevelSize-1)];
|
||||
while (b) {
|
||||
for (u32 i=0; i<b->obf_Count.Get(); i++)
|
||||
if (b->obf_Hashes[i].Get() == hashcode)
|
||||
return b->Data[i];
|
||||
b = b->obf_Next.Get();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
Bucket* m_Buckets[ToplevelSize];
|
||||
int m_Occupancy;
|
||||
bool m_bRegistrationComplete;
|
||||
};
|
||||
}
|
||||
25
src/detail/stacktrace.hpp
Normal file
25
src/detail/stacktrace.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace fxn::detail
|
||||
{
|
||||
struct Stackframe
|
||||
{
|
||||
void* address;
|
||||
std::string formatted;
|
||||
};
|
||||
|
||||
class Stacktrace
|
||||
{
|
||||
private:
|
||||
std::vector<Stackframe> m_Frames;
|
||||
|
||||
public:
|
||||
Stacktrace();
|
||||
|
||||
public:
|
||||
const std::vector<Stackframe>& GetFrames() const noexcept;
|
||||
std::string ToString() const;
|
||||
};
|
||||
}
|
||||
7
src/detail/stubs.hpp
Normal file
7
src/detail/stubs.hpp
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace fxn::detail
|
||||
{
|
||||
void* CreateNativeStub(std::uint64_t nativeHash);
|
||||
}
|
||||
78
src/impl/hooking.cpp
Normal file
78
src/impl/hooking.cpp
Normal file
@@ -0,0 +1,78 @@
|
||||
#include <windows.h>
|
||||
#include <array>
|
||||
|
||||
#include <detail/hooking.hpp>
|
||||
|
||||
namespace fxn::detail
|
||||
{
|
||||
HookManager::~HookManager()
|
||||
{
|
||||
for (const auto& hook : m_InlineHooks)
|
||||
{
|
||||
RemoveInlineHook(hook.target);
|
||||
}
|
||||
|
||||
m_InlineHooks.clear();
|
||||
}
|
||||
|
||||
HookManager& HookManager::GetInstance()
|
||||
{
|
||||
static HookManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool HookManager::InstallInlineHook(std::uintptr_t target, std::uintptr_t detour)
|
||||
{
|
||||
constexpr auto HOOK_SIZE = 2 + sizeof(std::uintptr_t) + 2; // mov rax, addr; jmp rax
|
||||
constexpr auto HOOK_INSTRUCTION = std::array<std::uint8_t, HOOK_SIZE>
|
||||
{
|
||||
0x48, 0xB8,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xFF, 0xE0
|
||||
};
|
||||
|
||||
DWORD oldProtect;
|
||||
if (!VirtualProtect(reinterpret_cast<LPVOID>(target), HOOK_SIZE, PAGE_EXECUTE_READWRITE, &oldProtect))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
InlineHookInfo hookInfo;
|
||||
hookInfo.target = target;
|
||||
hookInfo.detour = detour;
|
||||
hookInfo.originalBytes.resize(HOOK_SIZE);
|
||||
std::memcpy(hookInfo.originalBytes.data(), reinterpret_cast<void*>(target), HOOK_SIZE);
|
||||
|
||||
std::array<std::uint8_t, HOOK_SIZE> hookBytes = HOOK_INSTRUCTION;
|
||||
*reinterpret_cast<std::uintptr_t*>(&hookBytes[2]) = detour;
|
||||
std::memcpy(reinterpret_cast<void*>(target), hookBytes.data(), HOOK_SIZE);
|
||||
|
||||
VirtualProtect(reinterpret_cast<LPVOID>(target), HOOK_SIZE, oldProtect, &oldProtect);
|
||||
m_InlineHooks.push_back(std::move(hookInfo));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HookManager::RemoveInlineHook(std::uintptr_t target)
|
||||
{
|
||||
auto it = std::find_if(m_InlineHooks.begin(), m_InlineHooks.end(),
|
||||
[target](const InlineHookInfo& hook) { return hook.target == target; });
|
||||
|
||||
if (it == m_InlineHooks.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD oldProtect;
|
||||
if (!VirtualProtect(reinterpret_cast<LPVOID>(target), it->originalBytes.size(), PAGE_EXECUTE_READWRITE, &oldProtect))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::memcpy(reinterpret_cast<void*>(target), it->originalBytes.data(), it->originalBytes.size());
|
||||
VirtualProtect(reinterpret_cast<LPVOID>(target), it->originalBytes.size(), oldProtect, &oldProtect);
|
||||
m_InlineHooks.erase(it);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
124
src/impl/invoker.cpp
Normal file
124
src/impl/invoker.cpp
Normal file
@@ -0,0 +1,124 @@
|
||||
#include <fxn/invoker.hpp>
|
||||
#include <detail/stubs.hpp>
|
||||
#include <detail/rage.hpp>
|
||||
#include <detail/native_manager.hpp>
|
||||
#include <detail/hooking.hpp>
|
||||
#include <detail/native_thread.hpp>
|
||||
#include <detail/stacktrace.hpp>
|
||||
|
||||
#include <blackbase/pattern/matcher.hpp>
|
||||
#include <blackbase/library/library.hpp>
|
||||
|
||||
#define HOOK_INSERT_PATTERN "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
|
||||
{
|
||||
void HookedInsert(fxn::detail::rage::scrCommandHash<fxn::detail::NativeHandler>* table, u64 hashcode, fxn::detail::NativeHandler cmd)
|
||||
{
|
||||
static auto& manager = fxn::detail::NativeManager::GetInstance();
|
||||
|
||||
auto stacktrace = fxn::detail::Stacktrace();
|
||||
auto stub = fxn::detail::CreateNativeStub(hashcode);
|
||||
|
||||
auto trace = stacktrace.ToString();
|
||||
std::printf("HookedInsert called for native 0x%llx\nStacktrace:\n%s\n", hashcode, trace.c_str());
|
||||
|
||||
if (!stub)
|
||||
{
|
||||
table->Insert(hashcode, cmd);
|
||||
}
|
||||
else
|
||||
{
|
||||
table->Insert(hashcode, reinterpret_cast<fxn::detail::NativeHandler>(stub));
|
||||
manager.RegisterNativeHandler(hashcode, cmd);
|
||||
}
|
||||
}
|
||||
|
||||
NativeInvoker& NativeInvoker::GetInstance()
|
||||
{
|
||||
static NativeInvoker instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void NativeInvoker::Initialize()
|
||||
{
|
||||
static auto& hookManager = fxn::detail::HookManager::GetInstance();
|
||||
|
||||
auto library = blackbase::Library::GetCurrent();
|
||||
if (!library.has_value())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
blackbase::Pattern p(HOOK_INSERT_PATTERN);
|
||||
blackbase::Matcher m(library->GetBaseAddress(), library->GetEndAddress());
|
||||
|
||||
auto match = m.FindFirst(p);
|
||||
if (!match.has_value())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto address = match->Move(HOOK_INSERT_OFFSET).As<void*>();
|
||||
hookManager.InstallInlineHook(reinterpret_cast<std::uintptr_t>(address), reinterpret_cast<std::uintptr_t>(&HookedInsert));
|
||||
}
|
||||
|
||||
bool NativeInvoker::Invoke(fxn::NativeContext* context)
|
||||
{
|
||||
static auto& manager = fxn::detail::NativeManager::GetInstance();
|
||||
|
||||
auto handler = manager.GetNativeHandler(context->GetNativeHash());
|
||||
if (!handler)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
handler(context);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NativeInvoker::Schedule(fxn::NativeContext* context, bool waitForCompletion)
|
||||
{
|
||||
static auto& manager = fxn::detail::NativeManager::GetInstance();
|
||||
|
||||
auto handler = manager.GetNativeHandler(context->GetNativeHash());
|
||||
if (!handler)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
static auto& nativeThread = fxn::detail::NativeThread::GetInstance();
|
||||
nativeThread.QueueTask([handler, context]()
|
||||
{
|
||||
handler(context);
|
||||
|
||||
context->SetVectorResults();
|
||||
context->SetExecuted(true);
|
||||
});
|
||||
|
||||
if (waitForCompletion)
|
||||
{
|
||||
while (!context->IsExecuted())
|
||||
{
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void NativeInvoker::HookNative(std::uint64_t hash, std::function<bool(fxn::NativeContext* context, bool& shouldCallOriginal)> hook)
|
||||
{
|
||||
static auto& manager = fxn::detail::NativeManager::GetInstance();
|
||||
|
||||
manager.HookNative(hash, hook);
|
||||
}
|
||||
|
||||
void NativeInvoker::AddTickEvent(const std::function<void()>& tickEvent)
|
||||
{
|
||||
static auto& nativeThread = fxn::detail::NativeThread::GetInstance();
|
||||
|
||||
nativeThread.AddTickEvent(std::move(tickEvent));
|
||||
}
|
||||
}
|
||||
62
src/impl/native_manager.cpp
Normal file
62
src/impl/native_manager.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#include <detail/native_manager.hpp>
|
||||
|
||||
namespace fxn::detail
|
||||
{
|
||||
NativeManager& NativeManager::GetInstance()
|
||||
{
|
||||
static NativeManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void NativeManager::HookNative(std::uint64_t nativeHash, NativeHook hook)
|
||||
{
|
||||
m_NativeHooks[nativeHash].emplace_back(std::move(hook));
|
||||
}
|
||||
|
||||
void NativeManager::UnhookNative(std::uint64_t nativeHash)
|
||||
{
|
||||
m_NativeHooks.erase(nativeHash);
|
||||
}
|
||||
|
||||
void NativeManager::RegisterNativeHandler(std::uint64_t nativeHash, NativeHandler handler)
|
||||
{
|
||||
m_NativeHandlers[nativeHash] = handler;
|
||||
}
|
||||
|
||||
void NativeManager::DispatchNative(fxn::NativeContext* context, std::uint64_t nativeHash)
|
||||
{
|
||||
static auto& instance = GetInstance();
|
||||
|
||||
bool callOriginal = true;
|
||||
|
||||
const auto& hooks = instance.m_NativeHooks[nativeHash];
|
||||
for (const auto& hook : hooks)
|
||||
{
|
||||
if (hook(context, callOriginal) == false)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (callOriginal)
|
||||
{
|
||||
auto it = instance.m_NativeHandlers.find(nativeHash);
|
||||
if (it != instance.m_NativeHandlers.end())
|
||||
{
|
||||
const auto& handler = it->second;
|
||||
handler(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NativeHandler NativeManager::GetNativeHandler(std::uint64_t nativeHash)
|
||||
{
|
||||
auto it = m_NativeHandlers.find(nativeHash);
|
||||
if (it != m_NativeHandlers.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
407
src/impl/native_thread.cpp
Normal file
407
src/impl/native_thread.cpp
Normal file
@@ -0,0 +1,407 @@
|
||||
#include <stdexcept>
|
||||
|
||||
#include <fxn/invoker.hpp>
|
||||
#include <detail/native_thread.hpp>
|
||||
#include <detail/event.hpp>
|
||||
#include <detail/native_manager.hpp>
|
||||
|
||||
#include <blackbase/library/library.hpp>
|
||||
#include <blackbase/pattern/matcher.hpp>
|
||||
|
||||
#define RESOURCE_MANAGER_PATTERN "48 8B 0D ? ? ? ? 48 85 C9 74 06 48 8B 01 FF"
|
||||
#define RESOURCE_MANAGER_MODULE "citizen-resources-gta.dll"
|
||||
|
||||
namespace fxn::detail
|
||||
{
|
||||
struct scrThreadContext
|
||||
{
|
||||
unsigned int ThreadId;
|
||||
unsigned int ThreadHash;
|
||||
};
|
||||
|
||||
using GtaThread = void*;
|
||||
|
||||
struct CfxThread
|
||||
{
|
||||
private:
|
||||
GtaThread m_GtaThread;
|
||||
|
||||
public:
|
||||
GtaThread GetGtaThread()
|
||||
{
|
||||
return m_GtaThread;
|
||||
}
|
||||
|
||||
public:
|
||||
CfxThread()
|
||||
{
|
||||
auto library = blackbase::Library::FindByName("rage-scripting-five.dll");
|
||||
if (!library.has_value())
|
||||
{
|
||||
throw std::runtime_error("rage-scripting-five.dll not found");
|
||||
}
|
||||
|
||||
auto constructor = library->GetExport("??0CfxThread@@QEAA@XZ");
|
||||
if (!constructor.has_value())
|
||||
{
|
||||
throw std::runtime_error("CfxThread constructor not found");
|
||||
}
|
||||
|
||||
using ctor_t = void(*)(CfxThread*);
|
||||
ctor_t ctor = reinterpret_cast<ctor_t>(constructor->GetAddress());
|
||||
ctor(this);
|
||||
}
|
||||
|
||||
public:
|
||||
virtual ~CfxThread() = default;
|
||||
virtual void Reset() = 0;
|
||||
virtual void DoRun() = 0;
|
||||
virtual void Kill() = 0;
|
||||
|
||||
public:
|
||||
void Initialize()
|
||||
{
|
||||
using attach_t = void(*)(CfxThread*);
|
||||
|
||||
static auto attach = []() -> attach_t
|
||||
{
|
||||
auto library = blackbase::Library::FindByName("rage-scripting-five.dll");
|
||||
if (!library.has_value())
|
||||
{
|
||||
throw std::runtime_error("rage-scripting-five.dll not found");
|
||||
}
|
||||
|
||||
auto attachExport = library->GetExport("?AttachScriptHandler@CfxThread@@QEAAXXZ");
|
||||
if (!attachExport.has_value())
|
||||
{
|
||||
throw std::runtime_error("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("rage-scripting-five.dll");
|
||||
if (!library.has_value())
|
||||
{
|
||||
throw std::runtime_error("rage-scripting-five.dll not found");
|
||||
}
|
||||
|
||||
auto detachExport = library->GetExport("?DetachScriptHandler@CfxThread@@QEAAXXZ");
|
||||
if (!detachExport.has_value())
|
||||
{
|
||||
throw std::runtime_error("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("rage-scripting-five.dll");
|
||||
if (!library.has_value())
|
||||
{
|
||||
throw std::runtime_error("rage-scripting-five.dll not found");
|
||||
}
|
||||
|
||||
auto getContextExport = library->GetExport("?GetContext@scrThread@rage@@QEAAPEAUscrThreadContext@2@XZ");
|
||||
if (!getContextExport.has_value())
|
||||
{
|
||||
throw std::runtime_error("CfxThread::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_name_t = void(*)(void*, const char*);
|
||||
|
||||
static auto set_name = []() -> set_name_t
|
||||
{
|
||||
auto library = blackbase::Library::FindByName("rage-scripting-five.dll");
|
||||
if (!library.has_value())
|
||||
{
|
||||
throw std::runtime_error("rage-scripting-five.dll not found");
|
||||
}
|
||||
|
||||
auto setNameExport = library->GetExport("?SetScriptName@GtaThread@@QEAAXPEBD@Z");
|
||||
if (!setNameExport.has_value())
|
||||
{
|
||||
throw std::runtime_error("GtaThread::SetScriptName not found");
|
||||
}
|
||||
|
||||
return reinterpret_cast<set_name_t>(setNameExport->GetAddress());
|
||||
}();
|
||||
|
||||
if (set_name)
|
||||
{
|
||||
set_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("FxnThread");
|
||||
context->ThreadHash = HashString("FxnThread");
|
||||
|
||||
SetScriptName("FxnThread");
|
||||
Initialize();
|
||||
}
|
||||
|
||||
~FxnThread() override
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
};
|
||||
|
||||
using get_active_thread_t = GtaThread (*)();
|
||||
using set_active_thread_t = void (*)(GtaThread);
|
||||
|
||||
static get_active_thread_t g_GetActiveThread;
|
||||
static set_active_thread_t g_SetActiveThread;
|
||||
|
||||
static FxnThread* g_FxnThread;
|
||||
|
||||
struct NativeScope
|
||||
{
|
||||
private:
|
||||
GtaThread m_PreviousThread;
|
||||
|
||||
public:
|
||||
void InitializeFunctions()
|
||||
{
|
||||
if (g_GetActiveThread && g_SetActiveThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto library = blackbase::Library::FindByName("rage-scripting-five.dll");
|
||||
if (!library.has_value())
|
||||
{
|
||||
throw std::runtime_error("rage-scripting-five.dll not found");
|
||||
}
|
||||
|
||||
auto getActiveThreadExport = library->GetExport("?GetActiveThread@scrEngine@rage@@SAPEAVscrThread@2@XZ");
|
||||
if (!getActiveThreadExport.has_value())
|
||||
{
|
||||
throw std::runtime_error("GtaThread::GetActiveThread not found");
|
||||
}
|
||||
|
||||
auto setActiveThreadExport = library->GetExport("?SetActiveThread@scrEngine@rage@@SAXPEAVscrThread@2@@Z");
|
||||
if (!setActiveThreadExport.has_value())
|
||||
{
|
||||
throw std::runtime_error("GtaThread::SetActiveThread not found");
|
||||
}
|
||||
|
||||
g_GetActiveThread = reinterpret_cast<get_active_thread_t>(getActiveThreadExport->GetAddress());
|
||||
g_SetActiveThread = reinterpret_cast<set_active_thread_t>(setActiveThreadExport->GetAddress());
|
||||
}
|
||||
|
||||
NativeScope(FxnThread* fxnThread)
|
||||
{
|
||||
InitializeFunctions();
|
||||
|
||||
m_PreviousThread = g_GetActiveThread();
|
||||
g_SetActiveThread(fxnThread->GetGtaThread());
|
||||
}
|
||||
|
||||
~NativeScope()
|
||||
{
|
||||
g_SetActiveThread(m_PreviousThread);
|
||||
}
|
||||
};
|
||||
|
||||
void* GetResourceManager()
|
||||
{
|
||||
static void* resourceManager = []() -> void*
|
||||
{
|
||||
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("Resource Manager pattern not found");
|
||||
}
|
||||
|
||||
struct RefContainer
|
||||
{
|
||||
void* ptr;
|
||||
};
|
||||
|
||||
auto ref = match->ResolveRelative().As<RefContainer*>();
|
||||
return ref->ptr;
|
||||
}();
|
||||
|
||||
return resourceManager;
|
||||
}
|
||||
|
||||
fx::fwEvent<>* GetOnTickEvent()
|
||||
{
|
||||
auto resourceManager = GetResourceManager();
|
||||
if (!resourceManager)
|
||||
{
|
||||
throw std::runtime_error("Resource Manager is null");
|
||||
}
|
||||
|
||||
return reinterpret_cast<fx::fwEvent<>*>(
|
||||
reinterpret_cast<std::uintptr_t>(resourceManager) + 0x20
|
||||
);
|
||||
}
|
||||
|
||||
NativeThread::NativeThread()
|
||||
{
|
||||
auto event = GetOnTickEvent();
|
||||
if (!event)
|
||||
{
|
||||
throw std::runtime_error("OnTick event not found");
|
||||
}
|
||||
|
||||
m_EventCookie = event->Connect([this]()
|
||||
{
|
||||
this->Initialize();
|
||||
std::vector<std::function<void()>> tasksCopy;
|
||||
|
||||
{
|
||||
std::lock_guard lock(m_TasksMutex);
|
||||
tasksCopy = std::move(m_Tasks);
|
||||
}
|
||||
|
||||
NativeScope scope(g_FxnThread);
|
||||
for (const auto& task : tasksCopy)
|
||||
{
|
||||
try
|
||||
{
|
||||
task();
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
// Handle exceptions from tasks
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
NativeThread::~NativeThread()
|
||||
{
|
||||
auto event = GetOnTickEvent();
|
||||
if (event)
|
||||
{
|
||||
event->Disconnect(m_EventCookie);
|
||||
|
||||
for (const auto& cookie : m_TickEvents)
|
||||
{
|
||||
event->Disconnect(cookie);
|
||||
}
|
||||
}
|
||||
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
NativeThread& NativeThread::GetInstance()
|
||||
{
|
||||
static NativeThread instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void NativeThread::QueueTask(const std::function<void()>& task)
|
||||
{
|
||||
std::lock_guard lock(m_TasksMutex);
|
||||
m_Tasks.push_back(task);
|
||||
}
|
||||
|
||||
void NativeThread::Initialize()
|
||||
{
|
||||
if (!g_FxnThread)
|
||||
{
|
||||
g_FxnThread = new FxnThread();
|
||||
|
||||
NativeScope scope(g_FxnThread);
|
||||
|
||||
static auto& nativeManager = NativeManager::GetInstance();
|
||||
auto handler = nativeManager.GetNativeHandler(0xDB2434E51017FFCC);
|
||||
|
||||
if (handler)
|
||||
{
|
||||
fxn::NativeContext context { 0xDB2434E51017FFCC };
|
||||
context.PushArgument<int>(32);
|
||||
context.PushArgument(false);
|
||||
context.PushArgument<int>(-1);
|
||||
|
||||
handler(&context);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("Failed to find native handler for initialization native.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NativeThread::Shutdown()
|
||||
{
|
||||
if (g_FxnThread)
|
||||
{
|
||||
delete g_FxnThread;
|
||||
g_FxnThread = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void NativeThread::AddTickEvent(const std::function<void()>& callback)
|
||||
{
|
||||
auto event = GetOnTickEvent();
|
||||
if (!event)
|
||||
{
|
||||
throw std::runtime_error("OnTick event not found");
|
||||
}
|
||||
|
||||
auto cookie = event->Connect([callback]()
|
||||
{
|
||||
callback();
|
||||
return true;
|
||||
});
|
||||
|
||||
m_TickEvents.insert(cookie);
|
||||
}
|
||||
}
|
||||
90
src/impl/stacktrace.cpp
Normal file
90
src/impl/stacktrace.cpp
Normal file
@@ -0,0 +1,90 @@
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <Windows.h>
|
||||
#include <DbgHelp.h>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
|
||||
#include <detail/stacktrace.hpp>
|
||||
|
||||
#include <blackbase/library/library.hpp>
|
||||
|
||||
namespace fxn::detail
|
||||
{
|
||||
struct SymInitializer
|
||||
{
|
||||
SymInitializer()
|
||||
{
|
||||
HANDLE hProcess = GetCurrentProcess();
|
||||
SymInitialize(hProcess, nullptr, TRUE);
|
||||
}
|
||||
|
||||
~SymInitializer()
|
||||
{
|
||||
HANDLE hProcess = GetCurrentProcess();
|
||||
SymCleanup(hProcess);
|
||||
}
|
||||
};
|
||||
|
||||
Stacktrace::Stacktrace()
|
||||
{
|
||||
static thread_local SymInitializer symInitializer;
|
||||
|
||||
void* stack[64];
|
||||
USHORT frames = RtlCaptureStackBackTrace(0, 64, stack, nullptr);
|
||||
|
||||
SYMBOL_INFO* symbol = reinterpret_cast<SYMBOL_INFO*>(malloc(sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(char)));
|
||||
symbol->MaxNameLen = MAX_SYM_NAME;
|
||||
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
|
||||
|
||||
IMAGEHLP_LINE64 line;
|
||||
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
|
||||
DWORD displacement = 0;
|
||||
|
||||
for (size_t i = 0; i < frames; i++)
|
||||
{
|
||||
DWORD64 address = reinterpret_cast<DWORD64>(stack[i]);
|
||||
std::ostringstream oss;
|
||||
|
||||
if (SymFromAddr(GetCurrentProcess(), address, 0, symbol))
|
||||
{
|
||||
oss << symbol->Name << " + 0x" << std::hex << (address - symbol->Address);
|
||||
}
|
||||
else
|
||||
{
|
||||
oss << "<unknown: 0x" << std::hex << address << ">";
|
||||
}
|
||||
|
||||
auto library = blackbase::Library::FindByAddress(address);
|
||||
if (library.has_value())
|
||||
{
|
||||
oss << " (" << library->GetName() << "+0x" << std::hex << (address - library->GetBaseAddress()) << ")";
|
||||
}
|
||||
|
||||
if (SymGetLineFromAddr64(GetCurrentProcess(), address, &displacement, &line))
|
||||
{
|
||||
oss << "[" << line.FileName << ":" << line.LineNumber << "]";
|
||||
}
|
||||
|
||||
m_Frames.push_back(Stackframe{ stack[i], oss.str() });
|
||||
}
|
||||
|
||||
free(symbol);
|
||||
}
|
||||
|
||||
const std::vector<Stackframe>& Stacktrace::GetFrames() const noexcept
|
||||
{
|
||||
return m_Frames;
|
||||
}
|
||||
|
||||
std::string Stacktrace::ToString() const
|
||||
{
|
||||
std::ostringstream oss;
|
||||
for (const auto& frame : m_Frames)
|
||||
{
|
||||
oss << frame.formatted << "\n";
|
||||
}
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
}
|
||||
36
src/impl/stubs.cpp
Normal file
36
src/impl/stubs.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <windows.h>
|
||||
|
||||
#include <detail/stubs.hpp>
|
||||
#include <detail/native_manager.hpp>
|
||||
|
||||
namespace fxn::detail
|
||||
{
|
||||
void* CreateNativeStub(std::uint64_t nativeHash)
|
||||
{
|
||||
constexpr auto STUB_SIZE = 2 + sizeof(uintptr_t) + 2 + sizeof(uintptr_t) + 2;
|
||||
constexpr auto STUB_TEMPLATE = std::array<std::uint8_t, STUB_SIZE>
|
||||
{
|
||||
0x48, 0xB8,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rax, <native_dispatcher_address>
|
||||
0x48, 0xBA,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rdx, <native_hash>
|
||||
0xFF, 0xE0 // jmp rax
|
||||
};
|
||||
|
||||
auto stub = VirtualAllocEx(GetCurrentProcess(), nullptr, STUB_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
|
||||
if (!stub)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto stubBytes = STUB_TEMPLATE;
|
||||
auto dispatcherAddress = reinterpret_cast<std::uintptr_t>(&NativeManager::DispatchNative);
|
||||
std::memcpy(&stubBytes[2], &dispatcherAddress, sizeof(std::uintptr_t));
|
||||
std::memcpy(&stubBytes[12], &nativeHash, sizeof(std::uintptr_t));
|
||||
|
||||
std::memcpy(stub, stubBytes.data(), STUB_SIZE);
|
||||
return stub;
|
||||
}
|
||||
}
|
||||
1
vendor/blackbase
vendored
Submodule
1
vendor/blackbase
vendored
Submodule
Submodule vendor/blackbase added at 55d97f6b68
Reference in New Issue
Block a user