Skip to content

Fix EVENT_FRAME/EVENT_RESIZE never firing: InitializeStage3Hooks was never called - #83

Open
ScottButler87 wants to merge 1 commit into
AurieFramework:experimentalfrom
ScottButler87:fix/event-frame-stage3-hooks-never-called
Open

Fix EVENT_FRAME/EVENT_RESIZE never firing: InitializeStage3Hooks was never called#83
ScottButler87 wants to merge 1 commit into
AurieFramework:experimentalfrom
ScottButler87:fix/event-frame-stage3-hooks-never-called

Conversation

@ScottButler87

@ScottButler87 ScottButler87 commented Aug 8, 2026

Copy link
Copy Markdown

The bug

EVENT_FRAME/EVENT_RESIZE register successfully via CreateCallback but the callback never actually fires, on any registration timing or calling-convention signature. Root cause: Hooks::InitializeStage3Hooks (the function that installs the manual VMT hooks on IDXGISwapChain::Present/ResizeBuffers that these two events depend on) is defined in Hooks.cpp but is never called anywhere in the codebase.

YkSetupFinalInitialization() resolves the live D3D11 swapchain (m_EngineSwapchain) via YkFetchD3D11Info, specifically so it can be handed to InitializeStage3Hooks — but then just sets m_ThirdInitComplete = true and returns. The call that would actually install the hooks using that swapchain is missing.

CreateCallback has no way to detect this — it just registers into m_RegisteredCallbacks and reports AURIE_SUCCESS regardless of whether the underlying hook exists. So EVENT_FRAME/EVENT_RESIZE fail completely silently: no error, no crash, the callback is just permanently unreachable.

Confirmed via stable, and it's a regression from the Zeus rewrite

stable has the identical VMT-hooking code (byte-for-byte the same Present/ResizeBuffers hooking logic, just named Hooks::HkInitialize there instead of InitializeStage3Hooks) — and it is called, right after the swapchain is resolved: Interface.cpp:914.

So this isn't a gap that's always existed — it looks like a leftover from the v5 "Zeus" staged-init rewrite that split the old monolithic Interface.cpp into MI_Aurie.cpp/MI_Internal.cpp/MI_Public.cpp. InitializeStage3Hooks survived the move intact; its one call site did not.

I checked all four hook-installing functions in experimental's Hooks.cpp for call sites — InitializeStage3Hooks is the only one with zero:

Function Backs Called from
InitializeStage1Hooks (→ HkExecuteIt) EVENT_OBJECT_CALL MI_Aurie.cpp:80
InitializeStage2Hooks (→ HkYYError, HkWndProc) EVENT_WNDPROC MI_Aurie.cpp:434
InitializeStage3Hooks (→ HkPresent, HkResizeBuffers) EVENT_FRAME, EVENT_RESIZE nowhere ❌

The fix

Add the missing call in YkSetupFinalInitialization(), mirroring the exact call/log/error-check pattern already used for InitializeStage1Hooks/InitializeStage2Hooks two blocks above in the same function, and mirroring stable's working call site. Uses only m_WindowHandle/m_EngineSwapchain, both already set earlier in the same init chain.

Validation — build-tested and live-confirmed

Built this branch (plain MSBuild against YYToolkit.sln, no changes needed) and ran it against the real game that surfaced this bug, with a test plugin that registers EVENT_FRAME the normal way (CreateCallback, no workaround). Confirmed via aurie.log's own trace output that the new call actually runs and succeeds:

[20:32:16] Hooks::InitializeStage2Hooks => AURIE_SUCCESS
[20:32:18] Hooks::InitializeStage3Hooks => AURIE_SUCCESS

(the ~2s gap is the existing YkFetchD3D11Info polling loop waiting for the swapchain — expected.) Every other Zeus subsystem in the log still reports AURIE_SUCCESS — nothing else regressed.

Functionally: EVENT_FRAME fired on the very first frame after registration and sustained continuous calls with zero gaps for the whole session (GM 2026.0.0.23, LTS, YYC x64 runner), running alongside an unrelated EVENT_OBJECT_CALL subscriber the whole time with no interference either direction. Full narrative in the PR comment below.

Happy to answer questions or adjust the approach if there's a reason this call was left out that I'm not seeing.

…never called

YkSetupFinalInitialization() resolves the live D3D11 swapchain
(m_EngineSwapchain) via YkFetchD3D11Info specifically so it can be handed
to Hooks::InitializeStage3Hooks, which installs the manual VMT hooks on
IDXGISwapChain::Present/ResizeBuffers backing EVENT_FRAME/EVENT_RESIZE.
That call was missing -- the swapchain got resolved and stored, but the
hooks were never actually installed.

CreateCallback(..., EVENT_FRAME, ...) has no way to detect this: it just
registers into m_RegisteredCallbacks and reports AURIE_SUCCESS regardless
of whether the underlying hook exists. So EVENT_FRAME/EVENT_RESIZE
silently never fire -- no error, no crash, the callback is just
unreachable, since nothing ever hooks Present()/ResizeBuffers() to
dispatch to it in the first place.

Confirmed by comparing against the stable branch, which has the
identical VMT-hooking logic (there named Hooks::HkInitialize) and does
call it, right after resolving the swapchain (Interface.cpp:914). This
looks like a leftover from the v5 "Zeus" staged-init rewrite that split
the old monolithic Interface.cpp into MI_Aurie.cpp/MI_Internal.cpp/
MI_Public.cpp -- InitializeStage3Hooks survived the move intact (it's
byte-for-byte the same hooking code as stable's HkInitialize), but its
one call site did not.

This mirrors the exact call/log/error-check pattern already used two
blocks above for InitializeStage1Hooks/InitializeStage2Hooks in this
same function, using only members (m_WindowHandle, m_EngineSwapchain)
already set earlier in the same init chain.

Validated indirectly but concretely: an out-of-process plugin performing
the equivalent operation (resolve the swapchain via the same
os_get_info()-based technique YkFetchD3D11Info uses, then
Aurie::MmCreateHook directly on vtable[8]) was confirmed live against a
real GameMaker YYC game (GM 2026.0.0.23) -- hook installs successfully,
fires on the very next Present() call, and sustained 115,000+ calls over
16 minutes of live gameplay with no gaps, crashes, or rendering glitches.
Not build-tested against the full YYToolkit.dll (didn't set up the full
build environment for this), but the change itself is minimal, in-style,
and uses only symbols already exercised correctly elsewhere in this exact
function.
@ScottButler87

Copy link
Copy Markdown
Author

Built this branch locally (plain MSBuild against YYToolkit.sln, no changes needed to the build itself) and tested it live against the real target that surfaced this bug — no simulation, the actual patched YYToolkit.dll swapped into a running game.

Confirmed fixed, via the actual aurie.log trace output:

[20:32:16] Hooks::InitializeStage2Hooks => AURIE_SUCCESS
[20:32:18] Hooks::InitializeStage3Hooks => AURIE_SUCCESS

(The ~2s gap between stage 2 and stage 3 is the YkFetchD3D11Info polling loop waiting for the swapchain — expected.)

Every other Zeus subsystem in the log still reports AURIE_SUCCESS — nothing else regressed from this change.

Functional test: registered EVENT_FRAME the normal way (CreateCallback, no workaround, no custom hook of my own anywhere in this test build) and it fired on the very first frame, then sustained continuous calls with zero gaps for the whole session, running alongside an unrelated EVENT_OBJECT_CALL subscriber the whole time with no interference either direction.

Updating the "not build-tested" caveat in the PR description above — this is now build-tested and live-confirmed, not just reasoned-through.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant