server-re/docs/DATABASE.md

187 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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? **?**).