From 22639539c35f38b7e3f507a1abcf2154d471e468 Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 22 Jan 2024 06:14:13 -0800 Subject: [PATCH 1/9] Enigma: Set up NtProtectVirtualMemory immediately upon DLL load Doing this in a separate thread has the possibility of failing, so I did this instead --- src/Main.cpp | 3 +++ src/mods/IntegrityCheckBypass.cpp | 17 ++++++++++++++--- src/mods/IntegrityCheckBypass.hpp | 2 ++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/Main.cpp b/src/Main.cpp index da69062c4..e5c3b03c2 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -7,6 +7,7 @@ #include #include +#include "mods/IntegrityCheckBypass.hpp" #include "ExceptionHandler.hpp" #include "REFramework.hpp" @@ -81,6 +82,8 @@ void startup_thread(HMODULE reframework_module) { BOOL APIENTRY DllMain(HANDLE handle, DWORD reason, LPVOID reserved) { if (reason == DLL_PROCESS_ATTACH) { + IntegrityCheckBypass::setup_pristine_syscall(); + CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)startup_thread, handle, 0, nullptr); } diff --git a/src/mods/IntegrityCheckBypass.cpp b/src/mods/IntegrityCheckBypass.cpp index 84b9d97af..a0cb317d3 100644 --- a/src/mods/IntegrityCheckBypass.cpp +++ b/src/mods/IntegrityCheckBypass.cpp @@ -516,9 +516,13 @@ void IntegrityCheckBypass::remove_stack_destroyer() { spdlog::info("[IntegrityCheckBypass]: Patched stack destroyer!"); } -// hahahah i hate this -void IntegrityCheckBypass::fix_virtual_protect() try { - spdlog::info("[IntegrityCheckBypass]: Fixing VirtualProtect..."); +void IntegrityCheckBypass::setup_pristine_syscall() { + if (s_pristine_protect_virtual_memory != nullptr) { + spdlog::info("[IntegrityCheckBypass]: NtProtectVirtualMemory already setup!"); + return; + } + + spdlog::info("[IntegrityCheckBypass]: Copying pristine NtProtectVirtualMemory..."); auto nt_protect_virtual_memory = (NtProtectVirtualMemory_t)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtProtectVirtualMemory"); if (nt_protect_virtual_memory == nullptr) { @@ -536,6 +540,13 @@ void IntegrityCheckBypass::fix_virtual_protect() try { 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); +} + +// hahahah i hate this +void IntegrityCheckBypass::fix_virtual_protect() try { + spdlog::info("[IntegrityCheckBypass]: Fixing VirtualProtect..."); + + setup_pristine_syscall(); // Called earlier in DllMain // 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 diff --git a/src/mods/IntegrityCheckBypass.hpp b/src/mods/IntegrityCheckBypass.hpp index 358db9727..814a3f154 100644 --- a/src/mods/IntegrityCheckBypass.hpp +++ b/src/mods/IntegrityCheckBypass.hpp @@ -21,6 +21,8 @@ class IntegrityCheckBypass : public Mod { static void immediate_patch_re8(); static void immediate_patch_re4(); static void remove_stack_destroyer(); + + static void setup_pristine_syscall(); static void fix_virtual_protect(); private: From 70910c7b0f29b917789c3ca4b703c981c10ac14f Mon Sep 17 00:00:00 2001 From: praydog Date: Thu, 25 Jan 2024 14:18:48 -0800 Subject: [PATCH 2/9] via.Application: Add bruteforce scan for module entries when all fails This fixes Apollo Justice but will also fix future, unknown REE games. --- shared/sdk/Application.cpp | 86 +++++++++++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/shared/sdk/Application.cpp b/shared/sdk/Application.cpp index e6f6e54ea..c25e4b8d9 100644 --- a/shared/sdk/Application.cpp +++ b/shared/sdk/Application.cpp @@ -23,7 +23,7 @@ Application* Application::get() { } Application::Function* Application::get_functions() { - static auto functions_offset = []() -> std::optional { + static auto functions_offset = [this]() -> std::optional { spdlog::info("Searching for Application::functions offset..."); const auto mod = utility::get_executable(); @@ -72,6 +72,90 @@ Application::Function* Application::get_functions() { spdlog::info("Skipping invalid Application::functions offset: {:x}", candidate); } + bool found_wait_rendering = false; + bool found_begin_rendering = false; + bool found_end_rendering = false; + + const auto module_entry_enum = sdk::find_type_definition("via.ModuleEntry"); + + // screw that lets just bruteforce through the Application object looking for huge + // list of valid pointers within the current module + for (auto i = 0x100; i < 0x1000; i += sizeof(void*)) try { + const auto ptr = (Application::Function*)((uintptr_t)this + i); + + if (ptr == nullptr || IsBadReadPtr(ptr, sizeof(Application::Function) * 50)) { + continue; + } + + for (auto j = 0; j < 1024; ++j) try { + const auto& func = ptr[j]; + + if (func.description == nullptr || IsBadReadPtr(func.description, 32)) { + break; + } + + if (j == 0 && func.entry == nullptr || IsBadReadPtr(func.entry, sizeof(void*))) { + break; // the first one should always be valid. + } + + const auto name = std::string_view{func.description}; + + if (j == 0) { + if (module_entry_enum != nullptr) { + if (auto f = sdk::get_native_field(nullptr, module_entry_enum, name, true); f != nullptr) { + if (*f != func.priority) { + break; // the first one should always be valid. + } + } + } else if (func.priority != 1) { + break; // the first one should always be valid. + } + } + + if (name == "WaitRendering") { + if (module_entry_enum != nullptr) { + if (auto f = sdk::get_native_field(nullptr, module_entry_enum, "WaitRendering", true); f != nullptr) { + if (*f == func.priority) { + found_wait_rendering = true; + } + } + } else { + found_wait_rendering = true; + } + } else if (name == "BeginRendering") { + if (module_entry_enum != nullptr) { + if (auto f = sdk::get_native_field(nullptr, module_entry_enum, "BeginRendering", true); f != nullptr) { + if (*f == func.priority) { + found_begin_rendering = true; + } + } + } else { + found_begin_rendering = true; + } + } else if (name == "EndRendering") { + if (module_entry_enum != nullptr) { + if (auto f = sdk::get_native_field(nullptr, module_entry_enum, "EndRendering", true); f != nullptr) { + if (*f == func.priority) { + found_end_rendering = true; + } + } + } else { + found_end_rendering = true; + } + } + + if (found_wait_rendering && found_begin_rendering && found_end_rendering) { + spdlog::info("Application::functions offset: {:x}", i); + return i; + } + } catch (...) { + continue; + } + } catch(...) { + continue; + } + + spdlog::error("Cannot find Application::functions offset."); return std::nullopt; From bc6455b67c965151419f87a4f06c58c4d2e0db70 Mon Sep 17 00:00:00 2001 From: praydog Date: Thu, 25 Jan 2024 14:48:33 -0800 Subject: [PATCH 3/9] TDB: Use name pool bitmask to work around unknown games --- shared/sdk/RETypeDB.cpp | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/shared/sdk/RETypeDB.cpp b/shared/sdk/RETypeDB.cpp index 483808c26..4ed417413 100644 --- a/shared/sdk/RETypeDB.cpp +++ b/shared/sdk/RETypeDB.cpp @@ -213,9 +213,18 @@ sdk::RETypeDefinition* REField::get_type() const { const char* REField::get_name() const { auto tdb = RETypeDB::get(); + static uint32_t bitmask = [tdb]() -> uint32_t { + uint32_t out{1}; + while (out < tdb->numStringPool) { + out <<= 1; + } + + return out - 1; + }(); + #if TDB_VER >= 69 auto& impl = (*tdb->fieldsImpl)[this->impl_id]; - const auto name_offset = impl.name_offset; + const auto name_offset = impl.name_offset & bitmask; #else const auto name_offset = this->name_offset; #endif @@ -375,9 +384,18 @@ sdk::RETypeDefinition* REMethodDefinition::get_return_type() const { const char* REMethodDefinition::get_name() const { auto tdb = RETypeDB::get(); + static uint32_t bitmask = [tdb]() -> uint32_t { + uint32_t out{1}; + while (out < tdb->numStringPool) { + out <<= 1; + } + + return out - 1; + }(); + #if TDB_VER >= 69 auto& impl = (*tdb->methodsImpl)[this->impl_id]; - const auto name_offset = impl.name_offset; + const auto name_offset = impl.name_offset & bitmask; #else const auto name_offset = this->name_offset; #endif From 694d4f32892e955aba56c2f86b8610084aff71a9 Mon Sep 17 00:00:00 2001 From: praydog Date: Thu, 25 Jan 2024 14:53:32 -0800 Subject: [PATCH 4/9] TDB: Make string pool bitmask a bit more readable/re-usable --- shared/sdk/RETypeDB.cpp | 22 ++-------------------- shared/sdk/RETypeDB.hpp | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/shared/sdk/RETypeDB.cpp b/shared/sdk/RETypeDB.cpp index 4ed417413..e9c9ac2d0 100644 --- a/shared/sdk/RETypeDB.cpp +++ b/shared/sdk/RETypeDB.cpp @@ -213,18 +213,9 @@ sdk::RETypeDefinition* REField::get_type() const { const char* REField::get_name() const { auto tdb = RETypeDB::get(); - static uint32_t bitmask = [tdb]() -> uint32_t { - uint32_t out{1}; - while (out < tdb->numStringPool) { - out <<= 1; - } - - return out - 1; - }(); - #if TDB_VER >= 69 auto& impl = (*tdb->fieldsImpl)[this->impl_id]; - const auto name_offset = impl.name_offset & bitmask; + const auto name_offset = impl.name_offset & tdb->get_string_pool_bitmask(); #else const auto name_offset = this->name_offset; #endif @@ -384,18 +375,9 @@ sdk::RETypeDefinition* REMethodDefinition::get_return_type() const { const char* REMethodDefinition::get_name() const { auto tdb = RETypeDB::get(); - static uint32_t bitmask = [tdb]() -> uint32_t { - uint32_t out{1}; - while (out < tdb->numStringPool) { - out <<= 1; - } - - return out - 1; - }(); - #if TDB_VER >= 69 auto& impl = (*tdb->methodsImpl)[this->impl_id]; - const auto name_offset = impl.name_offset & bitmask; + const auto name_offset = impl.name_offset & tdb->get_string_pool_bitmask(); #else const auto name_offset = this->name_offset; #endif diff --git a/shared/sdk/RETypeDB.hpp b/shared/sdk/RETypeDB.hpp index 977605c0a..9ba58d320 100644 --- a/shared/sdk/RETypeDB.hpp +++ b/shared/sdk/RETypeDB.hpp @@ -761,10 +761,27 @@ struct RETypeDB : public sdk::RETypeDB_ { return numProperties; } + uint32_t get_string_pool_size() const { + return numStringPool; + } + const char* get_string(uint32_t offset) const; uint8_t* get_bytes(uint32_t offset) const; template T* get_data(uint32_t offset) const { return (T*)get_bytes(offset); } + + uint32_t get_string_pool_bitmask() const { + static auto result = [this]() -> uint32_t { + uint32_t out{1}; + while (out < get_string_pool_size()) { + out <<= 1; + } + + return out - 1; + }(); + + return result; + } }; } // namespace sdk From 193351e3cd91fe9e82132b5679d36301356ae37e Mon Sep 17 00:00:00 2001 From: praydog Date: Thu, 25 Jan 2024 15:57:32 -0800 Subject: [PATCH 5/9] TDB: Hacky workaround to some parameters being null (generics?) --- shared/sdk/RETypeDB.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/shared/sdk/RETypeDB.cpp b/shared/sdk/RETypeDB.cpp index e9c9ac2d0..1e4810754 100644 --- a/shared/sdk/RETypeDB.cpp +++ b/shared/sdk/RETypeDB.cpp @@ -992,8 +992,16 @@ std::vector REMethodDefinition::get_param_types() const auto tdb = RETypeDB::get(); std::vector out{}; + static const auto system_object = tdb->find_type("System.Object"); + for (auto id : typeids) { - out.push_back(tdb->get_type(id)); + const auto t = tdb->get_type(id); + + if (t == nullptr) { + out.push_back(system_object); // TODO: this is a hack. not sure why this happens + continue; + } + out.push_back(t); } return out; From 51a78576b3c37da6ca7768480120a249560f18cf Mon Sep 17 00:00:00 2001 From: praydog Date: Thu, 25 Jan 2024 17:13:36 -0800 Subject: [PATCH 6/9] TDB: Add some bitmasks for some of the other arrays --- shared/sdk/RETypeDB.cpp | 8 ++++++- shared/sdk/RETypeDB.hpp | 51 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/shared/sdk/RETypeDB.cpp b/shared/sdk/RETypeDB.cpp index 1e4810754..04922f21a 100644 --- a/shared/sdk/RETypeDB.cpp +++ b/shared/sdk/RETypeDB.cpp @@ -132,6 +132,8 @@ void* find_native_method(std::string_view type_name, std::string_view method_nam } sdk::RETypeDefinition* RETypeDB::get_type(uint32_t index) const { + index &= get_type_bitmask(); + if (index >= this->numTypes) { return nullptr; } @@ -164,6 +166,8 @@ sdk::REProperty* RETypeDB::get_property(uint32_t index) const { } const char* RETypeDB::get_string(uint32_t offset) const { + offset &= get_string_pool_bitmask(); + if (offset >= this->numStringPool) { return nullptr; } @@ -172,6 +176,8 @@ const char* RETypeDB::get_string(uint32_t offset) const { } uint8_t* RETypeDB::get_bytes(uint32_t offset) const { + offset &= get_byte_pool_bitmask(); + if (offset >= this->numBytePool) { return nullptr; } @@ -951,7 +957,7 @@ std::vector REMethodDefinition::get_param_typeids() const { // Parse all params for (auto f = 0; f < num_params; ++f) { - const auto param_index = param_ids->params[f]; + const auto param_index = param_ids->params[f] & tdb->get_param_bitmask(); if (param_index >= tdb->numParams) { break; diff --git a/shared/sdk/RETypeDB.hpp b/shared/sdk/RETypeDB.hpp index 9ba58d320..111c597ca 100644 --- a/shared/sdk/RETypeDB.hpp +++ b/shared/sdk/RETypeDB.hpp @@ -757,6 +757,12 @@ struct RETypeDB : public sdk::RETypeDB_ { return numFields; } +#if TDB_VER >= 69 + uint32_t get_num_params() const { + return numParams; + } +#endif + uint32_t get_num_properties() const { return numProperties; } @@ -765,6 +771,10 @@ struct RETypeDB : public sdk::RETypeDB_ { return numStringPool; } + uint32_t get_byte_pool_size() const { + return numBytePool; + } + const char* get_string(uint32_t offset) const; uint8_t* get_bytes(uint32_t offset) const; @@ -782,6 +792,47 @@ struct RETypeDB : public sdk::RETypeDB_ { return result; } + + uint32_t get_byte_pool_bitmask() const { + static auto result = [this]() -> uint32_t { + uint32_t out{1}; + while (out < get_byte_pool_size()) { + out <<= 1; + } + + return out - 1; + }(); + + return result; + } + + uint32_t get_type_bitmask() const { + static auto result = [this]() -> uint32_t { + uint32_t out{1}; + while (out < get_num_types()) { + out <<= 1; + } + + return out - 1; + }(); + + return result; + } + +#if TDB_VER >= 69 + uint32_t get_param_bitmask() const { + static auto result = [this]() -> uint32_t { + uint32_t out{1}; + while (out < get_num_params()) { + out <<= 1; + } + + return out - 1; + }(); + + return result; + } +#endif }; } // namespace sdk From 07945d4c3ef3bbf364f3c9e57fab15f5b7b4bffe Mon Sep 17 00:00:00 2001 From: praydog Date: Thu, 25 Jan 2024 17:14:04 -0800 Subject: [PATCH 7/9] VM: Hacky fix for correcting the static table offset --- shared/sdk/REContext.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/shared/sdk/REContext.cpp b/shared/sdk/REContext.cpp index 9261da3f7..95c3262d1 100644 --- a/shared/sdk/REContext.cpp +++ b/shared/sdk/REContext.cpp @@ -156,6 +156,20 @@ namespace sdk { spdlog::info("[VM::update_pointers] s_global_context: {:x}", (uintptr_t)s_global_context); spdlog::info("[VM::update_pointers] s_get_thread_context: {:x}", (uintptr_t)s_get_thread_context); + // Needed on TDB73/AJ. The 0x30 offset we have is not correct, so we need to find the correct one + // And the "correct" one is the first one that doesn't look like a BS pointer (crude, i know) + // so... TODO: find a better way to do this +#if TDB_VER >= 71 + if (s_global_context != nullptr && *s_global_context != nullptr) { + auto static_tbl = (REStaticTbl**)((uintptr_t)*s_global_context + s_static_tbl_offset); + while (IsBadReadPtr(*static_tbl, sizeof(void*)) || ((uintptr_t)*static_tbl & (sizeof(void*) - 1)) != 0) { + s_static_tbl_offset -= sizeof(void*); + static_tbl = (REStaticTbl**)((uintptr_t)*s_global_context + s_static_tbl_offset); + spdlog::info("[VM::update_pointers] Static table offset is bad, correcting to {:x}...", s_static_tbl_offset); + } + } +#endif + // Get invoke_tbl // this SEEMS to work on RE2 and onwards, but not on RE7 // look into it later From 1fa2715ca57461691670b5b7d96e2ff20ceb6a5f Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 28 Jan 2024 21:24:18 -0800 Subject: [PATCH 8/9] VM: Fix static table offset locator (and less hacky) --- shared/sdk/REContext.cpp | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/shared/sdk/REContext.cpp b/shared/sdk/REContext.cpp index 95c3262d1..a35bf4066 100644 --- a/shared/sdk/REContext.cpp +++ b/shared/sdk/REContext.cpp @@ -162,10 +162,38 @@ namespace sdk { #if TDB_VER >= 71 if (s_global_context != nullptr && *s_global_context != nullptr) { auto static_tbl = (REStaticTbl**)((uintptr_t)*s_global_context + s_static_tbl_offset); - while (IsBadReadPtr(*static_tbl, sizeof(void*)) || ((uintptr_t)*static_tbl & (sizeof(void*) - 1)) != 0) { - s_static_tbl_offset -= sizeof(void*); - static_tbl = (REStaticTbl**)((uintptr_t)*s_global_context + s_static_tbl_offset); - spdlog::info("[VM::update_pointers] Static table offset is bad, correcting to {:x}...", s_static_tbl_offset); + if (IsBadReadPtr(*static_tbl, sizeof(void*)) || ((uintptr_t)*static_tbl & (sizeof(void*) - 1)) != 0) { + spdlog::info("[VM::update_pointers] Static table offset is bad, correcting..."); + + // We are looking for the two arrays, the static field table, and the static field "initialized table" + // The initialized table tells whether a specific entry in the static field table has been initialized or not + // so they both should have the same size, easy to find + for (auto i = sizeof(void*); i < 0x100; i+= sizeof(void*)) { + const auto& ptr = *(REStaticTbl**)((uintptr_t)*s_global_context + (s_type_db_offset - i)); + + if (IsBadReadPtr(ptr, sizeof(void*)) || ((uintptr_t)ptr & (sizeof(void*) - 1)) != 0) { + continue; + } + + spdlog::info("[VM::update_pointers] Examining {:x}", (uintptr_t)ptr); + + const auto& potential_count = *(uint32_t*)((uintptr_t)&ptr + sizeof(void*)); + + if (potential_count < 2000) { + continue; + } + + constexpr auto array_size = (sizeof(void*) * 2); + const auto previous_offset = s_type_db_offset - i - array_size; + const auto& previous_ptr = *(REStaticTbl**)((uintptr_t)*s_global_context + previous_offset); + const auto& previous_count = *(uint32_t*)((uintptr_t)&previous_ptr + sizeof(void*)); + + if (previous_count == potential_count) { + spdlog::info("[VM::update_pointers] Found static table at {:x} (offset {:x})", (uintptr_t)ptr, previous_offset); + s_static_tbl_offset = previous_offset; + break; + } + } } } #endif From 0341611159bfc8cd669acc6ba752f85d72c0988d Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 30 Jan 2024 18:20:35 -0800 Subject: [PATCH 9/9] VDXR: Fix standing origin getting flung into the middle of nowhere --- src/mods/vr/runtimes/OpenXR.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/mods/vr/runtimes/OpenXR.cpp b/src/mods/vr/runtimes/OpenXR.cpp index 21bf3126d..8b2469ae7 100644 --- a/src/mods/vr/runtimes/OpenXR.cpp +++ b/src/mods/vr/runtimes/OpenXR.cpp @@ -61,12 +61,28 @@ VRRuntime::Error OpenXR::update_poses() { uint32_t view_count{}; - const auto display_time = this->frame_state.predictedDisplayTime + (XrDuration)(this->frame_state.predictedDisplayPeriod * this->prediction_scale); + // Only signal that we got the first valid pose if the display time becomes a sane value + if (!this->got_first_valid_poses) { + // Seen on VDXR + if (this->frame_state.predictedDisplayTime <= this->frame_state.predictedDisplayPeriod) { + spdlog::info("[VR] Frame state predicted display time is less than predicted display period!"); + return VRRuntime::Error::SUCCESS; + } + + // Seen on VDXR. If for some reason the above if statement doesn't work, this will catch it. + if (this->frame_state.predictedDisplayTime == 11111111) { + spdlog::info("[VR] Frame state predicted display time is 11111111!"); + return VRRuntime::Error::SUCCESS; + } + } - if (display_time <= 1000) { + if (this->frame_state.predictedDisplayTime <= 1000) { + spdlog::info("[VR] Frame state predicted display time is less than 1000!"); return VRRuntime::Error::SUCCESS; } + const auto display_time = this->frame_state.predictedDisplayTime + (XrDuration)(this->frame_state.predictedDisplayPeriod * this->prediction_scale); + XrViewLocateInfo view_locate_info{XR_TYPE_VIEW_LOCATE_INFO}; view_locate_info.viewConfigurationType = this->view_config; view_locate_info.displayTime = display_time;