// 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; }