diff --git a/.clang-format-ignore b/.clang-format-ignore new file mode 100644 index 0000000..d2ff733 --- /dev/null +++ b/.clang-format-ignore @@ -0,0 +1,5 @@ +# Repository-relative exclusions for shared FormatValidation. +# Only src/, include/, and tests/ are selected; dependencies and artifacts are excluded. +**/thirdparty/** +**/build/** +**/install/** diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml new file mode 100644 index 0000000..e7736b0 --- /dev/null +++ b/.github/workflows/clang-format.yml @@ -0,0 +1,31 @@ +name: clang-format + +on: + push: + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: clang-format-${{ github.ref }} + cancel-in-progress: true + +jobs: + clang-format: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Install pinned formatter + run: python -m pip install clang-format==23.1.3 + - name: Configure formatting targets + run: cmake -S . -B build/format -DFORMAT_VALIDATION_ONLY=ON + - name: Check all owned C/C++ sources + run: cmake --build build/format --target format-check diff --git a/.gitignore b/.gitignore index 81c7fe9..376022b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ /thirdparty/cache/ *.user __pycache__/ + +# Generated from the shared FormatValidation source during CMake configuration. +/.clang-format diff --git a/CMakeLists.txt b/CMakeLists.txt index f30266c..c524a17 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,10 @@ cmake_minimum_required(VERSION 3.21) +option(FORMAT_VALIDATION_ONLY "Configure only clang-format targets without native dependencies" OFF) +if(FORMAT_VALIDATION_ONLY) + project(FormatValidation LANGUAGES NONE) + include(cmake/FormatValidation.cmake) + return() +endif() cmake_policy(SET CMP0091 NEW) set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") project(ThreadGuard LANGUAGES CXX) @@ -95,3 +101,6 @@ if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) metahooksv_add_launch_game() endif() endif() + +# Shared formatting targets are explicit; ordinary native builds do not run them. +include(cmake/FormatValidation.cmake) diff --git a/README.md b/README.md index 93fbb0e..22035c5 100644 --- a/README.md +++ b/README.md @@ -179,3 +179,36 @@ resolution; those require live game verification. The handler test includes 100 stop/recreate cycles with idle sockets and continuous packets, creation failure, suspended creation, non-target forwarding and handle counts. Debugger output records the real stop request, safe sleep exit and signaled wait in a game run. + +## C/C++ formatting + +Formatting uses [MetaHookSv/FormatValidation](https://github.com/MetaHookSv/FormatValidation) +and clang-format **23.1.3**, with the DiligentCore style (4 spaces, preserved include +order). Install the formatter for the Python interpreter used by CMake: + +```sh +python -m pip install clang-format==23.1.3 +cmake -S . -B build/format "-DFORMAT_VALIDATION_ONLY=ON" +cmake --build build/format --target format-check +cmake --build build/format --target format +``` + +The format-only configuration needs CMake 3.21+, Git, Python 3.9+ (CI uses 3.12), +and a build generator; `-G Ninja` works without Visual Studio. It prepares no native +SDK or game dependencies. Formatting targets are explicit and are not part of a +normal DLL build. With a Visual Studio generator, add `--config Debug` or +`--config Release` when building a formatting target. + +The aggregate provides `FORMAT_VALIDATION_SOURCE_PATH=thirdparty/FormatValidation`. +Standalone components accept that CMake variable or its environment counterpart; +if empty, FetchContent downloads the fixed tooling commit. Quote relative paths, +for example `"-DFORMAT_VALIDATION_SOURCE_PATH=../../thirdparty/FormatValidation"`. +Configuration generates the ignored root `.clang-format` for editors; change the +shared style rather than that generated copy. An optional +`FORMAT_VALIDATION_CLANG_FORMAT_EXECUTABLE` selects an explicit formatter, whose +version must still match the pin. + +Checks cover owned C/C++ files in `src/`, `include/`, and `tests/`, including +non-ignored new files. Repository-relative exclusions live in `.clang-format-ignore`. +Third-party sources and build artifacts are excluded. The `clang-format` workflow +checks the full scope on pushes, pull requests, and manual runs. diff --git a/README.zh-CN.md b/README.zh-CN.md index 11138e7..c1ad2c6 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -96,3 +96,33 @@ ctest --test-dir build/tests -C Release --output-on-failure ``` 将 `Release` 换成 `Debug` 可测试调试配置。独立测试使用真实 Win32 线程、socket、锁和等待函数,但只提供宿主的致命错误回调,用于验证 handler 行为;真实 IAT 安装及 gamedata 解析仍需游戏实测。handler 测试包含空闲 socket 和持续取包条件下的 100 次停止/重建,以及创建失败、挂起创建、非目标转发和句柄计数检查。实机调试输出记录退出请求、安全 Sleep 退出及真实等待 signaled 三个阶段。 + +## C/C++ 格式化 + +使用 [MetaHookSv/FormatValidation](https://github.com/MetaHookSv/FormatValidation) +共享工具及固定版本 **clang-format 23.1.3**,采用 DiligentCore 风格(4 空格,保留 +include 顺序)。为 CMake 使用的 Python 解释器安装格式工具: + +```sh +python -m pip install clang-format==23.1.3 +cmake -S . -B build/format "-DFORMAT_VALIDATION_ONLY=ON" +cmake --build build/format --target format-check +cmake --build build/format --target format +``` + +格式专用配置需要 CMake 3.21+、Git、Python 3.9+(CI 使用 3.12)及构建生成器; +使用 `-G Ninja` 可无需 Visual Studio。它不准备原生 SDK 或游戏依赖。 +格式目标需显式执行,不加入普通 DLL 构建。使用 Visual Studio 生成器时,执行目标 +需追加 `--config Debug` 或 `--config Release`。 + +聚合仓库注入 `FORMAT_VALIDATION_SOURCE_PATH=thirdparty/FormatValidation`。 +独立组件支持该 CMake 参数及同名环境变量;为空时通过 FetchContent 获取固定工具 +提交。相对路径应加引号,例如 +`"-DFORMAT_VALIDATION_SOURCE_PATH=../../thirdparty/FormatValidation"`。 +配置时在仓库根目录生成被 gitignore 的 `.clang-format` 供编辑器使用;格式规则应 +在共享仓库修改,不修改生成副本。可通过 `FORMAT_VALIDATION_CLANG_FORMAT_EXECUTABLE` +指定工具路径,但版本仍须与固定版本一致。 + +检查覆盖 `src/`、`include/`、`tests/` 中维护的 C/C++ 文件,包括未被 Git 忽略的新文件。 +相对仓库根目录的排除规则位于 `.clang-format-ignore`;第三方源和构建产物不纳入检查。 +`clang-format` workflow 在 push、pull request 和手动运行时执行全量检查。 diff --git a/cmake/FormatValidation.cmake b/cmake/FormatValidation.cmake new file mode 100644 index 0000000..7c112a8 --- /dev/null +++ b/cmake/FormatValidation.cmake @@ -0,0 +1,26 @@ +# Shared tool implementation; only this dependency bootstrap belongs to the consumer. +if(NOT DEFINED FORMAT_VALIDATION_SOURCE_PATH) + set(FORMAT_VALIDATION_SOURCE_PATH "$ENV{FORMAT_VALIDATION_SOURCE_PATH}" CACHE PATH + "FormatValidation source tree; empty fetches the pinned tooling") +endif() +if(FORMAT_VALIDATION_SOURCE_PATH) + get_filename_component(_format_validation_source "${FORMAT_VALIDATION_SOURCE_PATH}" + ABSOLUTE BASE_DIR "${CMAKE_CURRENT_LIST_DIR}/..") +else() + include(FetchContent) + FetchContent_Declare(threadguard_format_validation + GIT_REPOSITORY https://github.com/MetaHookSv/FormatValidation.git + GIT_TAG 13c9fabe058e1f887ad1b03bb6884de911192c6a # v1.0.0; retained immutable tooling commit + GIT_SUBMODULES "" + # Populate the tooling without configuring its own test project. + SOURCE_SUBDIR cmake) + FetchContent_MakeAvailable(threadguard_format_validation) + set(_format_validation_source "${threadguard_format_validation_SOURCE_DIR}") +endif() +foreach(_format_validation_file .clang-format clang-format-validate.py cmake/FormatValidation.cmake) + if(NOT EXISTS "${_format_validation_source}/${_format_validation_file}") + message(FATAL_ERROR "FORMAT_VALIDATION_SOURCE_PATH is missing ${_format_validation_file}: ${_format_validation_source}") + endif() +endforeach() +include("${_format_validation_source}/cmake/FormatValidation.cmake") +format_validation_add_component(ThreadGuard "${CMAKE_CURRENT_SOURCE_DIR}") diff --git a/src/NetworkThreadState.h b/src/NetworkThreadState.h index 8bd73d3..56fef38 100644 --- a/src/NetworkThreadState.h +++ b/src/NetworkThreadState.h @@ -9,108 +9,111 @@ class NetworkThreadState { public: - enum class StopResult { NotTarget, Requested, Stopped, SelfWait, Failed }; + enum class StopResult + { + NotTarget, + Requested, + Stopped, + SelfWait, + Failed + }; - bool PrepareCreate() { return Reset(); } + bool PrepareCreate() { return Reset(); } - bool Track(HANDLE thread) - { - std::lock_guard lock(m_lock); - const DWORD id = GetThreadId(thread); - if (m_thread || !id) - return false; - if (!DuplicateHandle(GetCurrentProcess(), thread, GetCurrentProcess(), - &m_thread, 0, FALSE, DUPLICATE_SAME_ACCESS)) - return false; - m_exitRequested.store(false); - m_threadId.store(id); - return true; - } + bool Track(HANDLE thread) + { + std::lock_guard lock(m_lock); + const DWORD id = GetThreadId(thread); + if (m_thread || !id) + return false; + if (!DuplicateHandle(GetCurrentProcess(), thread, GetCurrentProcess(), + &m_thread, 0, FALSE, DUPLICATE_SAME_ACCESS)) + return false; + m_exitRequested.store(false); + m_threadId.store(id); + return true; + } - bool IsThread(DWORD currentId, DWORD engineId) const - { - return currentId != 0 && currentId == engineId && currentId == m_threadId.load(); - } + bool IsThread(DWORD currentId, DWORD engineId) const + { + return currentId != 0 && currentId == engineId && currentId == m_threadId.load(); + } - bool ShouldExit(DWORD currentId, DWORD engineId) const - { - return IsThread(currentId, engineId) && m_exitRequested.load(); - } + bool ShouldExit(DWORD currentId, DWORD engineId) const + { + return IsThread(currentId, engineId) && m_exitRequested.load(); + } - void RetireCurrentThread() - { - DWORD id = GetCurrentThreadId(); - m_threadId.compare_exchange_strong(id, 0); - } + void RetireCurrentThread() + { + DWORD id = GetCurrentThreadId(); + m_threadId.compare_exchange_strong(id, 0); + } - StopResult RequestStop(HANDLE thread, DWORD engineId, DWORD currentId = GetCurrentThreadId()) - { - std::lock_guard lock(m_lock); - return RequestStopLocked(thread, engineId, currentId); - } + StopResult RequestStop(HANDLE thread, DWORD engineId, DWORD currentId = GetCurrentThreadId()) + { + std::lock_guard lock(m_lock); + return RequestStopLocked(thread, engineId, currentId); + } - StopResult RequestTrackedStop(DWORD engineId, HANDLE* waitHandle = NULL) - { - std::lock_guard lock(m_lock); - if (waitHandle) - *waitHandle = NULL; - if (!m_thread) - return StopResult::NotTarget; - // A repeated shutdown may already have cleared the engine's ID. An - // exited object is safe; a live identity mismatch must block cleanup. - const auto result = WaitForSingleObject(m_thread, 0) == WAIT_OBJECT_0 - ? StopResult::Stopped : RequestStopLocked(m_thread, engineId, GetCurrentThreadId()); - if (result == StopResult::NotTarget) - return StopResult::Failed; - if (waitHandle && (result == StopResult::Requested || result == StopResult::Stopped) - && !DuplicateHandle(GetCurrentProcess(), m_thread, GetCurrentProcess(), - waitHandle, 0, FALSE, DUPLICATE_SAME_ACCESS)) - return StopResult::Failed; - return result; - } + StopResult RequestTrackedStop(DWORD engineId, HANDLE* waitHandle = NULL) + { + std::lock_guard lock(m_lock); + if (waitHandle) + *waitHandle = NULL; + if (!m_thread) + return StopResult::NotTarget; + // A repeated shutdown may already have cleared the engine's ID. An + // exited object is safe; a live identity mismatch must block cleanup. + const auto result = WaitForSingleObject(m_thread, 0) == WAIT_OBJECT_0 ? StopResult::Stopped : RequestStopLocked(m_thread, engineId, GetCurrentThreadId()); + if (result == StopResult::NotTarget) + return StopResult::Failed; + if (waitHandle && (result == StopResult::Requested || result == StopResult::Stopped) && !DuplicateHandle(GetCurrentProcess(), m_thread, GetCurrentProcess(), waitHandle, 0, FALSE, DUPLICATE_SAME_ACCESS)) + return StopResult::Failed; + return result; + } - bool Reset() - { - std::lock_guard lock(m_lock); - if (m_thread && WaitForSingleObject(m_thread, 0) != WAIT_OBJECT_0) - return false; - m_threadId.store(0); - m_exitRequested.store(false); - if (m_thread) - CloseHandle(m_thread); - m_thread = NULL; - return true; - } + bool Reset() + { + std::lock_guard lock(m_lock); + if (m_thread && WaitForSingleObject(m_thread, 0) != WAIT_OBJECT_0) + return false; + m_threadId.store(0); + m_exitRequested.store(false); + if (m_thread) + CloseHandle(m_thread); + m_thread = NULL; + return true; + } private: - StopResult RequestStopLocked(HANDLE thread, DWORD engineId, DWORD currentId) - { - if (!m_thread) - return StopResult::NotTarget; - const DWORD id = GetThreadId(thread); - // Keep the object identity for repeated stops even after the worker - // retires its active ID immediately before ExitThread. - if (!id || id != engineId || id != GetThreadId(m_thread)) - return StopResult::NotTarget; - if (id == currentId) - return StopResult::SelfWait; + StopResult RequestStopLocked(HANDLE thread, DWORD engineId, DWORD currentId) + { + if (!m_thread) + return StopResult::NotTarget; + const DWORD id = GetThreadId(thread); + // Keep the object identity for repeated stops even after the worker + // retires its active ID immediately before ExitThread. + if (!id || id != engineId || id != GetThreadId(m_thread)) + return StopResult::NotTarget; + if (id == currentId) + return StopResult::SelfWait; - const DWORD state = WaitForSingleObject(m_thread, 0); - if (state == WAIT_OBJECT_0) - { - // A finished thread's ID can be reused. Never request exit for a new - // live thread merely because it has the old numeric ID. - return WaitForSingleObject(thread, 0) == WAIT_OBJECT_0 - ? StopResult::Stopped : StopResult::NotTarget; - } - if (state != WAIT_TIMEOUT) - return StopResult::Failed; - m_exitRequested.store(true); - return StopResult::Requested; - } + const DWORD state = WaitForSingleObject(m_thread, 0); + if (state == WAIT_OBJECT_0) + { + // A finished thread's ID can be reused. Never request exit for a new + // live thread merely because it has the old numeric ID. + return WaitForSingleObject(thread, 0) == WAIT_OBJECT_0 ? StopResult::Stopped : StopResult::NotTarget; + } + if (state != WAIT_TIMEOUT) + return StopResult::Failed; + m_exitRequested.store(true); + return StopResult::Requested; + } - std::mutex m_lock; - HANDLE m_thread = NULL; - std::atomic m_threadId{ 0 }; - std::atomic m_exitRequested{ false }; + std::mutex m_lock; + HANDLE m_thread = NULL; + std::atomic m_threadId{0}; + std::atomic m_exitRequested{false}; }; diff --git a/src/ThreadManager.cpp b/src/ThreadManager.cpp index 47a95d3..4f62df3 100644 --- a/src/ThreadManager.cpp +++ b/src/ThreadManager.cpp @@ -8,495 +8,494 @@ #include #include -static std::mutex g_ThreadManagerLock; +static std::mutex g_ThreadManagerLock; static std::vector g_ThreadManagers; extern HANDLE g_MainThreadId; -static NetworkThreadState g_NetworkThread; -static IThreadManager* g_NetworkManager = NULL; -static LPTHREAD_START_ROUTINE g_NetworkEntry = NULL; -static DWORD* g_NetworkThreadId = NULL; -static int (__cdecl* g_pfn_NET_QueuePacket)(int) = NULL; -static void (__cdecl* g_pfn_NET_Shutdown)(void) = NULL; -static int (WSAAPI* g_pfn_select)(int, fd_set*, fd_set*, fd_set*, const timeval*) = NULL; -static hook_t* g_pHook_NetworkTerminate = NULL; -static hook_t* g_pHook_NetworkSelect = NULL; -static hook_t* g_pHook_NET_QueuePacket = NULL; -static hook_t* g_pHook_NET_Shutdown = NULL; -static constexpr long kNetworkPollMicroseconds = 20000; +static NetworkThreadState g_NetworkThread; +static IThreadManager* g_NetworkManager = NULL; +static LPTHREAD_START_ROUTINE g_NetworkEntry = NULL; +static DWORD* g_NetworkThreadId = NULL; +static int(__cdecl* g_pfn_NET_QueuePacket)(int) = NULL; +static void(__cdecl* g_pfn_NET_Shutdown)(void) = NULL; +static int(WSAAPI* g_pfn_select)(int, fd_set*, fd_set*, fd_set*, const timeval*) = NULL; +static hook_t* g_pHook_NetworkTerminate = NULL; +static hook_t* g_pHook_NetworkSelect = NULL; +static hook_t* g_pHook_NET_QueuePacket = NULL; +static hook_t* g_pHook_NET_Shutdown = NULL; +static constexpr long kNetworkPollMicroseconds = 20000; static bool IsNetworkThread() { - return g_NetworkThreadId && g_NetworkThread.IsThread(GetCurrentThreadId(), *g_NetworkThreadId); + return g_NetworkThreadId && g_NetworkThread.IsThread(GetCurrentThreadId(), *g_NetworkThreadId); } static bool NetworkExitRequested() { - return g_NetworkThreadId && g_NetworkThread.ShouldExit(GetCurrentThreadId(), *g_NetworkThreadId); + return g_NetworkThreadId && g_NetworkThread.ShouldExit(GetCurrentThreadId(), *g_NetworkThreadId); } static void NetworkTrace(const char* event, DWORD id) { - // The worker can log during DLL/FLS teardown after the GameUI console has - // shut down. Do not call the engine console from this thread/phase. - char message[160]; - sprintf_s(message, "[ThreadGuard] network thread %lu: %s\n", id, event); - OutputDebugStringA(message); + // The worker can log during DLL/FLS teardown after the GameUI console has + // shut down. Do not call the engine console from this thread/phase. + char message[160]; + sprintf_s(message, "[ThreadGuard] network thread %lu: %s\n", id, event); + OutputDebugStringA(message); } static bool WaitForNetworkThread(HANDLE thread) { - // No registry, state or manager-list lock is held here. This is the - // real Win32 wait, never the engine's zero-timeout polling wrapper. - const DWORD wait = WaitForSingleObject(thread, INFINITE); - if (wait != WAIT_OBJECT_0) - { - Sys_Error("Waiting for network thread failed (wait %lu, error %lu)!", wait, GetLastError()); - return false; - } - NetworkTrace("thread signaled; engine cleanup may continue", GetThreadId(thread)); - return true; + // No registry, state or manager-list lock is held here. This is the + // real Win32 wait, never the engine's zero-timeout polling wrapper. + const DWORD wait = WaitForSingleObject(thread, INFINITE); + if (wait != WAIT_OBJECT_0) + { + Sys_Error("Waiting for network thread failed (wait %lu, error %lu)!", wait, GetLastError()); + return false; + } + NetworkTrace("thread signaled; engine cleanup may continue", GetThreadId(thread)); + return true; } static BOOL WINAPI NewTerminateThread(HANDLE thread, DWORD exitCode) { - if (g_NetworkThreadId && FindThreadManagerByVirtualAddress(_ReturnAddress()) == g_NetworkManager) - { - const auto result = g_NetworkThread.RequestStop(thread, *g_NetworkThreadId); - if (result == NetworkThreadState::StopResult::SelfWait || result == NetworkThreadState::StopResult::Failed) - { - Sys_Error("Cannot safely stop network thread (self wait or invalid tracked handle)!"); - return FALSE; - } - if (result == NetworkThreadState::StopResult::Requested || result == NetworkThreadState::StopResult::Stopped) - { - NetworkTrace("cooperative stop requested", GetThreadId(thread)); - return WaitForNetworkThread(thread) ? TRUE : FALSE; - } - } - return TerminateThread(thread, exitCode); + if (g_NetworkThreadId && FindThreadManagerByVirtualAddress(_ReturnAddress()) == g_NetworkManager) + { + const auto result = g_NetworkThread.RequestStop(thread, *g_NetworkThreadId); + if (result == NetworkThreadState::StopResult::SelfWait || result == NetworkThreadState::StopResult::Failed) + { + Sys_Error("Cannot safely stop network thread (self wait or invalid tracked handle)!"); + return FALSE; + } + if (result == NetworkThreadState::StopResult::Requested || result == NetworkThreadState::StopResult::Stopped) + { + NetworkTrace("cooperative stop requested", GetThreadId(thread)); + return WaitForNetworkThread(thread) ? TRUE : FALSE; + } + } + return TerminateThread(thread, exitCode); } static int WSAAPI NewNetworkSelect(int nfds, fd_set* read, fd_set* write, fd_set* except, const timeval* timeout) { - // Bound this worker's waits even before a stop is requested: an already - // blocked select cannot observe a later atomic request. Preserve shorter - // timeouts and every other thread's original select semantics. - const timeval poll = { 0, kNetworkPollMicroseconds }; - if (IsNetworkThread() && (!timeout || timeout->tv_sec > 0 || timeout->tv_usec > poll.tv_usec)) - timeout = &poll; - return g_pfn_select(nfds, read, write, except, timeout); + // Bound this worker's waits even before a stop is requested: an already + // blocked select cannot observe a later atomic request. Preserve shorter + // timeouts and every other thread's original select semantics. + const timeval poll = {0, kNetworkPollMicroseconds}; + if (IsNetworkThread() && (!timeout || timeout->tv_sec > 0 || timeout->tv_usec > poll.tv_usec)) + timeout = &poll; + return g_pfn_select(nfds, read, write, except, timeout); } static int __cdecl NewNET_QueuePacket(int source) { - // False takes the normal no-packet path. The loop still unlocks and hands - // any already received message to its queue before reaching Sleep(1). - if (NetworkExitRequested()) - return 0; - return g_pfn_NET_QueuePacket(source); + // False takes the normal no-packet path. The loop still unlocks and hands + // any already received message to its queue before reaching Sleep(1). + if (NetworkExitRequested()) + return 0; + return g_pfn_NET_QueuePacket(source); } static void __cdecl NewNET_Shutdown(void) { - // All supported Windows engines close sockets before their late thread - // stop. Their worker's select is outside net_cs, so join before entering - // the original shutdown (and before it acquires net_cs to clear lag data). - HANDLE thread = NULL; - const auto result = g_NetworkThread.RequestTrackedStop(*g_NetworkThreadId, &thread); - if (result == NetworkThreadState::StopResult::SelfWait || result == NetworkThreadState::StopResult::Failed) - { - Sys_Error("Cannot safely stop network thread before resource shutdown!"); - return; - } - if (thread) - { - NetworkTrace("NET_Shutdown: cooperative stop requested", GetThreadId(thread)); - const bool stopped = WaitForNetworkThread(thread); - CloseHandle(thread); - if (!stopped) - return; - NetworkTrace("NET_Shutdown: network thread stopped before resource cleanup", *g_NetworkThreadId); - } - // Preserve the engine's resource cleanup and its later (now idempotent) - // TerminateThread call; do not clear IDs, flags, sockets or engine handles. - g_pfn_NET_Shutdown(); + // All supported Windows engines close sockets before their late thread + // stop. Their worker's select is outside net_cs, so join before entering + // the original shutdown (and before it acquires net_cs to clear lag data). + HANDLE thread = NULL; + const auto result = g_NetworkThread.RequestTrackedStop(*g_NetworkThreadId, &thread); + if (result == NetworkThreadState::StopResult::SelfWait || result == NetworkThreadState::StopResult::Failed) + { + Sys_Error("Cannot safely stop network thread before resource shutdown!"); + return; + } + if (thread) + { + NetworkTrace("NET_Shutdown: cooperative stop requested", GetThreadId(thread)); + const bool stopped = WaitForNetworkThread(thread); + CloseHandle(thread); + if (!stopped) + return; + NetworkTrace("NET_Shutdown: network thread stopped before resource cleanup", *g_NetworkThreadId); + } + // Preserve the engine's resource cleanup and its later (now idempotent) + // TerminateThread call; do not clear IDs, flags, sockets or engine handles. + g_pfn_NET_Shutdown(); } void NetworkThread_Configure(LPTHREAD_START_ROUTINE entry, DWORD* threadId, void* queuePacket, void* shutdown) { - g_NetworkEntry = entry; - g_NetworkThreadId = threadId; - g_pfn_NET_QueuePacket = (decltype(g_pfn_NET_QueuePacket))queuePacket; - g_pfn_NET_Shutdown = (decltype(g_pfn_NET_Shutdown))shutdown; + g_NetworkEntry = entry; + g_NetworkThreadId = threadId; + g_pfn_NET_QueuePacket = (decltype(g_pfn_NET_QueuePacket))queuePacket; + g_pfn_NET_Shutdown = (decltype(g_pfn_NET_Shutdown))shutdown; } void NetworkThread_InstallHook(IThreadManager* manager) { - g_NetworkManager = manager; - const auto module = manager->GetModule(); - const auto blob = manager->GetBlobModule(); - const char* socketModule = NULL; - for (const auto name : { "ws2_32.dll", "wsock32.dll" }) - { - if (module ? g_pMetaHookAPI->ModuleHasImportEx(module, name, "select") - : g_pMetaHookAPI->BlobHasImportEx(blob, name, "select")) - { - socketModule = name; - break; - } - } - if (!socketModule) - { - Sys_Error("Could not find engine select import for cooperative network shutdown!"); - return; - } - if (module) - { - g_pHook_NetworkTerminate = g_pMetaHookAPI->IATHook(module, "kernel32.dll", "TerminateThread", NewTerminateThread, NULL); - g_pHook_NetworkSelect = g_pMetaHookAPI->IATHook(module, socketModule, "select", NewNetworkSelect, (void**)&g_pfn_select); - } - else - { - g_pHook_NetworkTerminate = g_pMetaHookAPI->BlobIATHook(blob, "kernel32.dll", "TerminateThread", NewTerminateThread, NULL); - g_pHook_NetworkSelect = g_pMetaHookAPI->BlobIATHook(blob, socketModule, "select", NewNetworkSelect, (void**)&g_pfn_select); - } - g_pHook_NET_QueuePacket = g_pMetaHookAPI->InlineHook((void*)g_pfn_NET_QueuePacket, NewNET_QueuePacket, (void**)&g_pfn_NET_QueuePacket); - g_pHook_NET_Shutdown = g_pMetaHookAPI->InlineHook((void*)g_pfn_NET_Shutdown, NewNET_Shutdown, (void**)&g_pfn_NET_Shutdown); - if (!g_pHook_NetworkTerminate || !g_pHook_NetworkSelect || !g_pHook_NET_QueuePacket || !g_pHook_NET_Shutdown) - Sys_Error("Could not install cooperative network shutdown hooks!"); + g_NetworkManager = manager; + const auto module = manager->GetModule(); + const auto blob = manager->GetBlobModule(); + const char* socketModule = NULL; + for (const auto name : {"ws2_32.dll", "wsock32.dll"}) + { + if (module ? g_pMetaHookAPI->ModuleHasImportEx(module, name, "select") : g_pMetaHookAPI->BlobHasImportEx(blob, name, "select")) + { + socketModule = name; + break; + } + } + if (!socketModule) + { + Sys_Error("Could not find engine select import for cooperative network shutdown!"); + return; + } + if (module) + { + g_pHook_NetworkTerminate = g_pMetaHookAPI->IATHook(module, "kernel32.dll", "TerminateThread", NewTerminateThread, NULL); + g_pHook_NetworkSelect = g_pMetaHookAPI->IATHook(module, socketModule, "select", NewNetworkSelect, (void**)&g_pfn_select); + } + else + { + g_pHook_NetworkTerminate = g_pMetaHookAPI->BlobIATHook(blob, "kernel32.dll", "TerminateThread", NewTerminateThread, NULL); + g_pHook_NetworkSelect = g_pMetaHookAPI->BlobIATHook(blob, socketModule, "select", NewNetworkSelect, (void**)&g_pfn_select); + } + g_pHook_NET_QueuePacket = g_pMetaHookAPI->InlineHook((void*)g_pfn_NET_QueuePacket, NewNET_QueuePacket, (void**)&g_pfn_NET_QueuePacket); + g_pHook_NET_Shutdown = g_pMetaHookAPI->InlineHook((void*)g_pfn_NET_Shutdown, NewNET_Shutdown, (void**)&g_pfn_NET_Shutdown); + if (!g_pHook_NetworkTerminate || !g_pHook_NetworkSelect || !g_pHook_NET_QueuePacket || !g_pHook_NET_Shutdown) + Sys_Error("Could not install cooperative network shutdown hooks!"); } void NetworkThread_UninstallHook() { - if (!g_NetworkThread.Reset()) - { - Sys_Error("Network thread is still running during engine unload!"); - return; - } - for (auto hook : { g_pHook_NET_Shutdown, g_pHook_NET_QueuePacket, g_pHook_NetworkSelect, g_pHook_NetworkTerminate }) - if (hook) - g_pMetaHookAPI->UnHook(hook); - g_pHook_NET_QueuePacket = g_pHook_NetworkSelect = g_pHook_NetworkTerminate = NULL; - g_pHook_NET_Shutdown = NULL; - g_pfn_NET_QueuePacket = NULL; - g_pfn_NET_Shutdown = NULL; - g_pfn_select = NULL; - g_NetworkEntry = NULL; - g_NetworkThreadId = NULL; - g_NetworkManager = NULL; + if (!g_NetworkThread.Reset()) + { + Sys_Error("Network thread is still running during engine unload!"); + return; + } + for (auto hook : {g_pHook_NET_Shutdown, g_pHook_NET_QueuePacket, g_pHook_NetworkSelect, g_pHook_NetworkTerminate}) + if (hook) + g_pMetaHookAPI->UnHook(hook); + g_pHook_NET_QueuePacket = g_pHook_NetworkSelect = g_pHook_NetworkTerminate = NULL; + g_pHook_NET_Shutdown = NULL; + g_pfn_NET_QueuePacket = NULL; + g_pfn_NET_Shutdown = NULL; + g_pfn_select = NULL; + g_NetworkEntry = NULL; + g_NetworkThreadId = NULL; + g_NetworkManager = NULL; } -IThreadManager * FindThreadManagerByVirtualAddress(PVOID VirtualAddress) +IThreadManager* FindThreadManagerByVirtualAddress(PVOID VirtualAddress) { - std::lock_guard lock(g_ThreadManagerLock); + std::lock_guard lock(g_ThreadManagerLock); - for (auto p : g_ThreadManagers) - { - if (VirtualAddress >= p->GetTextBase() && VirtualAddress < (PUCHAR)p->GetTextBase() + p->GetTextSize()) - { - return p; - } - } + for (auto p : g_ThreadManagers) + { + if (VirtualAddress >= p->GetTextBase() && VirtualAddress < (PUCHAR)p->GetTextBase() + p->GetTextSize()) + { + return p; + } + } - return NULL; + return NULL; } HANDLE WINAPI NewCreateThread( - LPSECURITY_ATTRIBUTES lpThreadAttributes, - SIZE_T dwStackSize, - LPTHREAD_START_ROUTINE lpStartAddress, - LPVOID lpParameter, - DWORD dwCreationFlags, - LPDWORD lpThreadId -) + LPSECURITY_ATTRIBUTES lpThreadAttributes, + SIZE_T dwStackSize, + LPTHREAD_START_ROUTINE lpStartAddress, + LPVOID lpParameter, + DWORD dwCreationFlags, + LPDWORD lpThreadId) { - auto ThreadManager = FindThreadManagerByVirtualAddress(_ReturnAddress()); - const bool network = g_NetworkEntry && lpStartAddress == g_NetworkEntry && ThreadManager == g_NetworkManager; - if (network && !g_NetworkThread.PrepareCreate()) - { - Sys_Error("Cannot recreate a network thread before its predecessor exits!"); - return NULL; - } - - // Publish identity before the worker can enter its first blocking select. - // Preserve the caller's suspended state, entry point and original handle. - auto hThreadHandle = CreateThread(lpThreadAttributes, dwStackSize, lpStartAddress, lpParameter, - network ? dwCreationFlags | CREATE_SUSPENDED : dwCreationFlags, lpThreadId); - - if (hThreadHandle) - { - if (network && !g_NetworkThread.Track(hThreadHandle)) - { - Sys_Error("Could not retain network thread identity (error %lu)!", GetLastError()); - return hThreadHandle; - } - - if (ThreadManager) - { - HANDLE hNewThreadHandle = 0; - if (DuplicateHandle((HANDLE)(-1), hThreadHandle, (HANDLE)(-1), &hNewThreadHandle, THREAD_ALL_ACCESS, FALSE, DUPLICATE_SAME_ACCESS)) - { - ThreadManager->OnCreateThread(hNewThreadHandle); - } - } - if (network && !(dwCreationFlags & CREATE_SUSPENDED) && ResumeThread(hThreadHandle) == (DWORD)-1) - Sys_Error("Could not resume network thread (error %lu)!", GetLastError()); - } - - return hThreadHandle; + auto ThreadManager = FindThreadManagerByVirtualAddress(_ReturnAddress()); + const bool network = g_NetworkEntry && lpStartAddress == g_NetworkEntry && ThreadManager == g_NetworkManager; + if (network && !g_NetworkThread.PrepareCreate()) + { + Sys_Error("Cannot recreate a network thread before its predecessor exits!"); + return NULL; + } + + // Publish identity before the worker can enter its first blocking select. + // Preserve the caller's suspended state, entry point and original handle. + auto hThreadHandle = CreateThread(lpThreadAttributes, dwStackSize, lpStartAddress, lpParameter, + network ? dwCreationFlags | CREATE_SUSPENDED : dwCreationFlags, lpThreadId); + + if (hThreadHandle) + { + if (network && !g_NetworkThread.Track(hThreadHandle)) + { + Sys_Error("Could not retain network thread identity (error %lu)!", GetLastError()); + return hThreadHandle; + } + + if (ThreadManager) + { + HANDLE hNewThreadHandle = 0; + if (DuplicateHandle((HANDLE)(-1), hThreadHandle, (HANDLE)(-1), &hNewThreadHandle, THREAD_ALL_ACCESS, FALSE, DUPLICATE_SAME_ACCESS)) + { + ThreadManager->OnCreateThread(hNewThreadHandle); + } + } + if (network && !(dwCreationFlags & CREATE_SUSPENDED) && ResumeThread(hThreadHandle) == (DWORD)-1) + Sys_Error("Could not resume network thread (error %lu)!", GetLastError()); + } + + return hThreadHandle; } -DWORD WINAPI NewWaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds) +DWORD WINAPI NewWaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds) { - //Fuck valve - if (dwMilliseconds == 0) - { - auto ThreadManager = FindThreadManagerByVirtualAddress(_ReturnAddress()); - - if (ThreadManager) - { - if (ThreadManager->OnWaitForSingleObject(hHandle, dwMilliseconds)) - { - return WAIT_OBJECT_0; - } - } - } - - return WaitForSingleObject(hHandle, dwMilliseconds); + //Fuck valve + if (dwMilliseconds == 0) + { + auto ThreadManager = FindThreadManagerByVirtualAddress(_ReturnAddress()); + + if (ThreadManager) + { + if (ThreadManager->OnWaitForSingleObject(hHandle, dwMilliseconds)) + { + return WAIT_OBJECT_0; + } + } + } + + return WaitForSingleObject(hHandle, dwMilliseconds); } void WINAPI NewSleep(DWORD dwMilliseconds) { - if (dwMilliseconds == 1) - { - if (IsNetworkThread()) - { - if (NetworkExitRequested()) - { - // Verified for all supported Windows engines: this worker calls - // Sleep(1) only at the unlocked loop tail. No RAII scope is active. - NetworkTrace("safe Sleep(1); exiting", GetCurrentThreadId()); - g_NetworkThread.RetireCurrentThread(); - ExitThread(0); - } - return Sleep(dwMilliseconds); - } - auto ThreadManager = FindThreadManagerByVirtualAddress(_ReturnAddress()); - - if (ThreadManager) - { - if (ThreadManager->OnSleep(dwMilliseconds)) - { - ExitThread(0); - return; - } - } - } - - return Sleep(dwMilliseconds); + if (dwMilliseconds == 1) + { + if (IsNetworkThread()) + { + if (NetworkExitRequested()) + { + // Verified for all supported Windows engines: this worker calls + // Sleep(1) only at the unlocked loop tail. No RAII scope is active. + NetworkTrace("safe Sleep(1); exiting", GetCurrentThreadId()); + g_NetworkThread.RetireCurrentThread(); + ExitThread(0); + } + return Sleep(dwMilliseconds); + } + auto ThreadManager = FindThreadManagerByVirtualAddress(_ReturnAddress()); + + if (ThreadManager) + { + if (ThreadManager->OnSleep(dwMilliseconds)) + { + ExitThread(0); + return; + } + } + } + + return Sleep(dwMilliseconds); } static int WSAAPI NewSocketSelect(int nfds, fd_set* read, fd_set* write, fd_set* except, const timeval* timeout) { - auto manager = FindThreadManagerByVirtualAddress(_ReturnAddress()); - if (manager && (HANDLE)GetCurrentThreadId() != g_MainThreadId) - { - if (manager->OnSleep(1)) - { - if (read) FD_ZERO(read); - if (write) FD_ZERO(write); - if (except) FD_ZERO(except); - return 0; - } - const timeval poll = { 0, kNetworkPollMicroseconds }; - if (!timeout || timeout->tv_sec > 0 || timeout->tv_usec > poll.tv_usec) - return select(nfds, read, write, except, &poll); - } - return select(nfds, read, write, except, timeout); + auto manager = FindThreadManagerByVirtualAddress(_ReturnAddress()); + if (manager && (HANDLE)GetCurrentThreadId() != g_MainThreadId) + { + if (manager->OnSleep(1)) + { + if (read) FD_ZERO(read); + if (write) FD_ZERO(write); + if (except) FD_ZERO(except); + return 0; + } + const timeval poll = {0, kNetworkPollMicroseconds}; + if (!timeout || timeout->tv_sec > 0 || timeout->tv_usec > poll.tv_usec) + return select(nfds, read, write, except, &poll); + } + return select(nfds, read, write, except, timeout); } static int WSAAPI NewSocketRecvFrom(SOCKET socket, char* buffer, int length, int flags, sockaddr* from, int* fromLength) { - auto manager = FindThreadManagerByVirtualAddress(_ReturnAddress()); - // CSocketThread uses nonblocking sockets. Break its drain loop without - // exiting while it owns the socket-list lock; the loop unlocks normally - // before observing termination at WaitForSingleObject(event, 0). - if (manager && manager->OnSleep(1)) - { - WSASetLastError(WSAEWOULDBLOCK); - return SOCKET_ERROR; - } - return recvfrom(socket, buffer, length, flags, from, fromLength); + auto manager = FindThreadManagerByVirtualAddress(_ReturnAddress()); + // CSocketThread uses nonblocking sockets. Break its drain loop without + // exiting while it owns the socket-list lock; the loop unlocks normally + // before observing termination at WaitForSingleObject(event, 0). + if (manager && manager->OnSleep(1)) + { + WSASetLastError(WSAEWOULDBLOCK); + return SOCKET_ERROR; + } + return recvfrom(socket, buffer, length, flags, from, fromLength); } class CThreadManager : public IThreadManager { private: - std::mutex m_ThreadListLock; - - HANDLE m_hAliveThread[MAXIMUM_WAIT_OBJECTS]; - HANDLE m_hClosedThread[MAXIMUM_WAIT_OBJECTS]; - - HMODULE m_hModule; - BlobHandle_t m_hBlobModule; - - PVOID m_ImageBase; - ULONG m_ImageSize; - - PVOID m_TextBase; - ULONG m_TextSize; - - //for __beginthread - hook_t* m_pHook_CreateThread; + std::mutex m_ThreadListLock; - //for CSocketThread - hook_t* m_pHook_WaitForSingleObject; + HANDLE m_hAliveThread[MAXIMUM_WAIT_OBJECTS]; + HANDLE m_hClosedThread[MAXIMUM_WAIT_OBJECTS]; - //for hw.dll - hook_t* m_pHook_Sleep; - static constexpr unsigned kSocketImportLibraryCount = 2; - static constexpr unsigned kSocketHooksPerLibrary = 2; - hook_t* m_pHook_Socket[kSocketImportLibraryCount * kSocketHooksPerLibrary]; + HMODULE m_hModule; + BlobHandle_t m_hBlobModule; - std::atomic m_bStartTermination; -public: - CThreadManager(HMODULE hModule, BlobHandle_t hBlob) - { - m_hModule = hModule; - m_hBlobModule = hBlob; - - m_ImageBase = 0; - m_ImageSize = 0; - m_TextBase = 0; - m_TextSize = 0; - - if (m_hModule) - { - m_ImageBase = g_pMetaHookAPI->GetModuleBase(m_hModule); - m_TextBase = g_pMetaHookAPI->GetSectionByName(m_ImageBase, ".text\0\0\0", &m_TextSize); - } - - if (m_hBlobModule) - { - m_ImageBase = g_pMetaHookAPI->GetBlobModuleImageBase(m_hBlobModule); - m_TextBase = g_pMetaHookAPI->GetSectionByName(m_ImageBase, ".text\0\0\0", &m_TextSize); - } - - memset(m_hAliveThread, 0, sizeof(m_hAliveThread)); - memset(m_hClosedThread, 0, sizeof(m_hClosedThread)); - - m_pHook_CreateThread = NULL; - m_pHook_WaitForSingleObject = NULL; - m_pHook_Sleep = NULL; - memset(m_pHook_Socket, 0, sizeof(m_pHook_Socket)); - - m_bStartTermination = false; - } - - HMODULE GetModule(void) const - { - return m_hModule; - } - - BlobHandle_t GetBlobModule(void) const - { - return m_hBlobModule; - } - - PVOID GetImageBase(void) const - { - return m_ImageBase; - } - - ULONG GetImageSize(void) const - { - return m_ImageSize; - } - - PVOID GetTextBase(void) const - { - return m_TextBase; - } - - ULONG GetTextSize(void) const - { - return m_TextSize; - } - - bool OnWaitForSingleObject(HANDLE hObject, DWORD dwMilliseconds) override - { - if (m_bStartTermination) - { - return true; - } - return false; - } - - bool OnSleep(DWORD dwMilliseconds) override - { - if (m_bStartTermination) - { - if((HANDLE)GetCurrentThreadId() != g_MainThreadId) - return true; - } - return false; - } + PVOID m_ImageBase; + ULONG m_ImageSize; - void InstallHook(int flags) override - { - if (m_hModule) - { - if(flags & hookflag_CreateThread) - m_pHook_CreateThread = g_pMetaHookAPI->IATHook(m_hModule, "kernel32.dll", "CreateThread", NewCreateThread, NULL); + PVOID m_TextBase; + ULONG m_TextSize; - if (flags & hookflag_WaitForSingleObject) - m_pHook_WaitForSingleObject = g_pMetaHookAPI->IATHook(m_hModule, "kernel32.dll", "WaitForSingleObject", NewWaitForSingleObject, NULL); + //for __beginthread + hook_t* m_pHook_CreateThread; - if (flags & hookflag_Sleep) - m_pHook_Sleep = g_pMetaHookAPI->IATHook(m_hModule, "kernel32.dll", "Sleep", NewSleep, NULL); - if (flags & hookflag_Socket) - { - unsigned index = 0; - for (auto dll : { "wsock32.dll", "ws2_32.dll" }) - { - if (g_pMetaHookAPI->ModuleHasImportEx(m_hModule, dll, "select")) - { - m_pHook_Socket[index] = g_pMetaHookAPI->IATHook(m_hModule, dll, "select", NewSocketSelect, NULL); - if (!m_pHook_Socket[index]) Sys_Error("Could not hook socket select import!"); - } - ++index; - if (g_pMetaHookAPI->ModuleHasImportEx(m_hModule, dll, "recvfrom")) - { - m_pHook_Socket[index] = g_pMetaHookAPI->IATHook(m_hModule, dll, "recvfrom", NewSocketRecvFrom, NULL); - if (!m_pHook_Socket[index]) Sys_Error("Could not hook socket recvfrom import!"); - } - ++index; - } - } - } - else if (m_hBlobModule) - { - if (flags & hookflag_CreateThread) - m_pHook_CreateThread = g_pMetaHookAPI->BlobIATHook(m_hBlobModule, "kernel32.dll", "CreateThread", NewCreateThread, NULL); + //for CSocketThread + hook_t* m_pHook_WaitForSingleObject; - if (flags & hookflag_WaitForSingleObject) - m_pHook_WaitForSingleObject = g_pMetaHookAPI->BlobIATHook(m_hBlobModule, "kernel32.dll", "WaitForSingleObject", NewWaitForSingleObject, NULL); + //for hw.dll + hook_t* m_pHook_Sleep; + static constexpr unsigned kSocketImportLibraryCount = 2; + static constexpr unsigned kSocketHooksPerLibrary = 2; + hook_t* m_pHook_Socket[kSocketImportLibraryCount * kSocketHooksPerLibrary]; - if (flags & hookflag_Sleep) - m_pHook_Sleep = g_pMetaHookAPI->BlobIATHook(m_hBlobModule, "kernel32.dll", "Sleep", NewSleep, NULL); - } - } + std::atomic m_bStartTermination; - void UnistallHook(void) override - { - for (auto& hook : m_pHook_Socket) - { - if (hook) g_pMetaHookAPI->UnHook(hook); - hook = NULL; - } +public: + CThreadManager(HMODULE hModule, BlobHandle_t hBlob) + { + m_hModule = hModule; + m_hBlobModule = hBlob; + + m_ImageBase = 0; + m_ImageSize = 0; + m_TextBase = 0; + m_TextSize = 0; + + if (m_hModule) + { + m_ImageBase = g_pMetaHookAPI->GetModuleBase(m_hModule); + m_TextBase = g_pMetaHookAPI->GetSectionByName(m_ImageBase, ".text\0\0\0", &m_TextSize); + } + + if (m_hBlobModule) + { + m_ImageBase = g_pMetaHookAPI->GetBlobModuleImageBase(m_hBlobModule); + m_TextBase = g_pMetaHookAPI->GetSectionByName(m_ImageBase, ".text\0\0\0", &m_TextSize); + } + + memset(m_hAliveThread, 0, sizeof(m_hAliveThread)); + memset(m_hClosedThread, 0, sizeof(m_hClosedThread)); + + m_pHook_CreateThread = NULL; + m_pHook_WaitForSingleObject = NULL; + m_pHook_Sleep = NULL; + memset(m_pHook_Socket, 0, sizeof(m_pHook_Socket)); + + m_bStartTermination = false; + } + + HMODULE GetModule(void) const + { + return m_hModule; + } + + BlobHandle_t GetBlobModule(void) const + { + return m_hBlobModule; + } + + PVOID GetImageBase(void) const + { + return m_ImageBase; + } + + ULONG GetImageSize(void) const + { + return m_ImageSize; + } + + PVOID GetTextBase(void) const + { + return m_TextBase; + } + + ULONG GetTextSize(void) const + { + return m_TextSize; + } + + bool OnWaitForSingleObject(HANDLE hObject, DWORD dwMilliseconds) override + { + if (m_bStartTermination) + { + return true; + } + return false; + } + + bool OnSleep(DWORD dwMilliseconds) override + { + if (m_bStartTermination) + { + if ((HANDLE)GetCurrentThreadId() != g_MainThreadId) + return true; + } + return false; + } + + void InstallHook(int flags) override + { + if (m_hModule) + { + if (flags & hookflag_CreateThread) + m_pHook_CreateThread = g_pMetaHookAPI->IATHook(m_hModule, "kernel32.dll", "CreateThread", NewCreateThread, NULL); + + if (flags & hookflag_WaitForSingleObject) + m_pHook_WaitForSingleObject = g_pMetaHookAPI->IATHook(m_hModule, "kernel32.dll", "WaitForSingleObject", NewWaitForSingleObject, NULL); + + if (flags & hookflag_Sleep) + m_pHook_Sleep = g_pMetaHookAPI->IATHook(m_hModule, "kernel32.dll", "Sleep", NewSleep, NULL); + if (flags & hookflag_Socket) + { + unsigned index = 0; + for (auto dll : {"wsock32.dll", "ws2_32.dll"}) + { + if (g_pMetaHookAPI->ModuleHasImportEx(m_hModule, dll, "select")) + { + m_pHook_Socket[index] = g_pMetaHookAPI->IATHook(m_hModule, dll, "select", NewSocketSelect, NULL); + if (!m_pHook_Socket[index]) Sys_Error("Could not hook socket select import!"); + } + ++index; + if (g_pMetaHookAPI->ModuleHasImportEx(m_hModule, dll, "recvfrom")) + { + m_pHook_Socket[index] = g_pMetaHookAPI->IATHook(m_hModule, dll, "recvfrom", NewSocketRecvFrom, NULL); + if (!m_pHook_Socket[index]) Sys_Error("Could not hook socket recvfrom import!"); + } + ++index; + } + } + } + else if (m_hBlobModule) + { + if (flags & hookflag_CreateThread) + m_pHook_CreateThread = g_pMetaHookAPI->BlobIATHook(m_hBlobModule, "kernel32.dll", "CreateThread", NewCreateThread, NULL); + + if (flags & hookflag_WaitForSingleObject) + m_pHook_WaitForSingleObject = g_pMetaHookAPI->BlobIATHook(m_hBlobModule, "kernel32.dll", "WaitForSingleObject", NewWaitForSingleObject, NULL); + + if (flags & hookflag_Sleep) + m_pHook_Sleep = g_pMetaHookAPI->BlobIATHook(m_hBlobModule, "kernel32.dll", "Sleep", NewSleep, NULL); + } + } + + void UnistallHook(void) override + { + for (auto& hook : m_pHook_Socket) + { + if (hook) g_pMetaHookAPI->UnHook(hook); + hook = NULL; + } #if 0 if (m_pHook_DllMain) { @@ -504,11 +503,11 @@ class CThreadManager : public IThreadManager m_pHook_DllMain = NULL; } #endif - if (m_pHook_CreateThread) - { - g_pMetaHookAPI->UnHook(m_pHook_CreateThread); - m_pHook_CreateThread = NULL; - } + if (m_pHook_CreateThread) + { + g_pMetaHookAPI->UnHook(m_pHook_CreateThread); + m_pHook_CreateThread = NULL; + } #if 0 if (m_pHook_CloseHandle) { @@ -522,160 +521,160 @@ class CThreadManager : public IThreadManager m_pHook_TerminateThread = NULL; } #endif - if (m_pHook_WaitForSingleObject) - { - g_pMetaHookAPI->UnHook(m_pHook_WaitForSingleObject); - m_pHook_WaitForSingleObject = NULL; - } + if (m_pHook_WaitForSingleObject) + { + g_pMetaHookAPI->UnHook(m_pHook_WaitForSingleObject); + m_pHook_WaitForSingleObject = NULL; + } + + if (m_pHook_Sleep) + { + g_pMetaHookAPI->UnHook(m_pHook_Sleep); + m_pHook_Sleep = NULL; + } + } - if (m_pHook_Sleep) - { - g_pMetaHookAPI->UnHook(m_pHook_Sleep); - m_pHook_Sleep = NULL; - } - } private: - - bool FindAliveThread(HANDLE hThread) - { - for (DWORD i = 0; i < _ARRAYSIZE(m_hAliveThread); ++i) - { - if (m_hAliveThread[i] == hThread) - { - return true; - } - } - return false; - } - - bool FindAndRemoveAliveThread(HANDLE hThread) - { - for (DWORD i = 0; i < _ARRAYSIZE(m_hAliveThread); ++i) - { - if (m_hAliveThread[i] == hThread) - { - m_hAliveThread[i] = 0; - return true; - } - } - return false; - } - - bool AddAliveThread(HANDLE hThread) - { - for (DWORD i = 0; i < _ARRAYSIZE(m_hAliveThread); ++i) - { - if (m_hAliveThread[i] == 0) - { - m_hAliveThread[i] = hThread; - return true; - } - } - return false; - } - - bool FindClosedThread(HANDLE hThread) - { - for (DWORD i = 0; i < _ARRAYSIZE(m_hClosedThread); ++i) - { - if (m_hClosedThread[i] == hThread) - { - return true; - } - } - return false; - } - - bool AddClosedThread(HANDLE hThread) - { - for (DWORD i = 0; i < _ARRAYSIZE(m_hClosedThread); ++i) - { - if (m_hClosedThread[i] == 0) - { - m_hClosedThread[i] = hThread; - return true; - } - } - return false; - } - - bool FindAndRemoveSignaledAliveThread(DWORD* pIndex) - { - for (DWORD i = 0; i < _ARRAYSIZE(m_hAliveThread); ++i) - { - if (m_hAliveThread[i]) - { - if (WAIT_OBJECT_0 == WaitForSingleObject(m_hAliveThread[i], 0)) - { - CloseHandle(m_hAliveThread[i]); - m_hAliveThread[i] = 0; - if (pIndex) - *pIndex = i; - return true; - } - } - } - - return false; - } - - bool FindAndRemoveSignaledClosedThread(DWORD* pIndex) - { - for (DWORD i = 0; i < _ARRAYSIZE(m_hClosedThread); ++i) - { - if (m_hClosedThread[i]) - { - if (WAIT_OBJECT_0 == WaitForSingleObject(m_hClosedThread[i], 0)) - { - m_hClosedThread[i] = 0; - if (pIndex) - *pIndex = i; - return true; - } - } - } - - return false; - } - - void StartTermination(void) override - { - if (this == g_NetworkManager && g_NetworkThreadId) - { - // Preserve the old ExitGame fallback when an engine shutdown path - // reaches the manager without first calling TerminateThread. - const auto result = g_NetworkThread.RequestTrackedStop(*g_NetworkThreadId); - if (result == NetworkThreadState::StopResult::SelfWait || result == NetworkThreadState::StopResult::Failed) - Sys_Error("Cannot request network thread exit during engine termination!"); - } - m_bStartTermination = true; - } - - void WaitForAliveThreadsToShutdown(void) override - { - for (;;) - { - HANDLE hThreads[MAXIMUM_WAIT_OBJECTS] = { 0 }; - DWORD numThreads = 0; - { - std::lock_guard lock(m_ThreadListLock); - for (DWORD i = 0; i < _ARRAYSIZE(m_hAliveThread); ++i) - { - if (m_hAliveThread[i] != 0) - { - hThreads[numThreads++] = m_hAliveThread[i]; - // Transfer ownership to this batch before dropping the lock. - m_hAliveThread[i] = NULL; - } - } - } - if (!numThreads) - return; - if (WaitForMultipleObjects(numThreads, hThreads, TRUE, INFINITE) != WAIT_OBJECT_0) - { - Sys_Error("Waiting for managed threads failed (error %lu)!", GetLastError()); - return; - } + bool FindAliveThread(HANDLE hThread) + { + for (DWORD i = 0; i < _ARRAYSIZE(m_hAliveThread); ++i) + { + if (m_hAliveThread[i] == hThread) + { + return true; + } + } + return false; + } + + bool FindAndRemoveAliveThread(HANDLE hThread) + { + for (DWORD i = 0; i < _ARRAYSIZE(m_hAliveThread); ++i) + { + if (m_hAliveThread[i] == hThread) + { + m_hAliveThread[i] = 0; + return true; + } + } + return false; + } + + bool AddAliveThread(HANDLE hThread) + { + for (DWORD i = 0; i < _ARRAYSIZE(m_hAliveThread); ++i) + { + if (m_hAliveThread[i] == 0) + { + m_hAliveThread[i] = hThread; + return true; + } + } + return false; + } + + bool FindClosedThread(HANDLE hThread) + { + for (DWORD i = 0; i < _ARRAYSIZE(m_hClosedThread); ++i) + { + if (m_hClosedThread[i] == hThread) + { + return true; + } + } + return false; + } + + bool AddClosedThread(HANDLE hThread) + { + for (DWORD i = 0; i < _ARRAYSIZE(m_hClosedThread); ++i) + { + if (m_hClosedThread[i] == 0) + { + m_hClosedThread[i] = hThread; + return true; + } + } + return false; + } + + bool FindAndRemoveSignaledAliveThread(DWORD* pIndex) + { + for (DWORD i = 0; i < _ARRAYSIZE(m_hAliveThread); ++i) + { + if (m_hAliveThread[i]) + { + if (WAIT_OBJECT_0 == WaitForSingleObject(m_hAliveThread[i], 0)) + { + CloseHandle(m_hAliveThread[i]); + m_hAliveThread[i] = 0; + if (pIndex) + *pIndex = i; + return true; + } + } + } + + return false; + } + + bool FindAndRemoveSignaledClosedThread(DWORD* pIndex) + { + for (DWORD i = 0; i < _ARRAYSIZE(m_hClosedThread); ++i) + { + if (m_hClosedThread[i]) + { + if (WAIT_OBJECT_0 == WaitForSingleObject(m_hClosedThread[i], 0)) + { + m_hClosedThread[i] = 0; + if (pIndex) + *pIndex = i; + return true; + } + } + } + + return false; + } + + void StartTermination(void) override + { + if (this == g_NetworkManager && g_NetworkThreadId) + { + // Preserve the old ExitGame fallback when an engine shutdown path + // reaches the manager without first calling TerminateThread. + const auto result = g_NetworkThread.RequestTrackedStop(*g_NetworkThreadId); + if (result == NetworkThreadState::StopResult::SelfWait || result == NetworkThreadState::StopResult::Failed) + Sys_Error("Cannot request network thread exit during engine termination!"); + } + m_bStartTermination = true; + } + + void WaitForAliveThreadsToShutdown(void) override + { + for (;;) + { + HANDLE hThreads[MAXIMUM_WAIT_OBJECTS] = {0}; + DWORD numThreads = 0; + { + std::lock_guard lock(m_ThreadListLock); + for (DWORD i = 0; i < _ARRAYSIZE(m_hAliveThread); ++i) + { + if (m_hAliveThread[i] != 0) + { + hThreads[numThreads++] = m_hAliveThread[i]; + // Transfer ownership to this batch before dropping the lock. + m_hAliveThread[i] = NULL; + } + } + } + if (!numThreads) + return; + if (WaitForMultipleObjects(numThreads, hThreads, TRUE, INFINITE) != WAIT_OBJECT_0) + { + Sys_Error("Waiting for managed threads failed (error %lu)!", GetLastError()); + return; + } #if 0 { @@ -687,12 +686,12 @@ class CThreadManager : public IThreadManager } } #endif - for (DWORD i = 0; i < numThreads; ++i) - CloseHandle(hThreads[i]); - // A tracked parent can create a child while we are joining it. - // Drain that newly registered batch before permitting module unload. - } - } + for (DWORD i = 0; i < numThreads; ++i) + CloseHandle(hThreads[i]); + // A tracked parent can create a child while we are joining it. + // Drain that newly registered batch before permitting module unload. + } + } #if 0 void WaitForClosedThreadsToShutdown(void) override @@ -729,71 +728,71 @@ class CThreadManager : public IThreadManager #endif - void OnCreateThread(HANDLE hThreadHandle) override - { - std::lock_guard lock(m_ThreadListLock); - - bool bAdded = AddAliveThread(hThreadHandle); - - if (!bAdded) - { - DWORD NewIndex = 0; - if (FindAndRemoveSignaledAliveThread(&NewIndex)) - { - m_hAliveThread[NewIndex] = hThreadHandle; - bAdded = true; - } - } - - if (!bAdded) - { - g_pMetaHookAPI->SysError("Failed to insert thread to thread manager!"); - } - } + void OnCreateThread(HANDLE hThreadHandle) override + { + std::lock_guard lock(m_ThreadListLock); + + bool bAdded = AddAliveThread(hThreadHandle); + + if (!bAdded) + { + DWORD NewIndex = 0; + if (FindAndRemoveSignaledAliveThread(&NewIndex)) + { + m_hAliveThread[NewIndex] = hThreadHandle; + bAdded = true; + } + } + + if (!bAdded) + { + g_pMetaHookAPI->SysError("Failed to insert thread to thread manager!"); + } + } }; void AddThreadManager(IThreadManager* p) { - std::lock_guard lock(g_ThreadManagerLock); + std::lock_guard lock(g_ThreadManagerLock); - g_ThreadManagers.emplace_back(p); + g_ThreadManagers.emplace_back(p); } void DeleteThreadManager(IThreadManager* p) { - std::lock_guard lock(g_ThreadManagerLock); - - for (auto itor = g_ThreadManagers.begin(); itor != g_ThreadManagers.end();) - { - if ((*itor) == p) - { - itor = g_ThreadManagers.erase(itor); - return; - } - itor++; - } + std::lock_guard lock(g_ThreadManagerLock); + + for (auto itor = g_ThreadManagers.begin(); itor != g_ThreadManagers.end();) + { + if ((*itor) == p) + { + itor = g_ThreadManagers.erase(itor); + return; + } + itor++; + } } IThreadManager* CreateThreadManagerForModule(HMODULE hModule) { - auto p = new CThreadManager(hModule, NULL); - if (p) - { - AddThreadManager(p); - return p; - } - - return NULL; + auto p = new CThreadManager(hModule, NULL); + if (p) + { + AddThreadManager(p); + return p; + } + + return NULL; } IThreadManager* CreateThreadManagerForBlob(BlobHandle_t hBlob) { - auto p = new CThreadManager(NULL, hBlob); - if (p) - { - AddThreadManager(p); - return p; - } - - return NULL; + auto p = new CThreadManager(NULL, hBlob); + if (p) + { + AddThreadManager(p); + return p; + } + + return NULL; } diff --git a/src/ThreadManager.h b/src/ThreadManager.h index c99469b..b1a45dd 100644 --- a/src/ThreadManager.h +++ b/src/ThreadManager.h @@ -3,36 +3,35 @@ #include #include -const int hookflag_CreateThread = 1; +const int hookflag_CreateThread = 1; const int hookflag_WaitForSingleObject = 2; -const int hookflag_Sleep = 4; -const int hookflag_Socket = 8; +const int hookflag_Sleep = 4; +const int hookflag_Socket = 8; class IThreadManager : public IBaseInterface { public: - public: - virtual void StartTermination(void) = 0; - virtual void WaitForAliveThreadsToShutdown(void) = 0; - //virtual void WaitForClosedThreadsToShutdown(void) = 0; - - virtual void OnCreateThread(HANDLE hThreadHandle) = 0; - virtual bool OnWaitForSingleObject(HANDLE hObject, DWORD dwMilliseconds) = 0; - virtual bool OnSleep(DWORD dwMilliseconds) = 0; - - virtual void InstallHook(int flags) = 0; - virtual void UnistallHook(void) = 0; - virtual PVOID GetImageBase(void) const = 0; - virtual ULONG GetImageSize(void) const = 0; - virtual PVOID GetTextBase(void) const = 0; - virtual ULONG GetTextSize(void) const = 0; - virtual HMODULE GetModule(void) const = 0; - virtual BlobHandle_t GetBlobModule(void) const = 0; + virtual void StartTermination(void) = 0; + virtual void WaitForAliveThreadsToShutdown(void) = 0; + //virtual void WaitForClosedThreadsToShutdown(void) = 0; + + virtual void OnCreateThread(HANDLE hThreadHandle) = 0; + virtual bool OnWaitForSingleObject(HANDLE hObject, DWORD dwMilliseconds) = 0; + virtual bool OnSleep(DWORD dwMilliseconds) = 0; + + virtual void InstallHook(int flags) = 0; + virtual void UnistallHook(void) = 0; + virtual PVOID GetImageBase(void) const = 0; + virtual ULONG GetImageSize(void) const = 0; + virtual PVOID GetTextBase(void) const = 0; + virtual ULONG GetTextSize(void) const = 0; + virtual HMODULE GetModule(void) const = 0; + virtual BlobHandle_t GetBlobModule(void) const = 0; }; -void AddThreadManager(IThreadManager* p); -void DeleteThreadManager(IThreadManager* p); +void AddThreadManager(IThreadManager* p); +void DeleteThreadManager(IThreadManager* p); IThreadManager* FindThreadManagerByVirtualAddress(PVOID VirtualAddress); IThreadManager* CreateThreadManagerForModule(HMODULE hModule); IThreadManager* CreateThreadManagerForBlob(BlobHandle_t hBlob); diff --git a/src/enginedef.h b/src/enginedef.h index 964db81..140468f 100644 --- a/src/enginedef.h +++ b/src/enginedef.h @@ -1,8 +1,8 @@ #pragma once -#define DLL_INACTIVE 0 // no dll -#define DLL_ACTIVE 1 // dll is running -#define DLL_PAUSED 2 // dll is paused -#define DLL_CLOSE 3 // closing down dll -#define DLL_TRANS 4 // Level Transition -#define DLL_RESTART 5 // engine is shutting down but will restart right away +#define DLL_INACTIVE 0 // no dll +#define DLL_ACTIVE 1 // dll is running +#define DLL_PAUSED 2 // dll is paused +#define DLL_CLOSE 3 // closing down dll +#define DLL_TRANS 4 // Level Transition +#define DLL_RESTART 5 // engine is shutting down but will restart right away diff --git a/src/exportfuncs.cpp b/src/exportfuncs.cpp index 342f4ad..d616f9d 100644 --- a/src/exportfuncs.cpp +++ b/src/exportfuncs.cpp @@ -11,9 +11,9 @@ #include #include -cl_enginefunc_t gEngfuncs; -engine_studio_api_t IEngineStudio; -r_studio_interface_t **gpStudioInterface; +cl_enginefunc_t gEngfuncs; +engine_studio_api_t IEngineStudio; +r_studio_interface_t** gpStudioInterface; static xcommand_t g_pfn_Host_KillServer_f; static xcommand_t g_pfn_Host_Quit_Restart_f; @@ -24,21 +24,21 @@ static xcommand_t g_pfn_Host_Quit_Restart_f; void Host_Quit_Restart_f(void) { - g_pfn_Host_KillServer_f(); + g_pfn_Host_KillServer_f(); - return g_pfn_Host_Quit_Restart_f(); + return g_pfn_Host_Quit_Restart_f(); } void EngineCommand_InstallHook(void) { - auto entry = g_pMetaHookAPI->FindCmd("shutdownserver"); - if (entry) - { - g_pfn_Host_KillServer_f = entry->function; - g_pfn_Host_Quit_Restart_f = g_pMetaHookAPI->HookCmd("_restart", Host_Quit_Restart_f); - } - else - { - Sys_Error("Command \"shutdownserver\" not found!"); - } + auto entry = g_pMetaHookAPI->FindCmd("shutdownserver"); + if (entry) + { + g_pfn_Host_KillServer_f = entry->function; + g_pfn_Host_Quit_Restart_f = g_pMetaHookAPI->HookCmd("_restart", Host_Quit_Restart_f); + } + else + { + Sys_Error("Command \"shutdownserver\" not found!"); + } } \ No newline at end of file diff --git a/src/plugins.cpp b/src/plugins.cpp index 7ca3b10..a6351b7 100644 --- a/src/plugins.cpp +++ b/src/plugins.cpp @@ -3,91 +3,90 @@ #include "privatehook.h" #include "plugins.h" -cl_exportfuncs_t gExportfuncs = {0}; -mh_interface_t *g_pInterface = NULL; -metahook_api_t *g_pMetaHookAPI = NULL; -mh_enginesave_t *g_pMetaSave = NULL; -IFileSystem *g_pFileSystem = NULL; -IFileSystem_HL25 *g_pFileSystem_HL25 = NULL; - -int g_iEngineType = 0; +cl_exportfuncs_t gExportfuncs = {0}; +mh_interface_t* g_pInterface = NULL; +metahook_api_t* g_pMetaHookAPI = NULL; +mh_enginesave_t* g_pMetaSave = NULL; +IFileSystem* g_pFileSystem = NULL; +IFileSystem_HL25* g_pFileSystem_HL25 = NULL; + +int g_iEngineType = 0; DWORD g_dwEngineBuildnum = 0; -mh_dll_info_t g_EngineDLLInfo = { 0 }; +mh_dll_info_t g_EngineDLLInfo = {0}; HANDLE g_MainThreadId = 0; -void IPluginsV4::Init(metahook_api_t *pAPI, mh_interface_t *pInterface, mh_enginesave_t *pSave) +void IPluginsV4::Init(metahook_api_t* pAPI, mh_interface_t* pInterface, mh_enginesave_t* pSave) { - g_pInterface = pInterface; - g_pMetaHookAPI = pAPI; - g_pMetaSave = pSave; + g_pInterface = pInterface; + g_pMetaHookAPI = pAPI; + g_pMetaSave = pSave; } void IPluginsV4::Shutdown(void) { - g_pMetaHookAPI->UnregisterLoadDllNotificationCallback(DllLoadNotification); + g_pMetaHookAPI->UnregisterLoadDllNotificationCallback(DllLoadNotification); } -void IPluginsV4::LoadEngine(cl_enginefunc_t *pEngfuncs) +void IPluginsV4::LoadEngine(cl_enginefunc_t* pEngfuncs) { - if (g_pInterface->MetaHookAPIVersion < METAHOOK_API_VERSION) - { - Sys_Error("MetaHookAPIVersion too low! expect %d, got %d !", METAHOOK_API_VERSION, g_pInterface->MetaHookAPIVersion); - return; - } + if (g_pInterface->MetaHookAPIVersion < METAHOOK_API_VERSION) + { + Sys_Error("MetaHookAPIVersion too low! expect %d, got %d !", METAHOOK_API_VERSION, g_pInterface->MetaHookAPIVersion); + return; + } - g_pFileSystem = g_pInterface->FileSystem; - if (!g_pFileSystem) - { - g_pFileSystem_HL25 = g_pInterface->FileSystem_HL25; - } + g_pFileSystem = g_pInterface->FileSystem; + if (!g_pFileSystem) + { + g_pFileSystem_HL25 = g_pInterface->FileSystem_HL25; + } - g_iEngineType = g_pMetaHookAPI->GetEngineType(); - g_dwEngineBuildnum = g_pMetaHookAPI->GetEngineBuildnum(); + g_iEngineType = g_pMetaHookAPI->GetEngineType(); + g_dwEngineBuildnum = g_pMetaHookAPI->GetEngineBuildnum(); - g_EngineDLLInfo.ImageBase = g_pMetaHookAPI->GetEngineBase(); + g_EngineDLLInfo.ImageBase = g_pMetaHookAPI->GetEngineBase(); - memcpy(&gEngfuncs, pEngfuncs, sizeof(gEngfuncs)); + memcpy(&gEngfuncs, pEngfuncs, sizeof(gEngfuncs)); - g_MainThreadId = (HANDLE)GetCurrentThreadId(); + g_MainThreadId = (HANDLE)GetCurrentThreadId(); - Engine_FillAddress(); + Engine_FillAddress(); - g_pMetaHookAPI->RegisterLoadDllNotificationCallback(DllLoadNotification); + g_pMetaHookAPI->RegisterLoadDllNotificationCallback(DllLoadNotification); } -void IPluginsV4::LoadClient(cl_exportfuncs_t *pExportFunc) +void IPluginsV4::LoadClient(cl_exportfuncs_t* pExportFunc) { - memcpy(&gExportfuncs, pExportFunc, sizeof(gExportfuncs)); + memcpy(&gExportfuncs, pExportFunc, sizeof(gExportfuncs)); - EngineCommand_InstallHook(); + EngineCommand_InstallHook(); } void IPluginsV4::ExitGame(int iResult) { - Engine_WaitForShutdown(g_pMetaHookAPI->GetEngineModule(), g_pMetaHookAPI->GetBlobEngineModule()); + Engine_WaitForShutdown(g_pMetaHookAPI->GetEngineModule(), g_pMetaHookAPI->GetBlobEngineModule()); } const char completeVersion[] = + { + BUILD_YEAR_CH0, BUILD_YEAR_CH1, BUILD_YEAR_CH2, BUILD_YEAR_CH3, + '-', + BUILD_MONTH_CH0, BUILD_MONTH_CH1, + '-', + BUILD_DAY_CH0, BUILD_DAY_CH1, + 'T', + BUILD_HOUR_CH0, BUILD_HOUR_CH1, + ':', + BUILD_MIN_CH0, BUILD_MIN_CH1, + ':', + BUILD_SEC_CH0, BUILD_SEC_CH1, + '\0'}; + +const char* IPluginsV4::GetVersion(void) { - BUILD_YEAR_CH0, BUILD_YEAR_CH1, BUILD_YEAR_CH2, BUILD_YEAR_CH3, - '-', - BUILD_MONTH_CH0, BUILD_MONTH_CH1, - '-', - BUILD_DAY_CH0, BUILD_DAY_CH1, - 'T', - BUILD_HOUR_CH0, BUILD_HOUR_CH1, - ':', - BUILD_MIN_CH0, BUILD_MIN_CH1, - ':', - BUILD_SEC_CH0, BUILD_SEC_CH1, - '\0' -}; - -const char *IPluginsV4::GetVersion(void) -{ - return completeVersion; + return completeVersion; } EXPOSE_SINGLE_INTERFACE(IPluginsV4, IPluginsV4, METAHOOK_PLUGIN_API_VERSION_V4); diff --git a/src/plugins.h b/src/plugins.h index f100ad2..ae4f28b 100644 --- a/src/plugins.h +++ b/src/plugins.h @@ -5,51 +5,62 @@ class IFileSystem; extern IFileSystem* g_pFileSystem; -extern int g_iEngineType; +extern int g_iEngineType; extern DWORD g_dwEngineBuildnum; extern mh_dll_info_t g_EngineDLLInfo; -#define MHPluginName "ThreadGuard" -#define Sys_Error(msg, ...) g_pMetaHookAPI->SysError("[" MHPluginName "] " msg, __VA_ARGS__); -#define Sig_NotFound(name) Sys_Error("Could not found: %s\nEngine buildnum: %d", #name, g_dwEngineBuildnum); -#define Sig_VarNotFound(name) if(!name) Sig_NotFound(name) -#define Sig_AddrNotFound(name) if(!addr) Sig_NotFound(name) -#define Sig_FuncNotFound(name) if(!gPrivateFuncs.name) Sig_NotFound(name) +#define MHPluginName "ThreadGuard" +#define Sys_Error(msg, ...) g_pMetaHookAPI->SysError("[" MHPluginName "] " msg, __VA_ARGS__); +#define Sig_NotFound(name) Sys_Error("Could not found: %s\nEngine buildnum: %d", #name, g_dwEngineBuildnum); +#define Sig_VarNotFound(name) \ + if (!name) Sig_NotFound(name) +#define Sig_AddrNotFound(name) \ + if (!addr) Sig_NotFound(name) +#define Sig_FuncNotFound(name) \ + if (!gPrivateFuncs.name) Sig_NotFound(name) //Resolve a required gamedata symbol; a missing symbol is fatal, mirroring the //Sig_VarNotFound policy of the signature-scan locators. //moduleName identifies the module owning the symbol and is echoed by the diagnostic. inline PVOID GamedataResolvePtr(PVOID moduleBase, const char* moduleName, const char* symbolName, mh_gamesymbol_kind_t kind) { - PVOID address = nullptr; - mh_gamesymbol_status_t status = g_pMetaHookAPI->ResolveGameSymbol(moduleBase, symbolName, kind, &address); + PVOID address = nullptr; + mh_gamesymbol_status_t status = g_pMetaHookAPI->ResolveGameSymbol(moduleBase, symbolName, kind, &address); - if (status != MH_GAMESYMBOL_OK) - { - Sys_Error("Could not resolve gamedata symbol: %s (module %s, %s)\nEngine buildnum: %d", - symbolName, moduleName, g_pMetaHookAPI->GetGameSymbolStatusString(status), g_dwEngineBuildnum); - } + if (status != MH_GAMESYMBOL_OK) + { + Sys_Error("Could not resolve gamedata symbol: %s (module %s, %s)\nEngine buildnum: %d", + symbolName, moduleName, g_pMetaHookAPI->GetGameSymbolStatusString(status), g_dwEngineBuildnum); + } - return address; + return address; } -#define Sig_Length(a) (sizeof(a)-1) -#define Search_Pattern(sig, dllinfo) g_pMetaHookAPI->SearchPattern(dllinfo.TextBase, dllinfo.TextSize, sig, Sig_Length(sig)) -#define Search_Pattern_Data(sig, dllinfo) g_pMetaHookAPI->SearchPattern(dllinfo.DataBase, dllinfo.DataSize, sig, Sig_Length(sig)) -#define Search_Pattern_Rdata(sig, dllinfo) g_pMetaHookAPI->SearchPattern(dllinfo.RdataBase, dllinfo.RdataSize, sig, Sig_Length(sig)) -#define Search_Pattern_From_Size(fn, size, sig) g_pMetaHookAPI->SearchPattern((void *)(fn), size, sig, Sig_Length(sig)) -#define Search_Pattern_From(fn, sig, dllinfo) g_pMetaHookAPI->SearchPattern((void *)(fn), ((PUCHAR)dllinfo.TextBase + dllinfo.TextSize) - (PUCHAR)(fn), sig, Sig_Length(sig)) +#define Sig_Length(a) (sizeof(a) - 1) +#define Search_Pattern(sig, dllinfo) g_pMetaHookAPI->SearchPattern(dllinfo.TextBase, dllinfo.TextSize, sig, Sig_Length(sig)) +#define Search_Pattern_Data(sig, dllinfo) g_pMetaHookAPI->SearchPattern(dllinfo.DataBase, dllinfo.DataSize, sig, Sig_Length(sig)) +#define Search_Pattern_Rdata(sig, dllinfo) g_pMetaHookAPI->SearchPattern(dllinfo.RdataBase, dllinfo.RdataSize, sig, Sig_Length(sig)) +#define Search_Pattern_From_Size(fn, size, sig) g_pMetaHookAPI->SearchPattern((void*)(fn), size, sig, Sig_Length(sig)) +#define Search_Pattern_From(fn, sig, dllinfo) g_pMetaHookAPI->SearchPattern((void*)(fn), ((PUCHAR)dllinfo.TextBase + dllinfo.TextSize) - (PUCHAR)(fn), sig, Sig_Length(sig)) -#define Search_Pattern_NoWildCard(sig, dllinfo) g_pMetaHookAPI->SearchPatternNoWildCard(dllinfo.TextBase, dllinfo.TextSize, sig, Sig_Length(sig)) -#define Search_Pattern_NoWildCard_Data(sig, dllinfo) g_pMetaHookAPI->SearchPatternNoWildCard(dllinfo.DataBase, dllinfo.DataSize, sig, Sig_Length(sig)) +#define Search_Pattern_NoWildCard(sig, dllinfo) g_pMetaHookAPI->SearchPatternNoWildCard(dllinfo.TextBase, dllinfo.TextSize, sig, Sig_Length(sig)) +#define Search_Pattern_NoWildCard_Data(sig, dllinfo) g_pMetaHookAPI->SearchPatternNoWildCard(dllinfo.DataBase, dllinfo.DataSize, sig, Sig_Length(sig)) #define Search_Pattern_NoWildCard_Rdata(sig, dllinfo) g_pMetaHookAPI->SearchPatternNoWildCard(dllinfo.RdataBase, dllinfo.RdataSize, sig, Sig_Length(sig)) -#define Install_InlineHook(fn) if(!g_phook_##fn) { g_phook_##fn = g_pMetaHookAPI->InlineHook((void *)gPrivateFuncs.fn, fn, (void **)&gPrivateFuncs.fn); } -#define Uninstall_Hook(fn) if(g_phook_##fn){g_pMetaHookAPI->UnHook(g_phook_##fn);g_phook_##fn = NULL;} +#define Install_InlineHook(fn) \ + if (!g_phook_##fn) { g_phook_##fn = g_pMetaHookAPI->InlineHook((void*)gPrivateFuncs.fn, fn, (void**)&gPrivateFuncs.fn); } +#define Uninstall_Hook(fn) \ + if (g_phook_##fn) \ + { \ + g_pMetaHookAPI->UnHook(g_phook_##fn); \ + g_phook_##fn = NULL; \ + } #define GetCallAddress(addr) g_pMetaHookAPI->GetNextCallAddr((PUCHAR)addr, 1) #define RVA_from_VA(name, dllinfo) (ULONG)((ULONG_PTR)name##_VA - (ULONG_PTR)dllinfo.ImageBase) #define VA_from_RVA(name, dllinfo) ((ULONG_PTR)dllinfo.ImageBase + (ULONG_PTR)name##_RVA) -#define Convert_VA_to_RVA(name, dllinfo) if(name##_VA) name##_RVA = ((ULONG_PTR)name##_VA - (ULONG_PTR)dllinfo.ImageBase) -#define Convert_RVA_to_VA(name, dllinfo) if(name##_RVA) name##_VA = (decltype(name##_VA))VA_from_RVA(name, dllinfo) +#define Convert_VA_to_RVA(name, dllinfo) \ + if (name##_VA) name##_RVA = ((ULONG_PTR)name##_VA - (ULONG_PTR)dllinfo.ImageBase) +#define Convert_RVA_to_VA(name, dllinfo) \ + if (name##_RVA) name##_VA = (decltype(name##_VA))VA_from_RVA(name, dllinfo) diff --git a/src/privatehook.cpp b/src/privatehook.cpp index 284a556..56744da 100644 --- a/src/privatehook.cpp +++ b/src/privatehook.cpp @@ -11,33 +11,33 @@ static_assert(METAHOOK_API_VERSION >= 109, "ThreadGuard resolves the engine IEng hook_t* g_pHook_FreeLibrary_Engine = NULL; hook_t* g_pHook_FreeLibrary_GameUI = NULL; -static hook_t* g_pHook_SteamAPI_Shutdown = NULL; -static hook_t* g_pHook_GL_Shutdown = NULL; -static void (__cdecl* g_pfn_SteamAPI_Shutdown)(void) = NULL; -static void (__cdecl* g_pfn_GL_Shutdown)(HWND, HDC, HGLRC) = NULL; -static bool g_bSteamShutdown = false; +static hook_t* g_pHook_SteamAPI_Shutdown = NULL; +static hook_t* g_pHook_GL_Shutdown = NULL; +static void(__cdecl* g_pfn_SteamAPI_Shutdown)(void) = NULL; +static void(__cdecl* g_pfn_GL_Shutdown)(HWND, HDC, HGLRC) = NULL; +static bool g_bSteamShutdown = false; static void __cdecl NewSteamAPI_Shutdown(void) { - // SvEngine calls this too late on quit and skips it entirely on _restart. - // The GL_Shutdown hook owns the call for both paths. + // SvEngine calls this too late on quit and skips it entirely on _restart. + // The GL_Shutdown hook owns the call for both paths. } static void __cdecl NewGL_Shutdown(HWND window, HDC dc, HGLRC context) { - if (!g_bSteamShutdown) - { - g_bSteamShutdown = true; - g_pfn_SteamAPI_Shutdown(); - } + if (!g_bSteamShutdown) + { + g_bSteamShutdown = true; + g_pfn_SteamAPI_Shutdown(); + } - g_pfn_GL_Shutdown(window, dc, context); + g_pfn_GL_Shutdown(window, dc, context); } -IThreadManager* g_ThreadManager_Engine = NULL; -IThreadManager* g_ThreadManager_GameUI = NULL; +IThreadManager* g_ThreadManager_Engine = NULL; +IThreadManager* g_ThreadManager_GameUI = NULL; IThreadManager* g_ThreadManager_ServerBrowser = NULL; -IThreadManager* g_ThreadManager_ServerDLL = NULL; +IThreadManager* g_ThreadManager_ServerDLL = NULL; IEngine** eng = NULL; @@ -47,290 +47,290 @@ void ServerBrowser_WaitForShutdown(HMODULE hModule); int GetEngineDLLState() { - if (eng) - return (*eng)->GetState(); + if (eng) + return (*eng)->GetState(); - return DLL_INACTIVE; + return DLL_INACTIVE; } BOOL WINAPI NewFreeLibrary_Engine(HMODULE hModule) { - if (g_ThreadManager_GameUI && g_ThreadManager_GameUI->GetModule() == hModule) - { - GameUI_WaitForShutdown(hModule); - } - if (g_ThreadManager_ServerDLL && g_ThreadManager_ServerDLL->GetModule() == hModule) - { - ServerDLL_WaitForShutdown(hModule); - } - - return FreeLibrary(hModule); + if (g_ThreadManager_GameUI && g_ThreadManager_GameUI->GetModule() == hModule) + { + GameUI_WaitForShutdown(hModule); + } + if (g_ThreadManager_ServerDLL && g_ThreadManager_ServerDLL->GetModule() == hModule) + { + ServerDLL_WaitForShutdown(hModule); + } + + return FreeLibrary(hModule); } BOOL WINAPI NewFreeLibrary_GameUI(HMODULE hModule) { - if (g_ThreadManager_ServerBrowser && g_ThreadManager_ServerBrowser->GetModule() == hModule) - { - ServerBrowser_WaitForShutdown(hModule); - } + if (g_ThreadManager_ServerBrowser && g_ThreadManager_ServerBrowser->GetModule() == hModule) + { + ServerBrowser_WaitForShutdown(hModule); + } - return FreeLibrary(hModule); + return FreeLibrary(hModule); } void Engine_WaitForShutdown(HMODULE hModule, BlobHandle_t hBlobModule) { - // ExitGame runs after EngineAPI::Run returns and before the engine's CRT - // detach. CEngine::Unload may already have reset GetState() to DLL_INACTIVE; - // the lifecycle callback itself requires joining every remaining worker. - if (g_ThreadManager_Engine) - { - g_ThreadManager_Engine->StartTermination(); - g_ThreadManager_Engine->WaitForAliveThreadsToShutdown(); - } + // ExitGame runs after EngineAPI::Run returns and before the engine's CRT + // detach. CEngine::Unload may already have reset GetState() to DLL_INACTIVE; + // the lifecycle callback itself requires joining every remaining worker. + if (g_ThreadManager_Engine) + { + g_ThreadManager_Engine->StartTermination(); + g_ThreadManager_Engine->WaitForAliveThreadsToShutdown(); + } } void Engine_FillAddress(void) { - // gamedata provides the address of the engine module's global IEngine* slot, - // so GetEngineDLLState keeps dereferencing it exactly once. - eng = (decltype(eng))GamedataResolvePtr(g_EngineDLLInfo.ImageBase, "engine", "eng", MH_GAMESYMBOL_KIND_GLOBAL); - // Match the actual CreateThread entry, including engines where NET_StartThread - // is inlined (HL25) or shared as a tail block (old HL/BLOB). - auto networkEntry = (LPTHREAD_START_ROUTINE)GamedataResolvePtr( - g_EngineDLLInfo.ImageBase, "engine", "NET_ThreadFunc", MH_GAMESYMBOL_KIND_FUNCTION); - auto networkId = (DWORD*)GamedataResolvePtr( - g_EngineDLLInfo.ImageBase, "engine", "dwNetThreadId", MH_GAMESYMBOL_KIND_GLOBAL); - auto queuePacket = GamedataResolvePtr( - g_EngineDLLInfo.ImageBase, "engine", "NET_QueuePacket", MH_GAMESYMBOL_KIND_FUNCTION); - auto shutdown = GamedataResolvePtr( - g_EngineDLLInfo.ImageBase, "engine", "NET_Shutdown", MH_GAMESYMBOL_KIND_FUNCTION); - NetworkThread_Configure(networkEntry, networkId, queuePacket, shutdown); - if (g_iEngineType == ENGINE_SVENGINE) - { - g_pfn_GL_Shutdown = (decltype(g_pfn_GL_Shutdown))GamedataResolvePtr( - g_EngineDLLInfo.ImageBase, "engine", "GL_Shutdown", MH_GAMESYMBOL_KIND_FUNCTION); - } + // gamedata provides the address of the engine module's global IEngine* slot, + // so GetEngineDLLState keeps dereferencing it exactly once. + eng = (decltype(eng))GamedataResolvePtr(g_EngineDLLInfo.ImageBase, "engine", "eng", MH_GAMESYMBOL_KIND_GLOBAL); + // Match the actual CreateThread entry, including engines where NET_StartThread + // is inlined (HL25) or shared as a tail block (old HL/BLOB). + auto networkEntry = (LPTHREAD_START_ROUTINE)GamedataResolvePtr( + g_EngineDLLInfo.ImageBase, "engine", "NET_ThreadFunc", MH_GAMESYMBOL_KIND_FUNCTION); + auto networkId = (DWORD*)GamedataResolvePtr( + g_EngineDLLInfo.ImageBase, "engine", "dwNetThreadId", MH_GAMESYMBOL_KIND_GLOBAL); + auto queuePacket = GamedataResolvePtr( + g_EngineDLLInfo.ImageBase, "engine", "NET_QueuePacket", MH_GAMESYMBOL_KIND_FUNCTION); + auto shutdown = GamedataResolvePtr( + g_EngineDLLInfo.ImageBase, "engine", "NET_Shutdown", MH_GAMESYMBOL_KIND_FUNCTION); + NetworkThread_Configure(networkEntry, networkId, queuePacket, shutdown); + if (g_iEngineType == ENGINE_SVENGINE) + { + g_pfn_GL_Shutdown = (decltype(g_pfn_GL_Shutdown))GamedataResolvePtr( + g_EngineDLLInfo.ImageBase, "engine", "GL_Shutdown", MH_GAMESYMBOL_KIND_FUNCTION); + } } void Engine_InstallHook(HMODULE hModule, BlobHandle_t hBlobModule) { - if (hModule) - { - if (g_iEngineType == ENGINE_SVENGINE) - { - g_bSteamShutdown = false; - g_pHook_SteamAPI_Shutdown = g_pMetaHookAPI->IATHook(hModule, - "steam_api.dll", "SteamAPI_Shutdown", NewSteamAPI_Shutdown, (void**)&g_pfn_SteamAPI_Shutdown); - // During LoadEngine, MetaHook fills the original pointer at transaction commit. - if (!g_pHook_SteamAPI_Shutdown) - { - Sys_Error("Could not hook engine SteamAPI_Shutdown import!"); - return; - } - g_pHook_GL_Shutdown = g_pMetaHookAPI->InlineHook((void*)g_pfn_GL_Shutdown, - NewGL_Shutdown, (void**)&g_pfn_GL_Shutdown); - if (!g_pHook_GL_Shutdown) - { - g_pMetaHookAPI->UnHook(g_pHook_SteamAPI_Shutdown); - g_pHook_SteamAPI_Shutdown = NULL; - Sys_Error("Could not hook engine GL_Shutdown!"); - return; - } - } - - g_ThreadManager_Engine = CreateThreadManagerForModule(hModule); - g_ThreadManager_Engine->InstallHook(hookflag_CreateThread | hookflag_WaitForSingleObject | hookflag_Sleep); - - g_pHook_FreeLibrary_Engine = g_pMetaHookAPI->IATHook(hModule, "kernel32.dll", "FreeLibrary", NewFreeLibrary_Engine, NULL); - } - else if (hBlobModule) - { - g_ThreadManager_Engine = CreateThreadManagerForBlob(hBlobModule); - g_ThreadManager_Engine->InstallHook(hookflag_CreateThread | hookflag_WaitForSingleObject | hookflag_Sleep); - g_pHook_FreeLibrary_Engine = g_pMetaHookAPI->BlobIATHook(hBlobModule, "kernel32.dll", "FreeLibrary", NewFreeLibrary_Engine, NULL); - } - if (g_ThreadManager_Engine) - { - if (!g_pHook_FreeLibrary_Engine) - Sys_Error("Could not install engine FreeLibrary hook!"); - NetworkThread_InstallHook(g_ThreadManager_Engine); - } + if (hModule) + { + if (g_iEngineType == ENGINE_SVENGINE) + { + g_bSteamShutdown = false; + g_pHook_SteamAPI_Shutdown = g_pMetaHookAPI->IATHook(hModule, + "steam_api.dll", "SteamAPI_Shutdown", NewSteamAPI_Shutdown, (void**)&g_pfn_SteamAPI_Shutdown); + // During LoadEngine, MetaHook fills the original pointer at transaction commit. + if (!g_pHook_SteamAPI_Shutdown) + { + Sys_Error("Could not hook engine SteamAPI_Shutdown import!"); + return; + } + g_pHook_GL_Shutdown = g_pMetaHookAPI->InlineHook((void*)g_pfn_GL_Shutdown, + NewGL_Shutdown, (void**)&g_pfn_GL_Shutdown); + if (!g_pHook_GL_Shutdown) + { + g_pMetaHookAPI->UnHook(g_pHook_SteamAPI_Shutdown); + g_pHook_SteamAPI_Shutdown = NULL; + Sys_Error("Could not hook engine GL_Shutdown!"); + return; + } + } + + g_ThreadManager_Engine = CreateThreadManagerForModule(hModule); + g_ThreadManager_Engine->InstallHook(hookflag_CreateThread | hookflag_WaitForSingleObject | hookflag_Sleep); + + g_pHook_FreeLibrary_Engine = g_pMetaHookAPI->IATHook(hModule, "kernel32.dll", "FreeLibrary", NewFreeLibrary_Engine, NULL); + } + else if (hBlobModule) + { + g_ThreadManager_Engine = CreateThreadManagerForBlob(hBlobModule); + g_ThreadManager_Engine->InstallHook(hookflag_CreateThread | hookflag_WaitForSingleObject | hookflag_Sleep); + g_pHook_FreeLibrary_Engine = g_pMetaHookAPI->BlobIATHook(hBlobModule, "kernel32.dll", "FreeLibrary", NewFreeLibrary_Engine, NULL); + } + if (g_ThreadManager_Engine) + { + if (!g_pHook_FreeLibrary_Engine) + Sys_Error("Could not install engine FreeLibrary hook!"); + NetworkThread_InstallHook(g_ThreadManager_Engine); + } } void Engine_UninstallHook(HMODULE hModule, BlobHandle_t hBlobModule) { - NetworkThread_UninstallHook(); - eng = NULL; - if (g_pHook_GL_Shutdown) - { - g_pMetaHookAPI->UnHook(g_pHook_GL_Shutdown); - g_pHook_GL_Shutdown = NULL; - } - if (g_pHook_SteamAPI_Shutdown) - { - g_pMetaHookAPI->UnHook(g_pHook_SteamAPI_Shutdown); - g_pHook_SteamAPI_Shutdown = NULL; - } - g_pfn_GL_Shutdown = NULL; - g_pfn_SteamAPI_Shutdown = NULL; - g_bSteamShutdown = false; - - if (g_pHook_FreeLibrary_Engine) - { - g_pMetaHookAPI->UnHook(g_pHook_FreeLibrary_Engine); - g_pHook_FreeLibrary_Engine = NULL; - } - - if (g_ThreadManager_Engine) - { - g_ThreadManager_Engine->UnistallHook(); - DeleteThreadManager(g_ThreadManager_Engine); - delete g_ThreadManager_Engine; - g_ThreadManager_Engine = NULL; - } + NetworkThread_UninstallHook(); + eng = NULL; + if (g_pHook_GL_Shutdown) + { + g_pMetaHookAPI->UnHook(g_pHook_GL_Shutdown); + g_pHook_GL_Shutdown = NULL; + } + if (g_pHook_SteamAPI_Shutdown) + { + g_pMetaHookAPI->UnHook(g_pHook_SteamAPI_Shutdown); + g_pHook_SteamAPI_Shutdown = NULL; + } + g_pfn_GL_Shutdown = NULL; + g_pfn_SteamAPI_Shutdown = NULL; + g_bSteamShutdown = false; + + if (g_pHook_FreeLibrary_Engine) + { + g_pMetaHookAPI->UnHook(g_pHook_FreeLibrary_Engine); + g_pHook_FreeLibrary_Engine = NULL; + } + + if (g_ThreadManager_Engine) + { + g_ThreadManager_Engine->UnistallHook(); + DeleteThreadManager(g_ThreadManager_Engine); + delete g_ThreadManager_Engine; + g_ThreadManager_Engine = NULL; + } } void GameUI_InstallHook(HMODULE hModule) { - //It use callback instead of creating stupid thread. - if (g_pMetaHookAPI->ModuleHasImport(hModule, "steam_api.dll") && !g_pMetaHookAPI->ModuleHasImportEx(hModule, "kernel32.dll", "CreateThread")) - return; + //It use callback instead of creating stupid thread. + if (g_pMetaHookAPI->ModuleHasImport(hModule, "steam_api.dll") && !g_pMetaHookAPI->ModuleHasImportEx(hModule, "kernel32.dll", "CreateThread")) + return; - g_ThreadManager_GameUI = CreateThreadManagerForModule(hModule); - g_ThreadManager_GameUI->InstallHook(hookflag_CreateThread | hookflag_WaitForSingleObject | hookflag_Socket); + g_ThreadManager_GameUI = CreateThreadManagerForModule(hModule); + g_ThreadManager_GameUI->InstallHook(hookflag_CreateThread | hookflag_WaitForSingleObject | hookflag_Socket); - g_pHook_FreeLibrary_GameUI = g_pMetaHookAPI->IATHook(hModule, "kernel32.dll", "FreeLibrary", NewFreeLibrary_GameUI, NULL); + g_pHook_FreeLibrary_GameUI = g_pMetaHookAPI->IATHook(hModule, "kernel32.dll", "FreeLibrary", NewFreeLibrary_GameUI, NULL); } void GameUI_WaitForShutdown(HMODULE hModule) { - // Join before FreeLibrary enters the loader lock and the socket destructor. - if (g_ThreadManager_GameUI && (GetEngineDLLState() == DLL_CLOSE || GetEngineDLLState() == DLL_RESTART)) - { - g_ThreadManager_GameUI->StartTermination(); - g_ThreadManager_GameUI->WaitForAliveThreadsToShutdown(); - // GameUI's console may already be shut down; keep teardown diagnostics - // independent of engine/VGUI callbacks, even on the main thread. - OutputDebugStringA("[ThreadGuard] GameUI: threads stopped before FreeLibrary\n"); - } + // Join before FreeLibrary enters the loader lock and the socket destructor. + if (g_ThreadManager_GameUI && (GetEngineDLLState() == DLL_CLOSE || GetEngineDLLState() == DLL_RESTART)) + { + g_ThreadManager_GameUI->StartTermination(); + g_ThreadManager_GameUI->WaitForAliveThreadsToShutdown(); + // GameUI's console may already be shut down; keep teardown diagnostics + // independent of engine/VGUI callbacks, even on the main thread. + OutputDebugStringA("[ThreadGuard] GameUI: threads stopped before FreeLibrary\n"); + } } void GameUI_UnistallHook(HMODULE hModule) { - if (g_pHook_FreeLibrary_GameUI) - { - g_pMetaHookAPI->UnHook(g_pHook_FreeLibrary_GameUI); - g_pHook_FreeLibrary_GameUI = NULL; - } - - if (g_ThreadManager_GameUI) - { - g_ThreadManager_GameUI->UnistallHook(); - DeleteThreadManager(g_ThreadManager_GameUI); - delete g_ThreadManager_GameUI; - g_ThreadManager_GameUI = NULL; - } + if (g_pHook_FreeLibrary_GameUI) + { + g_pMetaHookAPI->UnHook(g_pHook_FreeLibrary_GameUI); + g_pHook_FreeLibrary_GameUI = NULL; + } + + if (g_ThreadManager_GameUI) + { + g_ThreadManager_GameUI->UnistallHook(); + DeleteThreadManager(g_ThreadManager_GameUI); + delete g_ThreadManager_GameUI; + g_ThreadManager_GameUI = NULL; + } } void ServerDLL_WaitForShutdown(HMODULE hModule) { - if (g_ThreadManager_ServerDLL && (GetEngineDLLState() == DLL_CLOSE || GetEngineDLLState() == DLL_RESTART)) - { - g_ThreadManager_ServerDLL->StartTermination(); - g_ThreadManager_ServerDLL->WaitForAliveThreadsToShutdown(); - } + if (g_ThreadManager_ServerDLL && (GetEngineDLLState() == DLL_CLOSE || GetEngineDLLState() == DLL_RESTART)) + { + g_ThreadManager_ServerDLL->StartTermination(); + g_ThreadManager_ServerDLL->WaitForAliveThreadsToShutdown(); + } } void ServerDLL_InstallHook(HMODULE hModule) { - //It use callback instead of creating stupid thread. - if (0 != stricmp(g_pMetaHookAPI->GetGameDirectory(), "svencoop")) - return; + //It use callback instead of creating stupid thread. + if (0 != stricmp(g_pMetaHookAPI->GetGameDirectory(), "svencoop")) + return; - //Fuck off the CPlayerDatabase_RunThread - g_ThreadManager_ServerDLL = CreateThreadManagerForModule(hModule); - g_ThreadManager_ServerDLL->InstallHook(hookflag_CreateThread); + //Fuck off the CPlayerDatabase_RunThread + g_ThreadManager_ServerDLL = CreateThreadManagerForModule(hModule); + g_ThreadManager_ServerDLL->InstallHook(hookflag_CreateThread); } void ServerDLL_UninstallHook(HMODULE hModule) { - if (g_ThreadManager_ServerDLL) - { - g_ThreadManager_ServerDLL->UnistallHook(); - DeleteThreadManager(g_ThreadManager_ServerDLL); - delete g_ThreadManager_ServerDLL; - g_ThreadManager_ServerDLL = NULL; - } + if (g_ThreadManager_ServerDLL) + { + g_ThreadManager_ServerDLL->UnistallHook(); + DeleteThreadManager(g_ThreadManager_ServerDLL); + delete g_ThreadManager_ServerDLL; + g_ThreadManager_ServerDLL = NULL; + } } void ServerBrowser_WaitForShutdown(HMODULE hModule) { - if (g_ThreadManager_ServerBrowser && (GetEngineDLLState() == DLL_CLOSE || GetEngineDLLState() == DLL_RESTART)) - { - g_ThreadManager_ServerBrowser->StartTermination(); - g_ThreadManager_ServerBrowser->WaitForAliveThreadsToShutdown(); - } + if (g_ThreadManager_ServerBrowser && (GetEngineDLLState() == DLL_CLOSE || GetEngineDLLState() == DLL_RESTART)) + { + g_ThreadManager_ServerBrowser->StartTermination(); + g_ThreadManager_ServerBrowser->WaitForAliveThreadsToShutdown(); + } } void ServerBrowser_InstallHook(HMODULE hModule) { - //It use callback instead of creating stupid thread. - if (g_pMetaHookAPI->ModuleHasImport(hModule, "steam_api.dll") && !g_pMetaHookAPI->ModuleHasImportEx(hModule, "kernel32.dll", "CreateThread")) - return; + //It use callback instead of creating stupid thread. + if (g_pMetaHookAPI->ModuleHasImport(hModule, "steam_api.dll") && !g_pMetaHookAPI->ModuleHasImportEx(hModule, "kernel32.dll", "CreateThread")) + return; - g_ThreadManager_ServerBrowser = CreateThreadManagerForModule(hModule); - g_ThreadManager_ServerBrowser->InstallHook(hookflag_CreateThread | hookflag_WaitForSingleObject | hookflag_Socket); + g_ThreadManager_ServerBrowser = CreateThreadManagerForModule(hModule); + g_ThreadManager_ServerBrowser->InstallHook(hookflag_CreateThread | hookflag_WaitForSingleObject | hookflag_Socket); } void ServerBrowser_UninstallHook(HMODULE hModule) { - if (g_ThreadManager_ServerBrowser) - { - g_ThreadManager_ServerBrowser->UnistallHook(); - DeleteThreadManager(g_ThreadManager_ServerBrowser); - delete g_ThreadManager_ServerBrowser; - g_ThreadManager_ServerBrowser = NULL; - } + if (g_ThreadManager_ServerBrowser) + { + g_ThreadManager_ServerBrowser->UnistallHook(); + DeleteThreadManager(g_ThreadManager_ServerBrowser); + delete g_ThreadManager_ServerBrowser; + g_ThreadManager_ServerBrowser = NULL; + } } void DllLoadNotification(mh_load_dll_notification_context_t* ctx) { - if (ctx->flags & LOAD_DLL_NOTIFICATION_IS_LOAD) - { - if (ctx->flags & LOAD_DLL_NOTIFICATION_IS_ENGINE) - { - Engine_InstallHook(ctx->hModule, ctx->hBlob); - } - else if (ctx->BaseDllName && ctx->hModule && !_wcsicmp(ctx->BaseDllName, L"GameUI.dll")) - { - GameUI_InstallHook(ctx->hModule); - } - else if (ctx->BaseDllName && ctx->hModule && !_wcsicmp(ctx->BaseDllName, L"ServerBrowser.dll")) - { - ServerBrowser_InstallHook(ctx->hModule); - } - else if (ctx->BaseDllName && ctx->hModule && !_wcsicmp(ctx->BaseDllName, L"server.dll")) - { - ServerDLL_InstallHook(ctx->hModule); - } - } - else if (ctx->flags & LOAD_DLL_NOTIFICATION_IS_UNLOAD) - { - if (ctx->flags & LOAD_DLL_NOTIFICATION_IS_ENGINE) - { - Engine_UninstallHook(ctx->hModule, ctx->hBlob); - } - else if (g_ThreadManager_GameUI && ctx->hModule == g_ThreadManager_GameUI->GetModule()) - { - GameUI_UnistallHook(ctx->hModule); - } - else if (g_ThreadManager_ServerBrowser && ctx->hModule == g_ThreadManager_ServerBrowser->GetModule()) - { - ServerBrowser_UninstallHook(ctx->hModule); - } - else if (g_ThreadManager_ServerDLL && ctx->hModule == g_ThreadManager_ServerDLL->GetModule()) - { - ServerDLL_UninstallHook(ctx->hModule); - } - } + if (ctx->flags & LOAD_DLL_NOTIFICATION_IS_LOAD) + { + if (ctx->flags & LOAD_DLL_NOTIFICATION_IS_ENGINE) + { + Engine_InstallHook(ctx->hModule, ctx->hBlob); + } + else if (ctx->BaseDllName && ctx->hModule && !_wcsicmp(ctx->BaseDllName, L"GameUI.dll")) + { + GameUI_InstallHook(ctx->hModule); + } + else if (ctx->BaseDllName && ctx->hModule && !_wcsicmp(ctx->BaseDllName, L"ServerBrowser.dll")) + { + ServerBrowser_InstallHook(ctx->hModule); + } + else if (ctx->BaseDllName && ctx->hModule && !_wcsicmp(ctx->BaseDllName, L"server.dll")) + { + ServerDLL_InstallHook(ctx->hModule); + } + } + else if (ctx->flags & LOAD_DLL_NOTIFICATION_IS_UNLOAD) + { + if (ctx->flags & LOAD_DLL_NOTIFICATION_IS_ENGINE) + { + Engine_UninstallHook(ctx->hModule, ctx->hBlob); + } + else if (g_ThreadManager_GameUI && ctx->hModule == g_ThreadManager_GameUI->GetModule()) + { + GameUI_UnistallHook(ctx->hModule); + } + else if (g_ThreadManager_ServerBrowser && ctx->hModule == g_ThreadManager_ServerBrowser->GetModule()) + { + ServerBrowser_UninstallHook(ctx->hModule); + } + else if (g_ThreadManager_ServerDLL && ctx->hModule == g_ThreadManager_ServerDLL->GetModule()) + { + ServerDLL_UninstallHook(ctx->hModule); + } + } } diff --git a/tests/network_hooks_test.cpp b/tests/network_hooks_test.cpp index 2f17cae..65462e7 100644 --- a/tests/network_hooks_test.cpp +++ b/tests/network_hooks_test.cpp @@ -12,448 +12,474 @@ static BOOL WINAPI TestFreeLibrary(HMODULE module); #include "../src/privatehook.cpp" #undef FreeLibrary -metahook_api_t* g_pMetaHookAPI = NULL; -HANDLE g_MainThreadId = NULL; -int g_iEngineType = 0; -DWORD g_dwEngineBuildnum = 0; -mh_dll_info_t g_EngineDLLInfo = {}; +metahook_api_t* g_pMetaHookAPI = NULL; +HANDLE g_MainThreadId = NULL; +int g_iEngineType = 0; +DWORD g_dwEngineBuildnum = 0; +mh_dll_info_t g_EngineDLLInfo = {}; -#define CHECK(condition) do { if (!(condition)) { std::fprintf(stderr, "FAIL line %d: %s\n", __LINE__, #condition); std::abort(); } } while (0) +#define CHECK(condition) \ + do \ + { \ + if (!(condition)) \ + { \ + std::fprintf(stderr, "FAIL line %d: %s\n", __LINE__, #condition); \ + std::abort(); \ + } \ + } while (0) static void Fatal(const char* format, ...) { - va_list args; - va_start(args, format); - vfprintf(stderr, format, args); - va_end(args); - std::abort(); + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + std::abort(); } class TestManager : public IThreadManager { public: - std::vector handles; - std::atomic stopping{ false }; - HMODULE module = NULL; - unsigned joined = 0; - void StartTermination() override { stopping = true; } - void WaitForAliveThreadsToShutdown() override { CHECK(stopping); ++joined; } - void OnCreateThread(HANDLE thread) override { handles.push_back(thread); } - bool OnWaitForSingleObject(HANDLE, DWORD) override { return stopping; } - bool OnSleep(DWORD) override { return stopping && (HANDLE)GetCurrentThreadId() != g_MainThreadId; } - void InstallHook(int) override {} - void UnistallHook() override {} - PVOID GetImageBase() const override { return NULL; } - ULONG GetImageSize() const override { return 0; } - PVOID GetTextBase() const override { return NULL; } - ULONG GetTextSize() const override { return MAXDWORD; } - HMODULE GetModule() const override { return module; } - BlobHandle_t GetBlobModule() const override { return NULL; } - void CloseDuplicates() - { - for (HANDLE h : handles) { CHECK(WAIT_OBJECT_0 == WaitForSingleObject(h, 0)); CloseHandle(h); } - handles.clear(); - } + std::vector handles; + std::atomic stopping{false}; + HMODULE module = NULL; + unsigned joined = 0; + void StartTermination() override { stopping = true; } + void WaitForAliveThreadsToShutdown() override + { + CHECK(stopping); + ++joined; + } + void OnCreateThread(HANDLE thread) override { handles.push_back(thread); } + bool OnWaitForSingleObject(HANDLE, DWORD) override { return stopping; } + bool OnSleep(DWORD) override { return stopping && (HANDLE)GetCurrentThreadId() != g_MainThreadId; } + void InstallHook(int) override {} + void UnistallHook() override {} + PVOID GetImageBase() const override { return NULL; } + ULONG GetImageSize() const override { return 0; } + PVOID GetTextBase() const override { return NULL; } + ULONG GetTextSize() const override { return MAXDWORD; } + HMODULE GetModule() const override { return module; } + BlobHandle_t GetBlobModule() const override { return NULL; } + void CloseDuplicates() + { + for (HANDLE h : handles) + { + CHECK(WAIT_OBJECT_0 == WaitForSingleObject(h, 0)); + CloseHandle(h); + } + handles.clear(); + } }; class TestEngine : public IEngine { public: - int state = DLL_ACTIVE; - bool Load(bool, char*, char*) override { return true; } - void Unload() override {} - void SetState(int value) override { state = value; } - int GetState() override { return state; } - void SetSubState(int) override {} - int GetSubState() override { return 0; } - int Frame() override { return 0; } - double GetFrameTime() override { return 0; } - double GetCurTime() override { return 0; } - void TrapKey_Event(int, bool) override {} - void TrapMouse_Event(int, bool) override {} - void StartTrapMode() override {} - bool IsTrapping() override { return false; } - bool CheckDoneTrapping(int&, int&) override { return false; } - int GetQuitting() override { return 0; } - void SetQuitting(int) override {} + int state = DLL_ACTIVE; + bool Load(bool, char*, char*) override { return true; } + void Unload() override {} + void SetState(int value) override { state = value; } + int GetState() override { return state; } + void SetSubState(int) override {} + int GetSubState() override { return 0; } + int Frame() override { return 0; } + double GetFrameTime() override { return 0; } + double GetCurTime() override { return 0; } + void TrapKey_Event(int, bool) override {} + void TrapMouse_Event(int, bool) override {} + void StartTrapMode() override {} + bool IsTrapping() override { return false; } + bool CheckDoneTrapping(int&, int&) override { return false; } + int GetQuitting() override { return 0; } + void SetQuitting(int) override {} }; static TestManager* unloadManager; -static HMODULE expectedModule; -static unsigned expectedJoins; -static unsigned unloadCalls; -static BOOL WINAPI TestFreeLibrary(HMODULE module) +static HMODULE expectedModule; +static unsigned expectedJoins; +static unsigned unloadCalls; +static BOOL WINAPI TestFreeLibrary(HMODULE module) { - CHECK(expectedModule == module); - CHECK(expectedJoins == unloadManager->joined); - ++unloadCalls; - return TRUE; + CHECK(expectedModule == module); + CHECK(expectedJoins == unloadManager->joined); + ++unloadCalls; + return TRUE; } static void CheckUnloadRouting() { - TestEngine engine; - IEngine* enginePointer = &engine; - eng = &enginePointer; - TestManager manager; - manager.module = (HMODULE)0x1234; - unloadManager = &manager; - g_ThreadManager_GameUI = &manager; - for (int state : { DLL_ACTIVE, DLL_CLOSE, DLL_RESTART }) - { - engine.state = state; - manager.stopping = false; - expectedModule = (HMODULE)0x5678; - expectedJoins = manager.joined; - CHECK(NewFreeLibrary_Engine(expectedModule)); - CHECK(!manager.stopping); - expectedModule = manager.module; - if (state != DLL_ACTIVE) ++expectedJoins; - CHECK(NewFreeLibrary_Engine(expectedModule)); - CHECK((state != DLL_ACTIVE) == manager.stopping); - } - g_ThreadManager_GameUI = NULL; - g_ThreadManager_ServerBrowser = &manager; - ++expectedJoins; - CHECK(NewFreeLibrary_GameUI(manager.module)); - g_ThreadManager_ServerBrowser = NULL; - CHECK(7 == unloadCalls); - eng = NULL; + TestEngine engine; + IEngine* enginePointer = &engine; + eng = &enginePointer; + TestManager manager; + manager.module = (HMODULE)0x1234; + unloadManager = &manager; + g_ThreadManager_GameUI = &manager; + for (int state : {DLL_ACTIVE, DLL_CLOSE, DLL_RESTART}) + { + engine.state = state; + manager.stopping = false; + expectedModule = (HMODULE)0x5678; + expectedJoins = manager.joined; + CHECK(NewFreeLibrary_Engine(expectedModule)); + CHECK(!manager.stopping); + expectedModule = manager.module; + if (state != DLL_ACTIVE) ++expectedJoins; + CHECK(NewFreeLibrary_Engine(expectedModule)); + CHECK((state != DLL_ACTIVE) == manager.stopping); + } + g_ThreadManager_GameUI = NULL; + g_ThreadManager_ServerBrowser = &manager; + ++expectedJoins; + CHECK(NewFreeLibrary_GameUI(manager.module)); + g_ThreadManager_ServerBrowser = NULL; + CHECK(7 == unloadCalls); + eng = NULL; } struct EngineExitContext { - IThreadManager* manager; - HANDLE started; - HANDLE cleaned; + IThreadManager* manager; + HANDLE started; + HANDLE cleaned; }; static DWORD WINAPI EngineExitWorker(void* argument) { - auto context = static_cast(argument); - SetEvent(context->started); - while (!context->manager->OnSleep(1)) Sleep(1); - // Resource cleanup must finish before the engine lifecycle callback returns. - SetEvent(context->cleaned); - return 0; + auto context = static_cast(argument); + SetEvent(context->started); + while (!context->manager->OnSleep(1)) Sleep(1); + // Resource cleanup must finish before the engine lifecycle callback returns. + SetEvent(context->cleaned); + return 0; } static void CheckEngineExit() { - TestEngine engine; - IEngine* enginePointer = &engine; - for (int state : { DLL_INACTIVE, DLL_CLOSE, DLL_RESTART, DLL_ACTIVE, -1 }) - { - engine.state = state; - eng = state == -1 ? NULL : &enginePointer; - CThreadManager manager(NULL, NULL); - g_ThreadManager_Engine = &manager; - EngineExitContext context = { &manager, CreateEvent(NULL, TRUE, FALSE, NULL), - CreateEvent(NULL, TRUE, FALSE, NULL) }; - CHECK(context.started && context.cleaned); - HANDLE worker = CreateThread(NULL, 0, EngineExitWorker, &context, 0, NULL); - CHECK(worker != NULL); - HANDLE duplicate = NULL; - CHECK(DuplicateHandle(GetCurrentProcess(), worker, GetCurrentProcess(), &duplicate, - 0, FALSE, DUPLICATE_SAME_ACCESS)); - static_cast(manager).OnCreateThread(duplicate); - CHECK(WAIT_OBJECT_0 == WaitForSingleObject(context.started, 5000)); - Engine_WaitForShutdown(NULL, NULL); - CHECK(WAIT_OBJECT_0 == WaitForSingleObject(context.cleaned, 0)); - CHECK(WAIT_OBJECT_0 == WaitForSingleObject(worker, 0)); - Engine_WaitForShutdown(NULL, NULL); - CloseHandle(worker); - CloseHandle(context.started); - CloseHandle(context.cleaned); - } - eng = NULL; - g_ThreadManager_Engine = NULL; - Engine_WaitForShutdown(NULL, NULL); + TestEngine engine; + IEngine* enginePointer = &engine; + for (int state : {DLL_INACTIVE, DLL_CLOSE, DLL_RESTART, DLL_ACTIVE, -1}) + { + engine.state = state; + eng = state == -1 ? NULL : &enginePointer; + CThreadManager manager(NULL, NULL); + g_ThreadManager_Engine = &manager; + EngineExitContext context = {&manager, CreateEvent(NULL, TRUE, FALSE, NULL), + CreateEvent(NULL, TRUE, FALSE, NULL)}; + CHECK(context.started && context.cleaned); + HANDLE worker = CreateThread(NULL, 0, EngineExitWorker, &context, 0, NULL); + CHECK(worker != NULL); + HANDLE duplicate = NULL; + CHECK(DuplicateHandle(GetCurrentProcess(), worker, GetCurrentProcess(), &duplicate, + 0, FALSE, DUPLICATE_SAME_ACCESS)); + static_cast(manager).OnCreateThread(duplicate); + CHECK(WAIT_OBJECT_0 == WaitForSingleObject(context.started, 5000)); + Engine_WaitForShutdown(NULL, NULL); + CHECK(WAIT_OBJECT_0 == WaitForSingleObject(context.cleaned, 0)); + CHECK(WAIT_OBJECT_0 == WaitForSingleObject(worker, 0)); + Engine_WaitForShutdown(NULL, NULL); + CloseHandle(worker); + CloseHandle(context.started); + CloseHandle(context.cleaned); + } + eng = NULL; + g_ThreadManager_Engine = NULL; + Engine_WaitForShutdown(NULL, NULL); } -static DWORD engineThreadId; -static SOCKET socketHandle = INVALID_SOCKET; -static HANDLE shutdownWorker = NULL; -static unsigned shutdownCalls = 0; -static HANDLE entered; -static CRITICAL_SECTION networkLock; -static std::atomic flood{ false }; -static std::atomic packets{ 0 }; -static int __cdecl Packet(int) { ++packets; return flood.load() ? 1 : 0; } +static DWORD engineThreadId; +static SOCKET socketHandle = INVALID_SOCKET; +static HANDLE shutdownWorker = NULL; +static unsigned shutdownCalls = 0; +static HANDLE entered; +static CRITICAL_SECTION networkLock; +static std::atomic flood{false}; +static std::atomic packets{0}; +static int __cdecl Packet(int) +{ + ++packets; + return flood.load() ? 1 : 0; +} static void OpenSocket() { - socketHandle = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - CHECK(INVALID_SOCKET != socketHandle); - sockaddr_in address = {}; - address.sin_family = AF_INET; - address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - CHECK(0 == bind(socketHandle, (sockaddr*)&address, sizeof(address))); + socketHandle = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + CHECK(INVALID_SOCKET != socketHandle); + sockaddr_in address = {}; + address.sin_family = AF_INET; + address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + CHECK(0 == bind(socketHandle, (sockaddr*)&address, sizeof(address))); } static void __cdecl ShutdownResources() { - // Match the binary's ordering: resource cleanup precedes its late stop. - // The hook must have joined before ANY original cleanup starts. - if (shutdownWorker) - CHECK(WAIT_OBJECT_0 == WaitForSingleObject(shutdownWorker, 0)); - CHECK(TryEnterCriticalSection(&networkLock)); - if (socketHandle != INVALID_SOCKET) - { - CHECK(0 == closesocket(socketHandle)); - socketHandle = INVALID_SOCKET; - } - LeaveCriticalSection(&networkLock); - if (shutdownWorker) - CHECK(NewTerminateThread(shutdownWorker, 0)); - ++shutdownCalls; + // Match the binary's ordering: resource cleanup precedes its late stop. + // The hook must have joined before ANY original cleanup starts. + if (shutdownWorker) + CHECK(WAIT_OBJECT_0 == WaitForSingleObject(shutdownWorker, 0)); + CHECK(TryEnterCriticalSection(&networkLock)); + if (socketHandle != INVALID_SOCKET) + { + CHECK(0 == closesocket(socketHandle)); + socketHandle = INVALID_SOCKET; + } + LeaveCriticalSection(&networkLock); + if (shutdownWorker) + CHECK(NewTerminateThread(shutdownWorker, 0)); + ++shutdownCalls; } static DWORD WINAPI Worker(void*) { - SetEvent(entered); - for (;;) - { - fd_set read; - FD_ZERO(&read); - FD_SET(socketHandle, &read); - if (!flood.load()) NewNetworkSelect(0, &read, NULL, NULL, NULL); - bool more; - do - { - EnterCriticalSection(&networkLock); - more = NewNET_QueuePacket(0) != 0; - LeaveCriticalSection(&networkLock); - } while (more); - NewSleep(1); - } + SetEvent(entered); + for (;;) + { + fd_set read; + FD_ZERO(&read); + FD_SET(socketHandle, &read); + if (!flood.load()) NewNetworkSelect(0, &read, NULL, NULL, NULL); + bool more; + do + { + EnterCriticalSection(&networkLock); + more = NewNET_QueuePacket(0) != 0; + LeaveCriticalSection(&networkLock); + } while (more); + NewSleep(1); + } } -static DWORD WINAPI OtherWorker(void*) { Sleep(INFINITE); return 0; } +static DWORD WINAPI OtherWorker(void*) +{ + Sleep(INFINITE); + return 0; +} struct SocketContext { - SOCKET socket; - sockaddr_in address; - HANDLE started; - HANDLE stop; - CRITICAL_SECTION lock; - bool flood; + SOCKET socket; + sockaddr_in address; + HANDLE started; + HANDLE stop; + CRITICAL_SECTION lock; + bool flood; }; static DWORD WINAPI SocketWorker(void* argument) { - auto context = static_cast(argument); - while (WAIT_OBJECT_0 != NewWaitForSingleObject(context->stop, 0)) - { - fd_set read; - FD_ZERO(&read); - FD_SET(context->socket, &read); - if (!context->flood) SetEvent(context->started); - if (NewSocketSelect(0, &read, NULL, NULL, NULL) > 0) - { - EnterCriticalSection(&context->lock); - char packet; - while (NewSocketRecvFrom(context->socket, &packet, 1, 0, NULL, NULL) == 1) - { - // Always leave another datagram queued: draining must observe stop. - CHECK(1 == sendto(context->socket, &packet, 1, 0, - (sockaddr*)&context->address, sizeof(context->address))); - SetEvent(context->started); - } - CHECK(WSAEWOULDBLOCK == WSAGetLastError()); - LeaveCriticalSection(&context->lock); - } - } - return 0; + auto context = static_cast(argument); + while (WAIT_OBJECT_0 != NewWaitForSingleObject(context->stop, 0)) + { + fd_set read; + FD_ZERO(&read); + FD_SET(context->socket, &read); + if (!context->flood) SetEvent(context->started); + if (NewSocketSelect(0, &read, NULL, NULL, NULL) > 0) + { + EnterCriticalSection(&context->lock); + char packet; + while (NewSocketRecvFrom(context->socket, &packet, 1, 0, NULL, NULL) == 1) + { + // Always leave another datagram queued: draining must observe stop. + CHECK(1 == sendto(context->socket, &packet, 1, 0, (sockaddr*)&context->address, sizeof(context->address))); + SetEvent(context->started); + } + CHECK(WSAEWOULDBLOCK == WSAGetLastError()); + LeaveCriticalSection(&context->lock); + } + } + return 0; } static void CheckSocketShutdown(TestManager& manager) { - for (bool flood : { false, true }) - { - SocketContext context = {}; - context.flood = flood; - context.socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - CHECK(INVALID_SOCKET != context.socket); - context.address.sin_family = AF_INET; - context.address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - CHECK(0 == bind(context.socket, (sockaddr*)&context.address, sizeof(context.address))); - int size = sizeof(context.address); - CHECK(0 == getsockname(context.socket, (sockaddr*)&context.address, &size)); - u_long nonblocking = 1; - CHECK(0 == ioctlsocket(context.socket, FIONBIO, &nonblocking)); - context.started = CreateEvent(NULL, TRUE, FALSE, NULL); - context.stop = CreateEvent(NULL, TRUE, FALSE, NULL); - InitializeCriticalSection(&context.lock); - if (flood) CHECK(1 == sendto(context.socket, "x", 1, 0, (sockaddr*)&context.address, size)); - manager.stopping = false; - HANDLE worker = CreateThread(NULL, 0, SocketWorker, &context, 0, NULL); - CHECK(WAIT_OBJECT_0 == WaitForSingleObject(context.started, 5000)); - manager.StartTermination(); - CHECK(WAIT_OBJECT_0 == WaitForSingleObject(worker, 2000)); - CHECK(TryEnterCriticalSection(&context.lock)); - LeaveCriticalSection(&context.lock); - // The main thread's socket calls still forward even during termination. - if (flood) - { - char packet; - CHECK(1 == NewSocketRecvFrom(context.socket, &packet, 1, 0, NULL, NULL)); - } - CloseHandle(worker); - CloseHandle(context.started); - CloseHandle(context.stop); - DeleteCriticalSection(&context.lock); - closesocket(context.socket); - } - manager.stopping = false; + for (bool flood : {false, true}) + { + SocketContext context = {}; + context.flood = flood; + context.socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + CHECK(INVALID_SOCKET != context.socket); + context.address.sin_family = AF_INET; + context.address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + CHECK(0 == bind(context.socket, (sockaddr*)&context.address, sizeof(context.address))); + int size = sizeof(context.address); + CHECK(0 == getsockname(context.socket, (sockaddr*)&context.address, &size)); + u_long nonblocking = 1; + CHECK(0 == ioctlsocket(context.socket, FIONBIO, &nonblocking)); + context.started = CreateEvent(NULL, TRUE, FALSE, NULL); + context.stop = CreateEvent(NULL, TRUE, FALSE, NULL); + InitializeCriticalSection(&context.lock); + if (flood) CHECK(1 == sendto(context.socket, "x", 1, 0, (sockaddr*)&context.address, size)); + manager.stopping = false; + HANDLE worker = CreateThread(NULL, 0, SocketWorker, &context, 0, NULL); + CHECK(WAIT_OBJECT_0 == WaitForSingleObject(context.started, 5000)); + manager.StartTermination(); + CHECK(WAIT_OBJECT_0 == WaitForSingleObject(worker, 2000)); + CHECK(TryEnterCriticalSection(&context.lock)); + LeaveCriticalSection(&context.lock); + // The main thread's socket calls still forward even during termination. + if (flood) + { + char packet; + CHECK(1 == NewSocketRecvFrom(context.socket, &packet, 1, 0, NULL, NULL)); + } + CloseHandle(worker); + CloseHandle(context.started); + CloseHandle(context.stop); + DeleteCriticalSection(&context.lock); + closesocket(context.socket); + } + manager.stopping = false; } static DWORD WINAPI FinishedWorker(void*) { return 0; } -struct SpawnContext { IThreadManager* manager; HANDLE childFinished; }; +struct SpawnContext +{ + IThreadManager* manager; + HANDLE childFinished; +}; static DWORD WINAPI ChildWorker(void* event) { - Sleep(100); - SetEvent((HANDLE)event); - return 0; + Sleep(100); + SetEvent((HANDLE)event); + return 0; } static DWORD WINAPI SpawningWorker(void* context) { - auto spawn = (SpawnContext*)context; - Sleep(30); - HANDLE child = CreateThread(NULL, 0, ChildWorker, spawn->childFinished, 0, NULL); - CHECK(child != NULL); - HANDLE duplicate = NULL; - CHECK(DuplicateHandle(GetCurrentProcess(), child, GetCurrentProcess(), &duplicate, 0, FALSE, DUPLICATE_SAME_ACCESS)); - spawn->manager->OnCreateThread(duplicate); - CloseHandle(child); - return 0; + auto spawn = (SpawnContext*)context; + Sleep(30); + HANDLE child = CreateThread(NULL, 0, ChildWorker, spawn->childFinished, 0, NULL); + CHECK(child != NULL); + HANDLE duplicate = NULL; + CHECK(DuplicateHandle(GetCurrentProcess(), child, GetCurrentProcess(), &duplicate, 0, FALSE, DUPLICATE_SAME_ACCESS)); + spawn->manager->OnCreateThread(duplicate); + CloseHandle(child); + return 0; } int main() { - metahook_api_t api = {}; - api.SysError = Fatal; - g_pMetaHookAPI = &api; - g_MainThreadId = (HANDLE)GetCurrentThreadId(); - CheckUnloadRouting(); - CheckEngineExit(); - TestManager manager; - AddThreadManager(&manager); - g_NetworkManager = &manager; - NetworkThread_Configure(Worker, &engineThreadId, (void*)Packet, (void*)ShutdownResources); - g_pfn_select = select; - WSADATA data; - CHECK(0 == WSAStartup(MAKEWORD(2, 2), &data)); - CheckSocketShutdown(manager); - entered = CreateEvent(NULL, TRUE, FALSE, NULL); - InitializeCriticalSection(&networkLock); - // Networking disabled: still forward resource cleanup on each invocation. - NewNET_Shutdown(); - NewNET_Shutdown(); - CHECK(2 == shutdownCalls); - OpenSocket(); - DWORD handlesBefore = 0, handlesAfter = 0; - NetworkTrace("handler test initialized", GetCurrentThreadId()); - GetProcessHandleCount(GetCurrentProcess(), &handlesBefore); - CHECK(NULL == NewCreateThread(NULL, SIZE_T(-1), Worker, NULL, - STACK_SIZE_PARAM_IS_A_RESERVATION, &engineThreadId)); - for (unsigned iteration = 0; iteration < 100; ++iteration) - { - if (socketHandle == INVALID_SOCKET) OpenSocket(); - flood.store((iteration & 1) != 0); - ResetEvent(entered); - HANDLE worker = NewCreateThread(NULL, 0, Worker, NULL, - iteration == 0 ? CREATE_SUSPENDED : 0, &engineThreadId); - CHECK(worker != NULL); - if (iteration == 0) - { - CHECK(WAIT_TIMEOUT == WaitForSingleObject(entered, 20)); - CHECK(1 == ResumeThread(worker)); - } - CHECK(WAIT_OBJECT_0 == WaitForSingleObject(entered, 5000)); - Sleep(5); - CHECK(WAIT_TIMEOUT == WaitForSingleObject(worker, 0)); - const ULONGLONG start = GetTickCount64(); - if (iteration == 99) - { - CThreadManager shutdownManager(NULL, NULL); - g_NetworkManager = &shutdownManager; - static_cast(shutdownManager).StartTermination(); - CHECK(WAIT_OBJECT_0 == WaitForSingleObject(worker, 2000)); - g_NetworkManager = &manager; - } - else if (iteration % 3 == 0) - { - shutdownWorker = worker; - const unsigned before = shutdownCalls; - NewNET_Shutdown(); - NewNET_Shutdown(); - CHECK(before + 2 == shutdownCalls); - shutdownWorker = NULL; - } - else - CHECK(NewTerminateThread(worker, 0)); - CHECK(GetTickCount64() - start < 2000); - CHECK(WAIT_OBJECT_0 == WaitForSingleObject(worker, 0)); - CHECK(!g_NetworkThread.IsThread(engineThreadId, engineThreadId)); - CHECK(!g_NetworkThread.ShouldExit(engineThreadId, engineThreadId)); - CHECK(NewTerminateThread(worker, 0)); - CHECK(TryEnterCriticalSection(&networkLock)); - LeaveCriticalSection(&networkLock); - CHECK(g_NetworkThread.Reset()); - CloseHandle(worker); - manager.CloseDuplicates(); - } - // Match the handle-count baseline even if the last cycle closed its socket. - if (socketHandle == INVALID_SOCKET) OpenSocket(); - // Non-network TerminateThread calls still reach Win32 with their exit code. - DWORD otherId; - HANDLE other = NewCreateThread(NULL, 0, OtherWorker, NULL, 0, &otherId); - CHECK(NewTerminateThread(other, 42)); - CHECK(WAIT_OBJECT_0 == WaitForSingleObject(other, 5000)); - DWORD code = 0; - CHECK(GetExitCodeThread(other, &code) && code == 42); - CloseHandle(other); - manager.CloseDuplicates(); - GetProcessHandleCount(GetCurrentProcess(), &handlesAfter); - std::fprintf(stderr, "handles: before=%lu after=%lu\n", handlesBefore, handlesAfter); - CHECK(handlesBefore == handlesAfter); - // More than a pool's worth of stopped/recreated workers reuses signaled - // slots. The displaced manager duplicate must be closed, too. - CThreadManager pool(NULL, NULL); - IThreadManager& poolApi = pool; - for (unsigned i = 0; i <= MAXIMUM_WAIT_OBJECTS; ++i) - { - HANDLE h = CreateThread(NULL, 0, FinishedWorker, NULL, 0, NULL); - CHECK(h != NULL && WAIT_OBJECT_0 == WaitForSingleObject(h, 5000)); - HANDLE duplicate = NULL; - CHECK(DuplicateHandle(GetCurrentProcess(), h, GetCurrentProcess(), &duplicate, 0, FALSE, DUPLICATE_SAME_ACCESS)); - poolApi.OnCreateThread(duplicate); - CloseHandle(h); - } - poolApi.WaitForAliveThreadsToShutdown(); - GetProcessHandleCount(GetCurrentProcess(), &handlesAfter); - CHECK(handlesBefore == handlesAfter); - HANDLE childFinished = CreateEvent(NULL, TRUE, FALSE, NULL); - CHECK(childFinished != NULL); - SpawnContext spawn = { &poolApi, childFinished }; - HANDLE parent = CreateThread(NULL, 0, SpawningWorker, &spawn, CREATE_SUSPENDED, NULL); - CHECK(parent != NULL); - HANDLE parentDuplicate = NULL; - CHECK(DuplicateHandle(GetCurrentProcess(), parent, GetCurrentProcess(), &parentDuplicate, 0, FALSE, DUPLICATE_SAME_ACCESS)); - poolApi.OnCreateThread(parentDuplicate); - CHECK(1 == ResumeThread(parent)); - poolApi.WaitForAliveThreadsToShutdown(); - CHECK(WAIT_OBJECT_0 == WaitForSingleObject(childFinished, 0)); - CloseHandle(parent); - CloseHandle(childFinished); - GetProcessHandleCount(GetCurrentProcess(), &handlesAfter); - CHECK(handlesBefore == handlesAfter); - CHECK(packets.load() > 0); - DeleteCriticalSection(&networkLock); - CloseHandle(entered); - closesocket(socketHandle); - WSACleanup(); - DeleteThreadManager(&manager); - std::puts("PASS 100 handler stop/recreate cycles: idle select, continuous packets, unlocked exit, forwarding, no handle growth"); + metahook_api_t api = {}; + api.SysError = Fatal; + g_pMetaHookAPI = &api; + g_MainThreadId = (HANDLE)GetCurrentThreadId(); + CheckUnloadRouting(); + CheckEngineExit(); + TestManager manager; + AddThreadManager(&manager); + g_NetworkManager = &manager; + NetworkThread_Configure(Worker, &engineThreadId, (void*)Packet, (void*)ShutdownResources); + g_pfn_select = select; + WSADATA data; + CHECK(0 == WSAStartup(MAKEWORD(2, 2), &data)); + CheckSocketShutdown(manager); + entered = CreateEvent(NULL, TRUE, FALSE, NULL); + InitializeCriticalSection(&networkLock); + // Networking disabled: still forward resource cleanup on each invocation. + NewNET_Shutdown(); + NewNET_Shutdown(); + CHECK(2 == shutdownCalls); + OpenSocket(); + DWORD handlesBefore = 0, handlesAfter = 0; + NetworkTrace("handler test initialized", GetCurrentThreadId()); + GetProcessHandleCount(GetCurrentProcess(), &handlesBefore); + CHECK(NULL == NewCreateThread(NULL, SIZE_T(-1), Worker, NULL, STACK_SIZE_PARAM_IS_A_RESERVATION, &engineThreadId)); + for (unsigned iteration = 0; iteration < 100; ++iteration) + { + if (socketHandle == INVALID_SOCKET) OpenSocket(); + flood.store((iteration & 1) != 0); + ResetEvent(entered); + HANDLE worker = NewCreateThread(NULL, 0, Worker, NULL, + iteration == 0 ? CREATE_SUSPENDED : 0, &engineThreadId); + CHECK(worker != NULL); + if (iteration == 0) + { + CHECK(WAIT_TIMEOUT == WaitForSingleObject(entered, 20)); + CHECK(1 == ResumeThread(worker)); + } + CHECK(WAIT_OBJECT_0 == WaitForSingleObject(entered, 5000)); + Sleep(5); + CHECK(WAIT_TIMEOUT == WaitForSingleObject(worker, 0)); + const ULONGLONG start = GetTickCount64(); + if (iteration == 99) + { + CThreadManager shutdownManager(NULL, NULL); + g_NetworkManager = &shutdownManager; + static_cast(shutdownManager).StartTermination(); + CHECK(WAIT_OBJECT_0 == WaitForSingleObject(worker, 2000)); + g_NetworkManager = &manager; + } + else if (iteration % 3 == 0) + { + shutdownWorker = worker; + const unsigned before = shutdownCalls; + NewNET_Shutdown(); + NewNET_Shutdown(); + CHECK(before + 2 == shutdownCalls); + shutdownWorker = NULL; + } + else + CHECK(NewTerminateThread(worker, 0)); + CHECK(GetTickCount64() - start < 2000); + CHECK(WAIT_OBJECT_0 == WaitForSingleObject(worker, 0)); + CHECK(!g_NetworkThread.IsThread(engineThreadId, engineThreadId)); + CHECK(!g_NetworkThread.ShouldExit(engineThreadId, engineThreadId)); + CHECK(NewTerminateThread(worker, 0)); + CHECK(TryEnterCriticalSection(&networkLock)); + LeaveCriticalSection(&networkLock); + CHECK(g_NetworkThread.Reset()); + CloseHandle(worker); + manager.CloseDuplicates(); + } + // Match the handle-count baseline even if the last cycle closed its socket. + if (socketHandle == INVALID_SOCKET) OpenSocket(); + // Non-network TerminateThread calls still reach Win32 with their exit code. + DWORD otherId; + HANDLE other = NewCreateThread(NULL, 0, OtherWorker, NULL, 0, &otherId); + CHECK(NewTerminateThread(other, 42)); + CHECK(WAIT_OBJECT_0 == WaitForSingleObject(other, 5000)); + DWORD code = 0; + CHECK(GetExitCodeThread(other, &code) && code == 42); + CloseHandle(other); + manager.CloseDuplicates(); + GetProcessHandleCount(GetCurrentProcess(), &handlesAfter); + std::fprintf(stderr, "handles: before=%lu after=%lu\n", handlesBefore, handlesAfter); + CHECK(handlesBefore == handlesAfter); + // More than a pool's worth of stopped/recreated workers reuses signaled + // slots. The displaced manager duplicate must be closed, too. + CThreadManager pool(NULL, NULL); + IThreadManager& poolApi = pool; + for (unsigned i = 0; i <= MAXIMUM_WAIT_OBJECTS; ++i) + { + HANDLE h = CreateThread(NULL, 0, FinishedWorker, NULL, 0, NULL); + CHECK(h != NULL && WAIT_OBJECT_0 == WaitForSingleObject(h, 5000)); + HANDLE duplicate = NULL; + CHECK(DuplicateHandle(GetCurrentProcess(), h, GetCurrentProcess(), &duplicate, 0, FALSE, DUPLICATE_SAME_ACCESS)); + poolApi.OnCreateThread(duplicate); + CloseHandle(h); + } + poolApi.WaitForAliveThreadsToShutdown(); + GetProcessHandleCount(GetCurrentProcess(), &handlesAfter); + CHECK(handlesBefore == handlesAfter); + HANDLE childFinished = CreateEvent(NULL, TRUE, FALSE, NULL); + CHECK(childFinished != NULL); + SpawnContext spawn = {&poolApi, childFinished}; + HANDLE parent = CreateThread(NULL, 0, SpawningWorker, &spawn, CREATE_SUSPENDED, NULL); + CHECK(parent != NULL); + HANDLE parentDuplicate = NULL; + CHECK(DuplicateHandle(GetCurrentProcess(), parent, GetCurrentProcess(), &parentDuplicate, 0, FALSE, DUPLICATE_SAME_ACCESS)); + poolApi.OnCreateThread(parentDuplicate); + CHECK(1 == ResumeThread(parent)); + poolApi.WaitForAliveThreadsToShutdown(); + CHECK(WAIT_OBJECT_0 == WaitForSingleObject(childFinished, 0)); + CloseHandle(parent); + CloseHandle(childFinished); + GetProcessHandleCount(GetCurrentProcess(), &handlesAfter); + CHECK(handlesBefore == handlesAfter); + CHECK(packets.load() > 0); + DeleteCriticalSection(&networkLock); + CloseHandle(entered); + closesocket(socketHandle); + WSACleanup(); + DeleteThreadManager(&manager); + std::puts("PASS 100 handler stop/recreate cycles: idle select, continuous packets, unlocked exit, forwarding, no handle growth"); } diff --git a/tests/network_thread_state_test.cpp b/tests/network_thread_state_test.cpp index 6187f6d..622de62 100644 --- a/tests/network_thread_state_test.cpp +++ b/tests/network_thread_state_test.cpp @@ -2,71 +2,79 @@ #include #include -#define CHECK(condition) do { if (!(condition)) { std::fprintf(stderr, "FAIL line %d: %s\n", __LINE__, #condition); std::abort(); } } while (0) +#define CHECK(condition) \ + do \ + { \ + if (!(condition)) \ + { \ + std::fprintf(stderr, "FAIL line %d: %s\n", __LINE__, #condition); \ + std::abort(); \ + } \ + } while (0) static DWORD WINAPI WaitForRelease(void* event) { - WaitForSingleObject(event, INFINITE); - return 0; + WaitForSingleObject(event, INFINITE); + return 0; } int main() { - NetworkThreadState state; - const DWORD mainThread = GetCurrentThreadId(); - CHECK(!state.IsThread(mainThread, mainThread)); - CHECK(state.PrepareCreate()); - // A failed CreateThread leaves no identity or exit request behind. - CHECK(!state.IsThread(mainThread, mainThread)); - CHECK(state.PrepareCreate()); - HANDLE release = CreateEvent(NULL, TRUE, FALSE, NULL); - CHECK(release != NULL); - DWORD id = 0; - HANDLE worker = CreateThread(NULL, 0, WaitForRelease, release, 0, &id); - CHECK(worker != NULL); - CHECK(state.Track(worker)); - CHECK(state.IsThread(id, id)); - CHECK(!state.IsThread(mainThread, id)); - CHECK(!state.IsThread(id, mainThread)); - CHECK(!state.ShouldExit(id, id)); - CHECK(!state.PrepareCreate()); - CHECK(NetworkThreadState::StopResult::NotTarget == state.RequestStop(GetCurrentThread(), id)); - CHECK(NetworkThreadState::StopResult::NotTarget == state.RequestStop(worker, mainThread)); - HANDLE waitHandle = NULL; - CHECK(NetworkThreadState::StopResult::Failed == state.RequestTrackedStop(mainThread, &waitHandle)); - CHECK(NULL == waitHandle); - CHECK(!state.ShouldExit(id, id)); - CHECK(NetworkThreadState::StopResult::Requested == state.RequestTrackedStop(id, &waitHandle)); - CHECK(NULL != waitHandle && waitHandle != worker); - CHECK(NetworkThreadState::StopResult::Requested == state.RequestStop(worker, id)); - CHECK(state.ShouldExit(id, id)); - CHECK(!state.ShouldExit(mainThread, id)); - CHECK(NetworkThreadState::StopResult::Requested == state.RequestStop(worker, id)); - CHECK(!state.Reset()); - SetEvent(release); - CHECK(WAIT_OBJECT_0 == WaitForSingleObject(worker, 5000)); - CHECK(NetworkThreadState::StopResult::Stopped == state.RequestStop(worker, id)); - // The state owns a duplicate, never the engine's original handle. - CHECK(state.Reset()); - CHECK(WAIT_OBJECT_0 == WaitForSingleObject(waitHandle, 0)); - CloseHandle(waitHandle); - CHECK(WAIT_OBJECT_0 == WaitForSingleObject(worker, 0)); - CloseHandle(worker); - ResetEvent(release); - CHECK(state.PrepareCreate()); - worker = CreateThread(NULL, 0, WaitForRelease, release, 0, &id); - CHECK(worker != NULL && state.Track(worker)); - CHECK(!state.ShouldExit(id, id)); - CHECK(NetworkThreadState::StopResult::SelfWait == state.RequestStop(worker, id, id)); - CHECK(!state.ShouldExit(id, id)); - SetEvent(release); - CHECK(WAIT_OBJECT_0 == WaitForSingleObject(worker, 5000)); - // Repeated cleanup is safe after the engine has cleared its public ID. - CHECK(NetworkThreadState::StopResult::Stopped == state.RequestTrackedStop(0, &waitHandle)); - CHECK(WAIT_OBJECT_0 == WaitForSingleObject(waitHandle, 0)); - CloseHandle(waitHandle); - CHECK(state.Reset()); - CloseHandle(worker); - CloseHandle(release); - std::puts("PASS network identity, requests, failed creation, repeated stop, rebuild and handle ownership"); + NetworkThreadState state; + const DWORD mainThread = GetCurrentThreadId(); + CHECK(!state.IsThread(mainThread, mainThread)); + CHECK(state.PrepareCreate()); + // A failed CreateThread leaves no identity or exit request behind. + CHECK(!state.IsThread(mainThread, mainThread)); + CHECK(state.PrepareCreate()); + HANDLE release = CreateEvent(NULL, TRUE, FALSE, NULL); + CHECK(release != NULL); + DWORD id = 0; + HANDLE worker = CreateThread(NULL, 0, WaitForRelease, release, 0, &id); + CHECK(worker != NULL); + CHECK(state.Track(worker)); + CHECK(state.IsThread(id, id)); + CHECK(!state.IsThread(mainThread, id)); + CHECK(!state.IsThread(id, mainThread)); + CHECK(!state.ShouldExit(id, id)); + CHECK(!state.PrepareCreate()); + CHECK(NetworkThreadState::StopResult::NotTarget == state.RequestStop(GetCurrentThread(), id)); + CHECK(NetworkThreadState::StopResult::NotTarget == state.RequestStop(worker, mainThread)); + HANDLE waitHandle = NULL; + CHECK(NetworkThreadState::StopResult::Failed == state.RequestTrackedStop(mainThread, &waitHandle)); + CHECK(NULL == waitHandle); + CHECK(!state.ShouldExit(id, id)); + CHECK(NetworkThreadState::StopResult::Requested == state.RequestTrackedStop(id, &waitHandle)); + CHECK(NULL != waitHandle && waitHandle != worker); + CHECK(NetworkThreadState::StopResult::Requested == state.RequestStop(worker, id)); + CHECK(state.ShouldExit(id, id)); + CHECK(!state.ShouldExit(mainThread, id)); + CHECK(NetworkThreadState::StopResult::Requested == state.RequestStop(worker, id)); + CHECK(!state.Reset()); + SetEvent(release); + CHECK(WAIT_OBJECT_0 == WaitForSingleObject(worker, 5000)); + CHECK(NetworkThreadState::StopResult::Stopped == state.RequestStop(worker, id)); + // The state owns a duplicate, never the engine's original handle. + CHECK(state.Reset()); + CHECK(WAIT_OBJECT_0 == WaitForSingleObject(waitHandle, 0)); + CloseHandle(waitHandle); + CHECK(WAIT_OBJECT_0 == WaitForSingleObject(worker, 0)); + CloseHandle(worker); + ResetEvent(release); + CHECK(state.PrepareCreate()); + worker = CreateThread(NULL, 0, WaitForRelease, release, 0, &id); + CHECK(worker != NULL && state.Track(worker)); + CHECK(!state.ShouldExit(id, id)); + CHECK(NetworkThreadState::StopResult::SelfWait == state.RequestStop(worker, id, id)); + CHECK(!state.ShouldExit(id, id)); + SetEvent(release); + CHECK(WAIT_OBJECT_0 == WaitForSingleObject(worker, 5000)); + // Repeated cleanup is safe after the engine has cleared its public ID. + CHECK(NetworkThreadState::StopResult::Stopped == state.RequestTrackedStop(0, &waitHandle)); + CHECK(WAIT_OBJECT_0 == WaitForSingleObject(waitHandle, 0)); + CloseHandle(waitHandle); + CHECK(state.Reset()); + CloseHandle(worker); + CloseHandle(release); + std::puts("PASS network identity, requests, failed creation, repeated stop, rebuild and handle ownership"); }