# Agent_Release_x64.exe — porting spec (Win32 → Linux) This was kept from the removed Linux reconstruction `src/agent_release/` (removed 2026-10-15). It records the facts about the original binary that a re-implementation needs, and the nearest Linux equivalent for each Win32 call. Sources: `evidence.md`, `cpp/AgentProcessControl.cpp`. Names are inferred; the behaviour is static-analysis evidence only. ## Behaviour to reproduce The servers being managed are read from config (`option\*.xml`, mapping not yet recovered). | Item | Behaviour | |---|---| | Single instance (`0x1400100b0`) | Named mutex `Global\RustyHearts_Agent` | | Launch (`0x1400094e0`) | `CreateProcessW(NULL, " ", …, CREATE_NEW_CONSOLE, …, workDir)`, console 700×110, fill attribute 0x17. Command line is limited to 260 characters (`MAX_PATH`). Returns the pid, or 0 on failure (logs `=======Server Fail=======`) | | After each launch attempt | `Sleep(1000)` | | Command line (`0x140008ea0`) | ` `. An alternative argument builder `FUN_140027540` exists, but its output is unknown | | Watcher (`0x140008c90`) | Polls `GetExitCodeProcess` every 10 ms. On exit it sends two RMIs to AgentManager, then relaunches if the per-server revive flag (+0xd5) or the global flag (+0x171) is set | | Kill (`0x140009780`) | Toolhelp32 snapshot, case-insensitive image-name match, terminates the **first** match only (`TerminateProcess(h,0)`) | | Main loop (`0x1400037a0`) | 100 ms tick, 1 s FPS counter. Optionally auto-starts servers after a configured delay (+0x178). At startup it kills a stale process of type 0x18 (identity unknown) | ## Win32 → Linux substitutions | Original (address) | Win32 | Linux replacement | Behaviour difference | |---|---|---|---| | Single instance (`0x1400100b0`) | `CreateMutexW("Global\\RustyHearts_Agent")` | `flock(LOCK_EX\|LOCK_NB)` on `/tmp/RustyHearts_Agent.lock` | Lock file instead of a kernel object; still released on exit or crash | | Launch (`0x1400094e0`) | `CreateProcessW(…, CREATE_NEW_CONSOLE, …, workDir)` + `STARTUPINFOW` console geometry | `posix_spawnp` + `POSIX_SPAWN_SETSID` + `posix_spawn_file_actions_addchdir_np` (glibc ≥ 2.29) | No console window; the command line is split on whitespace (no Windows quoting rules) | | Watcher (`0x140008c90`) | `GetExitCodeProcess` polled every 10 ms | blocking `waitpid` on a detached thread | No polling. Reaping fixes the original handle leak; passing the pid by value fixes the original stack-pointer race | | Kill (`0x140009780`) | Toolhelp32 + `OpenProcess` + `TerminateProcess(h,0)` | scan `/proc/*/cmdline` for the argv[0] basename with `strcasecmp`, then `kill(SIGKILL)` | Same rule: first match only, case-insensitive | | Main-loop timer | `QueryPerformanceCounter` (`TimeSourceHRWin32`) | `std::chrono::steady_clock` | — | | Thread start | `_beginthreadex` | `std::thread` | — | | `Sleep` | `Sleep` | `std::this_thread::sleep_for` | — | | Shutdown | console menu (`FUN_140001dc0`, not analysed) | `SIGINT` / `SIGTERM` | Different trigger | ## Original bugs (O) - **Process handle leak:** the watcher never closes the process handle. - **Stack-pointer race:** `LaunchProcess` hands the watcher a pointer to a stack `PROCESS_INFORMATION`. It is only safe because of the `Sleep(1000)` that follows. - **Snapshot handle leak:** `KillProcessByName` leaks the snapshot handle on the success path. ## Unknown / not recovered - **ProudNet RMI to AgentManager and the game servers:** proprietary transport. Wire-incompatible with ProudNet 1.8 (see the ProudNet section of `third-party/README.md`). The proxy vtable slots used are `+0x40` and `+0x50`. - **XML config field mapping** for the Agent. - **`FUN_140027540` argument builder; the `type == 1` exit follow-up (`FUN_140009f20`); the type 0x18 process killed at startup.** - **Locking around the server table:** not visible in the analysed code.