Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ThalissonTMora/shaiya-chat-native-re/llms.txt

Use this file to discover all available pages before exploring further.

cn_string.DB is the binary message database read by ps_game.exe at startup, providing text strings for script-driven 0x1109 and 0x110A zone chat packets. The file lives at data/cn_string.DB relative to the server working directory and is an ASCII text file with alternating decimal integer IDs and text lines. It is not embedded in the PE — all strings appear only in runtime data after the loader runs.
All addresses below are relative to the confirmed binary: ps_game.exe MD5 91b212afbe6623382713772489dc82ce, ImageBase 0x00400000. The parallel Game.exe (MD5 c1edd96639ad81835624b9c4516ac781) loads an identical copy client-side.

File Format

The DB is a plain-text file consumed by ZoneChat_TableLoader @ 0x00408C70 via a paired fscanf / fgets loop. There is no binary header — it is pure ASCII.
<message_id_decimal>
<text line>
<message_id_decimal>
<text line>
...
The following table documents every constraint confirmed from decompilation:
RuleDetailTag
ID lineDecimal ASCII, consumed by fscanf(fp, "%d\n", &message_id)CONFIRMED
Text lineOne fgets per ID; trailing \n/\r\n kept then stripped by copyCONFIRMED
Empty textSkipped — no map insertCONFIRMED
fgets read buffer0x400 (1024) bytes per call @ 0x00408CE2CONFIRMED
Stored text cap0x80 (128) bytes — memset before copy @ 0x00408D31CONFIRMED
EncodingNot verified in binary — treat as 8-bit extended ASCII / locale bytesHYPOTHESIS

Heap Record Layout

For each entry the loader allocates 0x84 bytes:
// Temp heap record — ZoneChat_TableLoader @ 0x00408C70
struct ZoneChat_LoadRecord {
    u32  message_id;      // +0x00  key
    char text[0x80];      // +0x04  NUL-terminated; zero-filled before strcpy
};

Runtime Data Structures

MSVC RB-tree Node

The server stores entries in an MSVC std::map<u32, std::string> (RB-tree). Node layout confirmed from BST walk @ 0x004C7250, insert @ 0x0040DE10, and node alloc @ 0x0040E1C0:
// CONFIRMED — ps_game.exe @ 0x004C7250 / 0x0040E1C0 (MSVC std::_Tree node)
struct ZoneChat_MapNode {
    ZoneChat_MapNode* _Left;    // +0x00
    ZoneChat_MapNode* _Parent;  // +0x04
    ZoneChat_MapNode* _Right;   // +0x08
    u32               message_id; // +0x0C  (key; BST compare @ 0x004C7266)
    std::string       text;     // +0x10  (MSVC basic_string object)
    u8                _Color;   // +0x14  (RB red/black)
    u8                _Isnil;   // +0x15  (1 = sentinel)
    // node heap size: 24 bytes when allocated via FUN_004088B0 (ecx=1 → 24*ecx)
};

BSS Globals

SymbolVASectionRole
DAT_00587c440x00587c44BSSMap allocator / _Getal stub
DAT_00587c5c0x00587c5cBSSMap object → pointer to sentinel node
DAT_00587c600x00587c60BSSmap.size() mirror; inc @ 0x0040DE9E

Server Loading

ZoneChat_TableLoader @ 0x00408C70

Called at startup via callers at 0x00409EE0, 0x0040D45E, 0x00474590, 0x004818DC, 0x00481977, 0x00481A64, 0x00553780 (server init chain). Returns false (al=0) if fopen fails, true (al=1) after successful read loop.
// Pseudocode from decompilation + asm (Ghidra @ 0x00408C70)
bool ZoneChat_TableLoader(void) {
    FILE *fp = fopen("data/cn_string.DB", "rt");  // path @ 0x0056F410
    if (!fp) return false;
    for (;;) {
        u32 message_id;
        if (fscanf(fp, "%d\n", &message_id) == EOF) break;  // format @ 0x0056F424
        char line[0x400];
        memset(line, 0, sizeof(line));
        fgets(line, 0x400, fp);
        if (strlen(line) == 0) continue;          // empty fgets row — no insert
        auto *rec = (u32*)operator_new(0x84);
        memset(rec + 1, 0, 0x80);                 // text field cap
        *rec = message_id;
        strcpy((char*)(rec + 1), line);
        ZoneChat_MapInsert(&iter, &pair{message_id, rec});
    }
    fclose(fp);
    return true;
}

Map Initialization Chain

SymbolVARole
ZoneChat_MapInit0x00407E40 (entry 0x00407E20, SEH)Allocates allocator stub + sentinel node; sets DAT_00587c60 = 0
ZoneChat_MapInsert0x0040DCE0MSVC RB-tree emplace
ZoneChat_TableLoader0x00408C70Reads cn_string.DB, inserts all valid entries

Message Resolution

ZoneChat_MessageResolver @ 0x004C6970

Calling convention: __stdcall, 1× u32 argument, ret 4.
// Resolver call — e.g. from Chat_PacketBuilder_1109_A @ 0x004C6AA8
void* str_obj = ZoneChat_MessageResolver(message_id);
if (!str_obj) return;
char* text = (char*)str_obj + 4;   // NUL-terminated; len 1..0x7F enforced
The resolver calls ZoneChat_MessageLookup @ 0x004C71D0 which performs a BST lower_bound(message_id) walk:
// BST walk @ 0x004C7250
root = [DAT_00587c5c + 4]           // _Parent of header = tree root
while (!node->_Isnil):
    if node->message_id >= key: best = node; node = node->_Left
    else:                         node = node->_Right
return {header, best}
If the iterator equals the sentinel (node == DAT_00587c5c), the resolver returns 0 (not found).

Client Counterpart

Game.exe loads a parallel copy of cn_string.DB and resolves IDs via:
  • GetMsg @ 0x00420DB0FUN_004163C0 @ 0x004163C0
  • Map root at DAT_007c3b4c
The client performs all tag expansion (<p>, <t>, <s>, etc.) — the server stores raw strings only.

0x110A Message ID Encoding

The script builtin Chat_ScriptWrapper_110A @ 0x004CB3D0 applies an or eax, 0xC00 bias to the message_id at 0x004CB402 before calling the builder. The wire field is a u16le.
PathID on wire / in mapEvidence
0x1109 buildersScript operand as-is (no 0xC00 OR in builder)Chat_PacketBuilder_1109_* calls resolver with raw script arg @ 0x004C6A80
0x110A builderu16le on wire; wrapper or eax, 0xC00 @ 0x004CB402 before builderCONFIRMED
Client 0x110A recvGetMsg(u16 message_id) @ 0x00420DB0same map on client
Text is only present on the wire for 0x1109 (Pattern D). For 0x110A the server sends only message_id — the client looks up the display string from its own copy of cn_string.DB via GetMsg.

Stock Fixture Entries

The vendored stock DB (test/fixtures/cn_string_stock.db) comes from PSM_Client/Bin/Data/cn_string.DB (31 entries, Nov 2023). IDs start at 1, not 0xC00 — the script wrappers apply the bias at send time, and the map stores raw script indices for this build.
message_idtext (truncated)
1Pullback to Heretics!
2Li laka Amon-Ra
3I’ll remove your flesh and bones!!
(28 more — see test/fixtures/cn_string_stock_parsed.tsv)

Offline Parser: parse_cn_string_db.py

The tool at tools/zonechat/parse_cn_string_db.py replicates the exact loader semantics — fgets max 0x400, stored text cap 0x80 — and outputs a message_id → text mapping.

Usage

# Parse stock DB — text output (default)
python3 tools/zonechat/parse_cn_string_db.py test/fixtures/cn_string_stock.db

# TSV output (message_id TAB text)
python3 tools/zonechat/parse_cn_string_db.py test/fixtures/cn_string_stock.db --format tsv

# JSON output, first 20 entries
python3 tools/zonechat/parse_cn_string_db.py test/fixtures/cn_string_stock.db --format json --limit 20

# Point at a live server tree
python3 tools/zonechat/parse_cn_string_db.py /path/to/server/data/cn_string.DB --format tsv

Output Example

# entries: 31 (fgets max 1024, stored max 128)
1: Pullback to Heretics!
2: Li laka Amon-Ra
3: I'll remove your flesh and bones!!
...

Fixtures

PathDescription
test/fixtures/cn_string_stock.db31-entry stock DB from ShaiyaServer mount
test/fixtures/cn_string_stock_parsed.tsvPre-parsed TSV output from stock DB
test/fixtures/cn_string_sample.dbSynthetic fixture for parser unit tests only — not a stock DB

Emulator Guidance

  1. Load data/cn_string.DB at startup using the same paired fscanf / fgets loop.
  2. Expose resolve(message_id) -> Option<&str> backed by HashMap<u32, String> or BTreeMap.
  3. Pattern D (0x1109): resolve script ID → UTF-8/legacy bytes → enforce 1 ≤ len ≤ 0x7F (builder @ 0x004C6ACE).
  4. Pattern E (0x110A): accept u16 message_id on wire; client calls GetMsg(id) — script path ORs 0xC00 before send; ensure map keys align with client expectations.
  5. Tag expansion (<p>, <t>, <s>, etc.) is client-side only via GetMsg @ 0x00420DB0.

Reproduce Decompilation

# Full loader chain
objdump -d -M intel --start-address=0x408c70 --stop-address=0x408da0 bin/ps_game.exe
objdump -d -M intel --start-address=0x407e20 --stop-address=0x407ea8 bin/ps_game.exe
objdump -d -M intel --start-address=0x40dce0 --stop-address=0x40ddd0 bin/ps_game.exe
objdump -d -M intel --start-address=0x4c6970 --stop-address=0x4c7290 bin/ps_game.exe
strings -a bin/ps_game.exe | grep cn_string

Build docs developers (and LLMs) love