Skip to content

Commit fe760ce

Browse files
X-rays5Copilot
andauthored
Enhanced migration (#83)
* Make it inject * W.I.P. * W.I.P. * Drop atl dep * WndProc use detour instead of SetWindowLongPtr Fixes random crashes due to a dead wndproc ptr * Rate limit Discord RPC updates * Update discord-rpc * Add exponential backoff to spinlock * Remove cached batch scanning With the speed of the current sig scanner caching the results is completely useless * Misc * Only log c++ exception in own module * Minor fixes * Renderer enhancements * Fix release build * Update menu/src/render/d3d12/context.cpp Co-authored-by: Copilot <[email protected]> * Fix review comments --------- Co-authored-by: Copilot <[email protected]>
1 parent 839ad34 commit fe760ce

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1276
-456
lines changed

common/src/concurrency/spinlock.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ namespace base::common::concurrency {
1111
return;
1212
}
1313

14-
while (lock_.load(std::memory_order_relaxed)) {
14+
// Exponential backoff
15+
for (int i = 0; i < 16; ++i) {
16+
if (!lock_.load(std::memory_order_relaxed)) break;
1517
std::this_thread::yield();
1618
}
1719
}
@@ -27,7 +29,7 @@ namespace base::common::concurrency {
2729

2830
void RecursiveSpinlock::Lock() noexcept {
2931
const auto thread_id = std::this_thread::get_id();
30-
if (cur_locking_thread_.load(std::memory_order_consume) == thread_id) {
32+
if (cur_locking_thread_.load(std::memory_order_acquire) == thread_id) {
3133
lock_count_ += 1;
3234
return;
3335
}

common/src/concurrency/spinlock.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ namespace base::common::concurrency {
7171
Spinlock spinlock_;
7272
std::atomic<std::thread::id> cur_locking_thread_;
7373
std::thread::id default_thread_id_;
74-
std::size_t lock_count_{};
74+
std::atomic<std::size_t> lock_count_{0};
7575
};
7676

7777
/**

common/src/globals.hpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
namespace base::common::globals {
99
constexpr static auto* kBASE_NAME = "base";
1010
constexpr static auto* target_window_name = "";
11-
constexpr static auto* target_window_class = "grcWindow";
12-
constexpr static auto* target_process_name = "GTA5.exe";
11+
constexpr static auto* target_window_class = "sgaWindow";
12+
constexpr static auto* target_process_name = "GTA5_Enhanced.exe";
13+
constexpr static auto* target_process_name_be = "GTA5_Enhanced_BE.exe";
1314
}
1415

1516
#endif //GLOBALS_HPP_02231200

common/src/logging/exception/exception_report.cpp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ namespace base::common::logging::exception {
5151
if (!mem_info_res) {
5252
LOG_CRITICAL("Failed to get memory information for exception at: 0x{:X}", addr);
5353
}
54-
MEMORY_BASIC_INFORMATION mem_info = mem_info_res.value();
54+
const MEMORY_BASIC_INFORMATION mem_info = mem_info_res.value();
5555

5656
if (mem_info.AllocationProtect != PAGE_EXECUTE_READ && mem_info.AllocationProtect != PAGE_EXECUTE_READWRITE && mem_info.AllocationProtect != PAGE_EXECUTE_WRITECOPY) {
5757
LOG_ERROR("Address is not in executable memory.");
@@ -121,6 +121,9 @@ namespace base::common::logging::exception {
121121
msg << "***** Exception flags: " << except_rec->ExceptionFlags << " *****\n";
122122
msg << "***** Exception instruction: " << GetInstructionsStr(ctx->Rip).value_or("Failed to decompile exception area.") << " *****\n";
123123

124+
msg << "\n***** STACK DUMP *****\n";
125+
msg << GetRegisters(ctx);
126+
msg << '\n' << std::stacktrace::current(stacktrace_skip_count) << '\n';
124127

125128
msg << "\nLoaded Modules:\n";
126129

@@ -134,10 +137,6 @@ namespace base::common::logging::exception {
134137
msg << '\n';
135138
}
136139

137-
msg << "\n***** STACKDUMP *****\n";
138-
msg << GetRegisters(ctx);
139-
msg << '\n' << std::stacktrace::current(stacktrace_skip_count) << '\n';
140-
141140
std::filesystem::path report_dir = common::fs::vfs::GetExceptionReportsDir() / fmt::format("report_{}", common::util::time::GetTimeStamp());
142141
if (!std::filesystem::create_directories(report_dir)) {
143142
LOG_ERROR("Failed to create exception report directory '{}'", report_dir);

common/src/logging/exception/vectored_handler.cpp

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,102 @@
33
//
44

55
#include "vectored_handler.hpp"
6+
#include <Zydis/Zydis.h>
67
#include "exception_report.hpp"
78
#include "util.hpp"
89
#include "../logger.hpp"
910
#include "../../win32/memory.hpp"
1011

12+
#define VECTORED_HANDLER_ATTEMPT_RECOVERY
13+
1114
namespace base::common::logging::exception {
1215
namespace {
1316
PVOID cur_handler = nullptr;
1417

18+
// Thread-local variables to track recovery attempts
19+
thread_local std::uint32_t recovery_attempts = 0;
20+
thread_local std::chrono::steady_clock::time_point last_exception_time{};
21+
22+
// Configuration constants
23+
constexpr std::uint32_t MAX_RECOVERY_ATTEMPTS = 100;
24+
constexpr std::chrono::milliseconds RECOVERY_RESET_TIMEOUT{5000};
25+
26+
std::optional<ZydisDecodedInstruction> GetInstructionAtAddr(const std::uintptr_t addr) {
27+
ZydisDecoder decoder;
28+
#ifdef _M_AMD64
29+
if (!ZYAN_SUCCESS(ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64)))
30+
#else
31+
if (!ZYAN_SUCCESS(ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_COMPAT_32, ZYDIS_STACK_WIDTH_32)))
32+
#endif
33+
return std::nullopt;
34+
35+
ZydisDecodedInstruction instruction{};
36+
ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT];
37+
if (ZYAN_SUCCESS(ZydisDecoderDecodeFull(&decoder, reinterpret_cast<void*>(addr), 15, &instruction, operands))) {
38+
return instruction;
39+
}
40+
41+
return std::nullopt;
42+
}
43+
44+
bool ShouldAttemptRecovery(std::uintptr_t exception_address) {
45+
const auto now = std::chrono::steady_clock::now();
46+
47+
if (now - last_exception_time > RECOVERY_RESET_TIMEOUT) {
48+
recovery_attempts = 0;
49+
last_exception_time = now;
50+
}
51+
52+
if (recovery_attempts >= MAX_RECOVERY_ATTEMPTS) {
53+
LOG_ERROR("Maximum recovery attempts ({}) reached for address {:X}, giving up", MAX_RECOVERY_ATTEMPTS, exception_address);
54+
return false;
55+
}
56+
57+
return true;
58+
}
59+
60+
bool SkipToNextInstruction(const PCONTEXT ctx) {
61+
const auto exception_address = ctx->Rip;
62+
63+
if (!ShouldAttemptRecovery(exception_address)) {
64+
return false;
65+
}
66+
67+
const auto instruction_opt = GetInstructionAtAddr(exception_address);
68+
if (!instruction_opt.has_value()) {
69+
LOG_ERROR("Failed to decode instruction at {:X}", exception_address);
70+
recovery_attempts++; // Count failed decode as an attempt
71+
return false;
72+
}
73+
74+
const auto& instruction = instruction_opt.value();
75+
auto next_instruction = exception_address + instruction.length;
76+
77+
recovery_attempts++;
78+
79+
LOG_WARN("Trying to recover from exception (attempt {}/{}): {:X} -> {:X}", recovery_attempts, MAX_RECOVERY_ATTEMPTS, exception_address, next_instruction);
80+
81+
ctx->Rip = next_instruction;
82+
return true;
83+
}
84+
1585
void MSVCException(const PEXCEPTION_POINTERS except) {
86+
const auto exception_address = except->ContextRecord->Rip;
87+
88+
// Only log exceptions from our own module
89+
if (!win32::memory::IsAddressInCurrentModule(exception_address)) {
90+
return;
91+
}
92+
1693
const std::uint32_t pid = GetCurrentProcessId();
1794

18-
auto mod_name_res = win32::memory::GetModuleNameFromAddress(pid, except->ContextRecord->Rip);
95+
auto mod_name_res = win32::memory::GetModuleNameFromAddress(pid, exception_address);
1996
std::string mod_name;
2097
if (mod_name_res.has_value()) {
2198
mod_name = mod_name_res.value();
2299
}
23100

24-
auto offset_res = win32::memory::GetModuleOffsetFromAddress(pid, except->ContextRecord->Rip);
101+
auto offset_res = win32::memory::GetModuleOffsetFromAddress(pid, exception_address);
25102
std::uintptr_t offset = 0;
26103
if (offset_res.has_value()) {
27104
offset = offset_res.value();
@@ -57,7 +134,12 @@ namespace base::common::logging::exception {
57134
return EXCEPTION_CONTINUE_SEARCH;
58135
}
59136

60-
// TODO: implement some cursed fatal exception recovery
137+
#ifdef VECTORED_HANDLER_ATTEMPT_RECOVERY
138+
// We will now attempt to recover by moving the RIP to the next instruction (Yes I'm serious)
139+
if (SkipToNextInstruction(except->ContextRecord)) {
140+
return EXCEPTION_CONTINUE_EXECUTION;
141+
}
142+
#endif
61143

62144
if (auto stacktrace_res = WriteExceptionReport(except, 7); stacktrace_res.has_value()) {
63145
LOG_CRITICAL(stacktrace_res.value());

common/src/win32/memory.cpp

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
//
44

55
#include "memory.hpp"
6+
#include <intrin.h>
67

78
namespace base::win32::memory {
8-
StatusOr<std::uintptr_t> GetModuleBaseAddress(std::uint32_t pid, const std::string& mod_name) {
9+
StatusOr<std::uintptr_t> GetModuleBaseAddress(const std::uint32_t pid, const std::string& mod_name) {
910
std::uint64_t mod_base_addr = 0;
10-
HANDLE h_snap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid);
11+
const HANDLE h_snap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid);
1112
if (h_snap == INVALID_HANDLE_VALUE) {
1213
return MakeFailure<ResultCode::kINVALID_HANDLE>();
1314
}
@@ -17,7 +18,7 @@ namespace base::win32::memory {
1718
if (Module32First(h_snap, &mod_entry)) {
1819
do {
1920
if (!_stricmp(mod_entry.szModule, mod_name.c_str())) {
20-
mod_base_addr = (std::uint64_t)mod_entry.modBaseAddr;
21+
mod_base_addr = reinterpret_cast<std::uint64_t>(mod_entry.modBaseAddr);
2122
break;
2223
}
2324
} while (Module32Next(h_snap, &mod_entry));
@@ -28,17 +29,17 @@ namespace base::win32::memory {
2829
return mod_base_addr;
2930
}
3031

31-
StatusOr<MODULEENTRY32> GetModuleFromAddress(std::uint32_t pid, std::uintptr_t addr) {
32+
StatusOr<MODULEENTRY32> GetModuleFromAddress(const std::uint32_t pid, const std::uintptr_t addr) {
3233
MODULEENTRY32 mod_entry;
33-
HANDLE h_snap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid);
34+
const HANDLE h_snap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid);
3435
if (h_snap == INVALID_HANDLE_VALUE) {
3536
return MakeFailure<ResultCode::kINVALID_HANDLE>();
3637
}
3738

3839
mod_entry.dwSize = sizeof(mod_entry);
3940
if (Module32First(h_snap, &mod_entry)) {
4041
do {
41-
if ((std::uint64_t)mod_entry.modBaseAddr <= addr && addr <= (std::uint64_t)mod_entry.modBaseAddr + mod_entry.modBaseSize) {
42+
if (reinterpret_cast<std::uint64_t>(mod_entry.modBaseAddr) <= addr && addr <= reinterpret_cast<std::uint64_t>(mod_entry.modBaseAddr) + mod_entry.modBaseSize) {
4243
break;
4344
}
4445
} while (Module32Next(h_snap, &mod_entry));
@@ -49,26 +50,26 @@ namespace base::win32::memory {
4950
return mod_entry;
5051
}
5152

52-
StatusOr<std::string> GetModuleNameFromAddress(std::uint32_t pid, std::uintptr_t addr) {
53+
StatusOr<std::string> GetModuleNameFromAddress(const std::uint32_t pid, const std::uintptr_t addr) {
5354
auto mod_addr = GetModuleFromAddress(pid, addr);
5455
if (!mod_addr)
5556
return mod_addr.error().Forward();
5657

5758
return mod_addr->szModule;
5859
}
5960

60-
StatusOr<uintptr_t> GetModuleOffsetFromAddress(std::uint32_t pid, std::uintptr_t addr) {
61+
StatusOr<uintptr_t> GetModuleOffsetFromAddress(const std::uint32_t pid, const std::uintptr_t addr) {
6162
auto mod_addr = GetModuleFromAddress(pid, addr);
6263
if (!mod_addr)
6364
return mod_addr.error();
6465

6566
return addr - reinterpret_cast<std::uintptr_t>(mod_addr->modBaseAddr);
6667
}
6768

68-
StatusOr<MODULEENTRY32> GetModuleFromHModule(HMODULE mod) {
69+
StatusOr<MODULEENTRY32> GetModuleFromHModule(const HMODULE mod) {
6970
MODULEENTRY32 mod_entry{};
7071
mod_entry.dwSize = sizeof(MODULEENTRY32);
71-
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId());
72+
const HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId());
7273
if (hSnapshot == INVALID_HANDLE_VALUE) {
7374
return MakeFailure<ResultCode::kINVALID_HANDLE>();
7475
}
@@ -83,4 +84,26 @@ namespace base::win32::memory {
8384
CloseHandle(hSnapshot);
8485
return mod_entry;
8586
}
87+
88+
__declspec(noinline) bool IsAddressInCurrentModule(const std::uintptr_t addr) {
89+
const std::uint32_t pid = GetCurrentProcessId();
90+
91+
// Get the module name of the caller (our DLL) using the return address
92+
auto caller_address = reinterpret_cast<std::uintptr_t>(_ReturnAddress());
93+
auto our_module_name = GetModuleNameFromAddress(pid, caller_address);
94+
95+
if (!our_module_name.has_value()) {
96+
return false;
97+
}
98+
99+
// Get the module name of the input address
100+
auto addr_module_name = GetModuleNameFromAddress(pid, addr);
101+
102+
if (!addr_module_name.has_value()) {
103+
return false;
104+
}
105+
106+
// Compare the module names
107+
return _stricmp(our_module_name->c_str(), addr_module_name->c_str()) == 0;
108+
}
86109
}

common/src/win32/memory.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ namespace base::win32::memory {
4444
* \return The module entry of the module
4545
*/
4646
StatusOr<MODULEENTRY32> GetModuleFromHModule(HMODULE mod);
47+
/**
48+
* \brief Check if an address is within the current module's memory space
49+
* \param addr address to check
50+
* \return true if the address is within the current module, false otherwise
51+
*/
52+
bool IsAddressInCurrentModule(std::uintptr_t addr);
4753
}
4854

4955
#endif //GTA_BASE_MEMORY_6E2CBAF0C25F49E69A8329E5694C9C0B_HPP

common/src/win32/misc.cpp

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,20 +90,21 @@ namespace base::win32 {
9090
return foreground_window == GetConsoleWindow() || window;
9191
}
9292

93-
bool IsTargetProcess(const std::string& target_process) {
94-
const auto exe_name = std::make_unique<CHAR[]>(MAX_PATH);
95-
const std::uint32_t str_len = GetModuleFileNameA(nullptr, exe_name.get(), MAX_PATH);
93+
bool IsGameProcess() {
94+
static const auto exe_name = std::make_unique<CHAR[]>(MAX_PATH);
95+
static const std::uint32_t str_len = GetModuleFileNameA(nullptr, exe_name.get(), MAX_PATH);
9696

9797
if (str_len == 0) {
9898
LOG_ERROR("Failed to get module file name. win32 err code: {}", GetLastError());
9999
return false;
100100
}
101101

102-
const std::string cur_proc_name = std::filesystem::path(std::string(exe_name.get(), str_len)).filename().string();
102+
static const std::string cur_proc_name = std::filesystem::path(std::string(exe_name.get(), str_len)).filename().string();
103103

104-
return cur_proc_name == target_process;
104+
return cur_proc_name == common::globals::target_process_name || cur_proc_name == common::globals::target_process_name_be;
105105
}
106106

107+
107108
std::string GetLastErrorStr() {
108109
DWORD err = GetLastError();
109110
if (err == 0) {

common/src/win32/misc.hpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,12 @@ namespace base::win32 {
6868
* \return bool
6969
*/
7070
bool IsForegroundWindow(HWND window = GetGameHwnd().value());
71+
7172
/**
72-
* \brief Check if the current process is the target process
73-
* \param target_process The name of the target process
73+
* \brief Check if the current process is the game process
7474
* \return bool
7575
*/
76-
bool IsTargetProcess(const std::string& target_process = common::globals::target_process_name);
76+
bool IsGameProcess();
7777

7878
/**
7979
* \brief Convert the return value of GetLastError() to a string message

common/src/win32/signal.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,4 @@ namespace base::win32 {
4949
HANDLE signal_h_;
5050
};
5151
}
52-
#endif //SIGNAL_HPP_02232550
52+
#endif //SIGNAL_HPP_02232550

0 commit comments

Comments
 (0)