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 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.
OffsetSizeFieldDescriptionStatus
+0x00u16opcode0xA101 LE — written by mov word [F+0x10], 0xA101 @ 0x404DCECONFIRMED
+0x02u8field_0Context selector; always 0 on server (mov byte [F+0x12], 0 @ 0x404DD5) → forces client recv CounterLoad path @ 0x40116CCONFIRMED
+0x03u8field_1block_b ack-vector slice length — (u8)(slot[+0x04] × 4); written @ 0x404E2CCONFIRMED @ 0x404F96
+0x04u8field_2block_a ack-vector slice length and HMAC inner message length on block_b(u8)(slot[+0x1C] × 4); written @ 0x404E3ACONFIRMED @ 0x404FEA and 0x404569
+0x05u8[64]block_a64-byte fixed wire slot copied from key-table slot[+0x08] (bigint limb array — e public exponent)CONFIRMED (rep movsd @ 0x404DF40x404E0F)
+0x45u8[128]block_b128-byte fixed wire slot copied from key-table slot[+0x20] (bigint limb array — N = p·q product)CONFIRMED (rep movsd @ 0x404E110x404E2A)
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)

StepActionKey VA
1Write opcode + field_0=00x404DCE, 0x404DD5
2lea edi,[F+0x15]block_a destination0x404DDA
3push 0xC5 — payload size0x404DDE
4PRNG index selection; stash slot* on connection0x404DBE, 0x404DF1
5rep movsd/movsbblock_a from slot+0x080x404DF4
6lea edi,[esp+0x59]block_b destination0x404E1F
7rep movsd/movsbblock_b from slot+0x200x404E23
8mov [F+0x13],dl — write field_10x404E2C
9lea ecx,[esp+0x14] — send pointer0x404E33
10push ecx
11mov [F+0x14],bl — write field_20x404E3A
12call Connection_Send @ 0x410AE00x404E3E

Receiver Side — Handler_Packet_A101_KeyMaterial @ 0x005E3D60 (Game.exe)

Five sequential PacketRead calls at 0x005F4700 parse the 195-byte body into locals:
PacketRead callWire offsetBytesClient localVA
1st — u8+0x021local_d0 (field_0)0x005E3D85
2nd — u8+0x031local_cc (field_1)0x005E3D93
3rd — u8+0x041local_c8 (field_2)0x005E3DA1
4th — string[0x40]+0x05..+0x4464local_c4[64] (block_a)0x005E3DAF
5th — string[0x80]+0x45..+0xC4128local_84[128] (block_b)0x005E3DBE
After reading, the vtable call at conn+0x254 (→ VA 0x00747798Connection_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

FieldHMAC inner lengthblock_b ack-vector sliceblock_a ack-vector slice
field_1Crypto_KeyMaterialAppend @ 0x404F96 (EBX)
field_2HMAC_SHA256 inner update @ 0x404569Crypto_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

ClaimStatus
197-byte wire record (0xC5)CONFIRMED (0x404DDE, 0x404E3E)
Body layout: 3 scalars + 64 B + 128 BCONFIRMED (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+0x45CONFIRMED (lea / rep movs @ 0x404DDA0x404E2A)
Slot stride 0x84, rand() % count indexCONFIRMED
field_1 = block_b ack-vector lengthCONFIRMED (0x404F96)
field_2 = HMAC inner length on block_bCONFIRMED (0x404569)
HMAC pre-key = SHA256(PRNG[128])CONFIRMED (0x404434, len 0x80)
Server outbound 0xA101 inbound client opcodeNOT MAPPED

Cross-References

DocumentContents
Wire CryptoTCP envelope, cipher modes, 0xA101 / 0xA102 handshake flow
Crypto CounterHMAC-SHA256 derivation, Crypto_CounterLoad internals
Server Key Blobps_login.exe key table slot initialization and BigInt pipeline

Build docs developers (and LLMs) love