Reversed the match accept flow in CS2 and figured I'd share since I haven't seen anyone break this down. There are some non-obvious gotchas that'll waste your time if you don't know about them.
Finding SetPlayerReady
This is the function that tells the GC "I'm ready." Find it in client.dll by searching for string xrefs to "prematch:" or "deferred" in IDA.
Signature: bool(__fastcall* SetPlayerReady)(void* thisptr, const char* szReadyType);
Pattern: 40 53 48 83 EC 20 48 8B DA 48 8D 15 ? ? ? ? 48 8B CB FF 15 ? ? ? ? 85 C0 75 14 BA 02 00
The "deferred" Trap
SetPlayerReady takes a string that controls which code path runs:
"deferred" → goes through an internal pointer that is often NULL. Accept silently fails. You'll sit there wondering why nothing happens.
"accept" (or any string that isn't "deferred") → takes the working path, sets ready state to 2, sends accept to GC.
The strcmp against "deferred" is the branch point. If it matches, broken path. Anything else, working path. First arg is nullptr — function doesn't use this in the accept path.
SetPlayerReady(nullptr, "deferred"); // WRONG — broken path, silently fails SetPlayerReady(nullptr, "accept"); // CORRECT — sends accept to GC
Hook 1: MatchFoundHandler (primary — instant accept)
Fires when the GC tells the client a match was found. Match data is already stored at this point so you can call SetPlayerReady immediately. No delay needed.
Pattern (client.dll): 48 83 EC 28 48 8B 0D ? ? ? ? 48 85 C9 74 ? 48 8B 01 48 89 7C 24 20 FF 50 78
Implementation: void __fastcall hkMatchFoundHandler() { if (g_bAutoAccept) { SetPlayerReady(nullptr, "accept");
FLASHWINFO fi = {}; fi.cbSize = sizeof(fi); fi.hwnd = g_hWindow; fi.dwFlags = FLASHW_ALL | FLASHW_TIMERNOFG; FlashWindowEx(&fi); } oOriginalMatchFoundHandler();
}
Hook 2: Panorama Event (backup — with delay)
The Panorama event "popup_accept_match_found" fires when the accept popup shows, but BEFORE the GC handler finishes storing match data. If you call SetPlayerReady immediately here, you read a null pointer and nothing happens (or you crash).
Fix: 1.5 second delay.
Pattern (client.dll): 40 56 57 41 57 48 83 EC 40 48 8B 3D ? ? ? ? 4D 85 C0 48 8B 35 ? ? ? ? 4C 8B FA 49 0F 45 F8
Implementation: __int64 __fastcall hkPanoramaEvent(void* pUnk, const char* szEventName, void* pUnk1, float flUnk) { if (szEventName && strcmp(szEventName, "popup_accept_match_found") == 0) { if (g_bAutoAccept) { FLASHWINFO fi = {}; fi.cbSize = sizeof(fi); fi.hwnd = g_hWindow; fi.dwFlags = FLASHW_ALL | FLASHW_TIMERNOFG; FlashWindowEx(&fi);
std::thread( { Sleep(1500); SetPlayerReady(nullptr, "accept"); }).detach(); } } return oOriginalPanoramaEvent(pUnk, szEventName, pUnk1, flUnk);
}
Key Points
Call with "accept", NOT "deferred"
MatchFoundHandler = instant accept, no delay needed
Panorama event = needs ~1.5s delay before calling accept
Use both hooks — MatchFoundHandler as primary, Panorama as fallback
If patterns break after an update, string refs "prematch:" / "deferred" will always lead you to SetPlayerReady in IDA
Patterns current as of latest CS2 update.
Any Q's. my discord is dev.reev
Finding SetPlayerReady
This is the function that tells the GC "I'm ready." Find it in client.dll by searching for string xrefs to "prematch:" or "deferred" in IDA.
Signature: bool(__fastcall* SetPlayerReady)(void* thisptr, const char* szReadyType);
Pattern: 40 53 48 83 EC 20 48 8B DA 48 8D 15 ? ? ? ? 48 8B CB FF 15 ? ? ? ? 85 C0 75 14 BA 02 00
The "deferred" Trap
SetPlayerReady takes a string that controls which code path runs:
"deferred" → goes through an internal pointer that is often NULL. Accept silently fails. You'll sit there wondering why nothing happens.
"accept" (or any string that isn't "deferred") → takes the working path, sets ready state to 2, sends accept to GC.
The strcmp against "deferred" is the branch point. If it matches, broken path. Anything else, working path. First arg is nullptr — function doesn't use this in the accept path.
SetPlayerReady(nullptr, "deferred"); // WRONG — broken path, silently fails SetPlayerReady(nullptr, "accept"); // CORRECT — sends accept to GC
Hook 1: MatchFoundHandler (primary — instant accept)
Fires when the GC tells the client a match was found. Match data is already stored at this point so you can call SetPlayerReady immediately. No delay needed.
Pattern (client.dll): 48 83 EC 28 48 8B 0D ? ? ? ? 48 85 C9 74 ? 48 8B 01 48 89 7C 24 20 FF 50 78
Implementation: void __fastcall hkMatchFoundHandler() { if (g_bAutoAccept) { SetPlayerReady(nullptr, "accept");
FLASHWINFO fi = {}; fi.cbSize = sizeof(fi); fi.hwnd = g_hWindow; fi.dwFlags = FLASHW_ALL | FLASHW_TIMERNOFG; FlashWindowEx(&fi); } oOriginalMatchFoundHandler();
}
Hook 2: Panorama Event (backup — with delay)
The Panorama event "popup_accept_match_found" fires when the accept popup shows, but BEFORE the GC handler finishes storing match data. If you call SetPlayerReady immediately here, you read a null pointer and nothing happens (or you crash).
Fix: 1.5 second delay.
Pattern (client.dll): 40 56 57 41 57 48 83 EC 40 48 8B 3D ? ? ? ? 4D 85 C0 48 8B 35 ? ? ? ? 4C 8B FA 49 0F 45 F8
Implementation: __int64 __fastcall hkPanoramaEvent(void* pUnk, const char* szEventName, void* pUnk1, float flUnk) { if (szEventName && strcmp(szEventName, "popup_accept_match_found") == 0) { if (g_bAutoAccept) { FLASHWINFO fi = {}; fi.cbSize = sizeof(fi); fi.hwnd = g_hWindow; fi.dwFlags = FLASHW_ALL | FLASHW_TIMERNOFG; FlashWindowEx(&fi);
std::thread( { Sleep(1500); SetPlayerReady(nullptr, "accept"); }).detach(); } } return oOriginalPanoramaEvent(pUnk, szEventName, pUnk1, flUnk);
}
Key Points
Call with "accept", NOT "deferred"
MatchFoundHandler = instant accept, no delay needed
Panorama event = needs ~1.5s delay before calling accept
Use both hooks — MatchFoundHandler as primary, Panorama as fallback
If patterns break after an update, string refs "prematch:" / "deferred" will always lead you to SetPlayerReady in IDA
Patterns current as of latest CS2 update.
Any Q's. my discord is dev.reev