mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-17 13:57:05 +02:00
124 lines
6.8 KiB
C++
124 lines
6.8 KiB
C++
#include "backtrace.h"
|
|
#include "protocol.h"
|
|
#include "backtrace_config.h"
|
|
#include <algorithm>
|
|
#include <format>
|
|
#include <vector>
|
|
|
|
namespace dingosdk::backtrace {
|
|
namespace {
|
|
std::array<HANDLE, handle_count> handles{};
|
|
HANDLE helper{};
|
|
SharedReport* report{};
|
|
// 0 = ready, 1 = capture owns the IPC, 2 = stopped/initializing.
|
|
volatile LONG capturing{2};
|
|
struct View {
|
|
void* value{};
|
|
~View() { if (value) UnmapViewOfFile(value); }
|
|
};
|
|
template<std::size_t N> bool copy(std::array<wchar_t, N>& target, const std::wstring& value) {
|
|
if (value.size() >= N) return false;
|
|
std::copy(value.begin(), value.end(), target.begin());
|
|
target[value.size()] = 0;
|
|
return true;
|
|
}
|
|
}
|
|
StartResult start(const std::filesystem::path& log_directory) noexcept {
|
|
try {
|
|
if (report) return StartResult::ready;
|
|
if (environment(L"RESKATE_CRASH_REPORTING") == L"0") return StartResult::disabled;
|
|
auto url = environment(L"RESKATE_BACKTRACE_URL");
|
|
if (url.empty()) url = configured_url;
|
|
if (url.empty()) return StartResult::disabled;
|
|
if (!url.starts_with(L"https://") || url.size() >= SharedReport{}.url.size()) return StartResult::failed;
|
|
HMODULE module{};
|
|
if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
|
|
reinterpret_cast<LPCWSTR>(&handles), &module)) return StartResult::failed;
|
|
std::array<wchar_t, 32768> module_path{}, process_path{};
|
|
const auto module_size = GetModuleFileNameW(module, module_path.data(), static_cast<DWORD>(module_path.size()));
|
|
const auto process_size = GetModuleFileNameW(nullptr, process_path.data(), static_cast<DWORD>(process_path.size()));
|
|
if (!module_size || module_size >= module_path.size() || !process_size || process_size >= process_path.size())
|
|
return StartResult::failed;
|
|
const auto executable = std::filesystem::path(module_path.data()).parent_path() / L"ReSkateLauncher.exe";
|
|
SECURITY_ATTRIBUTES security{sizeof(security), nullptr, TRUE};
|
|
std::array<Handle, handle_count> owned;
|
|
owned[mapping].value = CreateFileMappingW(INVALID_HANDLE_VALUE, &security, PAGE_READWRITE, 0, sizeof(SharedReport), nullptr);
|
|
for (std::size_t i = requested; i <= ready; ++i) owned[i].value = CreateEventW(&security, TRUE, FALSE, nullptr);
|
|
if (!DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(), GetCurrentProcess(), &owned[parent].value,
|
|
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | SYNCHRONIZE, TRUE, 0)) return StartResult::failed;
|
|
std::array<HANDLE, handle_count> inherited{};
|
|
for (std::size_t i = 0; i < handle_count; ++i) {
|
|
if (!owned[i].value) return StartResult::failed;
|
|
inherited[i] = owned[i].value;
|
|
}
|
|
View view{MapViewOfFile(owned[mapping].value, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(SharedReport))};
|
|
if (!view.value) return StartResult::failed;
|
|
auto* shared = new (view.value) SharedReport{};
|
|
if (!copy(shared->url, url) || !copy(shared->directory, std::filesystem::absolute(log_directory / L"crashes").wstring()) ||
|
|
!copy(shared->application, std::filesystem::path(process_path.data()).filename().wstring())) return StartResult::failed;
|
|
// Frostbite handles some fatal exceptions itself and writes a native dump
|
|
// before exiting, so those faults never reach our last-chance filter. The
|
|
// game's LocalAppData is redirected to ReSkate\Game (Extension/Boot/user_data_redirect.cpp).
|
|
if (_wcsicmp(shared->application.data(), L"Skate.exe") == 0) {
|
|
const auto local = environment(L"LOCALAPPDATA");
|
|
if (!local.empty() && !copy(shared->native_directory,
|
|
(std::filesystem::path(local) / L"ReSkate" / L"Game" / L"Skate" / L"CrashDumps").wstring()))
|
|
return StartResult::failed;
|
|
}
|
|
|
|
SIZE_T size{};
|
|
InitializeProcThreadAttributeList(nullptr, 1, 0, &size);
|
|
std::vector<unsigned char> storage(size);
|
|
auto* attributes = reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(storage.data());
|
|
if (!InitializeProcThreadAttributeList(attributes, 1, 0, &size)) return StartResult::failed;
|
|
struct Attributes { LPPROC_THREAD_ATTRIBUTE_LIST value; ~Attributes() { DeleteProcThreadAttributeList(value); } } cleanup{attributes};
|
|
if (!UpdateProcThreadAttribute(attributes, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
|
|
inherited.data(), sizeof(inherited), nullptr, nullptr)) return StartResult::failed;
|
|
auto command = std::format(L"\"{}\" --reskate-crash-helper", executable.wstring());
|
|
for (const auto handle : inherited) command += std::format(L" {}", reinterpret_cast<ULONG_PTR>(handle));
|
|
STARTUPINFOEXW startup{};
|
|
startup.StartupInfo.cb = sizeof(startup);
|
|
startup.lpAttributeList = attributes;
|
|
PROCESS_INFORMATION process{};
|
|
if (!CreateProcessW(executable.c_str(), command.data(), nullptr, nullptr, TRUE,
|
|
CREATE_NO_WINDOW | EXTENDED_STARTUPINFO_PRESENT, nullptr, executable.parent_path().c_str(),
|
|
&startup.StartupInfo, &process)) return StartResult::failed;
|
|
Handle child(process.hProcess), thread(process.hThread);
|
|
// Prevent later child processes from inheriting our IPC and parent handles.
|
|
for (const auto handle : inherited) SetHandleInformation(handle, HANDLE_FLAG_INHERIT, 0);
|
|
const HANDLE wait_for[]{owned[ready].value, child.value};
|
|
if (WaitForMultipleObjects(2, wait_for, FALSE, 5000) != WAIT_OBJECT_0) {
|
|
SetEvent(owned[stopping].value);
|
|
return StartResult::failed;
|
|
}
|
|
for (std::size_t i = 0; i < handle_count; ++i) handles[i] = owned[i].release();
|
|
helper = child.release();
|
|
report = shared;
|
|
view.value = nullptr;
|
|
InterlockedExchange(&capturing, 0);
|
|
return StartResult::ready;
|
|
} catch (...) { return StartResult::failed; }
|
|
}
|
|
void capture(EXCEPTION_POINTERS* exception) noexcept {
|
|
if (!exception || InterlockedCompareExchange(&capturing, 1, 0) != 0) return;
|
|
report->thread_id = GetCurrentThreadId();
|
|
report->exception = exception;
|
|
MemoryBarrier();
|
|
if (SetEvent(handles[requested])) {
|
|
const HANDLE wait_for[]{handles[captured], helper};
|
|
WaitForMultipleObjects(2, wait_for, FALSE, capture_timeout_ms);
|
|
}
|
|
}
|
|
void stop() noexcept {
|
|
// An exception already using these handles owns them until process exit.
|
|
// Never unmap its report or close its events from a concurrent shutdown.
|
|
if (InterlockedCompareExchange(&capturing, 2, 0) != 0) return;
|
|
SetEvent(handles[stopping]);
|
|
UnmapViewOfFile(report);
|
|
report = nullptr;
|
|
for (auto& handle : handles) { CloseHandle(handle); handle = nullptr; }
|
|
CloseHandle(helper);
|
|
helper = nullptr;
|
|
}
|
|
}
|