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 0xA101 key material packet travels from ps_login.exe to Game.exe to initiate the AES-CTR crypto handshake. It is not sent by ps_game.exe. Understanding the exact byte layout is required for any login emulator or wire-replay tool, because the client’s HMAC-SHA256 counter derivation depends on the literal octet values of block_a and block_b as serialized into the wire frame. This page maps every byte from the server stack build to the client PacketRead sequence, with the Ghidra virtual addresses that confirm each field.
Evidence: ps_login.exe (MD5 de5b348cca36e0585f06be93f013fa6d) and Game.exe (MD5 c1edd96639ad81835624b9c4516ac781), both at ImageBase 0x00400000.
Wire Layout
The full plaintext record is 197 bytes (push 0xC5 @ 0x404DDE confirms the payload size passed to Connection_Send @ 0x00410AE0). The u16 opcode occupies the first 2 bytes; the 195-byte body follows immediately.
| Offset | Size | Field | Description | Status |
|---|
+0x00 | u16 | opcode | 0xA101 LE — written by mov word [F+0x10], 0xA101 @ 0x404DCE | CONFIRMED |
+0x02 | u8 | field_0 | Context selector; always 0 on server (mov byte [F+0x12], 0 @ 0x404DD5) → forces client recv CounterLoad path @ 0x40116C | CONFIRMED |
+0x03 | u8 | field_1 | block_b ack-vector slice length — (u8)(slot[+0x04] × 4); written @ 0x404E2C | CONFIRMED @ 0x404F96 |
+0x04 | u8 | field_2 | block_a ack-vector slice length and HMAC inner message length on block_b — (u8)(slot[+0x1C] × 4); written @ 0x404E3A | CONFIRMED @ 0x404FEA and 0x404569 |
+0x05 | u8[64] | block_a | 64-byte fixed wire slot copied from key-table slot[+0x08] (bigint limb array — e public exponent) | CONFIRMED (rep movsd @ 0x404DF4–0x404E0F) |
+0x45 | u8[128] | block_b | 128-byte fixed wire slot copied from key-table slot[+0x20] (bigint limb array — N = p·q product) | CONFIRMED (rep movsd @ 0x404E11–0x404E2A) |
Wire total: 2 + 1 + 1 + 1 + 64 + 128 = 197 bytes (0xC5 decimal).
The field_1 and field_2 stores in SendKeyBlob_A101 use different esp bases. field_1 is written at esp+F+0x13 (one stack push after push 0xC5); field_2 is written at esp+F+0x14 (after a further push ecx). Ghidra auto-names these locals F+0x17/F+0x1C — those names are incorrect for wire offset calculations. Always use F+0x13 / F+0x14.
Sender Side — SendKeyBlob_A101 @ 0x00404DA0 (ps_login.exe)
The function opens with sub esp, 0xD4 to reserve its stack frame. Below, F is the value of esp after the prologue; W = F+0x10 is the wire send base.
// SendKeyBlob_A101 @ 0x00404DA0 (ps_login.exe, abbreviated)
// Stack: F = esp after "sub esp,0xD4" | Wire base W = F+0x10
void SendKeyBlob_A101(Connection *conn) {
// 1. Write opcode and field_0
*(uint16_t*)(F+0x10) = 0xA101; // W+0x00 opcode @ 0x404DCE
*(uint8_t* )(F+0x12) = 0; // W+0x02 field_0 @ 0x404DD5 (always 0)
// 2. Point block_a destination (before push 0xC5)
uint8_t *block_a_dest = (uint8_t*)(F+0x15); // W+0x05 @ 0x404DDA (lea)
// 3. Push payload length — esp is now F-4
push(0xC5); // 197 bytes total @ 0x404DDE
// 4. Select a random key-table slot
// KeyDeriv_PRNG @ 0x0042D77E → idx = rand() % DAT_454C70
uint32_t idx = KeyDeriv_PRNG() % DAT_454C70;
Slot *slot = (Slot*)(DAT_454C74 + idx * 0x84); // stride 0x84 @ 0x404DEA
conn->slot_ptr = slot; // @ 0x404DF1
// 5. Copy block_a limbs (slot+0x08, length = slot[+0x04]*4 bytes)
rep_movsd(block_a_dest, slot->limbs_a, // @ 0x404DF4–0x404E0F
slot->word_count_a * 4);
// 6. Point block_b destination (esp = F-4, so [esp+0x59] = F+0x55 = W+0x45)
uint8_t *block_b_dest = (uint8_t*)(esp + 0x59); // W+0x45 @ 0x404E1F (lea)
// 7. Copy block_b limbs (slot+0x20, length = slot[+0x1C]*4 bytes)
rep_movsd(block_b_dest, slot->limbs_b, // @ 0x404E23
slot->word_count_b * 4);
// 8. Write field_1 (esp still F-4 from push 0xC5)
// dl = low byte of block_a byte count
*(uint8_t*)(F+0x13) = (uint8_t)(slot->word_count_a * 4); // W+0x03 @ 0x404E2C
// 9. Prepare send pointer = F+0x10 (lea ecx,[esp+0x14])
uint8_t *send_ptr = (uint8_t*)(esp + 0x14); // = F+0x10 @ 0x404E33
// 10. Push send pointer — esp is now F-8
push(send_ptr);
// 11. Write field_2 (esp now F-8, so [esp+0x1C] = F+0x14)
// bl = low byte of block_b byte count
*(uint8_t*)(F+0x14) = (uint8_t)(slot->word_count_b * 4); // W+0x04 @ 0x404E3A
// 12. Send 197 bytes
Connection_Send(send_ptr, 0xC5); // @ 0x404E3E → 0x00410AE0
// 13. State machine transition
PostSend_StateTransition(); // @ 0x404E4A → 0x0042E427
}
Stack Build Order (Confirmed @ 0x404DA0)
| Step | Action | Key VA |
|---|
| 1 | Write opcode + field_0=0 | 0x404DCE, 0x404DD5 |
| 2 | lea edi,[F+0x15] — block_a destination | 0x404DDA |
| 3 | push 0xC5 — payload size | 0x404DDE |
| 4 | PRNG index selection; stash slot* on connection | 0x404DBE, 0x404DF1 |
| 5 | rep movsd/movsb — block_a from slot+0x08 | 0x404DF4 |
| 6 | lea edi,[esp+0x59] — block_b destination | 0x404E1F |
| 7 | rep movsd/movsb — block_b from slot+0x20 | 0x404E23 |
| 8 | mov [F+0x13],dl — write field_1 | 0x404E2C |
| 9 | lea ecx,[esp+0x14] — send pointer | 0x404E33 |
| 10 | push ecx | — |
| 11 | mov [F+0x14],bl — write field_2 | 0x404E3A |
| 12 | call Connection_Send @ 0x410AE0 | 0x404E3E |
Receiver Side — Handler_Packet_A101_KeyMaterial @ 0x005E3D60 (Game.exe)
Five sequential PacketRead calls at 0x005F4700 parse the 195-byte body into locals:
| PacketRead call | Wire offset | Bytes | Client local | VA |
|---|
1st — u8 | +0x02 | 1 | local_d0 (field_0) | 0x005E3D85 |
2nd — u8 | +0x03 | 1 | local_cc (field_1) | 0x005E3D93 |
3rd — u8 | +0x04 | 1 | local_c8 (field_2) | 0x005E3DA1 |
4th — string[0x40] | +0x05..+0x44 | 64 | local_c4[64] (block_a) | 0x005E3DAF |
5th — string[0x80] | +0x45..+0xC4 | 128 | local_84[128] (block_b) | 0x005E3DBE |
After reading, the vtable call at conn+0x254 (→ VA 0x00747798 → Connection_OnKeyMaterial @ 0x005A4D50) re-orders the locals to match Crypto_ProcessKeyPacket’s calling convention (see Crypto Counter for the argument shuffle table).
field_1 vs field_2 — Independence Table
| Field | HMAC inner length | block_b ack-vector slice | block_a ack-vector slice |
|---|
field_1 | — | ✅ Crypto_KeyMaterialAppend @ 0x404F96 (EBX) | — |
field_2 | ✅ HMAC_SHA256 inner update @ 0x404569 | — | ✅ Crypto_KeyMaterialAppend @ 0x404FEA (EBP) |
Typical slot values (slot[+0x04]=0x10, slot[+0x1C]=0x20): field_1 = 0x40 (64 bytes), field_2 = 0x80 (128 bytes). Client HMAC covers all 128 bytes of block_b; ack vector echoes 64 bytes of block_b + 128 bytes of block_a.
HMAC pre-key is SHA256(PRNG[128]), not SHA256(block_b). Confirmed at 0x404434 where the SHA256 data pointer [esp+0x11c] points to the 128-byte PRNG stack buffer, and length is hard-coded 0x80. The wire bytes in block_a / block_b are not hashed directly for the HMAC key.
Counter Derivation
After the handler completes the PacketRead sequence, the key material flows through Crypto_ProcessKeyPacket @ 0x00401100. The counter at ctx+0xF4 is derived as:
SHA256(PRNG[128]) → HMAC_SHA256(key, block_b[0..field_2]) → digest[0..15] → Crypto_CounterLoad → ctx+0xF4
See Crypto Counter for the full closed formula with stack offsets and confirmation addresses.
Unknown — Inbound Client 0xA101 Opcode on Server
The server-side opcode number that ps_login.exe receives from the client follow-up 0xA101 (131 bytes, sent by NetworkSendKeyFollowUp @ 0x5EC5A0) has not been mapped in the current RE corpus. The client sends 0x00A101 but the server dispatcher at 0x00404375 handles the dispatch — the exact branch and ack handler for this inbound client packet is NOT MAPPED.
Confidence Summary
| Claim | Status |
|---|
197-byte wire record (0xC5) | CONFIRMED (0x404DDE, 0x404E3E) |
| Body layout: 3 scalars + 64 B + 128 B | CONFIRMED (five handler reads @ 0x005E3D60) |
field_1 / field_2 at F+0x13 / F+0x14 (not F+0x17 / F+0x1C) | CONFIRMED (push-adjusted mov @ 0x404E2C, 0x404E3A) |
block_a @ W+0x05, block_b @ W+0x45 | CONFIRMED (lea / rep movs @ 0x404DDA–0x404E2A) |
Slot stride 0x84, rand() % count index | CONFIRMED |
field_1 = block_b ack-vector length | CONFIRMED (0x404F96) |
field_2 = HMAC inner length on block_b | CONFIRMED (0x404569) |
HMAC pre-key = SHA256(PRNG[128]) | CONFIRMED (0x404434, len 0x80) |
Server outbound 0xA101 inbound client opcode | NOT MAPPED |
Cross-References
| Document | Contents |
|---|
| Wire Crypto | TCP envelope, cipher modes, 0xA101 / 0xA102 handshake flow |
| Crypto Counter | HMAC-SHA256 derivation, Crypto_CounterLoad internals |
| Server Key Blob | ps_login.exe key table slot initialization and BigInt pipeline |