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.

Stock Shaiya Core V9 server builders copy the player name into a stack-allocated char[21] using a null-terminated do/while loop, but never call memset on that 21-byte region before sending. The wire send size is always strlen(name) + 0x18 — meaning the full 21-byte name field is placed on the wire every time. As a result, bytes [strlen(name)..20] are uninitialized stack residue rather than zero padding. This page presents the static evidence for that claim, documents the simulation tooling, and explains what emulator authors must account for when replaying or generating Pattern B chat packets.

Pattern B Paths — Stock Behavior

Every Pattern B chat builder in the V9 binary follows the same pattern: copy name via a null-terminated loop, skip any tail memset, and send with a fixed size that includes the entire 21-byte field.
The send size len + 0x18 (or len + 0x19 for some variants) accounts for the full 21-byte char[21] name field on every path. No Pattern B path conditionally shortens the name segment.
PathBuilder / VATail zeroed?Key addresses
Guild 0x1104Chat_BroadcastGuild @ 0x00432530Nocopy @ 0x004325B0, send @ 0x00432615
Admin guild 0xF104AdminChat_BuildGuildPacket @ 0x00432770Nosend @ 0x00432818
GM guild relay— @ 0x004070BC / 0x0040714ENo
Megaphone 0x1108Chat_ProcessIncomingChat_BroadcastNamedNocopy @ 0x0047F580, add edi,0x18 @ 0x0047F5A3
Trade 0x1103Chat_ProcessIncoming case 0x1103Nocopy @ 0x0047F760, queue flush
Area 0x1111Chat_ProcessIncoming case 0x1111Nocopy @ 0x0047FBD0
Monitor 0xF502Chat_BuildOutgoing_F502 @ 0x0047F260Nocopy @ 0x0047F2B0, send @ 0x0047F32C

Static Evidence

The table below lists every material claim in this analysis together with its verification status. All confirmations were produced by static disassembly of the V9 ps_game.exe and Game.exe binaries; no runtime execution was required.
ClaimStatusBasis
Send size includes all 21 name bytes (len+0x18 / len+0x19 everywhere)CONFIRMEDSize literal present at every Pattern B send callsite
Server does NOT memset name tail before SConnection_SendCONFIRMED5 chat-tagged Pattern B direct sends; 0 memset / rep stos found in 80-instruction window before each send
MSVC prologue does NOT zero stack (sub esp,N + cookie xor only; no rep stos before copy loop)CONFIRMEDPrologue disassembly on all 5 builders
Trade/area builders also skip tail memset (0x1103 copy @ 0x0047F760, 0x1111 copy @ 0x0047FBD0)CONFIRMED absentNo memset / rep stos in 80-insn window
Client recv pre-zeros its local name bufferCONFIRMEDHandler_ChatGuild @ 0x005E5310: xor eax,eax + 5× mov [esp+…],eax @ 0x005E53240x005E5342, executed before PacketRead_String
No memset / rep stos after call 0x005F4780 @ 0x005E534F; wire bytes [0..20] land verbatim in client bufferCONFIRMED absentDisassembly of post-call window
Bytes [strlen..20] are NOT zeroINFERREDNo code path on server zeroes the tail
Bytes [strlen..20] are prior-stack residue (non-zero, ~99% static confidence)INFERREDMSVC stack frame reuse; no zeroing observed
Stock client UI does NOT break on non-zero tailINFERREDChatWhisperTradeGuildZoneMega_vfn @ 0x0059BDB0 scans until NUL; the early NUL at name[strlen] hides all tail bytes
CONFIRMED rows are based on direct disassembly evidence — specific VA ranges were inspected and the claimed instruction (or its absence) was verified.
INFERRED rows are static inferences, not runtime observations. The ~99% confidence figure reflects the near-certainty that MSVC reuses prior stack frames without zeroing, but a wire capture is the only way to pin the exact byte distribution in production traffic. See Emulator Guidance below.

Simulation Tool — build_pattern_b_packet.py

The script tools/padding/build_pattern_b_packet.py constructs a Pattern B chat packet with three selectable tail-fill strategies, allowing emulators and test harnesses to reproduce or diverge from stock server behavior.

Modes

  • zero — tail bytes [strlen..20] are 0x00. Produces a well-formed C-string but does not match stock server output.
  • garbage — tail bytes are filled with a deterministic pseudo-random sequence seeded by --seed. Best approximation of stock uninitialized stack residue.
  • stock — tail bytes are filled with a repeating 0xCC-like pattern (MSVC debug stack fill). An approximation; use a wire capture to pin real values.

Usage

# Emulator-safe: intentional zero padding
python3 tools/padding/build_pattern_b_packet.py \
  --name "TestUser" \
  --text "hello" \
  --tail zero

# Garbage tail — closest static approximation of stock
python3 tools/padding/build_pattern_b_packet.py \
  --name "TestUser" \
  --text "hello" \
  --tail garbage --seed 0xDEADBEEF

# Stock approximation — repeating 0xCC-style stack fill
python3 tools/padding/build_pattern_b_packet.py \
  --name "TestUser" \
  --text "hello" \
  --tail stock

Expected Outcomes

Tail modeBytes [strlen..20]Stock server match?Stock client UI
zero0x00…No (stock does not memset)OK (valid C-string)
garbageNon-zero randomYes (static inference)OK if name[strlen] == '\0'
stockRepeating 0xCC-like stack fillApproximation onlyOK if early NUL present

Reproduce the Static Scan

Use the steps below to independently verify the claims in the Static Evidence table.
1

Scan all direct Pattern B chat sends

Run the bundled scanner against the server binary. It identifies every chat-tagged Pattern B SConnection_Send callsite and reports whether a memset or rep stos appears in the preceding 80 instructions.
python3 tools/padding/scan_pattern_b_sends.py bin/ps_game.exe
2

Inspect the guild builder (no rep stos between copy and send)

Disassemble the Chat_BroadcastGuild function. Confirm the copy loop at 0x004325B0, the absence of any rep stos or memset call, and the send at 0x00432615.
objdump -d --start-address=0x432530 --stop-address=0x432620 bin/ps_game.exe
3

Inspect the client recv pre-zero sequence

Disassemble Handler_ChatGuild. Confirm the xor eax,eax + five mov [esp+…],eax instructions at 0x005E53240x005E5342 occur before the PacketRead_String call, and that no memset follows the call at 0x005E534F.
objdump -d --start-address=0x5e5310 --stop-address=0x5e53a0 bin/Game.exe

Emulator Guidance

Safer interop (recommended for new emulator code): Call memset(name, 0, 21) before strncpy. This is not byte-for-byte stock-identical, but it is safe, deterministic, and the client UI handles it correctly because ChatWhisperTradeGuildZoneMega_vfn stops at the first NUL.
char name[21];
memset(name, 0, sizeof(name));
strncpy(name, source_name, 20);
/* name[20] is guaranteed NUL by memset */
Byte-for-byte stock replay: Preserve the garbage tail from the original captured packet, or reproduce it with build_pattern_b_packet.py --tail garbage --seed <captured_seed>. The client does not inspect tail bytes, so either approach is functionally equivalent from a UI perspective.
Wire capture is strongly recommended to pin the exact byte distribution present in production Pattern B traffic before writing a stock-replay emulator. Static analysis gives ~99% confidence that the tail is non-zero stack residue, but the precise byte values depend on the live stack frame layout at the time of packet construction. Capture at least one reference packet per builder path (guild, megaphone, trade, area, monitor) to cover all variants.

Build docs developers (and LLMs) love