Graceful disconnect: tell the DLL to unhook everything

On an explicit Disconnect and on graceful tool exit, the host now asks the
injected DLL to remove every subsystem so the game runs exactly as if it was
never hooked (each hook restores its original bytes). The DLL stays injected but
dormant, ready for a later reconnect -- we never eject it.

Before, both paths just dropped the IPC channel (IpcServer::stop) without telling
the DLL, leaving the hooks active with frozen forwarded state until the game
exited.

- IpcServer::request_unhook_all() sets every subsystem_disabled flag (the DLL
  reconciles to fully unhooked on its next tick); all_hooks_removed() reads the
  hook registry back so the host can confirm the game is vanilla.
- InjectionPanel::disconnect_graceful() requests the unhook, waits (bounded) for
  the registry to clear, then stops. Wired into the Disconnect button (700ms) and
  the destructor (300ms). The flags persist in the section the DLL keeps alive, so
  the unhook completes even if the host exits before confirming.

Tests (failing first):
- ipc_server_test: request_unhook_all() disables all subsystems; all_hooks_removed()
  tracks the registry. Deterministic, no game.
- mock_game_test test_graceful_disconnect: inject -> hooks installed -> request
  unhook-all -> every hook removed (game vanilla) while the DLL stays alive
  (heartbeat advancing). Full suite still passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-23 13:46:51 +02:00
parent 39558171e9
commit 6d96531ac7
8 changed files with 278 additions and 8 deletions

View File

@@ -38,6 +38,16 @@ add_executable(audio_ring_test audio_ring_test.cpp)
target_link_libraries(audio_ring_test PRIVATE coop_common)
add_test(NAME audio_ring_test COMMAND audio_ring_test)
# Unit test for the host-side IpcServer control ops behind graceful disconnect:
# request_unhook_all() disables every subsystem and all_hooks_removed() reads the hook
# registry back. Opens a second view of the section to stand in for the hook. No game.
add_executable(ipc_server_test
ipc_server_test.cpp
${CMAKE_SOURCE_DIR}/host/src/ipc/ipc_server.cpp)
target_include_directories(ipc_server_test PRIVATE ${CMAKE_SOURCE_DIR}/host/src)
target_link_libraries(ipc_server_test PRIVATE coop_common)
add_test(NAME ipc_server_test COMMAND ipc_server_test)
# Unit test for the DetourGate safe-unhook coordinator (hook/src/hook_guard.hpp): drain() must block
# while a detour Guard is in flight and return promptly otherwise. Fast/deterministic complement to
# the mock_game_test hook/unhook storm. Header-only (just needs the hook include dir + threads).
@@ -299,6 +309,7 @@ coop_output_subdir(tests
hook_selftest
dinput_hook_test
audio_ring_test
ipc_server_test
detour_gate_test
hook_install_test
mkb_ring_test