From a6288aad4e6c32d27f99b5cae7b6f1d7546de7f1 Mon Sep 17 00:00:00 2001 From: Emmanuel AYME Date: Thu, 31 Jul 2025 10:38:51 +0200 Subject: [PATCH 1/2] Added WuchangFF project. --- Reshade Plugins Core.sln | 10 + WuchangFF/WuchangFF.vcxproj | 231 ++++++++++++++++++++ WuchangFF/dllmain.cpp | 410 ++++++++++++++++++++++++++++++++++++ 3 files changed, 651 insertions(+) create mode 100644 WuchangFF/WuchangFF.vcxproj create mode 100644 WuchangFF/dllmain.cpp diff --git a/Reshade Plugins Core.sln b/Reshade Plugins Core.sln index 738e920..70a1e18 100644 --- a/Reshade Plugins Core.sln +++ b/Reshade Plugins Core.sln @@ -39,6 +39,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TLOU", "TLOU\TLOU.vcxproj", EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Starfield", "Starfield\Starfield.vcxproj", "{A41D75D0-D4F9-4688-93EE-C33CBC266F52}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Wuchang FF", "WuchangFF\WuchangFF.vcxproj", "{C3F58709-7AD9-4361-9C06-44A3BFFB9CE3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -183,6 +185,14 @@ Global {A41D75D0-D4F9-4688-93EE-C33CBC266F52}.Release|x64.Build.0 = Release|x64 {A41D75D0-D4F9-4688-93EE-C33CBC266F52}.Release|x86.ActiveCfg = Release|Win32 {A41D75D0-D4F9-4688-93EE-C33CBC266F52}.Release|x86.Build.0 = Release|Win32 + {C3F58709-7AD9-4361-9C06-44A3BFFB9CE3}.Debug|x64.ActiveCfg = Debug|x64 + {C3F58709-7AD9-4361-9C06-44A3BFFB9CE3}.Debug|x64.Build.0 = Debug|x64 + {C3F58709-7AD9-4361-9C06-44A3BFFB9CE3}.Debug|x86.ActiveCfg = Debug|Win32 + {C3F58709-7AD9-4361-9C06-44A3BFFB9CE3}.Debug|x86.Build.0 = Debug|Win32 + {C3F58709-7AD9-4361-9C06-44A3BFFB9CE3}.Release|x64.ActiveCfg = Release|x64 + {C3F58709-7AD9-4361-9C06-44A3BFFB9CE3}.Release|x64.Build.0 = Release|x64 + {C3F58709-7AD9-4361-9C06-44A3BFFB9CE3}.Release|x86.ActiveCfg = Release|Win32 + {C3F58709-7AD9-4361-9C06-44A3BFFB9CE3}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/WuchangFF/WuchangFF.vcxproj b/WuchangFF/WuchangFF.vcxproj new file mode 100644 index 0000000..47dcc12 --- /dev/null +++ b/WuchangFF/WuchangFF.vcxproj @@ -0,0 +1,231 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {C3F58709-7AD9-4361-9C06-44A3BFFB9CE3} + Wuchang FF + 10.0 + Wuchang FF + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + WuchangFFCore + .dll + + + WuchangFFCore + .dll + + + WuchangFFCore + .dll + + + WuchangFFCore + .dll + + + + Level3 + true + + + true + NotUsing + + + + $(SolutionDir)Memory;$(SolutionDir)Maths;$(SolutionDir)Obfuscate;$(SolutionDir)external;$(SolutionDir)external\safetyhook\include;$(SolutionDir)external\zydis\dependencies\zycore\include;$(SolutionDir)external\zydis\include;$(SolutionDir)zydis\src;$(SolutionDir)external\MinHook\include;%(AdditionalIncludeDirectories) + /utf-8 %(AdditionalOptions) + MultiThreadedDLL + stdcpp23 + true + + + Windows + true + false + $(SolutionDir)external\zydis\Libs;$(SolutionDir)external\Maths;%(AdditionalLibraryDirectories) + Zydis.lib;Maths.lib;%(AdditionalDependencies) + + + + + Level3 + true + true + true + + + true + NotUsing + + + + $(SolutionDir)Memory;$(SolutionDir)Maths;$(SolutionDir)Obfuscate;$(SolutionDir)external;$(SolutionDir)external\safetyhook\include;$(SolutionDir)external\zydis\dependencies\zycore\include;$(SolutionDir)external\zydis\include;$(SolutionDir)zydis\src;$(SolutionDir)external\MinHook\include;%(AdditionalIncludeDirectories) + /utf-8 %(AdditionalOptions) + MultiThreadedDLL + stdcpp23 + true + + + Windows + true + false + $(SolutionDir)external\zydis\Libs;$(SolutionDir)external\Maths;%(AdditionalLibraryDirectories) + Zydis.lib;Maths.lib;%(AdditionalDependencies) + + + + + Level3 + true + + + true + NotUsing + + + + $(SolutionDir)Memory;$(SolutionDir)Maths;$(SolutionDir)Obfuscate;$(SolutionDir)external;$(SolutionDir)external\safetyhook\include;$(SolutionDir)external\zydis\dependencies\zycore\include;$(SolutionDir)external\zydis\include;$(SolutionDir)zydis\src;$(SolutionDir)external\MinHook\include;%(AdditionalIncludeDirectories) + /utf-8 %(AdditionalOptions) + MultiThreadedDLL + stdcpp23 + true + + + Windows + true + false + $(SolutionDir)external\zydis\Libs;$(SolutionDir)external\Maths;%(AdditionalLibraryDirectories) + Zydis.lib;Maths.lib;%(AdditionalDependencies) + + + + + Level3 + true + true + true + + + true + NotUsing + + + + $(SolutionDir)Memory;$(SolutionDir)Maths;$(SolutionDir)Obfuscate;$(SolutionDir)external;$(SolutionDir)external\safetyhook\include;$(SolutionDir)external\zydis\dependencies\zycore\include;$(SolutionDir)external\zydis\include;$(SolutionDir)zydis\src;$(SolutionDir)external\MinHook\include;%(AdditionalIncludeDirectories) + /utf-8 %(AdditionalOptions) + MultiThreadedDLL + stdcpp23 + true + + + Windows + true + false + $(SolutionDir)external\zydis\Libs;$(SolutionDir)external\Maths;%(AdditionalLibraryDirectories) + Zydis.lib;Maths.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + {f9b5bbc6-67d4-4290-986f-08c6bac41ba3} + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WuchangFF/dllmain.cpp b/WuchangFF/dllmain.cpp new file mode 100644 index 0000000..ea55898 --- /dev/null +++ b/WuchangFF/dllmain.cpp @@ -0,0 +1,410 @@ +// At this point code injection into the game results in crash. +// Do not use this plugin dll injection. + +#include "Memory.hpp"; +#include "Maths.hpp"; +#include "ObfuscateString.h" +#include +#include +#include +//#include +#include +#include +#include +#include +#include + + +// Constants +const std::string PLUGIN_NAME = "WuchangFF"; +const std::string PLUGIN_LOG = PLUGIN_NAME + ".log"; +const std::string gameExecutable = "Project_Plague-Win64-Shipping.exe"; +const float baseAspect = 1.777777791; + +// Logger +std::shared_ptr logger; + +// Screen informations +static int screenWidth = GetSystemMetrics(SM_CXSCREEN); +static int screenHeight = GetSystemMetrics(SM_CYSCREEN); +static float aspectRatio = (float)screenWidth / screenHeight; + +// Plugin states +static bool AOBScanDone = false; +static bool g_fix_enabled = false; +static bool g_fov_fix_enabled = false; +static bool g_aspect_ratio_fix_enabled = false; +static bool g_DOF_fix_enabled = false; +static int g_AdditionalValue = 0; + +// Shared values +static float g_FOV_In = 0; +static float g_Compensated_FOV = 0; +static float g_FOV_Out = 0; + +// AOB Scan pointers +static uint8_t* FOVaddress = nullptr; +static uint8_t* Aspectaddress = nullptr; +static uint8_t* DOFaddress = nullptr; + +// Hooking +//static SafetyHookMid FOVHook{}; +//static SafetyHookMid AspectRatioHook{}; + +// Prototypes +static void FOVFixEnabled(bool fix_enabled); +static void AspectRatioFixEnabled(bool fix_enabled); +static void DOFFixEnabled(bool fix_enabled); + +bool IsReadableExecutable(void* addr) +{ + MEMORY_BASIC_INFORMATION mbi; + if (VirtualQuery(addr, &mbi, sizeof(mbi))) + { + DWORD protect = mbi.Protect; + return (protect & PAGE_EXECUTE_READ) || (protect & PAGE_EXECUTE_READWRITE) || (protect & PAGE_EXECUTE_WRITECOPY); + } + return false; +} + +extern "C" __declspec(dllexport) void SetFixEnabled(bool enabled) +{ + g_fix_enabled = enabled; + if (g_fix_enabled && !AOBScanDone) { + logger->info("--------------- AOB scan started ---------------"); + if (FOVaddress == nullptr) { + constexpr auto FOVStringObfuscated = make_obfuscated<0x4A>("EB ?? F3 0F ?? ?? ?? ?? ?? ?? F3 0F ?? ?? ?? 0F ?? ?? 8B 83"); + FOVaddress = Memory::aob_scan(gameExecutable, FOVStringObfuscated.decrypt(), PAGE_EXECUTE_READ); + + if (!FOVaddress) + logger->warn("FOV signature not found. Maybe your game has been updated and is no more compatible with this plugin."); + else { + logger->info("FOV signature found at address: 0x{:X}.", reinterpret_cast(FOVaddress)); + FOVaddress += 0xa; // Offset for the target opcode + } + } + if (Aspectaddress == nullptr) { + if (FOVaddress) { + Aspectaddress = FOVaddress + 0x0b; + logger->info("Aspect ratio signature found at address: 0x{:X}.", reinterpret_cast(Aspectaddress)); + } + } + if (DOFaddress == nullptr) { + constexpr auto DOFStringObfuscated = make_obfuscated<0x4A>("8B ?? ?? 48 ?? ?? E8 ?? ?? ?? ?? 0F ?? ?? 48 6B ?? ?? 48 8D"); + DOFaddress = Memory::aob_scan(gameExecutable, DOFStringObfuscated.decrypt(), PAGE_EXECUTE_READ); + + if (!DOFaddress) + logger->warn("DOF signature not found. Maybe your game has been updated and is no more compatible with this plugin."); + else { + logger->info("DOF ratio signature found at address: 0x{:X}.", reinterpret_cast(DOFaddress)); + } + if (FOVaddress != nullptr && Aspectaddress != nullptr && DOFaddress != nullptr) + logger->info("All AOB signatures found. Ready to patch..."); + if (FOVaddress && Aspectaddress && DOFaddress) AOBScanDone = true; + logger->info("--------------- AOB scan finished ---------------"); + } + } + if (g_fix_enabled) { + if (FOVaddress) FOVFixEnabled(g_fov_fix_enabled || g_aspect_ratio_fix_enabled); + if (Aspectaddress) AspectRatioFixEnabled(g_aspect_ratio_fix_enabled); + if (DOFaddress) DOFFixEnabled(g_DOF_fix_enabled); + } + else { + if (FOVaddress) FOVFixEnabled(false); + if (Aspectaddress) AspectRatioFixEnabled(false); + if (DOFaddress) DOFFixEnabled(false); + logger->info("All fixes disabled."); + } +} + +// Setters for Reshade addon call +extern "C" __declspec(dllexport) void SetFOVFixEnabled(bool enabled, bool init) +{ + g_fov_fix_enabled = enabled; + if (!init) FOVFixEnabled(g_fov_fix_enabled || g_aspect_ratio_fix_enabled); // FOV fix must be enabled when aspect ratio is too to compensate FOV +} + +extern "C" __declspec(dllexport) void SetAspectRatioFixEnabled(bool enabled, bool init) +{ + g_aspect_ratio_fix_enabled = enabled; + if (!init) AspectRatioFixEnabled(g_aspect_ratio_fix_enabled); +} + +extern "C" __declspec(dllexport) void SetDOFFixEnabled(bool enabled, bool init) +{ + g_DOF_fix_enabled = enabled; + if (!init) DOFFixEnabled(g_DOF_fix_enabled); +} + +extern "C" __declspec(dllexport) void SetFOV(int fov) +{ + g_AdditionalValue = fov; +} + +// Getters for Reshade addon call +extern "C" __declspec(dllexport) float GetFOVIn() { + return g_FOV_In; +} + +extern "C" __declspec(dllexport) float GetCompensatedFOV() { + return g_Compensated_FOV; +} + +extern "C" __declspec(dllexport) float GetFOVOut() { + return g_FOV_Out; +} + +DWORD GetCurrentThreadIdSafe() { + return GetCurrentThreadId(); +} +DWORD GetMainThreadId(DWORD processId) { + DWORD mainThreadId = 0; + FILETIME earliestCreateTime = { MAXDWORD, MAXDWORD }; + + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); + if (snapshot == INVALID_HANDLE_VALUE) return 0; + + THREADENTRY32 te; + te.dwSize = sizeof(te); + + if (Thread32First(snapshot, &te)) { + do { + if (te.th32OwnerProcessID == processId) { + HANDLE hThread = OpenThread(THREAD_QUERY_INFORMATION, FALSE, te.th32ThreadID); + if (hThread) { + FILETIME createTime, exitTime, kernelTime, userTime; + if (GetThreadTimes(hThread, &createTime, &exitTime, &kernelTime, &userTime)) { + if (CompareFileTime(&createTime, &earliestCreateTime) < 0) { + earliestCreateTime = createTime; + mainThreadId = te.th32ThreadID; + } + } + CloseHandle(hThread); + } + } + } while (Thread32Next(snapshot, &te)); + } + + CloseHandle(snapshot); + return mainThreadId; +} + +void FreezeOtherThreads() { + DWORD currentThreadId = GetCurrentThreadId(); + DWORD currentProcessId = GetCurrentProcessId(); + DWORD mainThreadId = GetMainThreadId(currentProcessId); + + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); + if (snapshot == INVALID_HANDLE_VALUE) return; + + THREADENTRY32 te; + te.dwSize = sizeof(te); + + if (Thread32First(snapshot, &te)) { + do { + if (te.th32OwnerProcessID == currentProcessId && + te.th32ThreadID != currentThreadId && + te.th32ThreadID != mainThreadId) { + HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, te.th32ThreadID); + if (hThread) { + SuspendThread(hThread); + CloseHandle(hThread); + } + } + } while (Thread32Next(snapshot, &te)); + } + + CloseHandle(snapshot); +} + + +void ResumeThreads() { + DWORD currentThreadId = GetCurrentThreadId(); + DWORD currentProcessId = GetCurrentProcessId(); + + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); + if (snapshot == INVALID_HANDLE_VALUE) return; + + THREADENTRY32 te; + te.dwSize = sizeof(te); + + if (Thread32First(snapshot, &te)) { + do { + if (te.th32OwnerProcessID == currentProcessId && te.th32ThreadID != currentThreadId) { + HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, te.th32ThreadID); + if (hThread) { + ResumeThread(hThread); + CloseHandle(hThread); + } + } + } while (Thread32Next(snapshot, &te)); + } + + CloseHandle(snapshot); +} + +void* g_Trampoline = nullptr; + +void* AllocateNear(void* nearAddr, size_t size) { + SYSTEM_INFO sysInfo; + GetSystemInfo(&sysInfo); + + uintptr_t startAddr = reinterpret_cast(nearAddr); + uintptr_t minAddr = (startAddr > 0x7FFFFFFF) ? startAddr - 0x7FFFFFFF : 0; // -2Go + uintptr_t maxAddr = startAddr + 0x7FFFFFFF; // +2Go + + uintptr_t addr = minAddr; + while (addr < maxAddr) { + void* alloc = VirtualAlloc(reinterpret_cast(addr), size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + if (alloc != nullptr) { + logger->info("Allocated memory at 0x{:X} near 0x{:X}", reinterpret_cast(alloc), reinterpret_cast(nearAddr)); + return alloc; + } + addr += sysInfo.dwPageSize; + } + + logger->info("Failed to allocate memory near 0x{:X}", reinterpret_cast(nearAddr)); + return nullptr; +} + +void* CreateTrampoline(void* target_addr, size_t length = 5) { + if (length < 5) length = 5; // minimum 5 octets pour jump + + // Allouer mémoire proche + //void* trampoline = AllocateNear(target_addr, length + 5); // +5 octets pour jump retour + void* trampoline = AllocateNear(target_addr, length + 5); // +5 octets pour jump retour + if (!trampoline) { + logger->info("Failed to allocate trampoline memory"); + return nullptr; + } + + // Copier les octets originaux + memcpy(trampoline, target_addr, length); + + // Calculer l'adresse de retour (target_addr + length) + uintptr_t retAddr = reinterpret_cast(target_addr) + length; + uintptr_t jmpFrom = reinterpret_cast(trampoline) + length; + + // Ecrire un jump relatif 5 octets à la fin du trampoline pour revenir au code original + int32_t relAddr = static_cast(retAddr - (jmpFrom + 5)); // rel = dest - (src+5) + uint8_t* p = reinterpret_cast(jmpFrom); + p[0] = 0xE9; // opcode JMP rel32 + memcpy(p + 1, &relAddr, sizeof(relAddr)); + + logger->info("Trampoline created at 0x{:X}, jump back to 0x{:X}", reinterpret_cast(trampoline), retAddr); + + return trampoline; +} + +//void HookFunction(void* target, void* destination, size_t length = 5) { +// DWORD oldProtect; +// VirtualProtect(target, length, PAGE_EXECUTE_READWRITE, &oldProtect); +// +// uintptr_t rel_addr = (uintptr_t)destination - (uintptr_t)target - 5; +// uint8_t patch[5] = { 0xE9 }; // jmp rel32 +// *reinterpret_cast(patch + 1) = static_cast(rel_addr); +// +// logger->info("Patching FOVaddress (0x{:X}) to jump to trampoline (0x{:X})", reinterpret_cast(target), reinterpret_cast(destination)); +// memcpy(target, patch, 5); +// VirtualProtect(target, length, oldProtect, &oldProtect); +//} +using FOVFuncType = void(__fastcall*)(); +FOVFuncType originalFOV = nullptr; + +static void FOVFixEnabled(bool fix_enabled) { + if (g_fix_enabled && fix_enabled && FOVaddress != nullptr) { + + //if (g_Trampoline) + // HookFunction(FOVaddress, g_Trampoline, 5); + + //if (!FOVHook) { // Hook only once + //if (IsReadableExecutable(FOVaddress)) { + //FOVHook = safetyhook::create_mid(FOVaddress, + // [](SafetyHookContext& ctx) { + // //g_FOV_In = ctx.xmm0.f32[0]; + // //if (g_aspect_ratio_fix_enabled) + // // g_Compensated_FOV = ctx.xmm0.f32[0] = Maths::CompensateHorizontalFOV(g_FOV_In, baseAspect, aspectRatio); + // //else + // // g_Compensated_FOV = ctx.xmm0.f32[0]; + // //g_FOV_Out = ctx.xmm0.f32[0] += (g_fov_fix_enabled ? g_AdditionalValue : 0); + // }); + //} + //} + //else FOVHook.enable(); + logger->info("FOV fix enabled"); + } + //if (!fix_enabled /* && FOVHook * / ) { + if (!fix_enabled) { + //FOVHook.disable(); + logger->info("FOV fix disabled"); + } +} + +static void AspectRatioFixEnabled(bool fix_enabled) { + //if (g_fix_enabled && fix_enabled && Aspectaddress != nullptr) { + // if (!AspectRatioHook) { + // AspectRatioHook = safetyhook::create_mid(Aspectaddress, + // [](SafetyHookContext& ctx) { + // ctx.rax = *reinterpret_cast(&aspectRatio); + // }); + // } + // else { + // AspectRatioHook.enable(); + // FOVFixEnabled(fix_enabled); // Usefull to compensate + // } + // logger->info("Aspect ratio fix enabled"); + //} + //if (!fix_enabled && AspectRatioHook && Aspectaddress) { + // AspectRatioHook.disable(); + // logger->info("Aspect ratio fix disabled"); + //} +} + +static void DOFFixEnabled(bool fix_enabled) { + //if (g_fix_enabled && fix_enabled && DOFaddress != nullptr) { + // Memory::PatchBytes(DOFaddress, "\x31\xFF\x90", 3); // xor edi,edi r.DepthOfFieldQuality = 0 + // logger->info("Depth of field fix enabled"); + //} + //if (!fix_enabled && DOFaddress) { + // Memory::RestoreBytes(DOFaddress); + // logger->info("Depth of field fix disabled"); + //} +} + + +static void InitializeLogger() +{ + try + { + std::filesystem::path log_path = std::filesystem::absolute(PLUGIN_LOG); + if (std::filesystem::exists(log_path)) + std::filesystem::remove(log_path); + logger = std::make_shared("Wuchang Fallen Feathers", std::make_shared(PLUGIN_LOG, 10 * 1024 * 1024, 1)); + logger->flush_on(spdlog::level::debug); // Flush automatically + } + catch (const spdlog::spdlog_ex& ex) + { + std::string plugin_error_message = "Could not open " + PLUGIN_LOG; + MessageBoxA(nullptr, plugin_error_message.c_str(), "Logger Error", MB_ICONERROR | MB_OK); + } +} + +// Standard dll entry +BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID) +{ + if (reason == DLL_PROCESS_ATTACH) + { + InitializeLogger(); + logger->info("Plugin {} loaded.", PLUGIN_NAME); + if (MH_Initialize() != MH_OK) { + logger->info("MinHook initialization failed!"); + return FALSE; + } + } + else if (reason == DLL_PROCESS_DETACH) + { + logger->info("Plugin {} unloaded.", PLUGIN_NAME); + spdlog::drop_all(); + } + return TRUE; +} \ No newline at end of file From 55ff20ebeb4c739eda525cc257514a1505ecfd13 Mon Sep 17 00:00:00 2001 From: Emmanuel AYME Date: Fri, 8 Aug 2025 10:46:57 +0200 Subject: [PATCH 2/2] Finalized Wuchang Faling Feathers core. --- WuchangFF/dllmain.cpp | 404 +++++++++++------------------------------- 1 file changed, 102 insertions(+), 302 deletions(-) diff --git a/WuchangFF/dllmain.cpp b/WuchangFF/dllmain.cpp index ea55898..2ff21b2 100644 --- a/WuchangFF/dllmain.cpp +++ b/WuchangFF/dllmain.cpp @@ -1,133 +1,116 @@ -// At this point code injection into the game results in crash. -// Do not use this plugin dll injection. - #include "Memory.hpp"; #include "Maths.hpp"; #include "ObfuscateString.h" #include #include #include -//#include #include -#include -#include -#include -#include // Constants const std::string PLUGIN_NAME = "WuchangFF"; const std::string PLUGIN_LOG = PLUGIN_NAME + ".log"; const std::string gameExecutable = "Project_Plague-Win64-Shipping.exe"; -const float baseAspect = 1.777777791; // Logger std::shared_ptr logger; -// Screen informations -static int screenWidth = GetSystemMetrics(SM_CXSCREEN); -static int screenHeight = GetSystemMetrics(SM_CYSCREEN); -static float aspectRatio = (float)screenWidth / screenHeight; - // Plugin states static bool AOBScanDone = false; static bool g_fix_enabled = false; -static bool g_fov_fix_enabled = false; -static bool g_aspect_ratio_fix_enabled = false; +static bool g_aspect_ratio_axis_constrain_fix_enabled = false; static bool g_DOF_fix_enabled = false; -static int g_AdditionalValue = 0; - -// Shared values -static float g_FOV_In = 0; -static float g_Compensated_FOV = 0; -static float g_FOV_Out = 0; +static bool g_Vignetting_fix_enabled = false; +static bool g_Fog_fix_enabled = false; // AOB Scan pointers -static uint8_t* FOVaddress = nullptr; -static uint8_t* Aspectaddress = nullptr; +static uint8_t* HORPLUSaddress = nullptr; static uint8_t* DOFaddress = nullptr; - -// Hooking -//static SafetyHookMid FOVHook{}; -//static SafetyHookMid AspectRatioHook{}; +static uint8_t* Vignettingaddress = nullptr; +static uint8_t* Fogaddress = nullptr; // Prototypes -static void FOVFixEnabled(bool fix_enabled); -static void AspectRatioFixEnabled(bool fix_enabled); +static void HORPlusFixEnabled(bool fix_enabled); static void DOFFixEnabled(bool fix_enabled); - -bool IsReadableExecutable(void* addr) -{ - MEMORY_BASIC_INFORMATION mbi; - if (VirtualQuery(addr, &mbi, sizeof(mbi))) - { - DWORD protect = mbi.Protect; - return (protect & PAGE_EXECUTE_READ) || (protect & PAGE_EXECUTE_READWRITE) || (protect & PAGE_EXECUTE_WRITECOPY); - } - return false; -} +static void VignettingFixEnabled(bool fix_enabled); +static void FogFixEnabled(bool fix_enabled); extern "C" __declspec(dllexport) void SetFixEnabled(bool enabled) { g_fix_enabled = enabled; if (g_fix_enabled && !AOBScanDone) { logger->info("--------------- AOB scan started ---------------"); - if (FOVaddress == nullptr) { - constexpr auto FOVStringObfuscated = make_obfuscated<0x4A>("EB ?? F3 0F ?? ?? ?? ?? ?? ?? F3 0F ?? ?? ?? 0F ?? ?? 8B 83"); - FOVaddress = Memory::aob_scan(gameExecutable, FOVStringObfuscated.decrypt(), PAGE_EXECUTE_READ); + if (HORPLUSaddress == nullptr) { + constexpr auto HORPLUSStringObfuscated = make_obfuscated<0x4A>("41 0F ?? ?? ?? ?? ?? ?? 48 8D ?? ?? ?? ?? ?? 4C ?? ?? 4D ?? ?? E8"); + HORPLUSaddress = Memory::aob_scan(gameExecutable, HORPLUSStringObfuscated.decrypt(), PAGE_EXECUTE_READ); - if (!FOVaddress) - logger->warn("FOV signature not found. Maybe your game has been updated and is no more compatible with this plugin."); - else { - logger->info("FOV signature found at address: 0x{:X}.", reinterpret_cast(FOVaddress)); - FOVaddress += 0xa; // Offset for the target opcode - } - } - if (Aspectaddress == nullptr) { - if (FOVaddress) { - Aspectaddress = FOVaddress + 0x0b; - logger->info("Aspect ratio signature found at address: 0x{:X}.", reinterpret_cast(Aspectaddress)); - } + if (!HORPLUSaddress) + logger->warn("HOR+ signature not found. Maybe your game has been updated and is no more compatible with this plugin."); + else + logger->info("HOR+ signature found at address: 0x{:X}.", reinterpret_cast(HORPLUSaddress)); } + if (DOFaddress == nullptr) { - constexpr auto DOFStringObfuscated = make_obfuscated<0x4A>("8B ?? ?? 48 ?? ?? E8 ?? ?? ?? ?? 0F ?? ?? 48 6B ?? ?? 48 8D"); + constexpr auto DOFStringObfuscated = make_obfuscated<0x4A>("48 ?? ?? 8B ?? ?? E8 ?? ?? ?? ?? 48 ?? ?? 48 6B ?? ?? 48 8D"); DOFaddress = Memory::aob_scan(gameExecutable, DOFStringObfuscated.decrypt(), PAGE_EXECUTE_READ); if (!DOFaddress) logger->warn("DOF signature not found. Maybe your game has been updated and is no more compatible with this plugin."); else { - logger->info("DOF ratio signature found at address: 0x{:X}.", reinterpret_cast(DOFaddress)); + logger->info("DOF signature found at address: 0x{:X}.", reinterpret_cast(DOFaddress)); + DOFaddress += 0x3; } - if (FOVaddress != nullptr && Aspectaddress != nullptr && DOFaddress != nullptr) - logger->info("All AOB signatures found. Ready to patch..."); - if (FOVaddress && Aspectaddress && DOFaddress) AOBScanDone = true; - logger->info("--------------- AOB scan finished ---------------"); } + + if (Vignettingaddress == nullptr) { + constexpr auto VignettingStringObfuscated = make_obfuscated<0x4A>("8B ?? 83 ?? ?? 7D ?? 89 B3 ?? ?? ?? ?? EB"); + Vignettingaddress = Memory::aob_scan(gameExecutable, VignettingStringObfuscated.decrypt(), PAGE_EXECUTE_READ); + + if (!Vignettingaddress) + logger->warn("Vignetting signature not found. Maybe your game has been updated and is no more compatible with this plugin."); + else { + logger->info("Vignetting signature found at address: 0x{:X}.", reinterpret_cast(Vignettingaddress)); + } + } + + if (Fogaddress == nullptr) { + constexpr auto FogStringObfuscated = make_obfuscated<0x4A>("75 ?? B3 ?? EB ?? 32 ?? 48 8B ?? ?? ?? 48 ?? ?? 74 ?? E8 ?? ?? ?? ?? 0F ?? ?? 48 8B"); + Fogaddress = Memory::aob_scan(gameExecutable, FogStringObfuscated.decrypt(), PAGE_EXECUTE_READ); + + if (!Fogaddress) + logger->warn("Fog signature not found. Maybe your game has been updated and is no more compatible with this plugin."); + else { + logger->info("Fog signature found at address: 0x{:X}.", reinterpret_cast(Fogaddress)); + } + } + + if (HORPLUSaddress && DOFaddress && Vignettingaddress && Fogaddress) { + logger->info("All AOB signatures found. Ready to patch..."); + AOBScanDone = true; + } + + logger->info("--------------- AOB scan finished ---------------"); } if (g_fix_enabled) { - if (FOVaddress) FOVFixEnabled(g_fov_fix_enabled || g_aspect_ratio_fix_enabled); - if (Aspectaddress) AspectRatioFixEnabled(g_aspect_ratio_fix_enabled); + if (HORPLUSaddress) HORPlusFixEnabled(g_aspect_ratio_axis_constrain_fix_enabled); if (DOFaddress) DOFFixEnabled(g_DOF_fix_enabled); + if (Vignettingaddress) VignettingFixEnabled(g_Vignetting_fix_enabled); + if (Fogaddress) FogFixEnabled(g_Fog_fix_enabled); } else { - if (FOVaddress) FOVFixEnabled(false); - if (Aspectaddress) AspectRatioFixEnabled(false); + if (HORPLUSaddress) HORPlusFixEnabled(false); if (DOFaddress) DOFFixEnabled(false); + if (Vignettingaddress) VignettingFixEnabled(false); + if (Fogaddress) FogFixEnabled(false); logger->info("All fixes disabled."); } } // Setters for Reshade addon call -extern "C" __declspec(dllexport) void SetFOVFixEnabled(bool enabled, bool init) +extern "C" __declspec(dllexport) void SetARAxisConstrainFixEnabled(bool enabled, bool init) { - g_fov_fix_enabled = enabled; - if (!init) FOVFixEnabled(g_fov_fix_enabled || g_aspect_ratio_fix_enabled); // FOV fix must be enabled when aspect ratio is too to compensate FOV -} - -extern "C" __declspec(dllexport) void SetAspectRatioFixEnabled(bool enabled, bool init) -{ - g_aspect_ratio_fix_enabled = enabled; - if (!init) AspectRatioFixEnabled(g_aspect_ratio_fix_enabled); + g_aspect_ratio_axis_constrain_fix_enabled = enabled; + if (!init) HORPlusFixEnabled(g_aspect_ratio_axis_constrain_fix_enabled); } extern "C" __declspec(dllexport) void SetDOFFixEnabled(bool enabled, bool init) @@ -136,241 +119,62 @@ extern "C" __declspec(dllexport) void SetDOFFixEnabled(bool enabled, bool init) if (!init) DOFFixEnabled(g_DOF_fix_enabled); } -extern "C" __declspec(dllexport) void SetFOV(int fov) +extern "C" __declspec(dllexport) void SetVignettingFixEnabled(bool enabled, bool init) { - g_AdditionalValue = fov; + g_Vignetting_fix_enabled = enabled; + if (!init) VignettingFixEnabled(g_Vignetting_fix_enabled); } -// Getters for Reshade addon call -extern "C" __declspec(dllexport) float GetFOVIn() { - return g_FOV_In; +extern "C" __declspec(dllexport) void SetFogFixEnabled(bool enabled, bool init) +{ + g_Fog_fix_enabled = enabled; + if (!init) FogFixEnabled(g_Fog_fix_enabled); } -extern "C" __declspec(dllexport) float GetCompensatedFOV() { - return g_Compensated_FOV; -} - -extern "C" __declspec(dllexport) float GetFOVOut() { - return g_FOV_Out; -} - -DWORD GetCurrentThreadIdSafe() { - return GetCurrentThreadId(); -} -DWORD GetMainThreadId(DWORD processId) { - DWORD mainThreadId = 0; - FILETIME earliestCreateTime = { MAXDWORD, MAXDWORD }; - - HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); - if (snapshot == INVALID_HANDLE_VALUE) return 0; - - THREADENTRY32 te; - te.dwSize = sizeof(te); - - if (Thread32First(snapshot, &te)) { - do { - if (te.th32OwnerProcessID == processId) { - HANDLE hThread = OpenThread(THREAD_QUERY_INFORMATION, FALSE, te.th32ThreadID); - if (hThread) { - FILETIME createTime, exitTime, kernelTime, userTime; - if (GetThreadTimes(hThread, &createTime, &exitTime, &kernelTime, &userTime)) { - if (CompareFileTime(&createTime, &earliestCreateTime) < 0) { - earliestCreateTime = createTime; - mainThreadId = te.th32ThreadID; - } - } - CloseHandle(hThread); - } - } - } while (Thread32Next(snapshot, &te)); +// Memory patch fixes +static void HORPlusFixEnabled(bool fix_enabled) { + if (g_fix_enabled && fix_enabled && HORPLUSaddress) { + Memory::PatchBytes(HORPLUSaddress, "\x31\xD2\x90\x90\x90\x90\x90\x90", 8); // xor edx,edx AspectRatioAxisConstraint=AspectRatio_MaintainYFOV + logger->info("HOR+ fix enabled"); } - - CloseHandle(snapshot); - return mainThreadId; -} - -void FreezeOtherThreads() { - DWORD currentThreadId = GetCurrentThreadId(); - DWORD currentProcessId = GetCurrentProcessId(); - DWORD mainThreadId = GetMainThreadId(currentProcessId); - - HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); - if (snapshot == INVALID_HANDLE_VALUE) return; - - THREADENTRY32 te; - te.dwSize = sizeof(te); - - if (Thread32First(snapshot, &te)) { - do { - if (te.th32OwnerProcessID == currentProcessId && - te.th32ThreadID != currentThreadId && - te.th32ThreadID != mainThreadId) { - HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, te.th32ThreadID); - if (hThread) { - SuspendThread(hThread); - CloseHandle(hThread); - } - } - } while (Thread32Next(snapshot, &te)); + if (!fix_enabled && HORPLUSaddress) { + Memory::RestoreBytes(HORPLUSaddress); + logger->info("HOR+ fix disabled"); } - - CloseHandle(snapshot); -} - - -void ResumeThreads() { - DWORD currentThreadId = GetCurrentThreadId(); - DWORD currentProcessId = GetCurrentProcessId(); - - HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); - if (snapshot == INVALID_HANDLE_VALUE) return; - - THREADENTRY32 te; - te.dwSize = sizeof(te); - - if (Thread32First(snapshot, &te)) { - do { - if (te.th32OwnerProcessID == currentProcessId && te.th32ThreadID != currentThreadId) { - HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, te.th32ThreadID); - if (hThread) { - ResumeThread(hThread); - CloseHandle(hThread); - } - } - } while (Thread32Next(snapshot, &te)); - } - - CloseHandle(snapshot); -} - -void* g_Trampoline = nullptr; - -void* AllocateNear(void* nearAddr, size_t size) { - SYSTEM_INFO sysInfo; - GetSystemInfo(&sysInfo); - - uintptr_t startAddr = reinterpret_cast(nearAddr); - uintptr_t minAddr = (startAddr > 0x7FFFFFFF) ? startAddr - 0x7FFFFFFF : 0; // -2Go - uintptr_t maxAddr = startAddr + 0x7FFFFFFF; // +2Go - - uintptr_t addr = minAddr; - while (addr < maxAddr) { - void* alloc = VirtualAlloc(reinterpret_cast(addr), size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); - if (alloc != nullptr) { - logger->info("Allocated memory at 0x{:X} near 0x{:X}", reinterpret_cast(alloc), reinterpret_cast(nearAddr)); - return alloc; - } - addr += sysInfo.dwPageSize; - } - - logger->info("Failed to allocate memory near 0x{:X}", reinterpret_cast(nearAddr)); - return nullptr; -} - -void* CreateTrampoline(void* target_addr, size_t length = 5) { - if (length < 5) length = 5; // minimum 5 octets pour jump - - // Allouer mémoire proche - //void* trampoline = AllocateNear(target_addr, length + 5); // +5 octets pour jump retour - void* trampoline = AllocateNear(target_addr, length + 5); // +5 octets pour jump retour - if (!trampoline) { - logger->info("Failed to allocate trampoline memory"); - return nullptr; - } - - // Copier les octets originaux - memcpy(trampoline, target_addr, length); - - // Calculer l'adresse de retour (target_addr + length) - uintptr_t retAddr = reinterpret_cast(target_addr) + length; - uintptr_t jmpFrom = reinterpret_cast(trampoline) + length; - - // Ecrire un jump relatif 5 octets à la fin du trampoline pour revenir au code original - int32_t relAddr = static_cast(retAddr - (jmpFrom + 5)); // rel = dest - (src+5) - uint8_t* p = reinterpret_cast(jmpFrom); - p[0] = 0xE9; // opcode JMP rel32 - memcpy(p + 1, &relAddr, sizeof(relAddr)); - - logger->info("Trampoline created at 0x{:X}, jump back to 0x{:X}", reinterpret_cast(trampoline), retAddr); - - return trampoline; -} - -//void HookFunction(void* target, void* destination, size_t length = 5) { -// DWORD oldProtect; -// VirtualProtect(target, length, PAGE_EXECUTE_READWRITE, &oldProtect); -// -// uintptr_t rel_addr = (uintptr_t)destination - (uintptr_t)target - 5; -// uint8_t patch[5] = { 0xE9 }; // jmp rel32 -// *reinterpret_cast(patch + 1) = static_cast(rel_addr); -// -// logger->info("Patching FOVaddress (0x{:X}) to jump to trampoline (0x{:X})", reinterpret_cast(target), reinterpret_cast(destination)); -// memcpy(target, patch, 5); -// VirtualProtect(target, length, oldProtect, &oldProtect); -//} -using FOVFuncType = void(__fastcall*)(); -FOVFuncType originalFOV = nullptr; - -static void FOVFixEnabled(bool fix_enabled) { - if (g_fix_enabled && fix_enabled && FOVaddress != nullptr) { - - //if (g_Trampoline) - // HookFunction(FOVaddress, g_Trampoline, 5); - - //if (!FOVHook) { // Hook only once - //if (IsReadableExecutable(FOVaddress)) { - //FOVHook = safetyhook::create_mid(FOVaddress, - // [](SafetyHookContext& ctx) { - // //g_FOV_In = ctx.xmm0.f32[0]; - // //if (g_aspect_ratio_fix_enabled) - // // g_Compensated_FOV = ctx.xmm0.f32[0] = Maths::CompensateHorizontalFOV(g_FOV_In, baseAspect, aspectRatio); - // //else - // // g_Compensated_FOV = ctx.xmm0.f32[0]; - // //g_FOV_Out = ctx.xmm0.f32[0] += (g_fov_fix_enabled ? g_AdditionalValue : 0); - // }); - //} - //} - //else FOVHook.enable(); - logger->info("FOV fix enabled"); - } - //if (!fix_enabled /* && FOVHook * / ) { - if (!fix_enabled) { - //FOVHook.disable(); - logger->info("FOV fix disabled"); - } -} - -static void AspectRatioFixEnabled(bool fix_enabled) { - //if (g_fix_enabled && fix_enabled && Aspectaddress != nullptr) { - // if (!AspectRatioHook) { - // AspectRatioHook = safetyhook::create_mid(Aspectaddress, - // [](SafetyHookContext& ctx) { - // ctx.rax = *reinterpret_cast(&aspectRatio); - // }); - // } - // else { - // AspectRatioHook.enable(); - // FOVFixEnabled(fix_enabled); // Usefull to compensate - // } - // logger->info("Aspect ratio fix enabled"); - //} - //if (!fix_enabled && AspectRatioHook && Aspectaddress) { - // AspectRatioHook.disable(); - // logger->info("Aspect ratio fix disabled"); - //} } static void DOFFixEnabled(bool fix_enabled) { - //if (g_fix_enabled && fix_enabled && DOFaddress != nullptr) { - // Memory::PatchBytes(DOFaddress, "\x31\xFF\x90", 3); // xor edi,edi r.DepthOfFieldQuality = 0 - // logger->info("Depth of field fix enabled"); - //} - //if (!fix_enabled && DOFaddress) { - // Memory::RestoreBytes(DOFaddress); - // logger->info("Depth of field fix disabled"); - //} + if (g_fix_enabled && fix_enabled && DOFaddress) { + Memory::PatchBytes(DOFaddress, "\x31\xFF\x90", 3); // xor edi,edi r.DepthOfFieldQuality = 0 + logger->info("Depth of field fix enabled"); + } + if (!fix_enabled && DOFaddress) { + Memory::RestoreBytes(DOFaddress); + logger->info("Depth of field fix disabled"); + } } +static void VignettingFixEnabled(bool fix_enabled) { + if (g_fix_enabled && fix_enabled && Vignettingaddress) { + Memory::PatchBytes(Vignettingaddress, "\x31\xC9", 2); // xor ecx,ecx r.Tonemapper.Quality=0 + logger->info("Vignetting fix enabled"); + } + if (!fix_enabled && Vignettingaddress) { + Memory::RestoreBytes(Vignettingaddress); + logger->info("Vignetting fix disabled"); + } +} + +static void FogFixEnabled(bool fix_enabled) { + if (g_fix_enabled && fix_enabled && Fogaddress) { + Memory::PatchBytes(Fogaddress, "\xEB", 1); // jmp "Project_Plague-Win64-Shipping.exe"+28E280E + logger->info("Fog fix enabled"); + } + if (!fix_enabled && Fogaddress) { + Memory::RestoreBytes(Fogaddress); + logger->info("Fog fix disabled"); + } +} static void InitializeLogger() { @@ -396,10 +200,6 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID) { InitializeLogger(); logger->info("Plugin {} loaded.", PLUGIN_NAME); - if (MH_Initialize() != MH_OK) { - logger->info("MinHook initialization failed!"); - return FALSE; - } } else if (reason == DLL_PROCESS_DETACH) {