add agent manager release decomp

This commit is contained in:
Benjamin Palko 2026-10-15 09:58:24 -04:00
parent 31ff800b2a
commit fac6117145
7019 changed files with 254032 additions and 5 deletions

View file

@ -0,0 +1,140 @@
# AgentManager_Release_x64.exe — finding ledger
- SHA-256 `8fd8ca91725858651a7839d34257b10c2970ac16c97e60c523b8b0c983fcb186`, 1,584,128 bytes
- PE32+ x86-64, Windows CUI, MSVC linker 10.0 (VS2010; imports MSVCR100/MSVCP100)
- PDB reference `F:\work\Project\Output\exe\AgentManager_Release_x64.pdb` (GUID cc211b01-ac46-4192-9b64-b1d16ba71956, age 1) — PDB not available
- Imports: WS2_32, MSWSOCK, WINMM, KERNEL32, USER32, ADVAPI32, OLEAUT32, MSVCP100, RPCRT4, MSVCR100, BugTrapU-x64, ole32, dbghelp
- Ghidra 12.1.2 via REA 6.5.0; 7,400 procedures, 1,613 defined strings (`ev_c3719a85…`)
- Evidence: `evidence-bundle.json` — overview `ev_c3719a85…`, batch decompiles `ev_7ee0b809…` (protocol/auth/command) and `ev_99e67015…` (startup/config/sequencer), RmiContext bytes `ev_3fe7cbab…`.
Static analysis only; nothing here was observed at runtime.
## Status: analysis complete for this pass (protocol, auth, config sources, command semantics, startup sequencer). REA session closed; `porting.md` intentionally skipped.
## Language / libraries
C++ (MSVC RTTI `.?AV…`, `MBISingleton<T>` templates, `__CxxFrameHandler3`, MSVCP100). Same
`Magi*` framework as the other servers (`MagiServerBase`, `MagiServerOption`, `MagiCustomOption`,
`MagiServerFile`, `MagiXmlFormat`, `MagiStatistics`, `MBCLog`, `Magi::Timer`) — see
`docs/SHARED_FRAMEWORK.md`.
Third-party (all already in `third-party/README.md`, no new libraries): ProudNet (incl. `VizC2S/VizS2C`
visualizer RMIs), TinyXML 2.5.x (`TiXml*`), BugTrap, ATL/`_com_error` + ADO (`sqloledb`).
## Outputs
| Path | Content |
|---|---|
| `full/functions/<addr>.c`, `full/index.tsv` | Ghidra pseudocode, 6,993 non-thunk functions (1 failed: `0x1400203d0`, overlapping varnodes) |
| `full/vtables.tsv` | 1,441 RTTI vftable slots → functions |
| `<addr>.pseudo.c` | REA pseudocode for the 19 functions in `ev_7ee0b809…` and `ev_99e67015…` |
| `binary_overview.json` | REA overview record |
## Findings
| # | Conclusion | Type | Evidence |
|---|---|---|---|
| 1 | Central hub between Agents (one per machine) and AgentMonitor (operator tool, presumably SMMonitoring): ProudNet server with two stub/proxy pairs: `AGENT2AGENTMANAGER` stub + `AGENTMANAGER2AGENT` proxy (class `MagiAgentManagerNetwork`), `AGENTMONITOR2AGENTMANAGER` stub + `AGENTMANAGER2AGENTMONITOR` proxy (class `MagiAgentManagerMonitorNetwork`) | Observed (RTTI, vftables); "SMMonitoring = AgentMonitor" is **inference** | `full/vtables.tsv` |
| 2 | Agent→AgentManager RMIs 0x8599–0x85a3 (table below); dispatcher `0x140026520`, handlers = `MagiAgentManagerNetwork` vftable `0x14012f1e8` slots 7–17 | Observed | `0x140026520.pseudo.c` |
| 3 | AgentMonitor→AgentManager RMIs 0x8661–0x866c; dispatcher `0x14002c3c0`, handlers = vftable `0x14012f6d8` slots 7–18 | Observed | `0x14002c3c0.pseudo.c` |
| 4 | Outgoing: AgentManager→Agent 0x85fd–0x860d, AgentManager→Monitor 0x86c5–0x86d2 | Observed (proxy functions) | `full/functions/0x140023930.c` … `0x14002c230.c` |
| 5 | Monitor auth (`REQ_AUTH` → `0x140015290`): logs `MONITOR AUTH : ID[%s]`, calls `dbo.up_auth_agentaccount(@Account,@Password)` via ADO (`0x140009d00`), reads column `permission`; permission < 1 → fail; else sends `RESP_AUTH`, server-option list and per-server info to the monitor | Observed | `0x140015290.pseudo.c`, `0x140009d00.pseudo.c`; SP in `decomp/schema/RustyHearts_Auth/procs/up_auth_agentaccount.sql` (plaintext compare against `dbo.AgentAccount`) |
| 6 | DB connection strings come from the option file (finding 11); the ADO layer detects `PROVIDER=`/`DRIVER=` and appends `;Application Name=` (`0x140074210`) | Observed | `full/functions/0x140074210.c` |
| 7 | `PT_SERVER_COMMAND` handler (`0x140015ac0`): refuses with `Not Active Server.!!!` unless option byte `+0x180` set; command 4 = **patch**: launches a configured executable (server-file entry type `0x18`) with `CreateProcessW(…, CREATE_NEW_CONSOLE …)`, logs `FAILED PATCH ON %s`; commands 0–3,5–7,0xd forwarded to agents (`FUN_14000cb70`); 0xc sets a flag at `+0x9c` | Observed; full code table in §Server commands | `0x140015ac0.pseudo.c` |
| 8 | `PT_SERVER_REGISTER` handler (`0x1400137f0`) stores agent/server info by server id; type `0x0d` entries get special handling (marks registered, pushes info to monitors) | Observed; 0x0d = Agent in the server-kind enum (`docs/CONFIG.md` §5) | `0x1400137f0.pseudo.c` |
| 9 | Single instance via mutex `Global\RustyHearts_AgentManager` (same pattern as Agent: ACCESS_DENIED/ALREADY_EXISTS → print, sleep 3 s, fail) | Observed | `0x14001d850.pseudo.c` |
| 10 | Option files actually used: `option\agentmanager_serveroption.xml` (main, path overridable by argv[1]), `option\customoption.xml` (`0x140038530`), `option\agent.xml` (`0x140033ff0`, `<server>` list), `option\total_option.xml` (only when argv[1] is numeric, agent mode, `0x140030300`/`0x14002fe40`), `option\patch.xml` (**written**, not read: `0x140034220` "Create Patch Option File Succeed"). `service_control.xml`: string only, no reader found. Log `Log\AgentManagerServer.log`, protocol log `Log\Protocol_AgentManager` | Observed | `0x140001390`, `0x140002400`, `0x14002fc00` |
| 11 | **DB config**: standalone loader `0x140031000` reads flat `<db_account string timeout/>` (→ +0x90/+0x130) and `<db_game_log …/>` (→ +0x108/+0x13c) (plus `db_web`, `db_game`, ignored here). Then `0x140032f70` re-reads the same file for `<db><agentmanager><db_account/><db_game_log/></agentmanager></db>` and overrides both; if absent it prints `DB PASE ERROR !!!` but continues (non-fatal). The shipped sample uses the flat form, so the override path fails and flat values are used (I for the shipped effect) | Observed | `0x140031000`, `0x140032f70.pseudo.c`, `0x140002400.pseudo.c` |
| 12 | `db_account` connection (→ `RustyHearts_Auth`) is used at startup for: `up_read_agentmanager(@code)` (own `ServerOption` row), `up_read_all_serveroption`, `up_read_serverfile`, `up_read_realtimeevent`, and later `up_auth_agentaccount`. `db_game_log` connection (→ `RustyHearts_Log`) is used for `up_insert_log_monitor_{command,user,notice}`, `up_insert_log_pvp_ladder`, `up_create_delete_table`. Each failed read prints `… Failed !!!`, sleeps 3 s and clears the run flag | Observed (SP names, objects `+0x49460`/`+0x49468`); DB mapping from SP location in schema | `0x140002790.pseudo.c`, `0x140007a20.pseudo.c`, `decomp/schema/` |
| 13 | **Listen ports**: XML `<server private_ip private_port public_ip public_port …>` gives initial values, but `up_read_agentmanager` row whose `Code` == `server.code` **overrides** them (`0x14002fe40`: World, Type, Private/PublicAddress, Private/Public/Udp port, UdpCount). Agent listener (`0x140005460`) binds `PrivateAddress:PrivatePort` (+0x30/+0x80); monitor listener (`0x140006380`, log `MONITOR NETWORK INIT : ADDRESS[%s] PORT[%u]`) binds `PublicAddress:PublicPort` (+0x58/+0x84). Shipped XML: 51091 / 50091; Agent's `agent.xml` `<agentmanager port="51091">` matches | Observed (code); sample values (S) | `0x140007a20.pseudo.c`, `0x14002fe40.pseudo.c`, `0x140005460.pseudo.c`, `0x140006380.pseudo.c` |
| 14 | `REQ_AGENT_UPDATE/SERVER_ON/AUTO` handlers (`0x1400166d0`/`0x1400169c0`/`0x140016cb0`) send `PT_STRING_COMMAND` (proxy slot 26, 0x8607) with literal `"update"`/`"serveron"`/`"auto"` to the addressed agent. On the Agent (`Agent:0x140005950`): `serveron` → start all its servers (`Agent:0x140009890`); `auto` → toggle global auto-revive (+0x171) and report cmd 7; `update` → iterates its server list and re-reports it (I) | Observed (Manager); Agent side observed except `update` intent | `0x1400169c0.pseudo.c`, `decomp/Agent_Release_x64/full/functions/0x140005950.c` |
| 15 | `PT_PATCH_SERVER` / `PT_BACKUP_CHARACTER_TABLE_COUNT/_END` from agents (slots 15–17) are relayed to monitors (`0x140011840`/`0x140011ba0`/`0x140011cc0` → monitor proxy) | Observed | `full/functions/0x140013f80.c` … `0x140013fd0.c` |
| 16 | Console hotkeys (`0x140003420`, `CConsoleControl`): e.g. 0x32 `CreateBackupTable` → `up_create_delete_table`, 0x33 toggle Active (+0x180), 0x34 reload ServerFile, 0x35 reload ServerOption, print on/off | Observed | `0x140003420.pseudo.c` |
| 17 | RMI payload encryption: every proxy call passes one shared RmiContext `DAT_14016f5d0` whose static image holds only `1` in its first field and zeros elsewhere; no secure-context variant is referenced. Inference: messages are sent with ProudNet's default (unencrypted) mode; runtime initializers or server start parameters could still change this — **unknown** | Observed bytes / inference | `ev_3fe7cbab…` |
### Server commands (`PT_SERVER_COMMAND` monitor → manager → agent)
Gate: AgentManager ignores all of these unless its Active flag (+0x180, console hotkey 0x33) is set.
| Code | AgentManager (`0x140015ac0`) | Agent (`Agent:0x14000e1e0`) |
|---|---|---|
| 0 | forward | **server on**: launch target server (`Agent:0x140008ea0`) |
| 1 | forward | **kill** target server by PID (`OpenProcess`+`TerminateProcess`, `Agent:0x140009720`) |
| 2, 5, 6, 0xd | forward | forwarded to the game server as `AGENT2SERVER PT_SERVER_COMMAND` (0x84d2); meaning is server-side. On DBCServer (`DBC:0x140153f00`): 1/2/5/6/8 set flags, 0xd toggles a flag (+0xd), 7 toggles auto-revive. That 0xd triggers the character-table backup is **inference** (adjacent `PT_BACKUP_CHARACTER_TABLE_*` RMIs and string `CharacterTable BackUp.`) |
| 3 | forward | log `REQ_SERVER_KILL`, then same as 1 |
| 4 | **patch**: if no patch server registered, writes `option\patch.xml`, launches `ServerFile` type 0x18 (PatchServer, not shipped) with `CREATE_NEW_CONSOLE`; logs `FAILED PATCH ON %s` on failure; then forwards | turns global auto-revive off, launches the patcher, logs `SUCCEED PATCH ON %s` (`Agent:0x14000a080`) |
| 7 | forward | if addressed to the agent's own code: toggle global auto-revive (+0x171) |
| 8, 9, 0xa | not forwarded (outside AgentManager's switch) | no-op |
| 0xb | not forwarded | toggle per-server auto-revive (reached only from other paths) |
| 0xc | **ordered start-all**: sets flag, main loop runs sequencer `0x140003d00`; not forwarded | no-op |
Sequencer (`0x140003d00`, one step per 100 ms tick; odd steps wait until every server of the group reports state 3, even steps send cmd 0 to stopped ones):
1–2 DBC kinds 12/17/18/19 → 3 `up_create_delete_table` (log DB) → 4 send cmd 0xd to every DBC_GAME (12) → 6–7 MSG/GATE/MANAGER/AUCTION/MATCH (6,8,9,10,11) → 8–9 GM/Guild (7,5) → 10–11 Lobby (1) → 12–13 Dungeon/PvP (2,3) → 14 done.
### RMI tables
| ID | Agent → AgentManager | ID | Monitor → AgentManager |
|---|---|---|---|
| 0x8599 | PT_SERVER_REGISTER | 0x8661 | REQ_AUTH |
| 0x859a | PT_SERVER_INFORMATION | 0x8662 | REQ_SERVER_INFORMATION |
| 0x859b | PT_SERVER_COMMAND_RESULT | 0x8663 | PT_SERVER_COMMAND |
| 0x859c | PWE_RESP_RESULT_CODE | 0x8664 | PT_SERVER_NOTICE |
| 0x859d | NOTIFY_STORAGE_ALARM | 0x8665 | PT_IP_BLOCK |
| 0x859e | NOTIFY_MAIL_ALARM | 0x8666 | PT_CHANNEL_STATUS_CHANGE |
| 0x859f | REQ_STATISTICS | 0x8667 | PT_USER_KICK |
| 0x85a0 | PT_MESSAGE | 0x8668 | PT_STRING_COMMAND |
| 0x85a1 | PT_PATCH_SERVER | 0x8669 | PT_REAL_TIME_EVENT |
| 0x85a2 | PT_BACKUP_CHARACTER_TABLE_COUNT | 0x866a | REQ_AGENT_UPDATE |
| 0x85a3 | PT_BACKUP_CHARACTER_TABLE_END | 0x866b | REQ_AGENT_SERVER_ON |
| | | 0x866c | REQ_AGENT_AUTO |
| ID | AgentManager → Agent | ID | AgentManager → Monitor |
|---|---|---|---|
| 0x85fd | PT_AGENT_DATA | 0x86c5 | RESP_AUTH |
| 0x85fe | PT_SERVER_OPTIONS | 0x86c6 | RESP_SERVER_OPTION |
| 0x85ff | PT_SERVER_NOTICE | 0x86c7 | RESP_SERVER_REGISTER |
| 0x8600 | PT_SERVER_COMMAND | 0x86c8 | RESP_SERVER_INFORMATION |
| 0x8601 | PT_SERVER_CONNECT | 0x86c9 | PT_SERVER_COMMAND_RESULT |
| 0x8602 | PT_REFRESH_SERVER_FILE | 0x86ca | RESP_REGISTER_MASTER |
| 0x8603 | PT_REFRESH_SERVER_OPTION | 0x86cb | RESP_MASTER_SERVER_COMMAND |
| 0x8604 | PT_CHANNEL_STATUS_CHANGE | 0x86cc | RESP_REAL_TIME_EVENT |
| 0x8605 | PT_USER_KICK | 0x86cd | NOTIFY_STORAGE_ALARM |
| 0x8606 | PT_CURRENT_USER_COUNT | 0x86ce | NOTIFY_MAIL_ALARM |
| 0x8607 | PT_STRING_COMMAND | 0x86cf | PT_MESSAGE |
| 0x8608 | PT_REAL_TIME_EVENT | 0x86d0 | PT_PATCH_SERVER |
| 0x8609 | PWE_REQ_PLAYER_KICK | 0x86d1 | PT_BACKUP_CHARACTER_TABLE_COUNT |
| 0x860a | PWE_REQ_PLAYER_BAN | 0x86d2 | PT_BACKUP_CHARACTER_TABLE_END |
| 0x860b | PWE_REQ_UPDATE_CASH | | |
| 0x860c | PWE_REQ_INSERT_ITEM | | |
| 0x860d | PWE_REQ_NOTICE | | |
Cross-check (resolved): Agent's `AgentManager2AgentStub` = 7 base slots + 17 RMI slots; its dispatcher (`Agent:0x14001d960`) handles exactly 0x85fd–0x860d with the same names. IDs match.
## Proposed names (not applied)
| Address | Name |
|---|---|
| `0x140026520` | `RNP::Agent2AgentManagerStub::ProcessReceivedMessage` |
| `0x14002c3c0` | `RNP::AgentMonitor2AgentManagerStub::ProcessReceivedMessage` |
| `0x140015290` | `MagiAgentManagerMonitorNetwork::REQ_AUTH` |
| `0x140009d00` | `AuthAgentAccount(account, password, &permission)` |
| `0x1400137f0` | `MagiAgentManagerNetwork::PT_SERVER_REGISTER` |
| `0x140015ac0` | `MagiAgentManagerMonitorNetwork::PT_SERVER_COMMAND` |
| `0x14001d850` | `AcquireSingleInstanceMutex` |
| `0x140074210` | `AdoConnection::BuildConnectionString` |
| `0x140001390` | `wmain` |
| `0x140002400` | `MagiAgentManagerServer::Init` (load options) |
| `0x140002790` | `MagiAgentManagerServer::Run` (DB load, networks, main loop) |
| `0x140031000` | `MagiServerOption::LoadServerXml` |
| `0x140032f70` | `MagiServerOption::LoadAgentManagerDb` |
| `0x14002fe40` | `MagiServerOption::ApplyServerOptionRow` |
| `0x140007a20` | `DbReadAgentManager` |
| `0x140005460` | `MagiAgentManagerNetwork::Start` |
| `0x140006380` | `MagiAgentManagerMonitorNetwork::Start` |
| `0x140003d00` | `StartAllSequencer::Step` |
| `0x140034220` | `WritePatchOptionFile` |
| `0x140003420` | `CConsoleControl::OnKey` |
## Remaining unknowns
- Exact meaning of codes 2, 5, 6, 0xd on each game-server kind (only DBC's flag handler was checked; its consumer of the 0xd flag was not traced).
- Whether ProudNet encryption is enabled at runtime (finding 17).
- Agent `update` string-command intent.
- `service_control.xml`: no reader found in this binary.