mirror of
https://github.com/brues-code/ClassicAPI.git
synced 2026-11-10 20:07:37 +00:00
event: remove the mis-timed EnsureCapacity table-grow valve
EnsureCapacity was meant to grow the event table when the engine's ~200 low NULL gaps ran short of our ~46 reservations, by reallocating the entry buffer via Storm and swapping the base/count globals. But it was called from LoadScriptFunctions_h, which runs BEFORE RebuildEventTable inside FUN_0048FBF0 -- so it always operated on the previous cycle's still-populated table, tripped its own "abort if any chain is live" guard, and never actually grew anything. Mis-timed dead code doing risky buffer surgery on the shared event table. Claim-NULL into the engine's low gaps is the sole mechanism now; it always suffices, and TryClaim logs once if it somehow doesn't. Drop the function, its Storm alloc/free helpers, and the now-inaccurate offset comments.
This commit is contained in:
@@ -60,13 +60,6 @@ static bool __fastcall FrameScript_Initialize_h() {
|
||||
static void __fastcall LoadScriptFunctions_h() {
|
||||
LoadScriptFunctions_o();
|
||||
Game::RunModuleRegistrations();
|
||||
// Now that every module has reserved its events (incl. the lazily-
|
||||
// reserved TTS ones), and before any frame has registered, ensure the
|
||||
// event table has enough NULL slots for our reservations — growing it
|
||||
// on-demand if a crowded install has drained the shared gap pool. No-op
|
||||
// in the common case. Must run here: reservations are complete and all
|
||||
// chains are still empty (a prerequisite for the safe buffer move).
|
||||
Event::Custom::EnsureCapacity();
|
||||
// Permit `Event::Custom::TryClaim` to actually write to the event
|
||||
// table from here on. Earlier writes (during the engine's own
|
||||
// boot-time `RegisterEvent` flurry, plus SuperWoWhook/etc.) can
|
||||
|
||||
+11
-12
@@ -3988,8 +3988,9 @@ enum Offsets {
|
||||
VAR_EVENT_TABLE_BASE_PTR = 0x00CEEF68,
|
||||
VAR_EVENT_TABLE_COUNT = 0x00CEEF64,
|
||||
// The FrameScript_EventObject is a `{cap@+0, count@+4, data@+8}` at
|
||||
// 0x00CEEF60, so COUNT/BASE above are its +4/+8. The +0 capacity mirror
|
||||
// is updated alongside them when `EnsureCapacity` grows the table.
|
||||
// 0x00CEEF60, so COUNT/BASE above are its +4/+8. (CAP is unused now —
|
||||
// an EnsureCapacity grow-the-table valve that wrote it was removed;
|
||||
// see the events "claim-NULL is the sole mechanism" note in CLAUDE.md.)
|
||||
VAR_EVENT_TABLE_CAP = 0x00CEEF60,
|
||||
EVENT_ENTRY_STRIDE = 0x10,
|
||||
OFF_EVENT_ENTRY_NAME = 0x00,
|
||||
@@ -4034,11 +4035,9 @@ enum Offsets {
|
||||
// Event-table allocator — `__thiscall(EventObject *this, uint32 count)`.
|
||||
// Called by `RebuildEventTable` to (re)size the entry array. SuperWoW and
|
||||
// nampower co-hook it to destructively clamp the FrameXML count (`> 200`;
|
||||
// glue is smaller) to 700. We deliberately DON'T hook it: a destructive
|
||||
// clamp can't be beaten in-chain (it overwrites whatever ran before it,
|
||||
// and hook order isn't controllable). Instead `Event::Custom::EnsureCapacity`
|
||||
// reallocates the entry buffer directly via Storm + swaps the base/count
|
||||
// globals, bypassing this clamp entirely. Kept for reference / RE trail.
|
||||
// glue is smaller) to 700. We deliberately DON'T hook it and don't resize
|
||||
// the table at all — `Event::Custom` only claims NULL slots from the ~200
|
||||
// low gaps the engine leaves, which always suffices. Kept for reference.
|
||||
FUN_FRAMESCRIPT_SET_EVENT_COUNT = 0x007053B0,
|
||||
|
||||
// `FireEvent` — the engine's event dispatcher. 149 callers in the
|
||||
@@ -4254,11 +4253,11 @@ enum Offsets {
|
||||
// from `SMemAlloc` (which it requires).
|
||||
FUN_STORM_SSTRDUP = 0x0064A620,
|
||||
|
||||
// Storm allocator/free — the same pair the engine uses for the event
|
||||
// table (see RebuildEventTable / SetEventCount). Both `__stdcall`, 4 args
|
||||
// (`ret 0x10`). `Event::Custom::EnsureCapacity` uses them to reallocate
|
||||
// the event table's entry buffer directly, bypassing SetEventCount's
|
||||
// clamp. SMemAlloc's flag bit 8 (`& 8`) zero-fills the block.
|
||||
// Storm allocator/free. Both `__stdcall`, 4 args (`ret 0x10`).
|
||||
// SMemAlloc's flag bit 8 (`& 8`) zero-fills the block. (Duplicates of
|
||||
// FUN_STORM_SMEM_ALLOC / FUN_STORM_SMEM_FREE above, same addresses;
|
||||
// currently unused — the EnsureCapacity buffer realloc that used them
|
||||
// was removed. Left as reference.)
|
||||
// void* __stdcall SMemAlloc(uint size, const char *file, int line, int flags)
|
||||
// char __stdcall SMemFree (void *ptr, const char *file, int line, int flags)
|
||||
FUN_STORM_SMEMALLOC = 0x006462E0,
|
||||
|
||||
+8
-101
@@ -49,9 +49,10 @@ bool g_writesEnabled = false;
|
||||
// One-shot latch so a genuinely full event table (no NULL slot left to
|
||||
// claim) is logged once per session instead of silently dropping the
|
||||
// event — the failure mode that would otherwise rot unnoticed. Re-armed on
|
||||
// `/reload` (the table is rebuilt fresh). `Event::Capacity` raises the
|
||||
// FrameXML table to >=700 to keep this from ever triggering, but the log is
|
||||
// the backstop if that hook is ever absent or the pool is somehow exhausted.
|
||||
// `/reload` (the table is rebuilt fresh). In practice this never triggers:
|
||||
// SuperWoW/nampower clamp the FrameXML table to 700 and the engine leaves
|
||||
// ~200 NULL gaps in the low range, dwarfing our ~46 reservations. The log is
|
||||
// purely a backstop if some future install somehow drains the gap pool.
|
||||
bool g_warnedTableFull = false;
|
||||
|
||||
// Engine's SStrDup at `FUN_STORM_SSTRDUP` — uses `SMemAlloc` internally
|
||||
@@ -68,22 +69,6 @@ char *SStrDup(const char *s) {
|
||||
return fn(s, __FILE__, __LINE__);
|
||||
}
|
||||
|
||||
// Storm allocator, matching what the engine uses for the event table itself
|
||||
// (so the engine's reload teardown frees our buffer the same way). Both are
|
||||
// `__stdcall`, 4 args (`ret 0x10`). SMemAlloc's flag bit 8 zero-fills.
|
||||
void *SMemAlloc(unsigned size) {
|
||||
using SMemAlloc_t = void *(__stdcall *)(unsigned size, const char *file,
|
||||
int line, int flags);
|
||||
auto fn = reinterpret_cast<SMemAlloc_t>(Offsets::FUN_STORM_SMEMALLOC);
|
||||
return fn(size, __FILE__, __LINE__, 8 /* zero-fill */);
|
||||
}
|
||||
void SMemFree(void *ptr) {
|
||||
using SMemFree_t = int(__stdcall *)(void *ptr, const char *file, int line,
|
||||
int flags);
|
||||
auto fn = reinterpret_cast<SMemFree_t>(Offsets::FUN_STORM_SMEMFREE);
|
||||
fn(ptr, __FILE__, __LINE__, 0);
|
||||
}
|
||||
|
||||
// Walk the engine's event table from the LOW end looking for a NULL-name
|
||||
// slot. The bulk-init at `0x0051AB30` leaves ~200 NULL gaps scattered
|
||||
// through the 548-slot input names array (it only writes ~350 of the
|
||||
@@ -95,10 +80,10 @@ void SMemFree(void *ptr) {
|
||||
// to 700), and other claim-based DLLs (VanillaMinimapTracking) also walk
|
||||
// from the tail — so the top is the contested end. Claiming from the low
|
||||
// engine gaps keeps us entirely out of their way and independent of their
|
||||
// clamp / hook order. `EnsureCapacity` (run once at load) guarantees enough
|
||||
// NULL slots exist down here before we ever get here; the skip-occupied
|
||||
// invariant keeps this safe against future engine additions regardless of
|
||||
// direction.
|
||||
// clamp / hook order. The engine's ~200 low NULL gaps dwarf our ~46
|
||||
// reservations, so a free slot is essentially always available; the
|
||||
// skip-occupied invariant keeps this safe against future engine additions
|
||||
// regardless of direction.
|
||||
int TryClaim(const char *eventName) {
|
||||
if (!g_writesEnabled)
|
||||
return -1;
|
||||
@@ -181,84 +166,6 @@ void RetryClaims() {
|
||||
|
||||
void EnableWrites() { g_writesEnabled = true; }
|
||||
|
||||
void EnsureCapacity() {
|
||||
// Read the live table fresh — never cache; another DLL may have resized
|
||||
// it (SuperWoW/nampower clamp to 700, another extender may have grown it).
|
||||
const int count = *reinterpret_cast<int *>(Offsets::VAR_EVENT_TABLE_COUNT);
|
||||
auto *base = *reinterpret_cast<uint8_t **>(Offsets::VAR_EVENT_TABLE_BASE_PTR);
|
||||
if (base == nullptr || count <= 0 || g_reservedCount <= 0)
|
||||
return;
|
||||
|
||||
// Count the NULL-name slots available to claim.
|
||||
int nulls = 0;
|
||||
for (int i = 0; i < count; ++i)
|
||||
if (*reinterpret_cast<const char *const *>(
|
||||
base + i * Offsets::EVENT_ENTRY_STRIDE +
|
||||
Offsets::OFF_EVENT_ENTRY_NAME) == nullptr)
|
||||
++nulls;
|
||||
|
||||
// Enough room already (the common case — the engine's low gaps are huge).
|
||||
// If another DLL already enlarged the table, we simply see its NULLs here
|
||||
// and don't grow. Cooperative, no coordination needed.
|
||||
if (nulls >= g_reservedCount)
|
||||
return;
|
||||
|
||||
// We're short — expand. But growing moves the buffer, which invalidates
|
||||
// the entries' self-referential list anchors, so it's ONLY safe while
|
||||
// every chain is empty (no frame registered yet). We run before the first
|
||||
// RegisterEvent, so that holds — but VERIFY it defensively: a populated
|
||||
// chain means someone registered early, and growing would corrupt their
|
||||
// subscriptions. If so, abort and let claiming use whatever NULLs exist
|
||||
// (the one-shot log fires if that's not enough).
|
||||
for (int i = 0; i < count; ++i) {
|
||||
const uint32_t head = *reinterpret_cast<const uint32_t *>(
|
||||
base + i * Offsets::EVENT_ENTRY_STRIDE + Offsets::OFF_EVENT_ENTRY_HEAD);
|
||||
const bool empty = (head == 0) || (head & 1); // 0 / low-bit sentinel
|
||||
if (!empty) {
|
||||
Debug::Log::Printf(
|
||||
"[event] %d NULL slots < %d reserved, but chains are live — "
|
||||
"skipping grow to avoid corrupting registrations",
|
||||
nulls, g_reservedCount);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Grow by the deficit plus a margin (so we don't claim the very last
|
||||
// slots and starve other claim-based DLLs). Pool-minimal.
|
||||
constexpr int kMargin = 16;
|
||||
const int newCount = count + (g_reservedCount - nulls) + kMargin;
|
||||
auto *newBuf = static_cast<uint8_t *>(
|
||||
SMemAlloc(static_cast<unsigned>(newCount) * Offsets::EVENT_ENTRY_STRIDE));
|
||||
if (newBuf == nullptr)
|
||||
return; // alloc failed — keep the old table; claiming logs if short
|
||||
|
||||
// Rebuild every entry at its NEW address: copy the name pointer (strings
|
||||
// are shared, NOT duplicated), and re-init the anchor as an empty sentinel
|
||||
// pointing at its own new location. Safe because all chains are empty.
|
||||
for (int i = 0; i < newCount; ++i) {
|
||||
auto *e = reinterpret_cast<uint32_t *>(
|
||||
newBuf + i * Offsets::EVENT_ENTRY_STRIDE);
|
||||
if (i < count) // new entries stay zero-filled (null name)
|
||||
e[0] = *reinterpret_cast<const uint32_t *>(
|
||||
base + i * Offsets::EVENT_ENTRY_STRIDE);
|
||||
const uint32_t anchor = static_cast<uint32_t>(
|
||||
reinterpret_cast<uintptr_t>(&e[2])); // entry+0x08
|
||||
e[2] = anchor; // self-referential list anchor
|
||||
e[3] = anchor | 1; // empty-chain sentinel head (+0x0C)
|
||||
}
|
||||
|
||||
// Publish base BEFORE count, so the global never advertises more entries
|
||||
// than the buffer holds. Then free the old buffer (buffer only — the names
|
||||
// live on in newBuf).
|
||||
*reinterpret_cast<uint8_t **>(Offsets::VAR_EVENT_TABLE_BASE_PTR) = newBuf;
|
||||
*reinterpret_cast<int *>(Offsets::VAR_EVENT_TABLE_CAP) = newCount;
|
||||
*reinterpret_cast<int *>(Offsets::VAR_EVENT_TABLE_COUNT) = newCount;
|
||||
SMemFree(base);
|
||||
|
||||
Debug::Log::Printf("[event] grew table %d -> %d (%d NULL < %d reserved)",
|
||||
count, newCount, nulls, g_reservedCount);
|
||||
}
|
||||
|
||||
void PrepareForReload() {
|
||||
for (int i = 0; i < g_reservedCount; ++i)
|
||||
g_reserved[i].slot = -1;
|
||||
|
||||
@@ -124,23 +124,4 @@ void EnableWrites();
|
||||
// slots point into the old layout and need to re-claim.
|
||||
void PrepareForReload();
|
||||
|
||||
// On-demand event-table expansion. Called once from `LoadScriptFunctions_h`
|
||||
// AFTER module registration (so every reservation, incl. the lazily-reserved
|
||||
// TTS ones, is counted) and BEFORE any frame registers (so all chains are
|
||||
// empty and a buffer move is safe). If the live table has fewer NULL slots
|
||||
// than we've reserved, it grows the table by the deficit — by reallocating
|
||||
// the buffer with the engine's own Storm allocator and swapping the base/
|
||||
// count globals, NOT by routing a size through `SetEventCount`. That
|
||||
// bypasses SuperWoW/nampower's destructive `= 700` clamp entirely, so it's
|
||||
// independent of DLL load / hook order.
|
||||
//
|
||||
// Normally a no-op: the engine's low NULL gaps (~200) dwarf our reservation
|
||||
// count, so claim-NULL alone suffices and this never grows. It's a
|
||||
// last-resort durability valve for the case where many claim-based DLLs have
|
||||
// drained the shared gap pool. Defensive throughout: reads the table fresh,
|
||||
// verifies all chains are empty before touching anchors (aborts otherwise),
|
||||
// frees only the old buffer (never the shared name strings), and publishes
|
||||
// base before count.
|
||||
void EnsureCapacity();
|
||||
|
||||
} // namespace Event::Custom
|
||||
|
||||
Reference in New Issue
Block a user