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.
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:
| Rule | Detail | Tag |
|---|
| ID line | Decimal ASCII, consumed by fscanf(fp, "%d\n", &message_id) | CONFIRMED |
| Text line | One fgets per ID; trailing \n/\r\n kept then stripped by copy | CONFIRMED |
| Empty text | Skipped — no map insert | CONFIRMED |
fgets read buffer | 0x400 (1024) bytes per call @ 0x00408CE2 | CONFIRMED |
| Stored text cap | 0x80 (128) bytes — memset before copy @ 0x00408D31 | CONFIRMED |
| Encoding | Not verified in binary — treat as 8-bit extended ASCII / locale bytes | HYPOTHESIS |
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
| Symbol | VA | Section | Role |
|---|
DAT_00587c44 | 0x00587c44 | BSS | Map allocator / _Getal stub |
DAT_00587c5c | 0x00587c5c | BSS | Map object → pointer to sentinel node |
DAT_00587c60 | 0x00587c60 | BSS | map.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
| Symbol | VA | Role |
|---|
ZoneChat_MapInit | 0x00407E40 (entry 0x00407E20, SEH) | Allocates allocator stub + sentinel node; sets DAT_00587c60 = 0 |
ZoneChat_MapInsert | 0x0040DCE0 | MSVC RB-tree emplace |
ZoneChat_TableLoader | 0x00408C70 | Reads 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 @ 0x00420DB0 → FUN_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.
| Path | ID on wire / in map | Evidence |
|---|
0x1109 builders | Script operand as-is (no 0xC00 OR in builder) | Chat_PacketBuilder_1109_* calls resolver with raw script arg @ 0x004C6A80 |
0x110A builder | u16le on wire; wrapper or eax, 0xC00 @ 0x004CB402 before builder | CONFIRMED |
Client 0x110A recv | GetMsg(u16 message_id) @ 0x00420DB0 | same 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_id | text (truncated) |
|---|
| 1 | Pullback to Heretics! |
| 2 | Li laka Amon-Ra |
| 3 | I’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
| Path | Description |
|---|
test/fixtures/cn_string_stock.db | 31-entry stock DB from ShaiyaServer mount |
test/fixtures/cn_string_stock_parsed.tsv | Pre-parsed TSV output from stock DB |
test/fixtures/cn_string_sample.db | Synthetic fixture for parser unit tests only — not a stock DB |
Emulator Guidance
- Load
data/cn_string.DB at startup using the same paired fscanf / fgets loop.
- Expose
resolve(message_id) -> Option<&str> backed by HashMap<u32, String> or BTreeMap.
- Pattern D (
0x1109): resolve script ID → UTF-8/legacy bytes → enforce 1 ≤ len ≤ 0x7F (builder @ 0x004C6ACE).
- 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.
- 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