Initial commit — leak-hunt project complete

Five bugs identified and patched in retail Asheron's Call client:
- v3b: palette refcount over-increment (3-byte NOP at two sites)
- v5: RenderSurface PurgeResource no-op stub (vtable slot 2 thunk)
- v11: two dangling-pointer crash guards (NULL-check + reorder)
- v14: CEnvCell::Destroy ClipPlaneList leak (18-byte JMP to cleanup thunk)
- v22: unpacker stale-pointer SEH guard (whole-function __try/__except)

All five ship in leakfix.dll (117 KB, SHA d282f23c…) which is loaded
by acclient.exe at process start via PE import table patching by
tools/install_leakfix.py.

Controlled 15-client fleet soak: unpatched control died at 26h with
palette exhaustion; all 14 patched clients survived past that point
and reached ≥5-day uptime.

Residual ~15 MB/h growth traced to d3d9.dll's internal slab allocator
(260KB surface backing buffers retained after Release). See REPORT.md
§10 for the full investigation; conclusion is that it's unfixable from
outside d3d9.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
acbot 2026-05-23 21:05:17 +02:00
commit 57b5e43d0e
199 changed files with 1648333 additions and 0 deletions

View file

@ -0,0 +1,74 @@
#include "logging.h"
#include <windows.h>
#include <cstdio>
#include <cstdarg>
#include <cstring>
namespace {
HANDLE g_log = INVALID_HANDLE_VALUE;
CRITICAL_SECTION g_cs;
bool g_cs_inited = false;
void ensure_cs() {
if (!g_cs_inited) {
InitializeCriticalSection(&g_cs);
g_cs_inited = true;
}
}
void write_line(const char* s, size_t len) {
if (g_log == INVALID_HANDLE_VALUE) return;
DWORD written = 0;
WriteFile(g_log, s, (DWORD)len, &written, nullptr);
}
}
namespace leakfix {
void log_init(const char* path) {
ensure_cs();
EnterCriticalSection(&g_cs);
if (g_log != INVALID_HANDLE_VALUE) { LeaveCriticalSection(&g_cs); return; }
g_log = CreateFileA(path, FILE_APPEND_DATA, FILE_SHARE_READ | FILE_SHARE_WRITE,
nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
SetFilePointer(g_log, 0, nullptr, FILE_END);
LeaveCriticalSection(&g_cs);
logf("===== leakfix.dll loaded (pid=%lu) =====", GetCurrentProcessId());
}
void log_close() {
ensure_cs();
EnterCriticalSection(&g_cs);
if (g_log != INVALID_HANDLE_VALUE) {
CloseHandle(g_log);
g_log = INVALID_HANDLE_VALUE;
}
LeaveCriticalSection(&g_cs);
}
void logf(const char* fmt, ...) {
ensure_cs();
char buf[1024];
SYSTEMTIME st;
GetLocalTime(&st);
int n = std::snprintf(buf, sizeof(buf),
"[%04d-%02d-%02d %02d:%02d:%02d.%03d] ",
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
va_list ap; va_start(ap, fmt);
int m = std::vsnprintf(buf + n, sizeof(buf) - n - 2, fmt, ap);
va_end(ap);
if (m < 0) m = 0;
int total = n + m;
if (total >= (int)sizeof(buf) - 1) total = sizeof(buf) - 2;
buf[total] = '\n';
buf[total + 1] = '\0';
EnterCriticalSection(&g_cs);
write_line(buf, (size_t)total + 1);
LeaveCriticalSection(&g_cs);
// Also forward to debugger if attached
OutputDebugStringA(buf);
}
} // namespace leakfix