This commit is contained in:
2025-11-13 20:56:11 +01:00
commit c80ca6db02
21 changed files with 1916 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
build/
.vscode/

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "vendor/blackbase"]
path = vendor/blackbase
url = https://git.slayercio.space/slayercio/blackbase

28
CMakeLists.txt Normal file
View 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
View 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()

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

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

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

View 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
View 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
View 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
View 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

Submodule vendor/blackbase added at 55d97f6b68