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
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? **?**).
|
||||
Loading…
Add table
Add a link
Reference in a new issue