diff --git a/libs/ASILoader/ASILoader.vcxproj b/libs/ASILoader/ASILoader.vcxproj new file mode 100644 index 0000000..b72b107 --- /dev/null +++ b/libs/ASILoader/ASILoader.vcxproj @@ -0,0 +1,266 @@ + + + + + Debug + Win32 + + + Release_internal + Win32 + + + Release_internal + x64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {0db8f2e7-eec8-4c7a-a1bf-c7d51af69d28} + ASILoader + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + Level3 + true + WIN32;_DEBUG;ASILOADER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + + + MultiThreaded + + stdcpp17 + + + Windows + true + false + + version.def + version.lib + false + false + + + + + Level3 + true + true + true + WIN32;NDEBUG;ASILOADER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + + + MultiThreaded + + stdcpp17 + + + Windows + true + false + + version.def + version.lib + false + false + + + + + Level3 + true + true + true + WIN32;NDEBUG;ASILOADER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + + + MultiThreaded + + + stdcpp17 + + + Windows + true + false + + + version.def + version.lib + false + false + + + + + Level3 + true + _DEBUG;ASILOADER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + + + MultiThreaded + + stdcpp17 + + + Windows + true + false + + version.def + version.lib + false + false + + + + + Level3 + true + true + true + NDEBUG;ASILOADER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + + + MultiThreaded + + stdcpp17 + + + Windows + true + false + + version.def + version.lib + false + false + + + + + Level3 + true + true + true + NDEBUG;ASILOADER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + + + MultiThreaded + + + stdcpp17 + + + Windows + true + false + + + version.def + version.lib + false + false + + + + + + + + + + + + \ No newline at end of file diff --git a/libs/ASILoader/version.cpp b/libs/ASILoader/version.cpp new file mode 100644 index 0000000..258f3d2 --- /dev/null +++ b/libs/ASILoader/version.cpp @@ -0,0 +1,147 @@ +#include +#include +namespace fs = std::filesystem; + +static HMODULE real_version = nullptr; + +void LoadRealVersionDLL() +{ + if (!real_version) + { + real_version = LoadLibraryW(L"C:\\Windows\\System32\\version.dll"); + } +} + +extern "C" BOOL WINAPI GetFileVersionInfoA(LPCSTR file, DWORD handle, DWORD len, LPVOID data) +{ + LoadRealVersionDLL(); + using Fn = BOOL(WINAPI*)(LPCSTR, DWORD, DWORD, LPVOID); + static Fn real = (Fn)GetProcAddress(real_version, "GetFileVersionInfoA"); + return real(file, handle, len, data); +} + +extern "C" BOOL WINAPI GetFileVersionInfoW(LPCWSTR file, DWORD handle, DWORD len, LPVOID data) +{ + LoadRealVersionDLL(); + using Fn = BOOL(WINAPI*)(LPCWSTR, DWORD, DWORD, LPVOID); + static Fn real = (Fn)GetProcAddress(real_version, "GetFileVersionInfoW"); + return real(file, handle, len, data); +} + +extern "C" BOOL WINAPI GetFileVersionInfoExA(DWORD dwFlags, LPCSTR lpstrFilename, DWORD dwHandle, DWORD dwLen, LPVOID lpData) +{ + LoadRealVersionDLL(); + using Fn = BOOL(WINAPI*)(DWORD, LPCSTR, DWORD, DWORD, LPVOID); + static Fn real = (Fn)GetProcAddress(real_version, "GetFileVersionInfoExA"); + return real(dwFlags, lpstrFilename, dwHandle, dwLen, lpData); +} + +extern "C" BOOL WINAPI GetFileVersionInfoExW(DWORD dwFlags, LPCWSTR lpwstrFilename, DWORD dwHandle, DWORD dwLen, LPVOID lpData) +{ + LoadRealVersionDLL(); + using Fn = BOOL(WINAPI*)(DWORD, LPCWSTR, DWORD, DWORD, LPVOID); + static Fn real = (Fn)GetProcAddress(real_version, "GetFileVersionInfoExW"); + return real(dwFlags, lpwstrFilename, dwHandle, dwLen, lpData); +} + +extern "C" DWORD WINAPI GetFileVersionInfoSizeA(LPCSTR lptstrFilename, LPDWORD lpdwHandle) +{ + LoadRealVersionDLL(); + using Fn = DWORD(WINAPI*)(LPCSTR, LPDWORD); + static Fn real = (Fn)GetProcAddress(real_version, "GetFileVersionInfoSizeA"); + return real(lptstrFilename, lpdwHandle); +} + +extern "C" DWORD WINAPI GetFileVersionInfoSizeW(LPCWSTR lptstrFilename, LPDWORD lpdwHandle) +{ + LoadRealVersionDLL(); + using Fn = DWORD(WINAPI*)(LPCWSTR, LPDWORD); + static Fn real = (Fn)GetProcAddress(real_version, "GetFileVersionInfoSizeW"); + return real(lptstrFilename, lpdwHandle); +} + +extern "C" DWORD WINAPI GetFileVersionInfoSizeExA(DWORD dwFlags, LPCSTR lpwstrFilename, LPDWORD lpdwHandle) +{ + LoadRealVersionDLL(); + using Fn = DWORD(WINAPI*)(DWORD, LPCSTR, LPDWORD); + static Fn real = (Fn)GetProcAddress(real_version, "GetFileVersionInfoSizeExA"); + return real(dwFlags, lpwstrFilename, lpdwHandle); +} + +extern "C" DWORD WINAPI GetFileVersionInfoSizeExW(DWORD dwFlags, LPCWSTR lpwstrFilename, LPDWORD lpdwHandle) +{ + LoadRealVersionDLL(); + using Fn = DWORD(WINAPI*)(DWORD, LPCWSTR, LPDWORD); + static Fn real = (Fn)GetProcAddress(real_version, "GetFileVersionInfoSizeExW"); + return real(dwFlags, lpwstrFilename, lpdwHandle); +} + +extern "C" DWORD WINAPI VerFindFileA(DWORD uFlags, LPCSTR szFileName, LPCSTR szWinDir, LPCSTR szAppDir, LPSTR szCurDir, UINT* pcchCurDir, LPSTR szDestDir, UINT* pcchDestDir) +{ + LoadRealVersionDLL(); + using Fn = DWORD(WINAPI*)(DWORD, LPCSTR, LPCSTR, LPCSTR, LPSTR, UINT*, LPSTR, UINT*); + static Fn real = (Fn)GetProcAddress(real_version, "VerFindFileA"); + return real(uFlags, szFileName, szWinDir, szAppDir, szCurDir, pcchCurDir, szDestDir, pcchDestDir); +} + +extern "C" DWORD WINAPI VerFindFileW(DWORD uFlags, LPCWSTR szFileName, LPCWSTR szWinDir, LPCWSTR szAppDir, LPWSTR szCurDir, UINT* pcchCurDir, LPWSTR szDestDir, UINT* pcchDestDir) +{ + LoadRealVersionDLL(); + using Fn = DWORD(WINAPI*)(DWORD, LPCWSTR, LPCWSTR, LPCWSTR, LPWSTR, UINT*, LPWSTR, UINT*); + static Fn real = (Fn)GetProcAddress(real_version, "VerFindFileW"); + return real(uFlags, szFileName, szWinDir, szAppDir, szCurDir, pcchCurDir, szDestDir, pcchDestDir); +} + +extern "C" DWORD WINAPI VerLanguageNameA(DWORD wLang, LPSTR szLang, DWORD cchLang) +{ + LoadRealVersionDLL(); + using Fn = BOOL(WINAPI*)(DWORD, LPSTR, DWORD); + static Fn real = (Fn)GetProcAddress(real_version, "VerLanguageNameA"); + return real(wLang, szLang, cchLang); +} + +extern "C" DWORD WINAPI VerLanguageNameW(DWORD wLang, LPWSTR szLang, DWORD cchLang) +{ + LoadRealVersionDLL(); + using Fn = BOOL(WINAPI*)(DWORD, LPWSTR, DWORD); + static Fn real = (Fn)GetProcAddress(real_version, "VerLanguageNameW"); + return real(wLang, szLang, cchLang); +} + +extern "C" BOOL WINAPI VerQueryValueA(LPCVOID pBlock, LPCSTR lpSubBlock, LPVOID* lplpBuffer, PUINT puLen) +{ + LoadRealVersionDLL(); + using Fn = BOOL(WINAPI*)(LPCVOID, LPCSTR, LPVOID*, PUINT); + static Fn real = (Fn)GetProcAddress(real_version, "VerQueryValueA"); + return real(pBlock, lpSubBlock, lplpBuffer, puLen); +} + +extern "C" BOOL WINAPI VerQueryValueW(LPCVOID pBlock, LPCWSTR lpSubBlock, LPVOID* lplpBuffer, PUINT puLen) +{ + LoadRealVersionDLL(); + using Fn = BOOL(WINAPI*)(LPCVOID, LPCWSTR, LPVOID*, PUINT); + static Fn real = (Fn)GetProcAddress(real_version, "VerQueryValueW"); + return real(pBlock, lpSubBlock, lplpBuffer, puLen); +} + +// Charge les ASI dans le répertoire courant +void LoadAllASI() +{ + auto path = fs::current_path(); + for (const auto& entry : fs::directory_iterator(path)) + { + if (entry.path().extension() == ".asi") + { + LoadLibraryW(entry.path().c_str()); + } + } +} + +BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID) +{ + if (reason == DLL_PROCESS_ATTACH) + { + LoadAllASI(); + } + return TRUE; +} diff --git a/libs/ASILoader/version.def b/libs/ASILoader/version.def new file mode 100644 index 0000000..15597f0 --- /dev/null +++ b/libs/ASILoader/version.def @@ -0,0 +1,16 @@ +LIBRARY version +EXPORTS + GetFileVersionInfoA + GetFileVersionInfoW + GetFileVersionInfoExA + GetFileVersionInfoExW + GetFileVersionInfoSizeA + GetFileVersionInfoSizeW + GetFileVersionInfoSizeExA + GetFileVersionInfoSizeExW + VerFindFileA + VerFindFileW + VerLanguageNameA + VerLanguageNameW + VerQueryValueA + VerQueryValueW diff --git a/libs/Maths/Maths.cpp b/libs/Maths/Maths.cpp new file mode 100644 index 0000000..84870a4 --- /dev/null +++ b/libs/Maths/Maths.cpp @@ -0,0 +1,27 @@ +// Maths.cpp : Définit les fonctions de la bibliothèque statique. +// +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#include "Maths.hpp" +#include + +double Maths::DegreesToRadians(double degrees) { + return degrees * M_PI / 180.0; +} + +// Convertit des radians en degrés +double Maths::RadiansToDegrees(double radians) { + return radians * 180.0 / M_PI; +} + +double Maths::CompensateHorizontalFOV(double baseHorizontalFOVDeg, double baseAspectRatio, double targetAspectRatio) { + double baseFOVRad = DegreesToRadians(baseHorizontalFOVDeg); + // Step 1 : FOV vertical from horizontal FOV + double verticalFOVRad = 2.0 * std::atan(std::tan(baseFOVRad / 2.0) / baseAspectRatio); + // Step 2 : New horizontal FOV for target aspect ratio + double newFOVRad = 2.0 * std::atan(std::tan(verticalFOVRad / 2.0) * targetAspectRatio); + + return RadiansToDegrees(newFOVRad); +} \ No newline at end of file diff --git a/libs/Maths/Maths.hpp b/libs/Maths/Maths.hpp new file mode 100644 index 0000000..f7d9892 --- /dev/null +++ b/libs/Maths/Maths.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include + +class Maths +{ + public: + // Compute new horizontal FOV based on native and target aspect ratio + static double CompensateHorizontalFOV(const double baseHorizontalFOVDeg, const double baseAspectRatio, const double targetAspectRatio); + + private: + static double DegreesToRadians(double degrees); + static double RadiansToDegrees(double radians); +}; diff --git a/libs/Maths/Maths.vcxproj b/libs/Maths/Maths.vcxproj new file mode 100644 index 0000000..b92ea5e --- /dev/null +++ b/libs/Maths/Maths.vcxproj @@ -0,0 +1,246 @@ + + + + + Debug + Win32 + + + Release_internal + Win32 + + + Release_internal + x64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {9a3c36e4-b32b-43db-ac7b-8ab45dc3097e} + Maths + 10.0 + + + + StaticLibrary + true + v143 + Unicode + + + StaticLibrary + false + v143 + true + Unicode + + + StaticLibrary + false + v143 + true + Unicode + + + StaticLibrary + true + v143 + Unicode + + + StaticLibrary + false + v143 + true + Unicode + + + StaticLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)bin\$(Configuration)\ + + + $(SolutionDir)bin\$(Configuration)\ + + + $(SolutionDir)bin\$(Configuration)\ + + + $(SolutionDir)bin\$(Configuration)\ + + + $(SolutionDir)bin\$(Configuration)\ + + + $(SolutionDir)bin\$(Configuration)\ + + + + Level3 + true + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + + + + /utf-8 %(AdditionalOptions) + + + + + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + + + + /utf-8 %(AdditionalOptions) + + + + + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + + + + + /utf-8 %(AdditionalOptions) + + + + + true + + + + + Level3 + true + _DEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + + + + /utf-8 %(AdditionalOptions) + + + + + true + + + + + Level3 + true + true + true + NDEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + + + + /utf-8 %(AdditionalOptions) + + + + + true + + + + + Level3 + true + true + true + NDEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + + + + + /utf-8 %(AdditionalOptions) + + + + + true + + + + + + + + + + + + \ No newline at end of file diff --git a/libs/Memory/Memory.cpp b/libs/Memory/Memory.cpp new file mode 100644 index 0000000..60291c4 --- /dev/null +++ b/libs/Memory/Memory.cpp @@ -0,0 +1,341 @@ +// MemoryScanner.cpp : Définit les fonctions de la bibliothèque statique. +// + +#include "Memory.hpp" +#include "UEngine.hpp" +#include +#include +#include +#include +#include +#include + +// Rich verbose for internal release only +#ifdef MY_VERBOSE_LOGS +#define LOG_SIGNATURE_FOUND(name, addr) \ + logger->info( \ + "{} signature found at address: 0x{:X}.", \ + name, addr \ + ) +#else +#define LOG_SIGNATURE_FOUND(name, addr) \ + logger->info("{} signature found", name) +#endif + +static std::shared_ptr _log; +std::unordered_map Memory::patches; + +uint8_t* Memory::GetOffsetFromOpcode(uint8_t* opcode, int extraOffset) { + if (!opcode) return nullptr; + + int32_t disp = 0; + std::memcpy(&disp, opcode, sizeof(int32_t)); + + if (disp < 0) + return nullptr; // optionnel : gérer ou pas les offsets négatifs + + // Retourne l'adresse "offsetée" (base + disp) + return opcode + 4 + disp + extraOffset; // +4 car disp32 fait 4 octets +} + +uint8_t* Memory::GetAddressFromOpcode(uint8_t* opcode, uint32_t dispOffset, uint32_t instructionLen) { + int32_t disp = 0; + std::memcpy(&disp, opcode + dispOffset, sizeof(disp)); // lit le disp32 de manière safe + return opcode + instructionLen + disp; // gère automatiquement disp négatif +} + +const char* Memory::Float32ToHexBytes(float value) { + static char bytes[4]; // buffer persistant (évite les problèmes de scope) + std::memcpy(bytes, &value, sizeof(float)); + return bytes; // pointeur vers les 4 octets bruts +} + +std::vector Memory::ReadBytes(const void* addr, std::size_t size) { + std::vector buffer(size); + std::memcpy(buffer.data(), addr, size); + return buffer; +} + +void Memory::PatchBytes(void* address, const char* bytes, size_t len) { + auto it = patches.find(address); + if (it == patches.end()) { + // If a patch doesn't exist, create a new one. + PatchInfo info; + info.address = address; + info.originalBytes.resize(len); + memcpy(info.originalBytes.data(), address, len); + // Store the patch info. + patches[address] = info; + } + + // Patch the bytes. + DWORD oldProtect; + VirtualProtect(address, len, PAGE_EXECUTE_READWRITE, &oldProtect); + memcpy(address, bytes, len); + VirtualProtect(address, len, oldProtect, &oldProtect); +} + +void Memory::RestoreBytes(void *address) { + auto it = patches.find(address); + if (it != patches.end()) { + // Restore the original bytes. + const auto& info = it->second; + DWORD oldProtect; + VirtualProtect(info.address, info.originalBytes.size(), PAGE_EXECUTE_READWRITE, &oldProtect); + memcpy(info.address, info.originalBytes.data(), info.originalBytes.size()); + VirtualProtect(info.address, info.originalBytes.size(), oldProtect, &oldProtect); + + // Remove the patch info. + patches.erase(it); + } +} + +MODULEINFO Memory::WaitForModule(const std::string& module_name, int timeoutMs, int intervalMs) +{ + const HANDLE hProc = GetCurrentProcess(); + MODULEINFO modInfo{}; + + for (int waited = 0; waited < timeoutMs; waited += intervalMs) { + HMODULE hMods[1024]; + DWORD cbNeeded; + + if (EnumProcessModules(hProc, hMods, sizeof(hMods), &cbNeeded)) { + for (unsigned int i = 0; i < (cbNeeded / sizeof(HMODULE)); ++i) { + char modName[MAX_PATH]; + if (GetModuleBaseNameA(hProc, hMods[i], modName, sizeof(modName))) { + if (_stricmp(modName, module_name.c_str()) == 0) { + if (GetModuleInformation(hProc, hMods[i], &modInfo, sizeof(modInfo))) + return modInfo; + } + } + } + } + + Sleep(intervalMs); + } + + if (_log) _log->warn("Timeout: module '{}' not found in process after {} ms.", module_name, timeoutMs); + return MODULEINFO{}; +} + +std::string Memory::ByteToHexEscaped(const BYTE byte) { + std::ostringstream oss; + oss << "\\x" << std::uppercase << std::hex << std::setw(2) + << std::setfill('0') << static_cast(byte); + return oss.str(); +} + +uint8_t* Memory::AOBScan( + const std::string& module_name, + const std::string& signature, + DWORD protect_flags = PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_READWRITE | PAGE_EXECUTE_WRITECOPY, + std::shared_ptr log) { + + bool found = false; + + _log = log; + HANDLE hProc = GetCurrentProcess(); + MODULEINFO modInfo{}; + HMODULE targetModule = nullptr; + // Get module when name is specidifed + if (!(module_name.empty() || module_name == "*")) { + if (_log) _log->info("Module name: {}", module_name); + MODULEINFO modinfo = WaitForModule(module_name); + if (modinfo.lpBaseOfDll == nullptr) { + if (_log) _log->warn("Skipping AOB scan because module '{}' is unavailable.", module_name); + return nullptr; + } + } + // Fallback to determine module loaded + if (!found || module_name.empty() || module_name == "*") { + char exeBuf[MAX_PATH] = { 0 }; + DWORD exeLen = GetModuleFileNameA(nullptr, exeBuf, MAX_PATH); + std::string exeName = (exeLen > 0) ? std::string(exeBuf, exeBuf + exeLen) : std::string(); + size_t pos = exeName.find_last_of("\\/"); + + if (pos != std::string::npos) exeName = exeName.substr(pos + 1); + if (_log && !exeName.empty()) _log->info("Module name: {}", exeName); + + targetModule = GetModuleHandleA(nullptr); + if (!targetModule || !GetModuleInformation(hProc, targetModule, &modInfo, sizeof(modInfo))) { + if (_log) _log->error("Failed to find main module."); + return nullptr; + } + } + // Convert AOB string into vector bytes + std::vector pattern_bytes; + std::istringstream stream(signature); + std::string byte_str; + while (stream >> byte_str) { + if (byte_str == "??" || byte_str == "?") + pattern_bytes.push_back(-1); + else + pattern_bytes.push_back(static_cast(std::strtol(byte_str.c_str(), nullptr, 16))); + } + + if (pattern_bytes.empty()) { + if (_log) _log->warn("Empty AOB pattern passed."); + return nullptr; + } + // Logging scanning area + uint8_t* base = reinterpret_cast(modInfo.lpBaseOfDll); + size_t size = modInfo.SizeOfImage; + if (_log) _log->info("Scanning memory region: 0x{:X} - 0x{:X}", + reinterpret_cast(base), reinterpret_cast(base + size)); + + // Memory scan + MEMORY_BASIC_INFORMATION mbi{}; + for (uint8_t* current = base; current < base + size;) { + if (!VirtualQuery(current, &mbi, sizeof(mbi))) + break; + + bool isCommitted = (mbi.State & MEM_COMMIT) != 0; + bool hasAccess = (mbi.Protect & protect_flags) != 0; + bool isNoAccess = (mbi.Protect & PAGE_NOACCESS) != 0; + bool isGuard = (mbi.Protect & PAGE_GUARD) != 0; + + if (isCommitted && hasAccess && !isNoAccess && !isGuard) { + uint8_t* regionBase = reinterpret_cast(mbi.BaseAddress); + size_t regionSize = mbi.RegionSize; + + for (size_t i = 0; i <= regionSize - pattern_bytes.size(); ++i) { + bool match = true; + for (size_t j = 0; j < pattern_bytes.size(); ++j) { + if (pattern_bytes[j] != -1 && regionBase[i + j] != static_cast(pattern_bytes[j])) { + match = false; + break; + } + } + + if (match) { + uint8_t* result = regionBase + i; + return result; + } + } + } + + current = reinterpret_cast(mbi.BaseAddress) + mbi.RegionSize; + } + return nullptr; +} + +void Memory::AOBScanBatch(const std::vector& entries, std::shared_ptr logger) { + for (auto scanEntry = entries.begin(); scanEntry != entries.end(); ++scanEntry) { + if (*scanEntry->address) continue; + + std::string decryptedSignature = scanEntry->getSignature(); + uint8_t* result = nullptr; + if (scanEntry == entries.begin()) // Only log module and area scanned once + result = Memory::AOBScan(scanEntry->moduleName, decryptedSignature, scanEntry->protection, logger); + else result = Memory::AOBScan(scanEntry->moduleName, decryptedSignature, scanEntry->protection); + + if (!result) { + logger->warn( + "{} signature not found. Maybe your game has been updated and is no more compatible with this plugin.", + scanEntry->featureName); + continue; + } + // Write final signature address + uintptr_t finalAddress = reinterpret_cast(result) + scanEntry->offset; + *scanEntry->address = reinterpret_cast(finalAddress); + + LOG_SIGNATURE_FOUND(scanEntry->featureName, finalAddress); + } +} + +void Memory::OffsetScanBatch(const std::vector& entries, uint8_t* baseModule, + std::shared_ptr logger, const std::string& moduleName) { + for (const auto& scanEntry : entries) { + if (!scanEntry.outAddress) continue; + + // Scan Unreal Engine Objects and functions + *scanEntry.outAddress = Memory::AOBScan(moduleName, scanEntry.getSignature(), scanEntry.protection); + if (!*scanEntry.outAddress) { + logger->warn("{} signature not found. Maybe your game has been updated and is no more compatible with this plugin.", scanEntry.name); + continue; + } + + UT::uint32 calculatedOffset = 0; + // Calculate offset according type + switch (scanEntry.calcType) { + case OffsetCalcType::GetOffsetFromOpcode: { + calculatedOffset = static_cast(Memory::GetOffsetFromOpcode(*scanEntry.outAddress + scanEntry.opcodeOffset) - baseModule); + break; + } + case OffsetCalcType::UE_CalculateOffset: { + auto opt = UE::CalculateOffset(moduleName, *scanEntry.outAddress); + if (!opt) continue; + calculatedOffset = *opt; + break; + } + } + // Write final Unreal Engine offset + if (scanEntry.outOffset) *scanEntry.outOffset = calculatedOffset; + + logger->info("{} offset is: 0x{:X}", scanEntry.name, calculatedOffset); + } +} + +PVOID Memory::SetupOrClearHardwareBreakPointForAllThreads(uintptr_t targetAddress, PVOID vehHandle, bool enable, PVECTORED_EXCEPTION_HANDLER pVEH, int hwIndex) +{ + DWORD pid = GetCurrentProcessId(); + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); + if (snapshot == INVALID_HANDLE_VALUE) return nullptr; + + THREADENTRY32 te; + te.dwSize = sizeof(te); + + // Add VectoredExceptionHandler + if (enable && !vehHandle && pVEH) + vehHandle = AddVectoredExceptionHandler(1, pVEH); + + if (Thread32First(snapshot, &te)) { + do { + if (te.th32OwnerProcessID != pid) continue; + + HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID); + if (!hThread) continue; + + CONTEXT ctx = {}; + ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; + + if (GetThreadContext(hThread, &ctx)) { + if (enable) { + switch (hwIndex) { + case 0: ctx.Dr0 = targetAddress; break; // Set Hardware breakpoint #1 + case 1: ctx.Dr1 = targetAddress; break; // Set Hardware breakpoint #2 + case 2: ctx.Dr2 = targetAddress; break; // Set Hardware breakpoint #3 + case 3: ctx.Dr3 = targetAddress; break; // Set Hardware breakpoint #4 + default: break; + } + ctx.Dr7 |= (1ULL << (hwIndex * 2)); // activate hardware breakpoint + } + else { + switch (hwIndex) { + case 0: ctx.Dr0 = 0; break; // Unset Hardware breakpoint #1 + case 1: ctx.Dr1 = 0; break; // Unset Hardware breakpoint #2 + case 2: ctx.Dr2 = 0; break; // Unset Hardware breakpoint #3 + case 3: ctx.Dr3 = 0; break; // Unset Hardware breakpoint #4 + default: break; + } + ctx.Dr7 &= ~(1ULL << (hwIndex * 2)); // deactivate hardware breakpoint + } + + SetThreadContext(hThread, &ctx); + } + + CloseHandle(hThread); + + } while (Thread32Next(snapshot, &te)); + } + + CloseHandle(snapshot); + + // Remove VectoredExceptionHandler + if (!enable && vehHandle) { + RemoveVectoredExceptionHandler(vehHandle); + vehHandle = nullptr; + } + + return vehHandle; +} \ No newline at end of file diff --git a/libs/Memory/Memory.hpp b/libs/Memory/Memory.hpp new file mode 100644 index 0000000..801aab7 --- /dev/null +++ b/libs/Memory/Memory.hpp @@ -0,0 +1,184 @@ +#pragma once +#include +#include +#include +#include + +#define AUTO_ASSEMBLE_TRAMPOLINE(ADDRESS, TRAMPOLINE_LENGTH, INSTRUCTIONS) \ +do { \ +auto allocMemory = Memory::AllocateNearbyMemory(ADDRESS, sizeof INSTRUCTIONS + 14); \ +Memory::CreateTrampoline(ADDRESS, allocMemory, TRAMPOLINE_LENGTH); \ +Memory::WriteInstructions(allocMemory, INSTRUCTIONS, sizeof INSTRUCTIONS, ADDRESS + TRAMPOLINE_LENGTH); \ +} while (false) + +namespace UT { // Typedef used by Unreal Engine + typedef int8_t int8; + typedef int16_t int16; + typedef int32_t int32; + typedef int64_t int64; + + typedef uint8_t uint8; + typedef uint16_t uint16; + typedef uint32_t uint32; + typedef uint64_t uint64; +} + +struct AOBScanEntry { + uint8_t** address; + std::function getSignature; + const char* featureName; + std::string moduleName = ""; // "" = main exe + DWORD protection = PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | + PAGE_READWRITE | PAGE_EXECUTE_WRITECOPY; + intptr_t offset = 0; +}; + +namespace AOBScan { + // Helper template to create entry with ObfuscatedString + template + static AOBScanEntry Make(uint8_t** addr, ObfStr& obf, const char* name, std::string module = "", DWORD prot = PAGE_EXECUTE_READ) { + return AOBScanEntry{ addr, [&, obf]() { return obf.decrypt(); }, name, module, prot, 0 }; + } +} + +enum class OffsetCalcType +{ + None, + GetOffsetFromOpcode, + UE_CalculateOffset +}; + +struct OffsetScanEntry +{ + uint8_t** outAddress; // Address where the pointer will be stored + std::function getSignature; // decrypted AOB + std::string name; // Name for the log (GWorlds ...) + OffsetCalcType calcType; // Method to calculate offset + UT::int32* outOffset = nullptr; // Offset pointer to update + size_t opcodeOffset = 0; // Relative offset for GetOffsetFromOpcode method + DWORD protection = PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | + PAGE_READWRITE | PAGE_EXECUTE_WRITECOPY; +}; + +namespace OffsetScan { + // Helper template to create an entry + template + static OffsetScanEntry Make(uint8_t** addr, ObfStr& obf, const char* featureName, + OffsetCalcType type, UT::int32* outOffsetPtr, + size_t opOffset = 0, DWORD prot = PAGE_EXECUTE_READ) { + return OffsetScanEntry{ + addr, + [&obf]() { return obf.decrypt(); }, // signature lambda + featureName, type, + outOffsetPtr, + opOffset, prot + }; + } +} + +class Memory +{ + public: + + /** + * Get offset from opcode. + * + * @param opcode : The address where the offset begins. + */ + static uint8_t* GetOffsetFromOpcode(uint8_t* opcode, int extraOffset = 0); + + /** + * Get Address from opcode. + * + * @param opcode : The opcode pointer targeted. + * @param dispOffset : The offset to target disp (4 bytes) in opcode. + * @param instructionLen : The total opcode length in bytes. + */ + static uint8_t* GetAddressFromOpcode(uint8_t* opcode, uint32_t dispOffset, uint32_t instructionLen); + + /** + * Converts flkoat 32 bits into a char*. + * + * @param value : The value to encode. + */ + static const char* Float32ToHexBytes(float value); + + /** + * Read x bytes in memory. + * + * @param address : The address to read. + * @param size : The size in bytes to read + * @std::vector : The bytes read. + */ + static std::vector ReadBytes(const void* addr, std::size_t size); + + /** + * Patch x bytes in memory. + * + * @param address : The address to patch. + * @param bytes : The bytes to patch + * @param len : The number of bytes to be patched + */ + static void PatchBytes(void* address, const char* bytes, size_t len); + + /** + * Restore x bytes in memory. + * + * @param address : The address to patch. + */ + static void RestoreBytes(void* address); + + /** + * Achieve an AOB scan in memory. + * + * @param module_name : The executable to scan. + * @param signature : The signature to search for (eg : 7F ?? F3 0F ?? ?? ?? F2) + * @param protect_flags : Page protection (PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_READWRITE | PAGE_EXECUTE_WRITECOPY) + * @param log : If any log is to be used + * @return uint_8* : Pointer to address where AOB is found. + */ + static uint8_t* AOBScan(const std::string& module_name, const std::string& signature, DWORD protect_flags, std::shared_ptr log = nullptr); + + /** + * Achieve an AOB scan in memory by batch. + * + * @param const std::vector& entries : AOB signatures of AOBScanEntry type. + * @param logger : If any log is to be used + */ + static void AOBScanBatch(const std::vector& entries, std::shared_ptr logger); + + /** + * Achieve an unreal offsets scan by batch. + * + * @param const std::vector& entries : AOB signatures of OffsetScanEntry type. + * @param baseModule : The starting address of module scanned. + * @param logger : If any log is to be used + * @param moduleName : The module targeted (.exe, .dll ...) + */ + static void OffsetScanBatch(const std::vector& entries, uint8_t* baseModule, + std::shared_ptr logger, const std::string& moduleName = ""); + + static std::string ByteToHexEscaped(const BYTE byte); + + /** + * Set or clear VEH hardware breakpoint. + * + * @param targetAddress : The memory target to set a VEH breakpoint. + * @param vehHandle : The VEH handle (nullptr when to set breakpoint or a handle when to unset + * @param enable : Set or unset the VEH debugger + * @param pVEH : The function where to detour (set to nullptr to unset) + * @param hwIndex : The hawdware breakpoint to set (0 - 4) + * @return hwIndex : The VEH breakpoint handle + */ + static PVOID SetupOrClearHardwareBreakPointForAllThreads(uintptr_t targetAddress, PVOID vehHandle, bool enable, PVECTORED_EXCEPTION_HANDLER pVEH = nullptr, int hwIndex = 0); + private: + static MODULEINFO WaitForModule(const std::string& module_name, int timeoutMs = 15000, int intervalMs = 500); + struct PatchInfo { + void* address; + std::vector originalBytes; + bool hasTrampoline = false; + void* trampolineDestination = nullptr; + }; + + static std::unordered_map patches; +}; diff --git a/libs/Memory/Memory.vcxproj b/libs/Memory/Memory.vcxproj new file mode 100644 index 0000000..bbe9882 --- /dev/null +++ b/libs/Memory/Memory.vcxproj @@ -0,0 +1,262 @@ + + + + + Debug + Win32 + + + Release_internal + Win32 + + + Release_internal + x64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {f9b5bbc6-67d4-4290-986f-08c6bac41ba3} + Memory + 10.0 + Memory + + + + StaticLibrary + true + v143 + Unicode + + + StaticLibrary + false + v143 + true + Unicode + + + StaticLibrary + false + v143 + true + Unicode + + + StaticLibrary + true + v143 + Unicode + + + StaticLibrary + false + v143 + true + Unicode + + + StaticLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)bin\$(Configuration)\ + + + $(SolutionDir)bin\$(Configuration)\ + + + $(SolutionDir)bin\$(Configuration)\ + + + $(SolutionDir)bin\$(Configuration)\ + + + $(SolutionDir)bin\$(Configuration)\ + + + $(SolutionDir)bin\$(Configuration)\ + + + + Level3 + true + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + + + $(SolutionDir)external;$(SolutionDir)UEngine;%(AdditionalIncludeDirectories) + + /utf-8 %(AdditionalOptions) + stdcpp20 + + + + + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + + + $(SolutionDir)external;$(SolutionDir)UEngine;%(AdditionalIncludeDirectories) + + /utf-8 %(AdditionalOptions) + stdcpp20 + + + + + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + + + $(SolutionDir)external;$(SolutionDir)UEngine;%(AdditionalIncludeDirectories) + + + /utf-8 %(AdditionalOptions) + stdcpp20 + + + + + true + + + + + Level3 + true + _DEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + + + $(SolutionDir)external;$(SolutionDir)UEngine;%(AdditionalIncludeDirectories) + + /utf-8 %(AdditionalOptions) + stdcpp20 + + + + + true + + + + + Level3 + true + true + true + NDEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + + + $(SolutionDir)external;$(SolutionDir)UEngine;%(AdditionalIncludeDirectories) + + /utf-8 %(AdditionalOptions) + stdcpp20 + + + + + true + + + + + Level3 + true + true + true + NDEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + + + $(SolutionDir)external;$(SolutionDir)UEngine;%(AdditionalIncludeDirectories) + + + /utf-8 %(AdditionalOptions) + stdcpp20 + + + + + true + + + + + + + + MY_VERBOSE_LOGS + MY_VERBOSE_LOGS + + + + + + \ No newline at end of file diff --git a/libs/Obfuscate/Obfuscate.vcxproj b/libs/Obfuscate/Obfuscate.vcxproj new file mode 100644 index 0000000..9cdaf10 --- /dev/null +++ b/libs/Obfuscate/Obfuscate.vcxproj @@ -0,0 +1,226 @@ + + + + + Debug + Win32 + + + Release_internal + Win32 + + + Release_internal + x64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {7e0aec88-78b4-43ea-bbef-216e00df1424} + Obfuscate + 10.0 + + + + StaticLibrary + true + v143 + Unicode + + + StaticLibrary + false + v143 + true + Unicode + + + StaticLibrary + false + v143 + true + Unicode + + + StaticLibrary + true + v143 + Unicode + + + StaticLibrary + false + v143 + true + Unicode + + + StaticLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + Level3 + true + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + + + stdcpp20 + + + + + + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + + + stdcpp20 + + + + + + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + + + stdcpp20 + + + + + + + true + + + + + Level3 + true + _DEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + + + stdcpp20 + + + + + + true + + + + + Level3 + true + true + true + NDEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + + + stdcpp20 + + + + + + true + + + + + Level3 + true + true + true + NDEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + + + stdcpp20 + + + + + + + true + + + + + + + + + + \ No newline at end of file diff --git a/libs/Obfuscate/ObfuscateString.h b/libs/Obfuscate/ObfuscateString.h new file mode 100644 index 0000000..9b420bc --- /dev/null +++ b/libs/Obfuscate/ObfuscateString.h @@ -0,0 +1,39 @@ +#pragma once +#include +#include + +template +class ObfuscatedString { +private: + std::array data; + +public: + + constexpr ObfuscatedString(const std::array& obfuscatedData) + : data(obfuscatedData) {} + + /** + * @brief Decrypt a string with a static key + */ + std::string decrypt() const { + std::string result; + result.resize(N - 1); // remove '\0' + + for (size_t i = 0; i < N - 1; ++i) + result[i] = data[i] ^ KEY; + + return result; + } +}; + +// Helper constexpr to encrypt at building +//template +template +constexpr auto make_obfuscated(const char(&str)[N]) +{ + std::array enc{}; + for (size_t i = 0; i < N; ++i) + enc[i] = str[i] ^ KEY; + + return ObfuscatedString(enc); +} diff --git a/libs/Obfuscate/framework.h b/libs/Obfuscate/framework.h new file mode 100644 index 0000000..afd886f --- /dev/null +++ b/libs/Obfuscate/framework.h @@ -0,0 +1,3 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN // Exclure les en-têtes Windows rarement utilisés diff --git a/libs/UEngine/UEMath.hpp b/libs/UEngine/UEMath.hpp new file mode 100644 index 0000000..0b13f12 --- /dev/null +++ b/libs/UEngine/UEMath.hpp @@ -0,0 +1,40 @@ +#pragma once +#include + + + +namespace UEMath +{ + struct Vector + { + float X; + float Y; + float Z; + }; + + struct Rotator + { + float Pitch; // X + float Yaw; // Y + float Roll; // Z + }; + + /** + * @brief Converts a rotator (Pitch, Yaw) to a forward direction vector. + * Computes a normalized forward vector from the given FRotator, + * using Pitch and Yaw angles (in degrees). + * @param rotator Input rotation (degrees). + * @return Forward direction vector. + */ + static inline Vector RotatorToForwardVector(const Rotator& rotator) + { + constexpr float DEG_TO_RAD = 3.14159265358979323846f / 180.0f; + + const float cp = cosf(rotator.Pitch * DEG_TO_RAD); + const float sp = sinf(rotator.Pitch * DEG_TO_RAD); + const float cy = cosf(rotator.Yaw * DEG_TO_RAD); + const float sy = sinf(rotator.Yaw * DEG_TO_RAD); + + return Vector(cp * cy, cp * sy, sp); + } +} \ No newline at end of file diff --git a/libs/UEngine/UEngine.cpp b/libs/UEngine/UEngine.cpp new file mode 100644 index 0000000..42a5328 --- /dev/null +++ b/libs/UEngine/UEngine.cpp @@ -0,0 +1,28 @@ +// UEngine.cpp : Defines all Unreal Engine static tools functions +// +#include +#include +#include +#include +#include +#include "UEngine.hpp" + +std::optional UE::CalculateOffset(const std::string& exeName, uint8_t* AOBResult) +{ + // Récupère la base du module via GetModuleHandleA + HMODULE hModule = GetModuleHandleA(exeName.c_str()); + if (!AOBResult) + return std::nullopt; + + if (!hModule) + hModule = GetModuleHandleA(nullptr); + + uintptr_t baseModule = reinterpret_cast(hModule); + uintptr_t AOBAbsoluteAdress = reinterpret_cast(AOBResult); + + if (AOBAbsoluteAdress < baseModule) + return std::nullopt; + + uintptr_t relativeOffset = AOBAbsoluteAdress - baseModule; + return static_cast(relativeOffset); +} \ No newline at end of file diff --git a/libs/UEngine/UEngine.hpp b/libs/UEngine/UEngine.hpp new file mode 100644 index 0000000..26192ff --- /dev/null +++ b/libs/UEngine/UEngine.hpp @@ -0,0 +1,12 @@ +class UE +{ +public: + /** + * Get offset calculated from an AOB scan result and an executable base address + * + * @param exeName : A string of the executable name + * @param AOBResult : A valid (uint8_t) pointer to the result of an AOB scan + * @return std::optional The offset calculated + */ + static std::optional CalculateOffset(const std::string& exeName, uint8_t* AOBResult); +}; diff --git a/libs/UEngine/UEngine.vcxproj b/libs/UEngine/UEngine.vcxproj new file mode 100644 index 0000000..3bf6313 --- /dev/null +++ b/libs/UEngine/UEngine.vcxproj @@ -0,0 +1,236 @@ + + + + + Debug + Win32 + + + Release_internal + Win32 + + + Release_internal + x64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + 17.0 + Win32Proj + {e6ea0264-0c8f-4050-88f0-02cd4f6ef457} + UEngine + 10.0 + UEngine + + + + StaticLibrary + true + v143 + Unicode + + + StaticLibrary + false + v143 + true + Unicode + + + StaticLibrary + false + v143 + true + Unicode + + + StaticLibrary + true + v143 + Unicode + + + StaticLibrary + false + v143 + true + Unicode + + + StaticLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)bin\$(Configuration)\ + + + $(SolutionDir)bin\$(Configuration)\ + + + $(SolutionDir)bin\$(Configuration)\ + + + $(SolutionDir)bin\$(Configuration)\ + + + $(SolutionDir)bin\$(Configuration)\ + + + $(SolutionDir)bin\$(Configuration)\ + + + + Level3 + true + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + Use + pch.h + stdcpp20 + + + + + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + Use + pch.h + stdcpp20 + + + + + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + Use + pch.h + stdcpp20 + + + + + true + + + + + Level3 + true + _DEBUG;_LIB;%(PreprocessorDefinitions) + true + Use + pch.h + stdcpp20 + + + + + true + + + + + Level3 + true + true + true + NDEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + + + stdcpp20 + + + + + true + + + + + Level3 + true + true + true + NDEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + + + stdcpp20 + + + + + true + + + + + + \ No newline at end of file diff --git a/libs/UEngine/UEvars.hpp b/libs/UEngine/UEvars.hpp new file mode 100644 index 0000000..c549808 --- /dev/null +++ b/libs/UEngine/UEvars.hpp @@ -0,0 +1,6 @@ + +// Common Unreal Engine variables used +static uint8_t* GWorldaddress = nullptr; +inline uint8_t* GObjectsaddress = nullptr; +inline uint8_t* AppendStringaddress = nullptr; +inline uint8_t* ProcessEventaddress = nullptr; \ No newline at end of file