Auto-attack: bow shots chain at full speed, arts start by their cooldown; hang watchdog
- The shot end's button check (0x4def52) answers "held" for the auto-attack target, like the melee swing end. - An art order counts as started when the art's cooldown rises, so bow arts and arts ordered mid-swing hand over to auto-attack. - re/crash.cpp: a watchdog writes sacred_re_hang.dmp when the engine tick stops for 10 s while the game is in front. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
432cac2ccf
commit
184c7058fd
6 files changed
+125
-13
No files matched your search
@@ -3,6 +3,11 @@
|
||||
// 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.
|
||||
//
|
||||
// Hangs: a watchdog thread writes sacred_re_hang.dmp (all threads) when the
|
||||
// engine tick (re::EngineHeartbeat) has stopped for kHangMs while the game
|
||||
// window is in front; once per hang. The game freezing on F2 while walking
|
||||
// (2026-10-18) left no report.
|
||||
|
||||
#include "re/crash.h"
|
||||
|
||||
@@ -105,6 +110,45 @@ void WriteReport(EXCEPTION_POINTERS* info) {
|
||||
}
|
||||
}
|
||||
|
||||
constexpr DWORD kHangMs = 10000;
|
||||
volatile LONG g_lastBeat = 0; // GetTickCount of the last engine tick, 0: no game running
|
||||
|
||||
bool GameInFront() {
|
||||
DWORD process = 0;
|
||||
GetWindowThreadProcessId(GetForegroundWindow(), &process);
|
||||
return process == GetCurrentProcessId();
|
||||
}
|
||||
|
||||
void WriteHangDump() {
|
||||
HANDLE dump = CreateFileW((g_dir + L"sacred_re_hang.dmp").c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS,
|
||||
FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (dump != INVALID_HANDLE_VALUE) {
|
||||
MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), dump,
|
||||
static_cast<MINIDUMP_TYPE>(MiniDumpWithIndirectlyReferencedMemory | MiniDumpWithThreadInfo),
|
||||
nullptr, nullptr, nullptr);
|
||||
CloseHandle(dump);
|
||||
}
|
||||
}
|
||||
|
||||
DWORD WINAPI Watchdog(void*) {
|
||||
bool reported = false;
|
||||
for (;;) {
|
||||
Sleep(1000);
|
||||
const DWORD beat = static_cast<DWORD>(g_lastBeat);
|
||||
// Signed: a tick may land between reading the beat and the clock.
|
||||
if (beat == 0 || static_cast<LONG>(GetTickCount() - beat) < static_cast<LONG>(kHangMs)) {
|
||||
reported = false;
|
||||
continue;
|
||||
}
|
||||
if (!reported && GameInFront()) {
|
||||
reported = true;
|
||||
Log("hang: no engine tick for %lu ms, writing sacred_re_hang.dmp", GetTickCount() - beat);
|
||||
WriteHangDump();
|
||||
Log("hang: dump written");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LONG WINAPI Filter(EXCEPTION_POINTERS* info) {
|
||||
if (InterlockedExchange(&g_reported, 1) == 0) {
|
||||
WriteReport(info);
|
||||
@@ -117,6 +161,13 @@ LONG WINAPI Filter(EXCEPTION_POINTERS* info) {
|
||||
void InstallCrashHandler(const std::wstring& dir) {
|
||||
g_dir = dir;
|
||||
g_gameFilter = SetUnhandledExceptionFilter(&Filter);
|
||||
if (HANDLE thread = CreateThread(nullptr, 0, &Watchdog, nullptr, 0, nullptr)) {
|
||||
CloseHandle(thread);
|
||||
}
|
||||
}
|
||||
|
||||
void EngineHeartbeat(bool running) {
|
||||
InterlockedExchange(&g_lastBeat, running ? static_cast<LONG>(GetTickCount() | 1) : 0);
|
||||
}
|
||||
|
||||
} // namespace re
|
||||
|
||||
Reference in new issue
Block a user