12 KiB
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,serveroptiontoggle per-category console logging. The parser printsLOGVIEWER Option [%d] - … View..<connect>section:dbc_game,dbc_log,dbc_chat,dbc_emergency,auction,msg,manager,match,gm,gate,lobby,dungeon,guild,agentmanager,agentselect which peers this server connects to. The parser printsConnect 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) readsagentmanager(peer kind 0x15),each_option,dns,code,guild,api,code_start,setting(multi,lock),start_server,revive_serverandopen_channel. It also reads the debug keysdevelop,preload,preload_char,enemy_debug,enemy_one,player_debug,lua_textandlua_send.MS:0x14005c9f0(per-server apply) reads, for DBC kinds,db,game,log,chatandemergency(each withstring/timeout) pluscount. 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:0x140004f00startup stepDB_LOAD): MessageServer builds four pools ofMGDBCMsgSession, one per DBC kind: 0x0c GAME, 0x11 LOG, 0x12 CHAT and 0x13 EMERGENCY. Each pool hasdb_cache session_countsessions (+0x144;MS:0x140009330).- Each session embeds an
RNP::Servers2DbcProxy, a subclass of the PIDL-generatedSERVERS2DBC_PROTOCOL::Proxy(MS:0x140008e20). - The main loop pumps the pools (
DBCSessionManager->Process) and retries failed connects (RetryConnectSession).
- Each session embeds an
- Request protocol (O):
SERVERS2DBC_PROTOCOLcontains 262 RMIs, IDs 0x2ee1–0x2fe6. The full name→ID table, extracted from the proxy marshal functions, is indecomp/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_*andREQ_CHAT_LOG(0x2fb2).
- Response protocol:
RNP::Dbc2ServersStubhas 85 slots. Its dispatcherMS:0x140039f20failed to decompile (Ghidra headless timeout, and the REA call timed out too). Recover it from DBCServer'sDbc2Serversproxy instead. - SQL side: not analysed yet. Mapping RMI → stored procedure/table needs
DBCServer, using
schema/*.mdfto 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)
- Decompile DBCServer:
- how it reads its DB connection strings;
- how RMIs map to SQL (stored procedure names);
- the
Dbc2Serversresponse table.
- Extract the
SERVERS2DBCtable from a second client binary to confirm the protocol is identical across binaries. - Explain the mismatch between the
customoption.xmlshape and its parser. - Decode the semantics of the
send,auto,lockandeach_optionelements, and of theapipeer. - Server kinds 0x0e–0x10 and 0x14.