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.

The char[21] is a fixed 21-byte player name field used across multiple Shaiya Core V9 chat opcodes. Its physical encoding is five u32 little-endian values followed by one u8 — always exactly 21 bytes on the wire, with no length prefix and no null terminator sent. All offsets below are plaintext bytes after the u16 LE opcode (tap at SConnection_Send / post-decrypt recv). See WIRE_CAPTURE_GUIDE.md for breakpoint locations.
BinaryMD5ImageBase
Game.exec1edd96639ad81835624b9c4516ac7810x00400000
ps_game.exe91b212afbe6623382713772489dc82ce0x00400000

Wire Layouts Involving char[21]

PatternDirection / OpcodesLayout (offsets after opcode)Plaintext size
BS→C 1103, 1104, 1108, 1111; admin F103, F104+0x02 char name[21] · +0x17 u8 len · +0x18 text[len]len + 0x18 (24 B min)
CS→C 1102; admin F102+0x02 u8 dir · +0x03 char name[21] · +0x18 u8 len · +0x19 text[len]len + 0x19 (25 B min)
HS→C F107, F109; C→S F107+0x02 char name[21] (name only, no text)0x17 (23 B, fixed)
IC→S F108 (bound whisper relay)+0x02 u8 len · +0x03 text[len]no name field; partner from CUser+0x5810len + 3
C→S whisperC→S 1102 / F102+0x02 target[21] (5×u32 LE + u8) · +0x17 u8 len · +0x18 text[len]len + 0x18 (24 B min)
AllianceS→C 0x0812Pattern B + +0x18+len u32 guildId appendedlen + 0x1C (28 B min)
/* Pattern B — S→C name + text */
+0x00  u16  opcode
+0x02  char name[21]    // 5×u32 LE + u8; fixed width, no length prefix
+0x17  u8   len
+0x18  u8   text[len]
// Plaintext size: len + 0x18

/* Pattern C — S→C dir + name + text (whisper) */
+0x00  u16  opcode
+0x02  u8   dir         // 0 = to whisper target; 1 = echo to sender
+0x03  char name[21]
+0x18  u8   len
+0x19  u8   text[len]
// Plaintext size: len + 0x19

/* Pattern H — name only (admin bind / clear notify) */
+0x00  u16  opcode      // 0xF107 or 0xF109
+0x02  char name[21]
// Plaintext size: 0x17 (fixed)

/* C→S whisper (PacketSend_Whisper @ 0x005ED160) */
+0x00  u16  opcode      // 0x1102 or 0xF102
+0x02  u8   target[21]  // 5×u32 LE + u8
+0x17  u8   msg_len
+0x18  u8   msg[msg_len]
// Plaintext size: msg_len + 0x18

Client Recv Sites

Every recv handler below calls PacketRead_String @ 0x005F4780 with count = 0x15 (21) to consume the name field. The function performs a fixed memcpy of up to param_2 bytes — there is no null terminator on the wire; underflow zero-fills only the destination buffer.
CONFIRMEDPacketRead_String @ 0x005F4780 always consumes exactly 21 bytes on recv (push 0x15 · call 0x005F4780). Evidence: game-chat-native/recv/PacketRead_String_005f4780.c + cross-checked against all handlers below.
OpcodeHandler VAEvidence (.c file)
0x11020x005E5180game-chat-native/handlers/Handler_ChatWhisper_005e5180.c
0x11030x005E5250handlers/Handler_ChatTrade_005e5250.c
0x11040x005E5310handlers/Handler_ChatGuild_005e5310.c
0x11080x005E5540handlers/Handler_ChatMegaphone_005e5540.c
0x11110x005E57D0handlers/Handler_Chat_Area_1111_005e57d0.c
0xF1020x005E5920handlers/Handler_Chat_Admin_F102_005e5920.c
0xF1030x005E59F0handlers/Handler_Chat_Admin_F103_005e59f0.c
0xF1040x005E5AB0handlers/Handler_Chat_Admin_F104_005e5ab0.c
0xF1070x005DE950handlers/Handler_Chat_Admin_F107_005de950.c
0xF1090x005DE9B0handlers/Handler_Chat_Admin_F109_005de9b0.c
Handlers 0xF107 / 0xF109 read char[21] then call vfn +0x344 / +0x348 @ 0x0056BCB0 — a ret 4 stub. No UI output.

Opcodes That Do NOT Use char[21]

OpcodePayload instead
0x1101, 0x1105, 0x1107Pattern Au32 id (entity / char id)
0x1109Pattern Du8 flag · u32 id · u8 len · text
0x110APattern Eu32 id · u16 message_id
0x110BPattern Gu32 entity · char[32] label (32 B @ +0x06, read as 0x20)
0xF105Pattern Au32 id
0xF106 / 0x1112Variable u16le len · text (different layout)

Client Send Sites

Only one client send function writes a char[21] to the wire. All other send paths use the compact opcode + u8 len + text layout; the server injects the player name from CUser+0x184 during broadcast.
CONFIRMEDPacketSend_Whisper @ 0x005ED160 is the sole client function that places 21 bytes of name on the wire. Asm evidence: copies param_2[0..4] (5×u32) + byte at param_2+5 → stack buffer; size arg to NetworkSend = msg_len + 0x18.
PathVAFileName on wire?
PacketSend_Whisper0x005ED160send/PacketSend_Whisper_005ed160.cYestarget[21] @ +0x02; size msg_len + 0x18
PacketSend_Chat0x005ED060send/PacketSend_Chat_005ed060.cNoopcode + u8 len + text only
PacketSend_Guild0x005ED220send/PacketSend_Guild_005ed220.cNo
PacketSend_Party0x005ED2B0send/PacketSend_Party_005ed2b0.cNo
PacketSend_Zone0x005ED340send/PacketSend_Zone_005ed340.cNo
Whisper UI source: ChatWindow_SendMessage @ 0x0047A5F0 passes param_1_00+0x198 to PacketSend_Whisper (channel case 1). Target is set by ChatWindow_SetWhisperTarget @ 0x0047C690 — null-terminated copy into offset +0x198.

Server Send Sites

The server copies the player name with a null-terminated do { *dst++ = *src++; } while (*src != '\0'); idiom — there is no memset of the tail region before SConnection_Send. The full 21-byte region (including bytes past the null) is included in the send size.
CONFIRMED — name source is CUser+0x184; null-terminated do/while copy into stack char[21]. SConnection_Send size args use the full 21-byte region: len + 0x18 for guild @ 0x00432607, len + 0x19 for whisper @ 0x0047F676.
Opcode / patternFunction VAFileName sourceSConnection_Send size
0x1103 Trade0x004192F0 → queueChat_BroadcastTrade_004192f0.cvia Chat_ProcessIncoming @ 0x0047F400len + 0x18
0x1104 Guild0x00432530Chat_BroadcastGuild_00432530.cparam_2 (CUser+0x184) → local_a2[21]len + 0x18 @ 0x004ED0E0
0x0812 Alliance0x00432530Chat_BroadcastGuild_00432530.cacStack_13e[21]len + 0x1C @ 0x004ED2D0
0x1108 Megaphone0x004D55B0Chat_BroadcastNamed_004d55b0.cChat_ProcessIncoming repacklen + 0x18
0x1111 Area0x00427090Chat_BroadcastZone_00427090.cChat_ProcessIncominglocal_aa[21]zone fanout
0x1102 Whisper S→C0x0047F400Chat_ProcessIncoming_0047f400.csender / target CUser+0x184len + 0x19 (×2 dual send)
0xF104 Admin guild0x00432770AdminChat_BuildGuildPacket_00432770.csame as guildlen + 0x18
Admin mirror0x0047FD10AdminChat_ProcessIncoming_0047fd10.csame patternssame

Server Whisper Forward Patch

CONFIRMED — Before building the S→C Pattern C packet, the server zeroes the last byte of the C→S target[21] field: *(packet + 0x16) = 0 @ 0x0047F608 (movb $0x0, 0x16(%ebx)). This clears any trailing byte written by the client before World_FindUserByName @ 0x00414D40 uses the name. The text slice is then taken from packet + 0x18 (asm 0x0047F661).

PacketRead_String @ 0x005F4780

/* Simplified model of PacketRead_String_005f4780.c */
void PacketRead_String(PacketBuf *buf, void *dst, size_t count) {
    // Fixed memcpy of exactly 'count' bytes from wire (no null on wire)
    memcpy(dst, buf->cursor, count);   // L18-20
    buf->cursor += count;
    // Underflow path: if remaining < count, zero-fills dst only (L22-24)
}
The function does not add a null terminator — the wire carries no null byte, and the destination buffer must be pre-zeroed by the caller to avoid reading stale stack bytes past the actual string.

Padding After '\0' on the Wire

This section records the static analysis verdict for the tail bytes of char[21] when the player name is shorter than 21 characters.
CONFIRMED — Send size includes all 21 name bytes (len + 0x18 / len + 0x19 everywhere). SConnection_Send always receives the full 21-byte region.
CONFIRMED — The server does not memset the name tail before SConnection_Send. Scan of all five chat-tagged Pattern B direct sends (0x1104 / 0xF104): zero memset(21) / rep stos instructions in the 80-instruction window before call 0x004ED0E0. Sites: guild @ 0x00432615, admin @ 0x00432818, GM relay @ 0x004070BC / 0x0040714E; megaphone indirect @ 0x0047F5AE / 0x0047FEEE.
CONFIRMED — MSVC stack prologue does not zero the name[21] frame before the copy loop. All builders use sub esp, N + cookie xor esp, eax only — no rep stos / xorps frame clear. Examples: Chat_BroadcastGuild @ 0x00432530 (sub $0x14c), Chat_ProcessIncoming @ 0x0047F40E.
CONFIRMED — Client recv pre-zeros the local name buffer before calling PacketRead_String. Handler_ChatGuild @ 0x005E5310: xor eax, eax + five mov [esp+…], eax instructions at 0x005E53240x005E5342 before PacketRead_String(..., 0x15).
INFERRED (~99% static) — Bytes at [strlen(name)..20] on the wire are prior-stack residue (non-zero), not zero padding. The copy loop stops at the source null; the prologue does not zero the frame; a scan of 626× SConnection_Send sites finds zero chat Pattern B sites with a tail memset. Wire capture is still recommended to pin the exact byte distribution — see WIRE_CAPTURE_GUIDE.md §3.
The client recv UI function ChatWhisperTradeGuildZoneMega_vfn @ 0x0059BDB0 compares param_4 with a byte loop that stops at '\0' — the null at name[strlen] effectively hides any non-zero tail bytes from the string comparison logic.

Emulator Guidance

Safe interop: memset(name, 0, 21) followed by strncpy produces a clean zero-padded name and is safe for emulator use. It is not byte-for-byte stock-identical — the stock server sends uninitialized stack residue in the tail region.Byte-for-byte stock replay: preserve the garbage tail bytes or capture a reference packet from a live server. See PADDING_SIMULATION.md for a simulated byte distribution and WIRE_CAPTURE_GUIDE.md §3 for capture breakpoints.

Cross-Reference

DocumentContents
PACKET_SPEC.mdFull wire layouts, TCP envelope, all opcode patterns
CHAT_CHANNEL_MAP.mdHandler VA, vtable offset, .c file per opcode
PADDING_SIMULATION.mdSimulated byte distribution for name tail region
WIRE_CAPTURE_GUIDE.mdBreakpoints and method for capturing reference packets
game-chat-native/handlers/Client recv handler .c evidence
psgame-chat-native/handlers/Chat_ProcessIncoming_0047f400.cServer recv + whisper patch evidence
psgame-chat-native/send/Chat_BroadcastGuild_00432530.cServer send + copy idiom evidence

Build docs developers (and LLMs) love