server-re/docs/SHARED_FRAMEWORK.md
2026-10-14 17:49:46 -04:00

12 KiB
Raw Blame History

Shared server framework ("Magi") — configuration and database access

The server EXEs share one in-house framework. Its classes are named Magi*, and the network sessions are MG*Session. This document defines the parts every binary shares:

  • server identity;
  • startup modes;
  • configuration files;
  • the path to the database.

Analyse each new binary against this document, and record in its own evidence.md where it differs, rather than re-deriving the framework.

Status: first pass. Primary evidence: MessageServer_Release_x64.exe (decomp/MessageServer_Release_x64/). Cross-binary checks: strings and RTTI only, unless a row says otherwise. Field offsets are from MessageServer. Other binaries share the same code, so they are expected to match, but this hasn't been verified. Every claim is static; nothing here was observed at runtime.

Notation: O = observed in decompiled code, S = observed in strings/RTTI only, I = inference, ? = unknown. MS:0x… means a function in decomp/MessageServer_Release_x64/full/functions/0x….c.


1. Framework presence across binaries (S)

Marker Present in
RTTI MagiServerOption, MagiCustomOption, MagiServerFile, MagiServerManager all 14 server EXEs. The two SMMonitoring EXEs only have MagiServerManager.
Option-loader keys (write_thread_count, each_option), the -AGNET MODE- banner [sic], and the DBC_CHAT/DBC_EMERGENCY names all 14 server EXEs; none in SMMonitoring
Servers2DbcProxy RTTI (client of the DBC servers) Auction, Dungeon, GameGateway, Game, GM, Guild, Manager, Message, PVP
ADO / _Connection strings (direct SQL access) DBCServer and DBCServer_LOG (4 hits each); 1–2 hits in Auction, Dungeon, Game, Guild, Manager and PVP (? — may be incidental)

Inference: only the DBC servers talk to SQL Server. Every other server reaches the database through ProudNet RMI to a DBC server (§5). This needs confirming by decompiling the ADO usage in the six non-DBC servers that have hits.

2. Server-kind enum (O, MS:0x140002930)

The one-byte server type is stored in the option object (+0x1e8) and in peer/session records. Names below are the wide strings the binary prints.

Value Name Value Name
1 Lobby 0x0c DBC_GAME
2 Dungeon 0x0d Agent
3 PVP 0x11 DBC_LOG
4 WAR 0x12 DBC_CHAT
5 Guild 0x13 DBC_EMERGENCY
6 Message 0x15 Agentmanager
7 GM 0x16 MiniGame
8 Gate 0x17 api peer (no display name; MS:0x14005dc20)
9 Manager 0x0e–0x10, 0x14 ?
0x0a Auction
0x0b Match

Separately, servers also have a numeric code (the instance ID). It appears in XML as code="61" and in file names such as MSG_61.xml. In the shipped deployment config every kind has one code: DBC_GAME=1, DBC_LOG=2, DBC_CHAT=3, DBC_EMERGENCY=4, Msg=61, GM=71, Gate=81, Manager=91, Auction=101, Match=111, Agent=800, AgentManager=900, Dungeon=2001, PvP=3003, Guild=5001, Lobby=21001 (S, binaries/Option/*.xml).

3. Startup modes (O, MS:0x14005c7b0)

MagiServerOption::Init(argv1) records the PID and the start time (+0x1ec, +0x1f0), then branches on the first character of the argument:

argv[1] Mode What is loaded
does not start with a digit standalone argv[1] is a path to a per-server XML file, which MS:0x14005dc20 parses (§4.1)
starts with a digit agent mode (prints -AGNET MODE-) atoi(argv1) is this server's code. MS:0x14005ceb0 reads option\total_option.xml (§4.3). The full server list then arrives from the Agent over RMI: the MGSAgentSession vftable slot 7 is MS:0x140065ab0. The entry whose code equals our code configures this server (MS:0x14005c9f0); every other entry is added as a peer (MS:0x14005ff50).

This matches the Agent side: the Agent launches a server with the numeric server code as its argument (decomp/Agent_Release_x64/evidence.md finding 3).

ServerInfo record pushed by the Agent (O, copy constructor MS:0x140065900; offsets in bytes):

Off Type Field (I)
+0x00 int world
+0x04 int code
+0x08 int ?
+0x0c u8 server kind (§2)
+0x10 int private_port
+0x14 int public_port
+0x18 int public_udpport
+0x1c int udp_count
+0x20 u8 ?
+0x28 std::wstring private_ip
+0x50 std::wstring public_ip
+0x78 std::wstring name

4. Configuration files

All of them are XML parsed with TinyXML 2.5.x (third-party/tinyxml-2.5.3). Paths are relative to the working directory, under option\. A sample deployment set ships in binaries/Option/.

That sample set contains placeholders such as YOUR_WAN_IP, DB_USER and DB_SERVER. It is probably a community deployment, not the original publisher files (I). It also contains plaintext DB passwords — never copy them into docs.

4.1 Per-server file — MagiServerOption (O, MS:0x14005dc20)

Default name per binary: option\<kind>_serveroption.xml. In MessageServer this is the global L"option\\msg_serveroption.xml" (MS:0x14012e5c0). Per-instance copies are <KIND>_<code>.xml, e.g. MSG_61.xml.

Top-level elements, the attributes read from each, and where the value is stored in the option object:

Element Attribute → field
server name → +0x08 wstr · code → +0x154 · private_ip → +0x30 wstr · private_port → +0x80 · public_ip → +0x58 wstr · public_port → +0x84 · public_udpport → +0x88 · udp_count → +0x8c · thread_num → +0x15c (clamped ≥1; defaults to 1 if missing) · agent → +0x150 (bool, =="1") · debug → +0x165 (bool) · world → +0x140 · private_dns / public_dns → override +0x30 / +0x58 (I: hostname form)
db_account, db_web, db_game, db_game_log string → +0x90 / +0xb8 / +0xe0 / +0x108 (std::string, ADO connection string) · timeout → +0x130 / +0x134 / +0x138 / +0x13c (MS:0x14005fa80)
dbc write_thread_count → +0x14c
db_cache session_count → +0x144 (also copied to +0x148)
db_log_cache session_count → +0x148
peer elements (table below) ip, port → peer map at +0x1a8, keyed by kind (MS:0x14005fb60)
dns dns → +0x151 (bool)
setting multi → +0x166 (bool) · lock → +0x168 (double)
auto id/value → +0x158 (auto-start flag)
start_server / revive_server / open_channel → +0x178 (double) / +0x171 (bool) / +0x170 (bool). The element-to-field pairing is from the order of assignments: I
send name, minlev, maxlev, group, type, id → ? (not yet traced)

Peer elements parsed by MessageServer, with the server kind each maps to:

Element Kind
agentmanager 0x15
db_cache 0x0c
db_log_cache 0x11
msg_server 6
gm_server 7
manager_server 9
auction_server 0x0a
match_server 0x0b
agent_server 0x0d
api 0x17

gate_server, dungeon_server, pvp_server, guild_server and lobby_server appear in the shipped msg_serveroption.xml, but MessageServer's loader never reads them (O: no string compare for them). Other binaries are expected to parse a different subset (I).

Mapping the db_* connection strings to schema files in schema/, by their Initial Catalog (S, sample config):

Element Database
db_account, db_web RustyHearts_Auth (server_info.ini points Web at RustyHearts_Account instead)
db_game RustyHearts
db_game_log RustyHearts_Log

4.2 option\customoption.xml — MagiCustomOption (O, ctor MS:0x140062820, parser MS:0x140062910)

The constructor initialises 16 boolean flags to 1 (+0x08..+0x17), then parses the file:

  • <log> section: log_account, log_item, log_pet, log_loginout, agentmanagers, agent, api, pvp, serveroption toggle per-category console logging. The parser prints LOGVIEWER Option [%d] - … View..
  • <connect> section: dbc_game, dbc_log, dbc_chat, dbc_emergency, auction, msg, manager, match, gm, gate, lobby, dungeon, guild, agentmanager, agent select which peers this server connects to. The parser prints Connect Option [%d] - ….
  • print: ?

The shipped customoption.xml has a different shape: flat log_*/dbc_* elements carrying string and timeout, which look like connection strings. It does not match the <log>/<connect> structure this parser expects. Either it was written for another binary, such as the DBCServer variants, or it isn't actually consumed (?). Resolve this when analysing DBCServer.

4.3 option\total_option.xml (O, MS:0x14005ceb0 agent mode; MS:0x14005c9f0 per-server apply)

This is a single combined file, used in agent mode.

  • MS:0x14005ceb0 (agent mode) reads agentmanager (peer kind 0x15), each_option, dns, code, guild, api, code_start, setting (multi, lock), start_server, revive_server and open_channel. It also reads the debug keys develop, preload, preload_char, enemy_debug, enemy_one, player_debug, lua_text and lua_send.
  • MS:0x14005c9f0 (per-server apply) reads, for DBC kinds, db, game, log, chat and emergency (each with string/timeout) plus count. The section chosen depends on the server kind: 0x0c→game, 0x11→log, …. For kind 5 (Guild) it sets the auto flag.

The shipped total_option.xml is empty apart from its XML declaration.

4.4 Other files referenced (S)

File Reference
option\agent.xml MS:0x14012e6d0, parsed by MS:0x1400602b0: <server …> entries and the agent peer (kind 0x0d)
option\patch.xml MS:0x14012e700; no reader traced
option\service_control.xml per-country auth/billing URLs and feature flags; reader not traced in MessageServer
log_control.xml, content_control.xml in binaries/Option/; readers unknown

5. Database access path

 Game/Guild/Msg/... server  --ProudNet RMI (SERVERS2DBC)-->  DBCServer[_LOG]  --ADO/SQLOLEDB-->  SQL Server
     MGDBCMsgSession pool  <--ProudNet RMI (DBC2SERVERS)--
  • Session pools (O, MS:0x140004f00 startup step DB_LOAD): MessageServer builds four pools of MGDBCMsgSession, one per DBC kind: 0x0c GAME, 0x11 LOG, 0x12 CHAT and 0x13 EMERGENCY. Each pool has db_cache session_count sessions (+0x144; MS:0x140009330).
    • Each session embeds an RNP::Servers2DbcProxy, a subclass of the PIDL-generated SERVERS2DBC_PROTOCOL::Proxy (MS:0x140008e20).
    • The main loop pumps the pools (DBCSessionManager->Process) and retries failed connects (RetryConnectSession).
  • Request protocol (O): SERVERS2DBC_PROTOCOL contains 262 RMIs, IDs 0x2ee1–0x2fe6. The full name→ID table, extracted from the proxy marshal functions, is in decomp/shared/rmi_from_MessageServer.tsv.
    • The proxy code is generated, so every client binary is expected to contain the same table (I). Verify by diffing the table extracted from each binary.
    • Name groups include UPDATE_* (62), READ_*, SAVE_*, INSERT_*, REQ_LOG_*, GUILD_*, MAIL_*, PET_*, CHARACTER_* and REQ_CHAT_LOG (0x2fb2).
  • Response protocol: RNP::Dbc2ServersStub has 85 slots. Its dispatcher MS:0x140039f20 failed to decompile (Ghidra headless timeout, and the REA call timed out too). Recover it from DBCServer's Dbc2Servers proxy instead.
  • SQL side: not analysed yet. Mapping RMI → stored procedure/table needs DBCServer, using schema/*.mdf to corroborate.

6. Other shared protocols seen in MessageServer (O)

Protocol IDs Direction
SERVERS2MSG (stub) / MSG2SERVERS (proxy) 0x3a99–0x3aa0 servers ↔ MessageServer (decomp/MessageServer_Release_x64/evidence.md)
SERVER2AGENT 12 proxy RMIs see the TSV
AGENT2SERVER (stub) ? Agent pushes ServerInfo (§3)

7. Open questions (priority order)

  1. Decompile DBCServer:
    • how it reads its DB connection strings;
    • how RMIs map to SQL (stored procedure names);
    • the Dbc2Servers response table.
  2. Extract the SERVERS2DBC table from a second client binary to confirm the protocol is identical across binaries.
  3. Explain the mismatch between the customoption.xml shape and its parser.
  4. Decode the semantics of the send, auto, lock and each_option elements, and of the api peer.
  5. Server kinds 0x0e–0x10 and 0x14.