server-re/docs/DATABASE.md

11 KiB
Raw Blame History

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