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.

ps_game.exe uses a script VM that dispatches to named builtins by hash. The zone chat builders (0x11090x110B) 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:
SymbolVARole
Script_OpcodeDispatch0x004A2210Main opcode dispatcher — dispatches by hash via vtable slot +4 on the NPC/script object (CMob). CMob ctor writes vtable 0x00572530 @ 0x004A0802.
Script_InstrResolver0x004A5720Resolves hash → function pointer
Script_ExecuteTick0x004A4710Tick execution loop — when instr+0x60 != NULL, calls builtin directly; otherwise dispatches via vtable
Script_ArgFetcher0x004A3000Argument 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.
HashCall siteBuilder VASymbolWire opcodeScript args
0x0B8820F20x004A22C60x004C6A80Chat_PacketBuilder_1109_A0x1109 flag 01× operand → message_idZoneChat_MessageResolver
0x7C8C3F640x004A26400x004C6F50Chat_PacketBuilder_1109_B0x1109 flag 11× 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 VABuilder VAOpcodeFile
0x00581F340x004CB3D00x004C83100x110Ascript/Chat_ScriptWrapper_110A_004cb3d0.c
0x00581F3C0x004CB4300x004C85200x110Bscript/Chat_ScriptWrapper_110B_004cb430.c
Direct call rel32 xrefs: 0x004CB41F0x004C8310 · 0x004CB45E0x004C8520.
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 0x004C5xxx0x004C6xxx range. None emit 0x11xx or call SConnection_Send @ 0x004ED0E0 (verified via objdump scan + Ghidra decompilation).
HashCall siteCallee VASymbolRoleExcluded because
0x1FE5EC340x004A23970x004C69F0Script_FlagSetter_2FCWrites CMob+0x2FC script flagsNo network I/O
0x2C14EF3C0x004A237A0x004C5D90Script_SpatialEntityPushSpatial user loop → FUN_004B4010 entity effectEntity FX / AI push, not chat packet
0x3246B7540x004A23B60x004C6BA0Script_SpatialRadiusEffectRadius scan → position write +0xD2CNPC movement / aggro, not chat
0x353AB14E0x004A24940x004C6A20Script_ZoneTableBroadcastZone table row → FUN_00424A90Zone event table (no 0x11xx)
0x540B160F0x004A250B0x004C5FD0Script_QuestDialogPushWorld_FindUserByIdFUN_004B9280Quest/dialog UI push
0x8A1FEEF80x004A272B0x004C6EC0Script_ZonePush_0610local_c = 0x610Zone_PSendViewCells @ 0x00427CF0Zone 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.
HashChatPrimary callee / effect
0x0B8820F2Chat_PacketBuilder_1109_A @ 0x004C6A80
0x7C8C3F64Chat_PacketBuilder_1109_B @ 0x004C6F50
0x1FE5EC34Script_FlagSetter_2FC @ 0x004C69F0
0x2C14EF3CScript_SpatialEntityPush @ 0x004C5D90
0x3246B754Script_SpatialRadiusEffect @ 0x004C6BA0
0x353AB14EScript_ZoneTableBroadcast @ 0x004C6A20
0x540B160FScript_QuestDialogPush @ 0x004C5FD0
0x8A1FEEF8Script_ZonePush_0610 @ 0x004C6EC0
0x7405AD04World_FindUserByName / FUN_004B4010 (entity attach)
0x335CFA6FReturn script field @ +0x2D6 via FUN_004A15D0
0x0FF3A2FDRead byte CMob+0x2D6
0x072A8C2FStore CMob+0x2D8
0x08A6F4C3Store CMob+700
0x0C726665FUN_004B4C50 → return field
0x327674E7Set bool CMob+0x2E8
0x368302A7Return CMob+0x1B0 if +0x1AC set
0x36824CB7Return CMob+0xD5C
0x38A1F081Store CMob+0x2F8
0x59CCB87BClear byte CMob+0x2DC
0x603DACB4_printf debug
0x6DD15366CMob+0x2FC = 0x01010101
0xBBFC6D86Return clamped CMob+0x2E0
0x8420F03AStore CMob+0x2F0
0x74AB875ERandom heading / FUN_004A0330 (movement)
0x899B2549Set byte CMob+0x2DC
0xAE8B3FE1FUN_004B4330 bool
0x9602FF4FStore CMob+0x2E4
0xA45C668BRead byte CMob+0x2D5
0xAEC1B010One-shot flag CMob+0x2D7
0xD0DFE84AStore CMob+0x2EC
0xD06189BEReturn float from FUN_004A02E0
0xC0785271Float compare gate
0xC78B4620HP ratio return
0xD0DFE20BStore CMob+0x2F4 + FUN_004A0DB0
0xE9F3A815_rand() % 100
0xEE13EBADCopy position +0x7C..+0x84+0xD20
0xF0A049A9Clear 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.

Build docs developers (and LLMs) love