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.
ps_game.exe uses a script VM that dispatches to named builtins by hash. The zone chat builders (0x1109–0x110B) are invoked by NPC/zone scripts via these hashed opcode identifiers. Understanding the dispatch table is essential for emulating server-side script chat and for setting breakpoints at the right call sites during live capture.
All addresses are relative to ps_game.exe MD5 91b212afbe6623382713772489dc82ce, ImageBase 0x00400000. Evidence from psgame-chat-native/script/Script_OpcodeDispatch_004a2210.c (Ghidra) cross-checked with objdump -d bin/ps_game.exe.
Script VM Overview
The script VM routes execution through four key functions:
| Symbol | VA | Role |
|---|
Script_OpcodeDispatch | 0x004A2210 | Main opcode dispatcher — dispatches by hash via vtable slot +4 on the NPC/script object (CMob). CMob ctor writes vtable 0x00572530 @ 0x004A0802. |
Script_InstrResolver | 0x004A5720 | Resolves hash → function pointer |
Script_ExecuteTick | 0x004A4710 | Tick execution loop — when instr+0x60 != NULL, calls builtin directly; otherwise dispatches via vtable |
Script_ArgFetcher | 0x004A3000 | Argument fetching for script operands |
Chat-Relevant Hashed Opcodes (CONFIRMED)
Only two hashes in Script_OpcodeDispatch reach SConnection_Send with opcode 0x1109. Opcodes 0x110A and 0x110B use builtin handler slots in the .data table, not hashed opcodes.
| Hash | Call site | Builder VA | Symbol | Wire opcode | Script args |
|---|
0x0B8820F2 | 0x004A22C6 | 0x004C6A80 | Chat_PacketBuilder_1109_A | 0x1109 flag 0 | 1× operand → message_id → ZoneChat_MessageResolver |
0x7C8C3F64 | 0x004A2640 | 0x004C6F50 | Chat_PacketBuilder_1109_B | 0x1109 flag 1 | 1× radius @ +4, then 1× message_id → spatial cell loop |
Wire format (both builders): u16le opcode=0x1109 · u8 flag · u32 charId (CUser+0x88) · u8 len · text[len] (len 1..0x7F after resolver, enforced @ 0x004C6ACE).
Builtin Wrappers: 0x110A and 0x110B (Not Hashed)
These are reached when Script_ExecuteTick @ 0x004A4710 finds instr+0x60 != NULL and calls the function pointer directly — not via the vtable hash dispatcher.
Builtin slot (.data) | Wrapper VA | Builder VA | Opcode | File |
|---|
0x00581F34 | 0x004CB3D0 | 0x004C8310 | 0x110A | script/Chat_ScriptWrapper_110A_004cb3d0.c |
0x00581F3C | 0x004CB430 | 0x004C8520 | 0x110B | script/Chat_ScriptWrapper_110B_004cb430.c |
Direct call rel32 xrefs: 0x004CB41F → 0x004C8310 · 0x004CB45E → 0x004C8520.
Chat_ScriptWrapper_110A @ 0x004CB3D0 applies or eax, 0xC00 to the message_id at 0x004CB402 before passing it to the builder. The wire field is a u16le. Map keys in cn_string.DB store raw script indices; the bias is applied only at send time.
Non-Chat Hashes in the Dispatch Table
Six additional hashes call functions in the 0x004C5xxx–0x004C6xxx range. None emit 0x11xx or call SConnection_Send @ 0x004ED0E0 (verified via objdump scan + Ghidra decompilation).
| Hash | Call site | Callee VA | Symbol | Role | Excluded because |
|---|
0x1FE5EC34 | 0x004A2397 | 0x004C69F0 | Script_FlagSetter_2FC | Writes CMob+0x2FC script flags | No network I/O |
0x2C14EF3C | 0x004A237A | 0x004C5D90 | Script_SpatialEntityPush | Spatial user loop → FUN_004B4010 entity effect | Entity FX / AI push, not chat packet |
0x3246B754 | 0x004A23B6 | 0x004C6BA0 | Script_SpatialRadiusEffect | Radius scan → position write +0xD2C | NPC movement / aggro, not chat |
0x353AB14E | 0x004A2494 | 0x004C6A20 | Script_ZoneTableBroadcast | Zone table row → FUN_00424A90 | Zone event table (no 0x11xx) |
0x540B160F | 0x004A250B | 0x004C5FD0 | Script_QuestDialogPush | World_FindUserById → FUN_004B9280 | Quest/dialog UI push |
0x8A1FEEF8 | 0x004A272B | 0x004C6EC0 | Script_ZonePush_0610 | local_c = 0x610 → Zone_PSendViewCells @ 0x00427CF0 | Zone stat push 0x0610, not chat |
Full Dispatch Hash Inventory
All param_2 == 0x… branches in Script_OpcodeDispatch_004a2210.c. The Chat column marks opcodes that affect the chat box, SysMsg, or zone chat push.
| Hash | Chat | Primary callee / effect |
|---|
0x0B8820F2 | ✅ | Chat_PacketBuilder_1109_A @ 0x004C6A80 |
0x7C8C3F64 | ✅ | Chat_PacketBuilder_1109_B @ 0x004C6F50 |
0x1FE5EC34 | — | Script_FlagSetter_2FC @ 0x004C69F0 |
0x2C14EF3C | — | Script_SpatialEntityPush @ 0x004C5D90 |
0x3246B754 | — | Script_SpatialRadiusEffect @ 0x004C6BA0 |
0x353AB14E | — | Script_ZoneTableBroadcast @ 0x004C6A20 |
0x540B160F | — | Script_QuestDialogPush @ 0x004C5FD0 |
0x8A1FEEF8 | — | Script_ZonePush_0610 @ 0x004C6EC0 |
0x7405AD04 | — | World_FindUserByName / FUN_004B4010 (entity attach) |
0x335CFA6F | — | Return script field @ +0x2D6 via FUN_004A15D0 |
0x0FF3A2FD | — | Read byte CMob+0x2D6 |
0x072A8C2F | — | Store CMob+0x2D8 |
0x08A6F4C3 | — | Store CMob+700 |
0x0C726665 | — | FUN_004B4C50 → return field |
0x327674E7 | — | Set bool CMob+0x2E8 |
0x368302A7 | — | Return CMob+0x1B0 if +0x1AC set |
0x36824CB7 | — | Return CMob+0xD5C |
0x38A1F081 | — | Store CMob+0x2F8 |
0x59CCB87B | — | Clear byte CMob+0x2DC |
0x603DACB4 | — | _printf debug |
0x6DD15366 | — | CMob+0x2FC = 0x01010101 |
0xBBFC6D86 | — | Return clamped CMob+0x2E0 |
0x8420F03A | — | Store CMob+0x2F0 |
0x74AB875E | — | Random heading / FUN_004A0330 (movement) |
0x899B2549 | — | Set byte CMob+0x2DC |
0xAE8B3FE1 | — | FUN_004B4330 bool |
0x9602FF4F | — | Store CMob+0x2E4 |
0xA45C668B | — | Read byte CMob+0x2D5 |
0xAEC1B010 | — | One-shot flag CMob+0x2D7 |
0xD0DFE84A | — | Store CMob+0x2EC |
0xD06189BE | — | Return float from FUN_004A02E0 |
0xC0785271 | — | Float compare gate |
0xC78B4620 | — | HP ratio return |
0xD0DFE20B | — | Store CMob+0x2F4 + FUN_004A0DB0 |
0xE9F3A815 | — | _rand() % 100 |
0xEE13EBAD | — | Copy position +0x7C..+0x84 → +0xD20 |
0xF0A049A9 | — | Clear CMob+0x2E0 |
Script-Driven Chat Call Graph
The two dispatch paths and two builtin wrapper paths for chat:
Script_ExecuteTick @ 0x004A4710
└─ [no instr+0x60 handler]
call [obj+4] → Script_OpcodeDispatch @ 0x004A2210
├─ hash 0x0B8820F2 @ 0x004A22C6
│ Script_ArgFetcher → Chat_PacketBuilder_1109_A @ 0x004C6A80
│ ZoneChat_MessageResolver @ 0x004C6970 → SConnection_Send (0x1109, flag=0)
└─ hash 0x7C8C3F64 @ 0x004A2640
Script_ArgFetcher ×2 → Chat_PacketBuilder_1109_B @ 0x004C6F50
ZoneChat_MessageResolver → spatial loop → SConnection_Send (0x1109, flag=1)
Script_ExecuteTick @ 0x004A4710
└─ [instr+0x60 set]
call builtin → Chat_ScriptWrapper_110A @ 0x004CB3D0
or eax, 0xC00 @ 0x004CB402
→ Chat_PacketBuilder_110A @ 0x004C8310 → SConnection_Send (0x110A)
call builtin → Chat_ScriptWrapper_110B @ 0x004CB430
→ Chat_PacketBuilder_110B @ 0x004C8520 → SConnection_Send (0x110B)
Verify Chat xrefs
# Expect exactly two calls into 1109 builders from dispatch
objdump -d bin/ps_game.exe | grep -E '4a22c6|4a2640|call.*4c6a80|call.*4c6f50'
# Regenerate full script chain decompilation
./tools/ghidra/decompile-psgame-chat.sh
# Subset: script chain only
"$GHIDRA_HOME/support/analyzeHeadless" tools/ghidra/project-psgame PsGameChat \
-scriptPath tools/ghidra/scripts -import bin/ps_game.exe \
-processor 'x86:LE:32:default' -overwrite -deleteProject \
-postScript ExportDecompileByAddress.java psgame-chat-native \
tools/ghidra/psgame-chat-script-chain.manifest
Assumptions and Limitations
- Hash values are build-specific identifiers for script VM opcodes, not wire opcodes.
- No additional hashed chat paths found beyond the two
0x1109 entries; 0x110A / 0x110B are not hashed.
FUN_004B4010 (called from hash 0x7405AD04 and Script_SpatialEntityPush) is entity-effect plumbing — not classified as chat without a 0x11xx send (confirmed absent).
- Dynamic validation: breakpoint on
SConnection_Send @ 0x004ED0E0 with filter *(u16*)buf == 0x1109 during NPC script ticks; backtrace should hit only 0x004C6A80 / 0x004C6F50 from Script_OpcodeDispatch.