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