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.

Shaiya Core V9 runs Windows-only binaries (ps_game.exe and Game.exe), so live capture and runtime debugging must be performed on Windows — either a native Windows host or WSL2 with the server running under Windows. Wireshark provides network-layer PCAP capture, while x64dbg gives plaintext access at the exact send/recv boundary before any encryption layer.
All VAs below are relative to confirmed binaries: ps_game.exe MD5 91b212afbe6623382713772489dc82ce and Game.exe MD5 c1edd96639ad81835624b9c4516ac781, both with ImageBase 0x00400000. Re-base addresses if you are using a different build.

Confirmed Paths on WSL / Windows

ArtifactWindows path
Server binaryC:\ShaiyaServer\PSM_Client\Bin\ps_game.exe
Client dataC:\ShaiyaServer\PSM_Client\Bin\Data\cn_string.DB
Server DLLC:\ShaiyaServer\PSM_Client\Bin\sdev.dll
Server logsC:\ShaiyaServer\PSM_Client\Bin\Log\PS_GAME__system.log__*.log
Session capturesC:\ShaiyaServer\ps_session_redis\captures\*.log
When working from WSL2, substitute C:\ with /mnt/c/.

Wireshark

Capture Filter

Set a BPF capture filter before starting capture. Replace 30800 with your actual game port (check Server.ini / connect.cfg):
tcp port 30800
For login traffic (0xA101):
tcp port 30900
1

Select the correct interface

Open Wireshark. Select the network interface that carries traffic between client and server (NIC, loopback for local, or a TAP adapter).
2

Apply the capture filter

In the capture filter bar, enter:
host SERVER_IP and tcp port 30800
This limits capture to only Shaiya game traffic.
3

Start capture and trigger a packet

Click Start, then trigger a chat event in the game client. For guilds, send any guild message. For NPC push, trigger the relevant NPC dialog or zone script event.
4

Apply a display filter to find opcodes

After stopping capture, use a Wireshark display filter:
tcp.payload[0:2] == 11:04
This matches guild chat (0x1104 in little-endian). For normal chat: tcp.payload[0:2] == 01:11.
5

Export the PCAP slice

Use File → Export Specified Packets to save a .pcapng slice. Name it with the build MD5, e.g.:
captures/guild_chat_91b212af_20260526.pcapng
If the session is encrypted (XOR / AES layer), Wireshark shows ciphertext. Use x64dbg hooks at the plaintext boundary (SConnection_Send on server, PacketPayload_Decrypt on client) instead of Wireshark for reading chat field values.

Loopback Capture

If client and server run on the same host, enable loopback capture:
  1. Install Npcap with loopback adapter support.
  2. In Wireshark, select Npcap Loopback Adapter as the interface.
  3. Apply capture filter tcp port 30800.

x64dbg — Server (ps_game.exe)

Attaching

1

Launch ps_game.exe

Start ps_game.exe normally (with sdev.dll in the same directory if required).
2

Attach x64dbg

Open x64dbg (32-bit). Go to File → Attach and select ps_game.exe from the process list.
3

Set the SConnection_Send breakpoint

In the command bar:
bp 004ED0E0
This is SConnection_Send — all outbound chat packets pass through here.

Reading Packet Buffers

On every hit at 0x004ED0E0 (stdcall, 32-bit calling convention):
Stack offsetValue
[ESP+4]SConnection* pointer
[ESP+8]Payload buffer pointer
[ESP+0xC]Payload size
Log commands:
?po [ESP+8]
?po [ESP+C]
?by [ESP+8] l min([ESP+C],40)

Key Breakpoints for Chat Analysis

VASymbolPurpose
0x004ED0E0SConnection_SendAll outbound packets — read [ESP+8] for payload
0x004C6970ZoneChat_MessageResolverFires when a zone message is resolved; [ESP+4] = message_id
0x00408C70ZoneChat_TableLoaderFires at startup when cn_string.DB is loaded
0x004A2210Script_OpcodeDispatchFires per script opcode tick
0x004CB3D0Chat_ScriptWrapper_110AFires when a 0x110A builtin is invoked; watch or eax, 0xC00 @ 0x004CB402

Filtering by Opcode

Add a conditional breakpoint at 0x004ED0E0 that only breaks for guild packets:
bp 004ED0E0
bpcnd 004ED0E0, "w(ptr([ESP+8])) == 0x1104"
For zone chat push:
bpcnd 004ED0E0, "w(ptr([ESP+8])) == 0x1109"

Capturing the char[21] Name Tail (D1)

At 0x004ED0E0 with a guild packet (0x1104):
?by [buf+2] l 21
Where buf = [ESP+8]. Bytes at offset strlen(name)+1 through 20 are the tail. Compare against the static inference from build_pattern_b_packet.py --tail stock.

x64dbg — Client (Game.exe)

Attaching

1

Launch Game.exe

Start Game.exe and log in to reach the character select or in-game state.
2

Attach x64dbg (32-bit)

Go to File → Attach, select Game.exe.
3

Set recv dispatch breakpoint

bp 005F1E10
This is PacketDispatcher — all inbound S→C packets are dispatched here.

Key Client Breakpoints

VASymbolPurpose
0x00401080PacketPayload_DecryptFires on inbound packet decryption — read plaintext before dispatch
0x005F1E10PacketDispatcherAll S→C packet dispatch
0x005ED060Send chatFires when player sends a chat message
0x00420DB0GetMsgFires when client resolves a cn_string.DB message ID (e.g., after receiving 0x110A)
0x00404610Crypto_PRNGFillFires during login PRNG fill — dump 128-byte buffer for D3
0x005E3D60Handler_Packet_A101_KeyMaterialFires on 0xA101 recv — cross-reference with PRNG capture for D3

Reading Inbound Packets at PacketPayload_Decrypt

bp 00401080
On hit, the buffer pointer and size are available in registers or stack — check calling convention. This is the cleanest tap for plaintext bytes before any dispatch.

Triggering Specific Packets

D4 — NPC Script Push (0x1109 / 0x110A)

1

Enter an NPC-populated zone

Travel to a zone with scripted NPC events (any zone with roaming NPCs using chat scripts).
2

Wait for or force a script tick

Server script dispatch fires via Script_ExecuteTick @ 0x004A4710. Set a breakpoint at 0x004C6A80 (Chat_PacketBuilder_1109_A) and wait, or force the event through GM commands.
3

Read the builder arguments

At 0x004C6A80, [ESP+4] = message_id passed to ZoneChat_MessageResolver @ 0x004C6970.
4

Capture the SConnection_Send hit

The builder calls SConnection_Send @ 0x004ED0E0 with size = len + 8. Read the full 0x1109 payload hex.

D5 — GM Tool Notice (0xF108)

0xF108 is only sent by the GM tool binary — it is outside Game.exe stock. Triggering it requires the separate GM client binary. Capture is the same as other Pattern B packets once triggered.

D3 — 0xA101 Counter (PRNG Seed)

1

Set breakpoints for PRNG and A101 handler

In x64dbg (32-bit) attached to Game.exe, set both breakpoints:
bp 00404610   ; Crypto_PRNGFill — fires when PRNG buffer is filled
bp 005E3D60   ; Handler_Packet_A101_KeyMaterial — fires on A101 recv
2

Log in with client

Trigger login. On hit at 0x00404610, dump the 128-byte PRNG buffer that is being filled:
?by prng_buf_ptr l 128
On hit at 0x005E3D60, confirm the 0xA101 handler is reached — correlate the timing with the Wireshark capture.
3

Capture the 0xA101 packet

Simultaneously capture the login port (30900) in Wireshark to get the full 197-byte 0xA101 plaintext.
4

Validate offline

python3 tools/crypto/validate_a101_counter.py \
  --hex <197-byte-hex> \
  --prng-hex <128-byte-prng-hex>

sdev.dll and Injected DLLs

sdev.dll on the server implements the Genesis Chat engine (GenesisChatEngine) using opcodes in the 0x13xx range. These are not the same as the stock wire 0x11xx opcodes — there is no automatic translation without an explicit bridge. For the client side, custom DLL hooks in custom_chat_native_bridge affect UI/render only and do not automatically log NetworkSend. To add wire logging via a DLL, hook at:
// Server: log before SConnection_Send @ 0x004ED0E0
// Client: log after PacketPayload_Decrypt, or at NetworkSend @ 0x005EA9A0

Post-Session: Scan and Copy Logs

After a capture session, scan all logs and copy new captures into the repo:
# Scan all captured logs in the ShaiyaServer tree
./tools/artifacts/find_missing.sh \
  /mnt/c/ShaiyaServer/PSM_Client/Bin \
  /mnt/c/ShaiyaServer/ps_session_redis/captures

python3 tools/wire/scan_capture_logs.py \
  /mnt/c/ShaiyaServer/ps_session_redis/captures

# Copy live capture into repo with date stamp
cp /mnt/c/ShaiyaServer/PSM_Client/Bin/chat_capture_live.log \
   test/captures/live_$(date +%Y%m%d).log
Then validate D1 padding on the new capture:
python3 tools/wire/validate_d1_padding.py test/captures/live_$(date +%Y%m%d).log

Matching Against Static Analysis Findings

Once you have a live capture, cross-reference against the static analysis documents:
  • Packet layouts: compare field offsets against docs/PACKET_SPEC.md and docs/CHAT_CHANNEL_MAP.md.
  • Builder VAs: confirm that backtraces from SConnection_Send hit the expected builders (0x004C6A80, 0x004C6F50, 0x004C8310, 0x004C8520).
  • cn_string.DB IDs: correlate script operand N with DB id N vs N+0xC00 for one known NPC line to confirm the bias constant.
  • Tail bytes: compare D1 tail hex from live capture against build_pattern_b_packet.py --tail stock output.

Build docs developers (and LLMs) love