Hook WriteFormattedLogMessage to inject session marker before first combat log write

Instead of hooking EnableChatLogging or SignalEvent (which fire too late
or before the log file is open), hook WriteFormattedLogMessage directly.
On the first write to the combat log, prepend COMBATLOG_SESSION with the
player name resolved via the name cache. This guarantees the session
marker is the very first line in the file.

Also add World_HandleLogoutCleanup (0x491180) hook to reset per-session
state on real logout/disconnect (not /reload), and move remove_on_shutdown
cleanup from shutdownDetour to logoutDetour.
This commit is contained in:
MarcelineVQ
2026-03-03 17:38:25 -08:00
parent 049f6cd851
commit 11851c4ece
4 changed files with 130 additions and 84 deletions
+54 -4
View File
@@ -269,17 +269,67 @@ On DLL init, scan `Logs\` directory for `WoWCombatLog_*.txt` files:
.ebx=true })` barrier at top of detour forces Zig to push/pop in
prologue/epilogue regardless of internal register allocation.
## Logout Detection
`CGGameUI_Shutdown` (0x490BD0) fires on `/reload` AND logout -- unsuitable for
per-session reset. The correct logout-only function is:
### World_HandleLogoutCleanup (0x491180) -- __stdcall(), no params
Only called from `ShutdownClientSystems` (0x401ee0), which is called from:
- `NetworkDisconnectHandler` (0x46c540)
- `handleDisconnectWithReason` (0x5aad70)
- `cleanupAfterDisconnect` (0x5aaeb0)
These are all real disconnect/logout paths. NOT called on `/reload` or map change.
`World_HandleLogoutCleanup` calls `World_HandlePlayerLogin` (0x490BD0) as its
first action, which fires `SignalEvent(271)` (PLAYER_LOGOUT).
Full cleanup sequence: World_HandlePlayerLogin (fires PLAYER_LOGOUT event),
UI_ProcessDirtyFaces, release_minimap_textures, ShutdownChatSubsystem,
SpellBookManager_Cleanup, CleanupUnitSystem, UnregisterPlayerStateHandlers, etc.
Hooked in `main.zig` as `logout_hook` -- available to all modules via `onLogout()`.
### Event IDs (verified via event table at 0xbe1198)
- PLAYER_LOGIN = 270 (table slot 0xbe15d0, string at 0x852d50)
- PLAYER_LOGOUT = 271 (table slot 0xbe15d4, string at 0x852d40)
- PLAYER_ENTERING_WORLD = 272 (table slot 0xbe15d8, string at 0x852d28)
- LOGOUT_CANCEL = event at table slot 0xbe15f0, string at 0x852cc4
### SignalEvent callers for login/logout events
- `SignalEvent(270)` PLAYER_LOGIN: from `CGGameUI::EnterWorld` (0x4908c0)
- `SignalEvent(271)` PLAYER_LOGOUT: from `World_HandlePlayerLogin` (0x490bd0)
- `SignalEvent(272)` PLAYER_ENTERING_WORLD: from `CGGameUI::EnterWorld` (0x4908c0)
- `SignalEvent(273)`: from `World_HandleEnterWorldCleanup` (0x490a80)
### Player Name Resolution
`GetObjectPtr` (0x464870) -> `GetUnitName` (0x609210) does NOT work during early
login -- player object not in object manager yet. Use name cache instead:
`RetrieveNPCDataFromCache` (0x55f080, cache at 0xc0e228) is populated before the
object manager and works for the local player GUID.
## Open Questions
- [ ] Where does SuperWoW write `WoWRawCombatLog.txt`? Need to check
SuperWoWhook.dll. Same redirect approach should work if it uses the
same table or a similar one.
- [ ] Exact prologue size of `EnableChatLogging` for hooking (need 5+ bytes).
- [x] Exact prologue size of `EnableChatLogging` for hooking (need 5+ bytes).
First 3 instructions = PUSH EBX; PUSH ESI; PUSH EDI = 3 bytes. Then
MOV ESI,EDX = 2 bytes. Total = 5 bytes -- just enough for a jmp hook.
- [x] **Markers permission check**: markers' `getNameFromGUID` uses
`RetrieveNPCDataFromCache` (0x55f080) and works correctly for roster GUIDs.
The combatlog crash (EAX=name bytes instead of char*) is specific to the
context — either the player's own GUID is handled differently, or the cache
state during `EnableChatLogging` is incomplete. Combatlog uses `GetObjectName`
(0x6264E0) instead, which avoids the issue. No change needed for markers.
context -- either the player's own GUID is handled differently, or the cache
state during `EnableChatLogging` is incomplete. Combatlog now uses
RetrieveNPCDataFromCache directly (same pattern as markers) and it works.
- [x] **Player name at EnableChatLogging time**: name cache IS populated when
LoggingCombat(1) fires. The GetObjectPtr path failed because the object
manager isn't ready, not because the name isn't known.
- [x] **Session marker ordering**: Hooking WriteFormattedLogMessage (0x65ac20)
directly guarantees our marker is the first line -- intercepts the first
write to the combat log handle and prepends the session marker.