Crash reports and a guard for burning bone particles without a device
- re/crash: unhandled exceptions write sacred_re_crash.txt (module+offset, registers, return addresses on the stack) and a minidump; the game's own filter is chained behind ours - guards: cParticleSystem_burningBone::Render (0x774e10) skips drawing when given a null device (crash at sacred.exe+0x374e39 after Esc while walking) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
e914bd1590
commit
5338527b49
5 files changed
+163
-1
No files matched your search
@@ -0,0 +1,118 @@
|
||||
// Crash report: on an unhandled exception, writes sacred_re_crash.txt (code,
|
||||
// address as module + offset, registers, return addresses found on the stack)
|
||||
// and sacred_re_crash.dmp next to the DLL. The game may install its own
|
||||
// filter later; SetUnhandledExceptionFilter is hooked so ours stays first and
|
||||
// passes the crash on to the game's afterwards.
|
||||
|
||||
#include "re/crash.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <dbghelp.h>
|
||||
|
||||
#include "re/hook.h"
|
||||
#include "re/log.h"
|
||||
|
||||
namespace re {
|
||||
namespace {
|
||||
|
||||
std::wstring g_dir;
|
||||
LPTOP_LEVEL_EXCEPTION_FILTER g_gameFilter = nullptr;
|
||||
volatile LONG g_reported = 0;
|
||||
|
||||
// "module+offset" for a code address.
|
||||
void DescribeAddress(uintptr_t address, char* out, size_t size) {
|
||||
HMODULE module = nullptr;
|
||||
char path[MAX_PATH] = "?";
|
||||
if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
|
||||
reinterpret_cast<LPCSTR>(address), &module)) {
|
||||
GetModuleFileNameA(module, path, MAX_PATH);
|
||||
const char* name = strrchr(path, '\\');
|
||||
snprintf(out, size, "%08x %s+%x", static_cast<unsigned>(address), name ? name + 1 : path,
|
||||
static_cast<unsigned>(address - reinterpret_cast<uintptr_t>(module)));
|
||||
} else {
|
||||
snprintf(out, size, "%08x", static_cast<unsigned>(address));
|
||||
}
|
||||
}
|
||||
|
||||
bool IsCode(uintptr_t address) {
|
||||
MEMORY_BASIC_INFORMATION info;
|
||||
if (!VirtualQuery(reinterpret_cast<void*>(address), &info, sizeof(info)) || info.State != MEM_COMMIT) {
|
||||
return false;
|
||||
}
|
||||
return (info.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE)) != 0;
|
||||
}
|
||||
|
||||
void WriteReport(EXCEPTION_POINTERS* info) {
|
||||
FILE* file = _wfopen((g_dir + L"sacred_re_crash.txt").c_str(), L"w");
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
const EXCEPTION_RECORD& record = *info->ExceptionRecord;
|
||||
const CONTEXT& c = *info->ContextRecord;
|
||||
char where[MAX_PATH + 32];
|
||||
DescribeAddress(reinterpret_cast<uintptr_t>(record.ExceptionAddress), where, sizeof(where));
|
||||
fprintf(file, "exception %08lx at %s, thread %lu\n", record.ExceptionCode, where, GetCurrentThreadId());
|
||||
if (record.ExceptionCode == EXCEPTION_ACCESS_VIOLATION && record.NumberParameters >= 2) {
|
||||
fprintf(file, "%s address %08x\n", record.ExceptionInformation[0] ? "writing" : "reading",
|
||||
static_cast<unsigned>(record.ExceptionInformation[1]));
|
||||
}
|
||||
fprintf(file, "eax %08lx ebx %08lx ecx %08lx edx %08lx esi %08lx edi %08lx ebp %08lx esp %08lx\n", c.Eax, c.Ebx,
|
||||
c.Ecx, c.Edx, c.Esi, c.Edi, c.Ebp, c.Esp);
|
||||
fprintf(file, "return addresses on the stack (innermost first):\n");
|
||||
const auto* stack = reinterpret_cast<const uintptr_t*>(c.Esp);
|
||||
int found = 0;
|
||||
for (int i = 0; i < 4096 && found < 48; ++i) {
|
||||
MEMORY_BASIC_INFORMATION mem;
|
||||
if (!VirtualQuery(stack + i, &mem, sizeof(mem)) || mem.State != MEM_COMMIT) {
|
||||
break;
|
||||
}
|
||||
const uintptr_t value = stack[i];
|
||||
if (IsCode(value)) {
|
||||
DescribeAddress(value, where, sizeof(where));
|
||||
fprintf(file, " [esp+%04x] %s\n", i * 4, where);
|
||||
++found;
|
||||
}
|
||||
}
|
||||
fclose(file);
|
||||
|
||||
HANDLE dump = CreateFileW((g_dir + L"sacred_re_crash.dmp").c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS,
|
||||
FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (dump != INVALID_HANDLE_VALUE) {
|
||||
MINIDUMP_EXCEPTION_INFORMATION exception = {GetCurrentThreadId(), info, FALSE};
|
||||
MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), dump, MiniDumpWithIndirectlyReferencedMemory,
|
||||
&exception, nullptr, nullptr);
|
||||
CloseHandle(dump);
|
||||
}
|
||||
}
|
||||
|
||||
LONG WINAPI Filter(EXCEPTION_POINTERS* info) {
|
||||
if (InterlockedExchange(&g_reported, 1) == 0) {
|
||||
WriteReport(info);
|
||||
}
|
||||
return g_gameFilter ? g_gameFilter(info) : EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void InstallCrashHandler(const std::wstring& dir) {
|
||||
g_dir = dir;
|
||||
g_gameFilter = SetUnhandledExceptionFilter(&Filter);
|
||||
}
|
||||
|
||||
} // namespace re
|
||||
|
||||
// The game's own filter goes behind ours.
|
||||
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI Hooked_SetUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER filter);
|
||||
RE_HOOK_API(SetUnhandledExceptionFilter, Hooked_SetUnhandledExceptionFilter);
|
||||
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI Hooked_SetUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER filter) {
|
||||
if (filter == &re::Filter) {
|
||||
return Hooked_SetUnhandledExceptionFilter_original(filter);
|
||||
}
|
||||
LPTOP_LEVEL_EXCEPTION_FILTER previous = re::g_gameFilter;
|
||||
re::g_gameFilter = filter;
|
||||
return previous;
|
||||
}
|
||||
Reference in new issue
Block a user