From 0e7031674017d8d8798248b5c29db9bbdd4efa26 Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 16 Jan 2024 00:15:58 -0800 Subject: [PATCH 1/2] GamePass: DelayLoad certain DLLs to fix startup crash --- CMakeLists.txt | 22 +++++++++++----------- cmake.toml | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b0255ebda..ee76a9f20 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2140,7 +2140,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework OUTPUT_NAME dinput8 LINK_FLAGS - "/DELAYLOAD:openvr_api.dll /DELAYLOAD:openxr_loader.dll" + "/DELAYLOAD:openvr_api.dll /DELAYLOAD:openxr_loader.dll /DELAYLOAD:d3d11.dll /DELAYLOAD:d3d12.dll /DELAYLOAD:D3DCOMPILER_47.dll" RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO @@ -2337,7 +2337,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework OUTPUT_NAME dinput8 LINK_FLAGS - "/DELAYLOAD:openvr_api.dll /DELAYLOAD:openxr_loader.dll" + "/DELAYLOAD:openvr_api.dll /DELAYLOAD:openxr_loader.dll /DELAYLOAD:d3d11.dll /DELAYLOAD:d3d12.dll /DELAYLOAD:D3DCOMPILER_47.dll" RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO @@ -3971,7 +3971,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework OUTPUT_NAME dinput8 LINK_FLAGS - "/DELAYLOAD:openvr_api.dll /DELAYLOAD:openxr_loader.dll" + "/DELAYLOAD:openvr_api.dll /DELAYLOAD:openxr_loader.dll /DELAYLOAD:d3d11.dll /DELAYLOAD:d3d12.dll /DELAYLOAD:D3DCOMPILER_47.dll" RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO @@ -4168,7 +4168,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework OUTPUT_NAME dinput8 LINK_FLAGS - "/DELAYLOAD:openvr_api.dll /DELAYLOAD:openxr_loader.dll" + "/DELAYLOAD:openvr_api.dll /DELAYLOAD:openxr_loader.dll /DELAYLOAD:d3d11.dll /DELAYLOAD:d3d12.dll /DELAYLOAD:D3DCOMPILER_47.dll" RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO @@ -4365,7 +4365,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework OUTPUT_NAME dinput8 LINK_FLAGS - "/DELAYLOAD:openvr_api.dll /DELAYLOAD:openxr_loader.dll" + "/DELAYLOAD:openvr_api.dll /DELAYLOAD:openxr_loader.dll /DELAYLOAD:d3d11.dll /DELAYLOAD:d3d12.dll /DELAYLOAD:D3DCOMPILER_47.dll" RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO @@ -6719,7 +6719,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework OUTPUT_NAME dinput8 LINK_FLAGS - "/DELAYLOAD:openvr_api.dll /DELAYLOAD:openxr_loader.dll" + "/DELAYLOAD:openvr_api.dll /DELAYLOAD:openxr_loader.dll /DELAYLOAD:d3d11.dll /DELAYLOAD:d3d12.dll /DELAYLOAD:D3DCOMPILER_47.dll" RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO @@ -6916,7 +6916,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework OUTPUT_NAME dinput8 LINK_FLAGS - "/DELAYLOAD:openvr_api.dll /DELAYLOAD:openxr_loader.dll" + "/DELAYLOAD:openvr_api.dll /DELAYLOAD:openxr_loader.dll /DELAYLOAD:d3d11.dll /DELAYLOAD:d3d12.dll /DELAYLOAD:D3DCOMPILER_47.dll" RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO @@ -7833,7 +7833,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework OUTPUT_NAME dinput8 LINK_FLAGS - "/DELAYLOAD:openvr_api.dll /DELAYLOAD:openxr_loader.dll" + "/DELAYLOAD:openvr_api.dll /DELAYLOAD:openxr_loader.dll /DELAYLOAD:d3d11.dll /DELAYLOAD:d3d12.dll /DELAYLOAD:D3DCOMPILER_47.dll" RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO @@ -8748,7 +8748,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework OUTPUT_NAME dinput8 LINK_FLAGS - "/DELAYLOAD:openvr_api.dll /DELAYLOAD:openxr_loader.dll" + "/DELAYLOAD:openvr_api.dll /DELAYLOAD:openxr_loader.dll /DELAYLOAD:d3d11.dll /DELAYLOAD:d3d12.dll /DELAYLOAD:D3DCOMPILER_47.dll" RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO @@ -9665,7 +9665,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework OUTPUT_NAME dinput8 LINK_FLAGS - "/DELAYLOAD:openvr_api.dll /DELAYLOAD:openxr_loader.dll" + "/DELAYLOAD:openvr_api.dll /DELAYLOAD:openxr_loader.dll /DELAYLOAD:d3d11.dll /DELAYLOAD:d3d12.dll /DELAYLOAD:D3DCOMPILER_47.dll" RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO @@ -10582,7 +10582,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework OUTPUT_NAME dinput8 LINK_FLAGS - "/DELAYLOAD:openvr_api.dll /DELAYLOAD:openxr_loader.dll" + "/DELAYLOAD:openvr_api.dll /DELAYLOAD:openxr_loader.dll /DELAYLOAD:d3d11.dll /DELAYLOAD:d3d12.dll /DELAYLOAD:D3DCOMPILER_47.dll" RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO diff --git a/cmake.toml b/cmake.toml index df8c3fd92..166345030 100644 --- a/cmake.toml +++ b/cmake.toml @@ -233,7 +233,7 @@ link-libraries = [ [template.game.properties] OUTPUT_NAME = "dinput8" -LINK_FLAGS = "/DELAYLOAD:openvr_api.dll /DELAYLOAD:openxr_loader.dll" +LINK_FLAGS = "/DELAYLOAD:openvr_api.dll /DELAYLOAD:openxr_loader.dll /DELAYLOAD:d3d11.dll /DELAYLOAD:d3d12.dll /DELAYLOAD:D3DCOMPILER_47.dll" RUNTIME_OUTPUT_DIRECTORY_RELEASE = "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO = "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" LIBRARY_OUTPUT_DIRECTORY_RELEASE = "${CMAKE_BINARY_DIR}/lib/${CMKR_TARGET}" From 1ed9f31177926139e7fc19e8b851c3fb85c34658 Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 16 Jan 2024 06:27:34 -0800 Subject: [PATCH 2/2] GamePass (RE2): Enigma workaround + fixes for new build --- shared/sdk/REContext.cpp | 18 +++++ shared/sdk/REGlobals.cpp | 3 +- src/Mod.hpp | 1 + src/Mods.cpp | 29 +++++++ src/Mods.hpp | 1 + src/REFramework.cpp | 47 ++++++++++- src/REFramework.hpp | 4 + src/mods/Hooks.cpp | 4 +- src/mods/IntegrityCheckBypass.cpp | 125 ++++++++++++++++++++++++++++++ src/mods/IntegrityCheckBypass.hpp | 8 ++ src/mods/VR.cpp | 12 ++- src/mods/VR.hpp | 2 +- 12 files changed, 247 insertions(+), 7 deletions(-) diff --git a/shared/sdk/REContext.cpp b/shared/sdk/REContext.cpp index 4150ba759..9261da3f7 100644 --- a/shared/sdk/REContext.cpp +++ b/shared/sdk/REContext.cpp @@ -168,8 +168,26 @@ namespace sdk { std::vector invoke_patterns { "40 53 48 83 ec 20 48 8b 41 30 4c 8b d2 48 8b 51 40 48 8b d9 4c 8b 00 48 8b 41 10", // RE2 - MHRise v1 "40 53 48 83 ec 20 48 8b 41 10 48 8b da 8b 48 08", // MHRise Sunbreak/newer games? + "40 53 48 83 EC ? 48 8B 41 30 4C 8B D2 4C 8B 49 10 48 8B D9 48 8B 51 40 49 8B CA 4C 8B 00 41 FF" // seen in game pass RE2 }; + // ok so if these patterns above are failing, we can find the invoke table by looking for these set of instructions: + // 8D 56 FF lea edx, [rsi-1] + // 48 8B CF mov rcx, rdi + // E8 67 FD 6C 01 call sub_[removed] + + // Then, scroll up from here, and you'll see something that looks like this: + /* + E8 D4 D6 6C 01 call sub_[removed] + 41 B8 53 15 00 00 mov r8d, 1553h ; dead giveaway is a number like this that is the size of the invoke table + 48 8D 15 37 C7 6B 06 lea rdx, g_invokeTbl ; this is what we want + 48 8B 08 mov rcx, [rax] + 48 8B 05 2D 47 86 06 mov rax, cs:off_[removed] + 48 89 08 mov [rax], rcx + 48 8B CF mov rcx, rdi + */ + + std::optional method_inside_invoke_tbl{std::nullopt}; for (const auto& pat : invoke_patterns) { diff --git a/shared/sdk/REGlobals.cpp b/shared/sdk/REGlobals.cpp index 3efb927c2..030db4c18 100644 --- a/shared/sdk/REGlobals.cpp +++ b/shared/sdk/REGlobals.cpp @@ -70,7 +70,8 @@ REGlobals::REGlobals() { if (name.find(game_namespace("SingletonBehavior`1")) != std::string::npos || name.find(game_namespace("SingletonBehaviorRoot`1")) != std::string::npos || - name.find(game_namespace("SnowSingletonBehaviorRoot`1")) != std::string::npos) + name.find(game_namespace("SnowSingletonBehaviorRoot`1")) != std::string::npos || + name.find(game_namespace("RopewaySingletonBehaviorRoot`1")) != std::string::npos) { const auto type_definition = utility::re_type::get_type_definition(t); diff --git a/src/Mod.hpp b/src/Mod.hpp index a0be1373a..f48df9cc2 100644 --- a/src/Mod.hpp +++ b/src/Mod.hpp @@ -362,6 +362,7 @@ class Mod { // Called when REFramework::initialize finishes in the first render frame // Returns an error string if it fails virtual std::optional on_initialize() { return std::nullopt; }; + virtual std::optional on_initialize_d3d_thread() { return std::nullopt; }; virtual void on_lua_state_created(sol::state& lua) {}; virtual void on_lua_state_destroyed(sol::state& lua) {}; diff --git a/src/Mods.cpp b/src/Mods.cpp index 7926ba329..01dbd4080 100644 --- a/src/Mods.cpp +++ b/src/Mods.cpp @@ -90,6 +90,35 @@ std::optional Mods::on_initialize() const { return std::nullopt; } + +std::optional Mods::on_initialize_d3d_thread() const { + std::scoped_lock _{g_framework->get_hook_monitor_mutex()}; + + utility::Config cfg{ (REFramework::get_persistent_dir() / "re2_fw_config.txt").string() }; + + // once here to at least setup the values + for (auto& mod : m_mods) { + spdlog::info("{:s}::on_config_load()", mod->get_name().data()); + mod->on_config_load(cfg); + } + + for (auto& mod : m_mods) { + spdlog::info("{:s}::on_initialize_d3d_thread()", mod->get_name().data()); + + if (auto e = mod->on_initialize_d3d_thread(); e != std::nullopt) { + spdlog::info("{:s}::on_initialize_d3d_thread() has failed: {:s}", mod->get_name().data(), *e); + return e; + } + } + + for (auto& mod : m_mods) { + spdlog::info("{:s}::on_config_load()", mod->get_name().data()); + mod->on_config_load(cfg); + } + + return std::nullopt; +} + void Mods::on_pre_imgui_frame() const { for (auto& mod : m_mods) { mod->on_pre_imgui_frame(); diff --git a/src/Mods.hpp b/src/Mods.hpp index fda2db0bc..b9d60d213 100644 --- a/src/Mods.hpp +++ b/src/Mods.hpp @@ -8,6 +8,7 @@ class Mods { virtual ~Mods() {} std::optional on_initialize() const; + std::optional on_initialize_d3d_thread() const; void on_pre_imgui_frame() const; void on_frame() const; diff --git a/src/REFramework.cpp b/src/REFramework.cpp index d461710eb..93a786d19 100644 --- a/src/REFramework.cpp +++ b/src/REFramework.cpp @@ -168,6 +168,8 @@ REFramework::REFramework(HMODULE reframework_module) const auto pre_allocated_buffer = (uintptr_t)AllocateBuffer((LPVOID)halfway_module); // minhook function spdlog::info("Preallocated buffer: {:x}", pre_allocated_buffer); + IntegrityCheckBypass::fix_virtual_protect(); + IMGUI_CHECKVERSION(); ImGui::CreateContext(); @@ -503,7 +505,7 @@ void REFramework::on_frame_d3d11() { return; } - const bool is_init_ok = m_error.empty() && m_game_data_initialized; + bool is_init_ok = m_error.empty() && m_game_data_initialized; if (is_init_ok) { // Write default config once if it doesn't exist. @@ -514,6 +516,8 @@ void REFramework::on_frame_d3d11() { } } + is_init_ok = first_frame_initialize(); + if (!m_has_frame) { if (!is_init_ok) { update_fonts(); @@ -606,7 +610,7 @@ void REFramework::on_frame_d3d12() { return; } - const bool is_init_ok = m_error.empty() && m_game_data_initialized; + bool is_init_ok = m_error.empty() && m_game_data_initialized; if (is_init_ok) { // Write default config once if it doesn't exist. @@ -629,6 +633,8 @@ void REFramework::on_frame_d3d12() { ImGui_ImplDX12_NewFrame(); }; + is_init_ok = first_frame_initialize(); + if (!m_has_frame) { if (!is_init_ok) { update_fonts(); @@ -1632,6 +1638,43 @@ bool REFramework::initialize_windows_message_hook() { return false; } +// Ran on the first valid frame after pre-initialization of mods has taken place and hasn't failed +// This one allows mods to run any initialization code in the context of the D3D thread (like VR code) +// It also is the one that actually loads any config files +bool REFramework::first_frame_initialize() { + const bool is_init_ok = m_error.empty() && m_game_data_initialized; + + if (!is_init_ok || !m_first_frame_d3d_initialize) { + return is_init_ok; + } + + std::scoped_lock _{get_hook_monitor_mutex()}; + + spdlog::info("Running first frame D3D initialization of mods..."); + + m_first_frame_d3d_initialize = false; + auto e = m_mods->on_initialize_d3d_thread(); + + if (e) { + if (e->empty()) { + m_error = "An unknown error has occurred."; + } else { + m_error = *e; + } + + spdlog::error("Initialization of mods failed. Reason: {}", m_error); + m_game_data_initialized = false; + m_mods_fully_initialized = false; + return false; + } else { + // Do an initial config save to set the default values for the frontend + save_config(); + m_mods_fully_initialized = true; + } + + return true; +} + void REFramework::call_on_frame() { const bool is_init_ok = m_error.empty() && m_game_data_initialized; diff --git a/src/REFramework.hpp b/src/REFramework.hpp index 722ff465d..916e70c5b 100644 --- a/src/REFramework.hpp +++ b/src/REFramework.hpp @@ -147,11 +147,14 @@ class REFramework { bool initialize_game_data(); bool initialize_windows_message_hook(); + bool first_frame_initialize(); + void call_on_frame(); HMODULE m_reframework_module{}; bool m_first_frame{true}; + bool m_first_frame_d3d_initialize{true}; bool m_is_d3d12{false}; bool m_is_d3d11{false}; bool m_valid{false}; @@ -160,6 +163,7 @@ class REFramework { bool m_started_game_data_thread{false}; std::atomic m_terminating{false}; // Destructor is called std::atomic m_game_data_initialized{false}; + std::atomic m_mods_fully_initialized{false}; // UI bool m_has_frame{false}; diff --git a/src/mods/Hooks.cpp b/src/mods/Hooks.cpp index 95b3bf414..bbae15ecd 100644 --- a/src/mods/Hooks.cpp +++ b/src/mods/Hooks.cpp @@ -196,7 +196,9 @@ std::optional Hooks::hook_update_transform() { m_update_transform_hook = std::make_unique(update_transform, &update_transform_hook); if (!m_update_transform_hook->create()) { - return "Failed to hook UpdateTransform"; + //return "Failed to hook UpdateTransform"; + spdlog::error("Failed to hook UpdateTransform"); + return std::nullopt; // who cares } return std::nullopt; diff --git a/src/mods/IntegrityCheckBypass.cpp b/src/mods/IntegrityCheckBypass.cpp index cf89c2f5f..84b9d97af 100644 --- a/src/mods/IntegrityCheckBypass.cpp +++ b/src/mods/IntegrityCheckBypass.cpp @@ -514,4 +514,129 @@ void IntegrityCheckBypass::remove_stack_destroyer() { static auto patch = Patch::create(*fn, { 0xC3 }, true); spdlog::info("[IntegrityCheckBypass]: Patched stack destroyer!"); +} + +// hahahah i hate this +void IntegrityCheckBypass::fix_virtual_protect() try { + spdlog::info("[IntegrityCheckBypass]: Fixing VirtualProtect..."); + + auto nt_protect_virtual_memory = (NtProtectVirtualMemory_t)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtProtectVirtualMemory"); + if (nt_protect_virtual_memory == nullptr) { + spdlog::error("[IntegrityCheckBypass]: Could not find NtProtectVirtualMemory!"); + return; + } + + spdlog::info("[IntegrityCheckBypass]: Found NtProtectVirtualMemory at 0x{:X}", (uintptr_t)nt_protect_virtual_memory); + + if (*(uint8_t*)nt_protect_virtual_memory == 0xE9) { + spdlog::info("[IntegrityCheckBypass]: Found jmp at 0x{:X}, resolving...", (uintptr_t)nt_protect_virtual_memory); + nt_protect_virtual_memory = (decltype(nt_protect_virtual_memory))utility::calculate_absolute((uintptr_t)nt_protect_virtual_memory + 1); + } + + s_pristine_protect_virtual_memory = (decltype(s_pristine_protect_virtual_memory))VirtualAlloc(nullptr, 256, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + memcpy(s_pristine_protect_virtual_memory, nt_protect_virtual_memory, 256); + spdlog::info("[IntegrityCheckBypass]: Copied NtProtectVirtualMemory to 0x{:X}", (uintptr_t)s_pristine_protect_virtual_memory); + + // Now disassemble our pristine function and just remove anything that looks like its a displacement or memory reference with nops + // im doing this because im too lazy to fix up the relocations + struct PatchJob { + uintptr_t addr{}; + uint32_t size{}; + }; + + std::vector patch_jobs{}; + + utility::exhaustive_decode((uint8_t*)s_pristine_protect_virtual_memory, 100, [&](utility::ExhaustionContext& ctx) -> utility::ExhaustionResult { + if (*(uint8_t*)ctx.addr == 0x0f && *(uint8_t*)(ctx.addr + 1) == 0x05) { + spdlog::info("[IntegrityCheckBypass]: Found syscall at 0x{:X}", ctx.addr); + return utility::ExhaustionResult::CONTINUE; + } + + if (std::string_view{ctx.instrux.Mnemonic}.starts_with("RET") || std::string_view{ctx.instrux.Mnemonic}.starts_with("INT")) { + return utility::ExhaustionResult::CONTINUE; + } + + if (ctx.instrux.BranchInfo.IsBranch) { + spdlog::info("[IntegrityCheckBypass]: Found branch at 0x{:X} ({:x})", ctx.addr, ctx.addr - (uintptr_t)s_pristine_protect_virtual_memory); + spdlog::info("[IntegrityCheckBypass]: {}", ctx.instrux.Mnemonic); + patch_jobs.push_back(PatchJob{ctx.addr, ctx.instrux.Length}); + return utility::ExhaustionResult::CONTINUE; + } + + if (utility::resolve_displacement(ctx.addr).has_value()) { + spdlog::info("[IntegrityCheckBypass]: Found displacement at 0x{:X} ({:x})", ctx.addr, ctx.addr - (uintptr_t)s_pristine_protect_virtual_memory); + patch_jobs.push_back(PatchJob{ctx.addr, ctx.instrux.Length}); + return utility::ExhaustionResult::CONTINUE; + } + + // Go through any operands and check if any mem + for (auto i = 0; i < ctx.instrux.OperandsCount; ++i) { + const auto& op = ctx.instrux.Operands[i]; + + if (op.Type == ND_OP_MEM) { + spdlog::info("[IntegrityCheckBypass]: Found mem operand at 0x{:X} ({:x})", ctx.addr, ctx.addr - (uintptr_t)s_pristine_protect_virtual_memory); + patch_jobs.push_back(PatchJob{ctx.addr, ctx.instrux.Length}); + return utility::ExhaustionResult::CONTINUE; + } + } + + return utility::ExhaustionResult::CONTINUE; + }); + + for (auto& patch_job : patch_jobs) { + spdlog::info("[IntegrityCheckBypass]: Patching 0x{:X} with {} nops", patch_job.addr, patch_job.size); + memset((void*)patch_job.addr, 0x90, patch_job.size); + } + + // Hook VirtualProtect + s_virtual_protect_hook = std::make_unique(VirtualProtect, (uintptr_t)virtual_protect_hook); + if (!s_virtual_protect_hook->create()) { + spdlog::error("[IntegrityCheckBypass]: Could not hook VirtualProtect!"); + return; + } + + spdlog::info("[IntegrityCheckBypass]: Hooked VirtualProtect!"); +} catch(...) { + spdlog::error("[IntegrityCheckBypass]: Could not fix VirtualProtect!"); +} + +// This allows our calls to VirtualProtect to go through without being hindered by... something. +BOOL WINAPI IntegrityCheckBypass::virtual_protect_hook(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect) try { + static bool once = true; + if (once) { + spdlog::info("[IntegrityCheckBypass]: VirtualProtect called"); + once = false; + } + + static const auto this_process = GetCurrentProcess(); + + LPVOID address_to_protect = lpAddress; + NTSTATUS result = s_pristine_protect_virtual_memory(this_process, (PVOID*)&address_to_protect, &dwSize, flNewProtect, lpflOldProtect); + + constexpr NTSTATUS STATUS_INVALID_PAGE_PROTECTION = 0xC0000045; + + // recreated from kernelbase to be correct + if (result == STATUS_INVALID_PAGE_PROTECTION) { + using RtlFlushSecureMemoryCache_t = BOOLEAN (NTAPI*)(PVOID, SIZE_T); + static const auto rtl_flush_secure_memory_cache = (RtlFlushSecureMemoryCache_t)GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlFlushSecureMemoryCache"); + + if (rtl_flush_secure_memory_cache != nullptr) { + if (NT_SUCCESS(rtl_flush_secure_memory_cache(address_to_protect, dwSize))) { + result = s_pristine_protect_virtual_memory(this_process, (PVOID*)&address_to_protect, &dwSize, flNewProtect, lpflOldProtect); + + if ((result & 0x80000000) == 0) { + return TRUE; + } + } + } + } + + if (!NT_SUCCESS(result)) { + spdlog::error("[IntegrityCheckBypass]: NtProtectVirtualMemory(-1, {:x}, {:x}, {:x}, {:x}) failed with {:x}", (uintptr_t)address_to_protect, dwSize, flNewProtect, (uintptr_t)lpflOldProtect, (uint32_t)result); + } + + return NT_SUCCESS(result); +} catch(...) { + spdlog::error("[IntegrityCheckBypass]: VirtualProtect hook failed! falling back to original"); + return s_virtual_protect_hook->get_original()(lpAddress, dwSize, flNewProtect, lpflOldProtect); } \ No newline at end of file diff --git a/src/mods/IntegrityCheckBypass.hpp b/src/mods/IntegrityCheckBypass.hpp index dd3fbc8e2..358db9727 100644 --- a/src/mods/IntegrityCheckBypass.hpp +++ b/src/mods/IntegrityCheckBypass.hpp @@ -5,6 +5,7 @@ #include "Mod.hpp" #include "utility/Patch.hpp" +#include "utility/FunctionHook.hpp" // Always on for RE3 // Because we use hooks that modify the integrity of the executable @@ -20,8 +21,15 @@ class IntegrityCheckBypass : public Mod { static void immediate_patch_re8(); static void immediate_patch_re4(); static void remove_stack_destroyer(); + static void fix_virtual_protect(); private: + static BOOL WINAPI virtual_protect_hook(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect); + + using NtProtectVirtualMemory_t = NTSTATUS(NTAPI*)(HANDLE ProcessHandle, PVOID* BaseAddress, SIZE_T* NumberOfBytesToProtect, ULONG NewAccessProtection, PULONG OldAccessProtection); + static inline NtProtectVirtualMemory_t s_pristine_protect_virtual_memory{ nullptr }; + static inline std::unique_ptr s_virtual_protect_hook{}; + #ifdef RE3 // This is what the game uses to bypass its integrity checks altogether or something bool* m_bypass_integrity_checks{ nullptr }; diff --git a/src/mods/VR.cpp b/src/mods/VR.cpp index bf2994f69..5f8f7c093 100644 --- a/src/mods/VR.cpp +++ b/src/mods/VR.cpp @@ -584,7 +584,7 @@ float VR::get_sharpness_hook(void* tonemapping) { */ // Called when the mod is initialized -std::optional VR::on_initialize() try { +std::optional VR::on_initialize_d3d_thread() try { auto openvr_error = initialize_openvr(); if (openvr_error || !m_openvr->loaded) { @@ -1108,6 +1108,8 @@ std::optional VR::hijack_resolution() { std::optional VR::hijack_input() { #if defined(RE2) || defined(RE3) + spdlog::info("[VR] Hijacking InputSystem"); + // We're going to hook InputSystem.update so we can // override the analog stick values with the VR controller's auto func = sdk::find_native_method(game_namespace("InputSystem"), "update"); @@ -1130,6 +1132,8 @@ std::optional VR::hijack_input() { } std::optional VR::hijack_camera() { + spdlog::info("[VR] Hijacking Camera"); + const auto get_projection_matrix = (uintptr_t)sdk::find_native_method("via.Camera", "get_ProjectionMatrix"); /////////////////////////////// @@ -1165,6 +1169,8 @@ std::optional VR::hijack_camera() { std::optional VR::hijack_wwise_listeners() { #ifndef RE4 #ifndef SF6 + spdlog::info("[VR] Hijacking WwiseListener"); + const auto t = sdk::find_type_definition("via.wwise.WwiseListener"); if (t == nullptr) { @@ -1195,13 +1201,15 @@ std::optional VR::hijack_wwise_listeners() { const auto vtable_index = *(uint8_t*)(*jmp + 3) / sizeof(void*); spdlog::info("via.wwise.WwiseListener.update vtable index: {}", vtable_index); + spdlog::info("Attempting to create fake via.wwise.WwiseListener instance"); const void* fake_obj = t->create_instance_full(); if (fake_obj == nullptr) { return "VR init failed: Failed to create fake via.wwise.WwiseListener instance."; } - + + spdlog::info("Attempting to read vtable from fake via.wwise.WwiseListener instance"); auto obj_vtable = *(void***)fake_obj; if (obj_vtable == nullptr) { diff --git a/src/mods/VR.hpp b/src/mods/VR.hpp index a7f98f35f..15dae7f46 100644 --- a/src/mods/VR.hpp +++ b/src/mods/VR.hpp @@ -33,7 +33,7 @@ class VR : public Mod { std::string_view get_name() const override { return "VR"; } // Called when the mod is initialized - std::optional on_initialize() override; + std::optional on_initialize_d3d_thread() override; void on_lua_state_created(sol::state& lua) override;