leakhunt/bin/keepalive_f5.ps1
acbot 57b5e43d0e 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>
2026-05-23 21:07:58 +02:00

72 lines
2.7 KiB
PowerShell

# keepalive_f5.ps1
# Pulses F5 to the AC client window every N seconds to defeat Coldeve's
# idle-kick. Finds the window by class "Turbine Device Class" so it's
# robust to title-string encoding quirks.
param(
[int]$IntervalSec = 60,
[string]$LogFile = "C:\Users\acbot\leakhunt\artifacts\phase1\keepalive.log"
)
$ErrorActionPreference = "Stop"
Add-Type @"
using System;
using System.Runtime.InteropServices;
using System.Text;
public class LhKA {
public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
[DllImport("user32.dll")] public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
[DllImport("user32.dll", CharSet=CharSet.Unicode)] public static extern int GetClassName(IntPtr hWnd, StringBuilder text, int count);
[DllImport("user32.dll")] public static extern int GetWindowThreadProcessId(IntPtr hWnd, out uint pid);
[DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll")] public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
}
"@ -ErrorAction SilentlyContinue
function W([string]$msg) {
$t = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
"[$t] $msg" | Out-File -Append -Encoding utf8 -FilePath $LogFile
}
function Find-AcWindow {
param([int]$ProcessId)
$found = $null
$cb = [LhKA+EnumWindowsProc]{
param($h, $l)
$pid_ = 0
[void][LhKA]::GetWindowThreadProcessId($h, [ref]$pid_)
if ($pid_ -eq $ProcessId -and [LhKA]::IsWindowVisible($h)) {
$cls = New-Object System.Text.StringBuilder 256
[void][LhKA]::GetClassName($h, $cls, $cls.Capacity)
if ($cls.ToString() -eq "Turbine Device Class") {
$script:found = $h
return $false
}
}
return $true
}
[void][LhKA]::EnumWindows($cb, [IntPtr]::Zero)
return $script:found
}
$WM_KEYDOWN = 0x0100
$WM_KEYUP = 0x0101
$VK_F5 = 0x74
W "keepalive_f5 starting, pulse every $IntervalSec s"
while ($true) {
$ac = Get-Process -Name acclient -ErrorAction SilentlyContinue | Sort-Object StartTime -Descending | Select-Object -First 1
if (-not $ac) { W "acclient gone - exiting"; break }
$hwnd = Find-AcWindow -ProcessId $ac.Id
if (-not $hwnd) { W "AC window not found for PID $($ac.Id); waiting"; Start-Sleep -Seconds $IntervalSec; continue }
$ok1 = [LhKA]::PostMessage($hwnd, $WM_KEYDOWN, [IntPtr]$VK_F5, [IntPtr]0)
Start-Sleep -Milliseconds 50
$ok2 = [LhKA]::PostMessage($hwnd, $WM_KEYUP, [IntPtr]$VK_F5, [IntPtr]0)
W "F5 pulse -> hwnd=0x$($hwnd.ToInt64().ToString('X')) pid=$($ac.Id) PM=$([math]::Round($ac.PrivateMemorySize64/1MB,1))MB ok=$ok1,$ok2"
Start-Sleep -Seconds $IntervalSec
}