commit c80ca6db02a93d18f89db5b1b41e67108edc653f Author: slayercio Date: Thu Nov 13 20:56:11 2025 +0100 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0aacf63 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build/ +.vscode/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..bf89a07 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "vendor/blackbase"] + path = vendor/blackbase + url = https://git.slayercio.space/slayercio/blackbase diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..1af3ba7 --- /dev/null +++ b/CMakeLists.txt @@ -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() \ No newline at end of file diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..1c93366 --- /dev/null +++ b/examples/CMakeLists.txt @@ -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() \ No newline at end of file diff --git a/examples/natives_test_dll.cpp b/examples/natives_test_dll.cpp new file mode 100644 index 0000000..afae889 --- /dev/null +++ b/examples/natives_test_dll.cpp @@ -0,0 +1,249 @@ +#include +#include +#include + +#include + +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(REQUEST_MODEL, model); + while (!invoker.Schedule(HAS_MODEL_LOADED, model)) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + int playerPedId = invoker.Schedule(PLAYER_PED_ID); + auto playerCoords = invoker.Schedule(GET_ENTITY_COORDS, playerPedId, true); + int vehicle = invoker.Schedule(CREATE_VEHICLE, model, playerCoords.x + 5.0f, playerCoords.y, playerCoords.z, 0.0f, true, false, false); + invoker.Schedule(SET_PED_INTO_VEHICLE, playerPedId, vehicle, -1); + invoker.Schedule(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(stub + 8) = reinterpret_cast(reinterpret_cast(baseThreadInitThunk) + 0x6); + std::memcpy(mem, stub, sizeof(stub)); + + return mem; + }(); + + return addr; +} + +void CreateThreadBypass() +{ + HANDLE hThread = CreateThread(nullptr, 0, reinterpret_cast(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(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(reinterpret_cast(startUserThreadAddr) + offset + 3); + std::printf("[natives_test_dll] RtlUserThreadStart RVA offset: 0x%X\n", rvaOffset); + + void** targetAddr = reinterpret_cast(reinterpret_cast(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(0); + uint32_t weaponHash = context->GetArgument(1); + int32_t ammoCount = context->GetArgument(2); + bool isHidden = context->GetArgument(3); + bool forceInHand = context->GetArgument(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(); + 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(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; +} \ No newline at end of file diff --git a/include/fxn/context.hpp b/include/fxn/context.hpp new file mode 100644 index 0000000..bba5660 --- /dev/null +++ b/include/fxn/context.hpp @@ -0,0 +1,173 @@ +#pragma once +#include +#include +#include +#include + +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 + 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(m_Stack + (m_ArgumentCount * ARGUMENT_SIZE)) = 0; + } + + *reinterpret_cast(m_Stack + (m_ArgumentCount * ARGUMENT_SIZE)) = arg; + m_ArgumentCount++; + } + + template<> + inline void PushArgument(const Vector3& arg) + { + PushArgument(arg.x); + PushArgument(arg.y); + PushArgument(arg.z); + } + + template + inline T GetResult() + { + return *reinterpret_cast(m_ReturnBuffer); + } + + template<> + inline Vector3 GetResult() + { + auto vec = *reinterpret_cast(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 + 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(m_Arguments); + return *reinterpret_cast(&functionData[index]); + } + + inline std::uint64_t GetNativeHash() const + { + return m_NativeHash; + } + }; +} \ No newline at end of file diff --git a/include/fxn/invoker.hpp b/include/fxn/invoker.hpp new file mode 100644 index 0000000..3219c36 --- /dev/null +++ b/include/fxn/invoker.hpp @@ -0,0 +1,92 @@ +#pragma once +#include +#include +#include + +#include + +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& 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 + R Invoke(std::uint64_t hash, Args&&... args) + { + fxn::NativeContext context { hash }; + + (context.PushArgument(std::forward(args)), ...); + Invoke(&context); + + if constexpr (!std::is_same_v) + { + return context.GetResult(); + } + } + + template + R Schedule(std::uint64_t hash, Args&&... args) + { + fxn::NativeContext context { hash }; + + (context.PushArgument(std::forward(args)), ...); + Schedule(&context, true); + + if constexpr (!std::is_same_v) + { + return context.GetResult(); + } + } + + public: + void HookNative(std::uint64_t hash, std::function hook); + }; +} \ No newline at end of file diff --git a/src/detail/event.hpp b/src/detail/event.hpp new file mode 100644 index 0000000..cd367a5 --- /dev/null +++ b/src/detail/event.hpp @@ -0,0 +1,198 @@ +#pragma once +#include +#include +#include +#include + +namespace fx +{ + template + class fwEvent + { + public: + using TFunc = std::function; + + public: + struct callback + { + TFunc function; + std::unique_ptr 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 m_callbacks; + std::atomic m_connectCookie = 0; + public: + fwEvent() + : m_callbacks(nullptr) + { + } + + ~fwEvent() + { + Reset(); + } + + template + auto Connect(T func) + { + return ConnectInternal(func, 0); + } + + template + auto Connect(T func, int order) + { + if constexpr (std::is_same_v, 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(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& func) + { + callback* prev = nullptr; + std::unique_ptr* 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; + } + } + } + }; +} \ No newline at end of file diff --git a/src/detail/hooking.hpp b/src/detail/hooking.hpp new file mode 100644 index 0000000..83c0bbf --- /dev/null +++ b/src/detail/hooking.hpp @@ -0,0 +1,30 @@ +#pragma once +#include +#include +#include + +namespace fxn::detail +{ + struct InlineHookInfo + { + std::uintptr_t target; + std::uintptr_t detour; + std::vector originalBytes; + }; + + class HookManager + { + protected: + std::vector 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); + }; +} \ No newline at end of file diff --git a/src/detail/native_manager.hpp b/src/detail/native_manager.hpp new file mode 100644 index 0000000..0040460 --- /dev/null +++ b/src/detail/native_manager.hpp @@ -0,0 +1,43 @@ +#pragma once +#include +#include +#include +#include + +#include + +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; + + class NativeManager + { + private: + std::unordered_map m_NativeHandlers; + std::unordered_map> 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); + }; +} \ No newline at end of file diff --git a/src/detail/native_thread.hpp b/src/detail/native_thread.hpp new file mode 100644 index 0000000..d39fc71 --- /dev/null +++ b/src/detail/native_thread.hpp @@ -0,0 +1,33 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace fxn::detail +{ + class NativeThread + { + private: + std::vector> m_Tasks; + std::set 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& task); + void AddTickEvent(const std::function& callback); + }; +} \ No newline at end of file diff --git a/src/detail/rage.hpp b/src/detail/rage.hpp new file mode 100644 index 0000000..383b9ea --- /dev/null +++ b/src/detail/rage.hpp @@ -0,0 +1,218 @@ +#pragma once +#include + +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 + class sysObfuscated + { + public: + sysObfuscated() { CompileTimeAssert((sizeof(T) & 3) == 0); Init(); } + sysObfuscated(const sysObfuscated& rhs) { Init(); Set(rhs.Get()); } + template sysObfuscated(const sysObfuscated& 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 operator==(const sysObfuscated& rhs) const { return Get() == rhs.Get(); } + template bool operator!=(const sysObfuscated& rhs) const { return Get() != rhs.Get(); } + template bool operator<(const sysObfuscated& rhs) const { return Get() < rhs.Get(); } + template bool operator<=(const sysObfuscated& rhs) const { return Get() <= rhs.Get(); } + template bool operator>(const sysObfuscated& rhs) const { return Get() > rhs.Get(); } + template bool operator>=(const sysObfuscated& rhs) const { return Get() >= rhs.Get(); } + + public: + template sysObfuscated& operator=(const sysObfuscated& rhs) { Set(rhs.Get()); return *this; } + sysObfuscated& operator=(const T& data) { Set(data); return *this; } + + public: + template sysObfuscated& operator+=(const sysObfuscated& rhs) { Set(Get()+rhs.Get()); return *this; } + sysObfuscated& operator+=(const T& data) { Set(Get()+data); return *this; } + template sysObfuscated& operator-=(const sysObfuscated& rhs) { Set(Get()-rhs.Get()); return *this; } + sysObfuscated& operator-=(const T& data) { Set(Get()-data); return *this; } + template sysObfuscated& operator*=(const sysObfuscated& rhs) { Set(Get()*rhs.Get()); return *this; } + sysObfuscated& operator*=(const T& data) { Set(Get()*data); return *this; } + template sysObfuscated& operator/=(const sysObfuscated& rhs) { Set(Get()/rhs.Get()); return *this; } + sysObfuscated& 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 __forceinline void sysObfuscated::Init() + { + m_xor = sysObfuscatedTypes::obfRand(); + if(TMutate) + { + m_mutate = sysObfuscatedTypes::obfRand(); + } + } + + template __forceinline T sysObfuscated::Get() const + { + u32 xorVal = m_xor ^ (u32)(size_t)this; + u32 ret[sizeof(T)/sizeof(u32)]; + u32* src = const_cast(&m_data[0]); + u32* dest = (u32*)&ret; + for(size_t i=0; i> 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*>(this)->Set(*(T*)&ret); + } + + return *(T*)&ret; + } + + template __forceinline void sysObfuscated::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 + class scrCommandHash + { + private: + static const int ToplevelSize = 256; + static const int PerBucket = 7; + + struct Bucket + { + sysObfuscated obf_Next; + _T Data[PerBucket]; + sysObfuscated obf_Count; + sysObfuscated 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; iobf_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; iobf_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; + }; +} \ No newline at end of file diff --git a/src/detail/stacktrace.hpp b/src/detail/stacktrace.hpp new file mode 100644 index 0000000..639a030 --- /dev/null +++ b/src/detail/stacktrace.hpp @@ -0,0 +1,25 @@ +#pragma once +#include +#include + +namespace fxn::detail +{ + struct Stackframe + { + void* address; + std::string formatted; + }; + + class Stacktrace + { + private: + std::vector m_Frames; + + public: + Stacktrace(); + + public: + const std::vector& GetFrames() const noexcept; + std::string ToString() const; + }; +} \ No newline at end of file diff --git a/src/detail/stubs.hpp b/src/detail/stubs.hpp new file mode 100644 index 0000000..de36d83 --- /dev/null +++ b/src/detail/stubs.hpp @@ -0,0 +1,7 @@ +#pragma once +#include + +namespace fxn::detail +{ + void* CreateNativeStub(std::uint64_t nativeHash); +} \ No newline at end of file diff --git a/src/impl/hooking.cpp b/src/impl/hooking.cpp new file mode 100644 index 0000000..0b7a052 --- /dev/null +++ b/src/impl/hooking.cpp @@ -0,0 +1,78 @@ +#include +#include + +#include + +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 + { + 0x48, 0xB8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xE0 + }; + + DWORD oldProtect; + if (!VirtualProtect(reinterpret_cast(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(target), HOOK_SIZE); + + std::array hookBytes = HOOK_INSTRUCTION; + *reinterpret_cast(&hookBytes[2]) = detour; + std::memcpy(reinterpret_cast(target), hookBytes.data(), HOOK_SIZE); + + VirtualProtect(reinterpret_cast(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(target), it->originalBytes.size(), PAGE_EXECUTE_READWRITE, &oldProtect)) + { + return false; + } + + std::memcpy(reinterpret_cast(target), it->originalBytes.data(), it->originalBytes.size()); + VirtualProtect(reinterpret_cast(target), it->originalBytes.size(), oldProtect, &oldProtect); + m_InlineHooks.erase(it); + + return true; + } +} \ No newline at end of file diff --git a/src/impl/invoker.cpp b/src/impl/invoker.cpp new file mode 100644 index 0000000..9da0437 --- /dev/null +++ b/src/impl/invoker.cpp @@ -0,0 +1,124 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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* 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(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(); + hookManager.InstallInlineHook(reinterpret_cast(address), reinterpret_cast(&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 hook) + { + static auto& manager = fxn::detail::NativeManager::GetInstance(); + + manager.HookNative(hash, hook); + } + + void NativeInvoker::AddTickEvent(const std::function& tickEvent) + { + static auto& nativeThread = fxn::detail::NativeThread::GetInstance(); + + nativeThread.AddTickEvent(std::move(tickEvent)); + } +} \ No newline at end of file diff --git a/src/impl/native_manager.cpp b/src/impl/native_manager.cpp new file mode 100644 index 0000000..b1272b9 --- /dev/null +++ b/src/impl/native_manager.cpp @@ -0,0 +1,62 @@ +#include + +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; + } +} \ No newline at end of file diff --git a/src/impl/native_thread.cpp b/src/impl/native_thread.cpp new file mode 100644 index 0000000..707e836 --- /dev/null +++ b/src/impl/native_thread.cpp @@ -0,0 +1,407 @@ +#include + +#include +#include +#include +#include + +#include +#include + +#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(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(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(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(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(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(getActiveThreadExport->GetAddress()); + g_SetActiveThread = reinterpret_cast(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(); + return ref->ptr; + }(); + + return resourceManager; + } + + fx::fwEvent<>* GetOnTickEvent() + { + auto resourceManager = GetResourceManager(); + if (!resourceManager) + { + throw std::runtime_error("Resource Manager is null"); + } + + return reinterpret_cast*>( + reinterpret_cast(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> 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& 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(32); + context.PushArgument(false); + context.PushArgument(-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& 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); + } +} \ No newline at end of file diff --git a/src/impl/stacktrace.cpp b/src/impl/stacktrace.cpp new file mode 100644 index 0000000..2e4d0ea --- /dev/null +++ b/src/impl/stacktrace.cpp @@ -0,0 +1,90 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include + +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(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(stack[i]); + std::ostringstream oss; + + if (SymFromAddr(GetCurrentProcess(), address, 0, symbol)) + { + oss << symbol->Name << " + 0x" << std::hex << (address - symbol->Address); + } + else + { + oss << ""; + } + + 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& 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(); + } +} \ No newline at end of file diff --git a/src/impl/stubs.cpp b/src/impl/stubs.cpp new file mode 100644 index 0000000..1bd9623 --- /dev/null +++ b/src/impl/stubs.cpp @@ -0,0 +1,36 @@ +#include +#include +#include + +#include +#include + +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 + { + 0x48, 0xB8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rax, + 0x48, 0xBA, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rdx, + 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(&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; + } +} \ No newline at end of file diff --git a/vendor/blackbase b/vendor/blackbase new file mode 160000 index 0000000..55d97f6 --- /dev/null +++ b/vendor/blackbase @@ -0,0 +1 @@ +Subproject commit 55d97f6b6839ac2d56273d3f7709d0ebfd41a413