decomp DBCServer_Release and inspect/document database
This commit is contained in:
parent
4b267cbe05
commit
7834b98bf0
28222 changed files with 843300 additions and 144 deletions
219
docs/CONFIG.md
Normal file
219
docs/CONFIG.md
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
# Configuration — definitions shared by all server binaries
|
||||
|
||||
Every source of configuration the `Magi*` framework reads:
|
||||
- per-server XML files;
|
||||
- combined agent-mode XML;
|
||||
- custom-option XML;
|
||||
- **database-backed topology**.
|
||||
|
||||
Notation as in `SHARED_FRAMEWORK.md`. Field offsets are into the
|
||||
`MagiServerOption` object, a single global (`DAT_140353190` in DBC). They are
|
||||
**identical in MessageServer (`MS:`) and DBCServer (`DBC:`)** — O, compared
|
||||
loader-by-loader. That makes it very likely shared by every binary (I).
|
||||
|
||||
A sample deployment set ships in `binaries/Option/`. It contains
|
||||
**plaintext DB passwords**; never copy values from it into docs or code.
|
||||
|
||||
---
|
||||
|
||||
## 1. Which file a server reads (O, `MS:0x14005c7b0`)
|
||||
|
||||
| How launched | Mode | Files |
|
||||
|---|---|---|
|
||||
| `Server.exe <path>` (argv[1] not numeric) | standalone | `<path>` → per-server XML (§2) |
|
||||
| `Server.exe <code>` (argv[1] numeric) | agent mode (`-AGNET MODE-`) | `option\total_option.xml` (§3); then the server list pushed by the Agent (§5) |
|
||||
| always | | `option\customoption.xml` (§4) |
|
||||
|
||||
Default per-server file names (S): `option\<kind>_serveroption.xml`, e.g.
|
||||
`msg_serveroption.xml` (MS), `dbc_serveroption.xml` (DBC). The shipped
|
||||
per-instance copies use the pattern `<KIND>_<code>.xml`, e.g. `MSG_61.xml` and
|
||||
`DBC_CHAT_3.xml`. Other file names referenced in every binary are
|
||||
`option\agent.xml`, `option\patch.xml` and `option\service_control.xml`.
|
||||
DBCServer also contains `option\server_info.ini` (§6).
|
||||
|
||||
## 2. Per-server XML (`MagiServerOption`) — schema
|
||||
|
||||
Loader: `MS:0x14005dc20`, `DBC:0x140156650`. Each top-level element is looked
|
||||
up by name. A missing element or attribute leaves the default in place.
|
||||
|
||||
```xml
|
||||
<server name="MSG_61" code="61" world="10101" agent="1" debug="0"
|
||||
private_ip="127.0.0.1" private_port="51052" private_dns=""
|
||||
public_ip="" public_port="0" public_udpport="0" public_dns=""
|
||||
udp_count="0" thread_num="2" />
|
||||
<dbc write_thread_count="3" /> <!-- DBC servers -->
|
||||
<db_account string="Provider=SQLOLEDB;…" timeout="5000"/> <!-- DBC servers -->
|
||||
<db_web string="…" timeout="…"/>
|
||||
<db_game string="…" timeout="…"/>
|
||||
<db_game_log string="…" timeout="…"/>
|
||||
<db_cache ip="…" port="51055" code="1" session_count="5"/>
|
||||
<db_log_cache ip="…" port="51056" code="2" session_count="5"/>
|
||||
<agentmanager ip port code send_time_second/>
|
||||
<agent_server|msg_server|gm_server|manager_server|auction_server|match_server ip port code/>
|
||||
<api ip port/>
|
||||
<dns dns="0"/>
|
||||
<setting multi="1" lock="…"/>
|
||||
<auto id="…" value="1"/> <start_server …/> <revive_server …/> <open_channel …/>
|
||||
<send name minlev maxlev group type id/>
|
||||
```
|
||||
|
||||
| Element.attribute | Field | Type | Meaning (I unless noted) |
|
||||
|---|---|---|---|
|
||||
| `server.name` | +0x08 | wstring | display name |
|
||||
| `server.code` | +0x154 | int | instance code. Primary key of `ServerOption` (§5) |
|
||||
| `server.world` | +0x140 | int | world ID (`WorldServer.World`, e.g. 10101) |
|
||||
| `server.private_ip` / `private_dns` | +0x30 | wstring | listen address; `_dns` overrides |
|
||||
| `server.private_port` | +0x80 | int | listen port (ProudNet TCP) |
|
||||
| `server.public_ip` / `public_dns` | +0x58 | wstring | address advertised to clients |
|
||||
| `server.public_port` | +0x84 | int | |
|
||||
| `server.public_udpport` | +0x88 | int | |
|
||||
| `server.udp_count` | +0x8c | int | |
|
||||
| `server.thread_num` | +0x15c | int | worker threads, clamped ≥1, default 1 (O) |
|
||||
| `server.agent` | +0x150 | bool (`=="1"`) | launched under the Agent |
|
||||
| `server.debug` | +0x165 | bool | |
|
||||
| `db_account.string` / `.timeout` | +0x90 / +0x130 | string / int | DBC conn. type 2 "AUTH" → `RustyHearts_Auth`. **The timeout is ignored; type 2 uses +0x134** (O, original bug) |
|
||||
| `db_web.*` | +0xb8 / +0x134 | | type 1 "WEB" → `RustyHearts_Account` (sample `server_info.ini`) or `_Auth` (sample XML) |
|
||||
| `db_game.*` | +0xe0 / +0x138 | | type 3 "GAME" → `RustyHearts` |
|
||||
| `db_game_log.*` | +0x108 / +0x13c | | type 4 "GAME LOG" → `RustyHearts_Log` |
|
||||
| `dbc.write_thread_count` | +0x14c | int | DBC write-queue worker threads |
|
||||
| `db_cache.session_count` | +0x144 (and +0x148) | int | sessions per DBC pool on the client side (MS creates 4 pools of this size) |
|
||||
| `db_log_cache.session_count` | +0x148 | int | |
|
||||
| peer elements `ip`,`port` | map at +0x1a8, keyed by kind | | `MS:0x14005fb60` |
|
||||
| `dns.dns` | +0x151 | bool | |
|
||||
| `setting.multi` / `.lock` | +0x166 / +0x168 | bool / double | |
|
||||
| `auto.value` | +0x158 | int(bool) | auto-start |
|
||||
| `start_server` / `revive_server` / `open_channel` | +0x178 / +0x171 / +0x170 | double / bool / bool | delayed auto start, auto-revive, open channels. Agent finding 5 links +0x171 to relaunch-on-exit (I) |
|
||||
| `send.*` | ? | | not traced |
|
||||
|
||||
DBC-only children (`DBC:0x140156650`): `guildlobby`, `channel`, and an
|
||||
`Option` element. Their purpose is **?**.
|
||||
|
||||
Connection strings longer than 255 characters are rejected. A timeout of 0
|
||||
becomes 500 ms (O, `DBC:0x1400a7aa0`).
|
||||
|
||||
Peer elements and 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 |
|
||||
|
||||
MessageServer and DBCServer **do not parse** `gate_server`, `dungeon_server`,
|
||||
`pvp_server`, `guild_server` or `lobby_server`, although the sample files
|
||||
contain them (O).
|
||||
|
||||
## 3. `option\total_option.xml` (agent mode)
|
||||
|
||||
Loaders: `MS:0x14005ceb0` / `MS:0x14005c9f0`; DBC per-kind apply at
|
||||
`DBC:0x140155420`.
|
||||
|
||||
The global part contains `agentmanager`, `each_option`, `dns`, `code`, `guild`,
|
||||
`api`, `code_start`, `setting` (`multi`, `lock`), `start_server`,
|
||||
`revive_server` and `open_channel`. It also contains debug switches: `develop`,
|
||||
`preload`, `preload_char`, `enemy_debug`, `enemy_one`, `player_debug`,
|
||||
`lua_text` and `lua_send`.
|
||||
|
||||
DB sections are chosen by **this server's kind** (O, `DBC:0x140155420`):
|
||||
|
||||
```xml
|
||||
<db>
|
||||
<game> <!-- kind 0x0c DBC_GAME; 0x11→<log>, 0x12→<chat>, 0x13→<emergency> -->
|
||||
<db_account string="…" timeout="…"/> <!-- → +0x90/+0x130 -->
|
||||
<db_web string="…" timeout="…"/> <!-- → +0xb8/+0x134 -->
|
||||
<db_game string="…" timeout="…"/> <!-- → +0xe0/+0x138 -->
|
||||
<db_log string="…" timeout="…"/> <!-- → +0x108/+0x13c (= db_game_log in §2) -->
|
||||
<write_thread count="3"/> <!-- → +0x14c -->
|
||||
</game>
|
||||
</db>
|
||||
```
|
||||
|
||||
For kind 5 (Guild) the per-kind apply only sets the auto flag (+0x158).
|
||||
|
||||
The shipped `total_option.xml` is empty, which means the sample deployment
|
||||
runs every server standalone (I).
|
||||
|
||||
## 4. `option\customoption.xml` (`MagiCustomOption`)
|
||||
|
||||
Constructor `MS:0x140062820`; parser `MS:0x140062910` = `DBC:0x14015ca50`, the
|
||||
same code in both.
|
||||
|
||||
```xml
|
||||
<log> <log_account/> <log_item/> <log_pet/> <log_loginout/> <agentmanagers/>
|
||||
<agent/> <api/> <pvp/> <serveroption/> </log>
|
||||
<connect> <dbc_game/> <dbc_log/> <dbc_chat/> <dbc_emergency/> <auction/> <msg/>
|
||||
<manager/> <match/> <gm/> <gate/> <lobby/> <dungeon/> <guild/>
|
||||
<agentmanager/> <agent/> </connect>
|
||||
<print …/>
|
||||
```
|
||||
|
||||
All 16 flags default to **on**. The parser only looks for `<log>` and
|
||||
`<connect>` children, toggling the console log categories and the peers to
|
||||
connect to.
|
||||
|
||||
**The shipped `customoption.xml` has no `<log>`/`<connect>` element.** It
|
||||
contains flat `log_*`/`dbc_*` elements with connection strings. Under both
|
||||
parsers these are ignored, so every flag stays on (O for the parser; the
|
||||
effect is I).
|
||||
|
||||
## 5. Database-backed topology (`RustyHearts_Auth`, D)
|
||||
|
||||
The authoritative server list lives in the database, not in XML:
|
||||
|
||||
| Table | Columns | Shipped content |
|
||||
|---|---|---|
|
||||
| `ServerOption` | `World, AgentCode, Code (PK), Type, PrivateAddress, PrivatePort, PublicAddress, PublicPort, UdpPort, UdpCount, SessionCount, TypeMemo, Useable` | 14 rows. One per server, all under Agent 800, world 10101 |
|
||||
| `ServerOptionDNS` | same, hostnames | |
|
||||
| `ServerFile` | `type → filename` | server kind → EXE name |
|
||||
| `WorldServer` | `World, Name, UseAble` | 10101 "Ragezone" |
|
||||
| `ChannelOption` | `Code, CH_Group, CH_Type, MinLevel, MaxLevel, CH_NUM, WorldID` | empty. Matches the `send`/`open_channel` attributes `minlev`/`maxlev`/`group`/`type` (I) |
|
||||
| `GlobalVariableTable` | `VarName, Value` | `LastAuthID`, `LastGMID` |
|
||||
|
||||
How these tables relate to the binaries:
|
||||
- `up_read_all_serveroption` (`SELECT * FROM ServerOption WHERE Useable='Y'`)
|
||||
and `up_read_agentmanager` appear only in **AgentManager** (S).
|
||||
- The probable flow (I) is: AgentManager reads `ServerOption` and `ServerFile`,
|
||||
then pushes them to each Agent. The Agent launches `ServerFile.filename`
|
||||
with the `Code` as argument, then pushes each row to the server as the
|
||||
ServerInfo record (`SHARED_FRAMEWORK.md` §3, whose fields line up 1:1 with
|
||||
`ServerOption` columns).
|
||||
- To confirm this, decompile AgentManager.
|
||||
|
||||
`ServerOption.Type` uses the **server-kind enum** (D, cross-checked against
|
||||
`MS:0x140002930`):
|
||||
|
||||
| Type | TypeMemo | Code | EXE (`ServerFile`) |
|
||||
|---|---|---|---|
|
||||
| 1 | Lobby | 21001 | **GameServer_Release_USA_x64.exe** |
|
||||
| 2 | DUNGEON | 2001 | DungeonServer |
|
||||
| 3 | Pvp | 3003 | PVPServer |
|
||||
| 5 | Guild | 5001 | GuildServer |
|
||||
| 6 | MSG | 61 | MessageServer |
|
||||
| 7 | GM | 71 | GMServer |
|
||||
| 8 | GATE | 81 | GameGatewayServer |
|
||||
| 9 | MANAGER | 91 | ManagerServer |
|
||||
| 10 | Auction | 101 | AuctionServer |
|
||||
| 11 | Match | 111 | MatchServer |
|
||||
| 12 | DBC | 1 | DBCServer |
|
||||
| 13 | Agent | 800 | Agent |
|
||||
| 17 | DBC_LOG | 2 | DBCServer_LOG |
|
||||
| 21 | AgentManager | 900 | AgentManager |
|
||||
| 24 | — | — | PatchServer_Release_x64.exe (**not shipped**) |
|
||||
|
||||
So the "Lobby" kind *is* the GameServer binary.
|
||||
|
||||
## 6. Other files
|
||||
|
||||
| File | Status |
|
||||
|---|---|
|
||||
| `server_info.ini` | The string is in DBCServer, but there is no INI API import (`GetPrivateProfile*`) and no code reference was found. It is **probably unused** (I) |
|
||||
| `agent.xml` | `<server>` list and the agent peer (`MS:0x1400602b0`) |
|
||||
| `service_control.xml` | per-country `auth_url`, `billing_url`, feature flags (S). Reader not yet traced |
|
||||
| `patch.xml`, `log_control.xml`, `content_control.xml` | readers unknown |
|
||||
187
docs/DATABASE.md
Normal file
187
docs/DATABASE.md
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
# Database — schema and access path
|
||||
|
||||
This document covers:
|
||||
- how the servers reach SQL Server;
|
||||
- which database each request lands in;
|
||||
- the schema of the five databases.
|
||||
|
||||
**Sources:**
|
||||
- `DBCServer_Release_x64.exe`, decompiled (`decomp/DBCServer_Release_x64/`);
|
||||
- `MessageServer_Release_x64.exe`, the client side;
|
||||
- a catalog dump of `schema/*.mdf` (`decomp/schema/`).
|
||||
|
||||
Notation as in `SHARED_FRAMEWORK.md`: **O** observed in code, **D** observed in
|
||||
the schema dump, **S** strings only, **I** inference, **?** unknown. `DBC:0x…`
|
||||
refers to `decomp/DBCServer_Release_x64/full/functions/0x….c`.
|
||||
|
||||
Everything here is static. No server was run against the database.
|
||||
|
||||
---
|
||||
|
||||
## 1. Databases (D)
|
||||
|
||||
| Catalog | Config element (DBC) | DBC conn. type | Tables | Routines | Role |
|
||||
|---|---|---|---|---|---|
|
||||
| `RustyHearts` | `db_game` | 3 GAME | 63 | 273 | Characters, items, guilds, mail, pets, quests, base data tables (`Base_*`) |
|
||||
| `RustyHearts_Auth` | `db_account` | 2 AUTH | 14 | 51 | Game-side auth (`AuthTable`, keyed by `WindyCode`), **server topology** (`ServerOption`, `ServerFile`, `WorldServer`, `ChannelOption`), events, inquiries, GM accounts |
|
||||
| `RustyHearts_Log` | `db_game_log` | 4 GAME LOG | 5 static | 110 | Logs. Most log tables are created **per day** by `Local_Create*Table` procedures, e.g. `Chat_Log_<y>_<m>_<d>`, so they are not in the static catalog |
|
||||
| `RustyHearts_Account` | `db_web` (I) | 1 WEB | 4 | 11 | Web/launcher accounts (`AccountTable`, `CashTable`, `BillingLog`, `VerificationCode`) |
|
||||
| `GMRustyHearts` | — | — | 8 | 1 | GM tool database (`GMInfoTable`, `GMAudit`, `AdminRoles`). No server binary analysed so far references it (**?**: GMServer / tools) |
|
||||
|
||||
Facts about the dump:
|
||||
- **No foreign keys** are declared in any database (D). Relationships are by
|
||||
convention: `AuthID`/`auth_id` (uniqueidentifier), `character_id`,
|
||||
`bcust_id` = `WindyCode` (account name), `item_uid`, `guild_id`.
|
||||
- The `.mdf` files are in **SQL Server 2022** on-disk format (version 957).
|
||||
SQL Server 2019 refuses to attach them. This contradicts the "up to 2019"
|
||||
note in `AGENTS.md` for these *files*, not necessarily for the servers
|
||||
(I: the dump was last attached on a 2022 instance).
|
||||
- Several procedures carry **Korean comments** (D), which points to the
|
||||
original developer's code (I). Others reference `WindyCode` (the original
|
||||
publisher's account ID) and the linked servers `RustyHeartsLinkAuth` /
|
||||
`RustyHearts_LogDB`, which `up_init_auth_link` / `up_init_log_link` create.
|
||||
- Some procedures are **empty bodies**: 7 in RustyHearts, 2 in Auth and 6 in
|
||||
Log (`decomp/schema/*/empty_procs.txt`). An example is
|
||||
`up_insert_log_connection`. Calls to these succeed but do nothing (I: they
|
||||
were stubbed out in this deployment).
|
||||
|
||||
Full per-database dumps are in `decomp/schema/<db>/`: `tables.tsv` (name, row
|
||||
count, column count), `columns.tsv`, `indexes.tsv`, `routines.tsv`
|
||||
(parameters) and `procs/*.sql`. `decomp/schema/proc_tables.tsv` lists the
|
||||
tables each procedure reads (R) or writes (W) and its cross-DB references.
|
||||
That list is derived from the SQL text, so it is **I** for dynamic SQL. How to
|
||||
regenerate everything: `decomp/schema/README.md`.
|
||||
|
||||
### Core tables (D)
|
||||
|
||||
| Table | Key | Notes |
|
||||
|---|---|---|
|
||||
| `RustyHearts.CharacterTable` | `character_id` | 53 cols: `AuthID`, `bcust_id`, `server`, `Name`, `Class`, `Job`, `Level`, `Experience`, `gold`, `GuildID`, `LobbyID`, … Written by 57 procedures. `CharacterTable_DELETE` holds soft-deleted characters |
|
||||
| `RustyHearts.N_InventoryItem` / `N_EquipItem` / `N_MailItem` / `tbl_Account_Storage` / `tbl_Guild_Storage_Item` / `tbl_Pet_Inventory` | `item_uid` | Share one item-row layout (41–44 cols: `code`, `page_index`, `slot_index`, `durability`, `enhance_level`, 3 options, sockets, …). "Moving" an item means moving its row between these tables |
|
||||
| `RustyHearts.KeyMapTable` | `AuthID` | Per-account key bindings (100 cols) |
|
||||
| `RustyHearts.QuickSlotExTable[02/03]` | `character_id` | Quick slots, 65 cols |
|
||||
| `RustyHearts.GuildTable` / `GuildMemberTable` | `guild_id` / `index` | |
|
||||
| `RustyHearts.MailTable` | `ID` | |
|
||||
| `RustyHearts.PetTable`, `RearingTable` | `pet_id`, `rearing_id` | |
|
||||
| `RustyHearts.Base_*` | various | Static game data (items 22.5k rows, quests, skills, exp tables) |
|
||||
| `RustyHearts_Auth.AuthTable` | `WindyCode` | `AuthID`, `online`, `ServerIP`, `ServerType`, `HostID`, `DBCIndex`, cash/mileage, MAC addresses, `second_pass` |
|
||||
| `RustyHearts_Auth.ServerOption` | `Code` | Server topology. See `CONFIG.md` §5 |
|
||||
| `RustyHearts_Auth.ServerFile` | `type` | Server kind → EXE file name |
|
||||
| `RustyHearts_Account.AccountTable` | `AccountID` | Web login (`AccountPwd`, `Token`) |
|
||||
|
||||
## 2. Access path
|
||||
|
||||
```
|
||||
client servers (Game/Guild/Msg/…) DBCServer
|
||||
MGDBCMsgSession pool ──SERVERS2DBC RMI 0x2ee1–0x2fe6──▶ CDBCServer stub handler
|
||||
│ push {size, cmd 1001–1317, args}
|
||||
▼ (1 of N write queues, idx<1000)
|
||||
CDBLobbyWriteCmdQueue worker
|
||||
│ switch(cmd) DBC:0x1400a8190
|
||||
▼
|
||||
CDBLobbyWriteQuery::DBReqXxx
|
||||
│ ADODB.Command "dbo.up_xxx"
|
||||
▼
|
||||
SQL Server (one of 4 connections)
|
||||
◀──DBC2SERVERS RMI 0x2af9–0x2b46 (76 replies)───────────┘
|
||||
```
|
||||
|
||||
| Step | Type | Evidence |
|
||||
|---|---|---|
|
||||
| Only the two DBC servers hold ADO connections. Other servers send RMIs | O for MessageServer; I for the others (they show `Servers2DbcProxy` RTTI) | `SHARED_FRAMEWORK.md` §1, §5 |
|
||||
| `SERVERS2DBC` is byte-for-byte the same 262-entry name/ID table in MessageServer and DBCServer | O (diffed) | `decomp/shared/rmi_from_MessageServer.tsv`, `DBC:0x140041b90` (ID→name) |
|
||||
| Stub handler, e.g. `DBC:0x140050d20`: increments a per-RMI counter, then calls a `CDBCServer` method that packs a command `{size, cmd_id, …}` and pushes it to write queue `[idx]`, signalling a semaphore (`DBC:0x140144730`) | O | |
|
||||
| Worker dispatch `DBC:0x1400a8190`: `switch (cmd - 1001)` over 317 cases through a jump table at `0x1400a9dd4`. Ghidra could not decompile it; decoded from the raw jump table | O (disassembly) | `decomp/DBCServer_Release_x64/cmd_dispatch.tsv` |
|
||||
| Each `DBReq*` method runs a single stored procedure through `ADODB.Command`, with named parameters (`bcust_id`, `auth_id`, …). On COM error it logs `ADO_ON_ERROR : [..] Proc [..]` with source file and line | O | `DBC:0x1400acbb0` (`up_create_character`), catch funclets |
|
||||
| Original source files: 28 `DBLobbyWriteQuery<Area>.cpp` (Character 176 call sites, Guild 72, Item 43, User 30, Pet 28, …) and `DBWriteQueryServerOption.cpp` | O (embedded `__FILE__` strings) | `decomp/DBCServer_Release_x64/dbreq_methods.tsv` |
|
||||
|
||||
### Connections (O, `DBC:0x1400a7aa0`, REA `ev_00545a3e…`)
|
||||
|
||||
`CDBConnection::Init(type)` copies a connection string from the global option
|
||||
object into the connection:
|
||||
|
||||
| type | Option field | Config element | Console label | Timeout field |
|
||||
|---|---|---|---|---|
|
||||
| 1 | +0xb8 | `db_web` | `WEB` | +0x134 |
|
||||
| 2 | +0x90 | `db_account` | `AUTH` | **+0x134** (the `db_web` timeout. The `db_account` timeout at +0x130 is never used: an original bug, O) |
|
||||
| 3 | +0xe0 | `db_game` | `GAME` | +0x138 |
|
||||
| 4 | +0x108 | `db_game_log` | `GAME LOG` | +0x13c |
|
||||
|
||||
Further details:
|
||||
- Connection strings are limited to 255 characters (`Error … String Max.!`).
|
||||
- A timeout of 0 becomes 500.
|
||||
- On failure it logs `DB Connect failed\n Connection String = %s` to
|
||||
`Error_ADO DB Connect.Log` (`DBC:0x1400a80c0`).
|
||||
- Startup (`DBC:0x140042cf0`) opens a type-2 (AUTH) and a type-3 (GAME)
|
||||
connection for the server itself. It also allocates write-queue arrays of
|
||||
125 slots.
|
||||
|
||||
**Which connection each procedure uses?** The connection object passed to each
|
||||
`DBReq` method hasn't been traced yet. The **I** answer is: the database that
|
||||
contains the procedure (column `proc_db` below). That holds for 210 of 210
|
||||
mapped RMIs, with 7 exceptions that aren't in the schema dump (§3).
|
||||
|
||||
## 3. RMI → stored procedure map
|
||||
|
||||
`decomp/DBCServer_Release_x64/rmi_to_proc.tsv` has one row per SERVERS2DBC
|
||||
RMI. Its columns are: `rmi_id`, `rmi_name`, `stub_slot`, `stub_handler`,
|
||||
`cmd_id`, `dbreq_fn`, `procs`, `dbreq_method`, `source_cpp` and `proc_db`.
|
||||
|
||||
| Outcome | RMIs |
|
||||
|---|---|
|
||||
| Resolved to a procedure in `RustyHearts` | 166 |
|
||||
| … in `RustyHearts_Auth` | 21 |
|
||||
| … in `RustyHearts_Log` | 16 |
|
||||
| Procedure **not present** in the schema dump | 7 |
|
||||
| Not resolved | 52 |
|
||||
|
||||
The 7 procedures missing from the dump are `up_item_move_pet_inventory_to_inventory`,
|
||||
`up_read_item_inventory_slot`, `up_read_extern_cash`,
|
||||
`up_insert_log_learnguildskill`, `up_insert_log_cash_info`,
|
||||
`up_insert_log_auth_login` and `up_skill_delete_all`. In this deployment, those
|
||||
RMIs would fail with an ADO error (I).
|
||||
|
||||
Of the 52 unresolved RMIs:
|
||||
- Most are `REQ_LOG_*`, `*_LOG`, `REQ_SAVE_ITEM_*`, `REQ_MAIL_SEND`,
|
||||
`REQ_LOGOUT` and `REQ_AUTH`.
|
||||
- 53 RMIs have no command ID in the stub. They are handled directly or
|
||||
forwarded, probably to DBCServer_LOG for logs (I).
|
||||
- Others reach a `DBReq` function that calls the procedure deeper than two
|
||||
call levels.
|
||||
|
||||
**How the mapping was built:**
|
||||
1. Stub vtable slot k ↔ RMI ID `0x2ee1 + (k-7)`. This is the PIDL convention,
|
||||
verified on MessageServer (slots 7–14 = 0x3a99–0x3aa0) and by name
|
||||
agreement, e.g. `REQ_READ_ALL_GUILD`→`up_read_all_guild`.
|
||||
2. Stub handler → command ID (constant in the packing call).
|
||||
3. Command ID → `DBReq` function (jump table).
|
||||
4. `DBReq` function → `"dbo.up_…"` literal.
|
||||
5. Procedure → database (schema dump).
|
||||
|
||||
Steps 1–4 are **O**, but slot ordering is **I**; it was checked by name
|
||||
agreement rather than decoded from the failed stub dispatcher `DBC:0x14000e4d0`.
|
||||
|
||||
DBCServer embeds 297 `dbo.*` names. 273 match a procedure in the dump
|
||||
case-sensitively; 10 more differ only in case (`*Inquiry*` vs `*inquiry*`,
|
||||
harmless on a case-insensitive collation). The remaining 14 are absent from the
|
||||
dump. Seven of them are reachable through mapped RMIs (listed above).
|
||||
|
||||
## 4. Response protocol (O)
|
||||
|
||||
`DBC2SERVERS_PROTOCOL::Proxy` in DBCServer has 76 RMIs, IDs 0x2af9–0x2b46.
|
||||
`decomp/DBCServer_Release_x64/rmi_tables.tsv` lists them. This recovers the
|
||||
table that failed to decompile in MessageServer (`Dbc2ServersStub`, 85 slots =
|
||||
7 base + 78).
|
||||
|
||||
## 5. Open questions
|
||||
|
||||
1. Trace which connection object each `DBReq` receives (per-queue connections
|
||||
vs. the two startup connections). Explain `db_log` / `log` elements, which
|
||||
are parsed but have no matching connection type.
|
||||
2. Resolve the 52 unmapped RMIs. Decode `DBC:0x14000e4d0` from its jump table,
|
||||
as was done for the worker dispatcher.
|
||||
3. Analyse `DBCServer_LOG_Release_x64.exe`, the expected owner of the `LOG`
|
||||
RMIs and of RustyHearts_Log.
|
||||
4. Map `DBC2SERVERS` replies to their requests.
|
||||
5. Find who uses `GMRustyHearts` and `RustyHearts_Account` (web/launcher,
|
||||
outside these binaries? **?**).
|
||||
|
|
@ -44,7 +44,7 @@ peer/session records. Names below are the wide strings the binary prints.
|
|||
|
||||
| Value | Name | Value | Name |
|
||||
|---|---|---|---|
|
||||
| 1 | Lobby | 0x0c | DBC_GAME |
|
||||
| 1 | Lobby (= GameServer EXE, D: `ServerFile`) | 0x0c | DBC_GAME |
|
||||
| 2 | Dungeon | 0x0d | Agent |
|
||||
| 3 | PVP | 0x11 | DBC_LOG |
|
||||
| 4 | WAR | 0x12 | DBC_CHAT |
|
||||
|
|
@ -54,7 +54,10 @@ peer/session records. Names below are the wide strings the binary prints.
|
|||
| 8 | Gate | 0x17 | `api` peer (no display name; `MS:0x14005dc20`) |
|
||||
| 9 | Manager | 0x0e–0x10, 0x14 | ? |
|
||||
| 0x0a | Auction | | |
|
||||
| 0x0b | Match | | |
|
||||
| 0x0b | Match | 0x18 (24) | PatchServer (D: `ServerFile` only; not shipped) |
|
||||
|
||||
The same numbers are stored in `RustyHearts_Auth.ServerOption.Type`, and
|
||||
`ServerFile` maps each one to an EXE (D; see `CONFIG.md` §5).
|
||||
|
||||
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
|
||||
|
|
@ -95,139 +98,27 @@ server code as its argument (`decomp/Agent_Release_x64/evidence.md` finding 3).
|
|||
|
||||
## 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/`.
|
||||
Moved to [`CONFIG.md`](CONFIG.md). It covers:
|
||||
- the per-server XML schema with field offsets;
|
||||
- `total_option.xml` and `customoption.xml`;
|
||||
- the database-backed server topology (`RustyHearts_Auth.ServerOption` /
|
||||
`ServerFile`).
|
||||
|
||||
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 |
|
||||
The `MagiServerOption` field layout matches between MessageServer and
|
||||
DBCServer (O).
|
||||
|
||||
## 5. Database access path
|
||||
|
||||
```
|
||||
Game/Guild/Msg/... server --ProudNet RMI (SERVERS2DBC)--> DBCServer[_LOG] --ADO/SQLOLEDB--> SQL Server
|
||||
MGDBCMsgSession pool <--ProudNet RMI (DBC2SERVERS)--
|
||||
```
|
||||
Moved to [`DATABASE.md`](DATABASE.md). It covers the RMI → command queue →
|
||||
`DBReq*` → stored procedure chain, the four connection types, the
|
||||
RMI → procedure map and the schema of the five databases.
|
||||
|
||||
- **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.
|
||||
Summary:
|
||||
- **Clients and connections:** client servers keep 4 `MGDBCMsgSession` pools
|
||||
(GAME/LOG/CHAT/EMERGENCY), sized by `db_cache session_count`. They send
|
||||
`SERVERS2DBC` RMIs (262, IDs 0x2ee1–0x2fe6); this table is **identical in
|
||||
MessageServer and DBCServer**, O. DBCServer runs stored procedures over ADO
|
||||
and answers with `DBC2SERVERS` (76 RMIs, 0x2af9–0x2b46).
|
||||
|
||||
## 6. Other shared protocols seen in MessageServer (O)
|
||||
|
||||
|
|
@ -239,13 +130,10 @@ The shipped `total_option.xml` is empty apart from its XML declaration.
|
|||
|
||||
## 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.
|
||||
1. AgentManager: confirm that it reads `ServerOption`/`ServerFile` and pushes
|
||||
the topology (`CONFIG.md` §5).
|
||||
2. DBCServer_LOG: the LOG RMIs and RustyHearts_Log (`DATABASE.md` §5).
|
||||
3. Diff the `SERVERS2DBC` table in the remaining client binaries.
|
||||
4. Semantics of `send`, `auto`, `lock`, `each_option`, `guildlobby`,
|
||||
`channel`, and of the `api` peer.
|
||||
5. Server kinds 0x0e–0x10 and 0x14.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue