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 blob originates exclusively from ps_login.exe — it is absent from ps_game.exe and every other server binary in the Shaiya Core V9 tree. A static key table of 16 pre-generated BigInt-derived slots is built at process startup; each outbound 0xA101 picks a random slot and serializes its 64-byte and 128-byte limb arrays directly into the wire frame. This page documents the full outbound send chain, the key-table slot structure, and the BigInt RSA-like generation pipeline, as confirmed by E8 cross-reference scans and Ghidra decompilation of ps_login.exe (MD5 de5b348cca36e0585f06be93f013fa6d, ImageBase 0x00400000).

Outbound Send Chain

The 0xA101 send path begins at process startup when the key table is populated, and is triggered per-connection when the login state machine fires at 0x00401CDF.
KeyTable_GlobalInit @ 0x00406380
  └─ KeyTable_SlotInit @ 0x00409AE0  (×16 slots, 0x84-byte stride)
       └─ [BigInt RSA-like generation — see below]

[per-connection trigger @ 0x00401CDF]
  └─ SendKeyBlob_A101 @ 0x00404DA0
       └─ Connection_EnqueueKeyBlob @ 0x00401C70
            └─ Connection_Send @ 0x00410AE0
                 └─ IOCP_EnqueueSend @ 0x004116C0

Send Path Function Table

VANameRole
0x00406380KeyTable_GlobalInitStartup init; iterates 0x454C74..0x4554B4 (16 slots, stride 0x84), calls KeyTable_SlotInit(0x400, 0) for each
0x00404DA0SendKeyBlob_A101Picks slot via rand() % DAT_454C70; serializes block_a/block_b limb arrays to wire frame; push 0xC5Connection_Send
0x00401C70Connection_EnqueueKeyBlobEnqueues the 197-byte payload on the connection send queue
0x00410AE0Connection_SendIOCP-aware send primitive; hands buffer to OS
0x004116C0IOCP_EnqueueSendPosts the overlapped I/O buffer to the completion port
0x00401CDFCaller_StateMachineLogin connection state machine; caller site for SendKeyBlob_A101 on the established-socket transition
0x0042E427PostSend_StateTransitionCalled after Connection_Send returns; advances the per-connection state machine
0x00403810Login_DispatcherTop-level packet dispatcher for the login socket
0x004042F0Login_OpcodeDispatchSwitch/table dispatch; handles 0xA101, 0xA102, 0xA100, 0xA200 opcodes

KeyTable Slot Structure

The key table lives at DAT_00454C74 with a fixed stride of 0x84 (132 bytes) per slot. There are exactly 16 slots ((0x4554B4 − 0x454C74) / 0x84 = 16). Each slot is a dense array of approximately 11 GMP-style bigint objects (12-byte header each: capacity, limb_count, limbs*).

Slot Layout (132-byte stride)

Slot offsetRole in KeyTable_SlotInitSendKeyBlob_A101 useStatus
+0x00Public exponent e — random odd integer, trial-division screened against NCONFIRMED
+0x04word_count_a — limb count of e; field_1 = (u8)(count × 4)mov [F+0x13], dl @ 0x404E2CCONFIRMED
+0x08limbs_a* — limb array of e (wire block_a source)rep movsd @ 0x404DF4CONFIRMED
+0x0CModulus N = p·q — product from BigInt_modMulnot sent on wireCONFIRMED
+0x18Wire blob #2 — overwritten by final BigInt_modMul(p, q) @ slot init L78CONFIRMED
+0x1Cword_count_b — limb count of result; field_2 = (u8)(count × 4)mov [F+0x14], bl @ 0x404E3ACONFIRMED
+0x20limbs_b* — limb array (wire block_b source)rep movsd @ 0x404E23CONFIRMED
+0x24Temp prime candidate pinternalCONFIRMED
+0x30Temp prime candidate qinternalCONFIRMED
+0x3CCopy of p + 1 (BigInt_exportCopy)internalCONFIRMED
+0x48Copy of q + 1 (BigInt_exportCopy)internalCONFIRMED
+0x54Comparison intermediate (BigInt_cmp vs p, q)internalCONFIRMED
block_a and block_b on the wire are the raw limb arrays of the first and third BigInt objects in the slot — not separately malloc-ed buffers. SendKeyBlob_A101 copies limb_count × 4 bytes little-endian directly from the slot’s in-place limb storage into the stack send frame.

BigInt RSA-Like Pipeline

KeyTable_SlotInit @ 0x00409AE0 performs what is inferred to be a 512-bit-per-prime RSA-like key generation. The exact semantic of the final powMod + second modMul is opaque in isolation, but the wire-level behavior — reading limbs_a[+0x08] and limbs_b[+0x20] — is fully confirmed.

BigInt Operations Map

FunctionVARole
BigInt_ctor0x00423430Construct BigInt with pool allocator
BigInt_assign0x00422910Assign value; force odd / pass prime screen
BigInt_powMod0x00421E50Modular exponentiation (a^b mod m)
BigInt_powModCore0x00423850Core exponentiation inner loop
BigInt_modMul0x00421EF0Modular multiplication
BigInt_mod0x004221B0Modular reduction
BigInt_modInv0x004229B0Modular inverse
BigInt_gcd0x00422950Greatest common divisor
BigInt_cmp0x004236B0Magnitude comparison
BigInt_randBits0x004233D0Generate random BigInt of specified bit length
BigInt_randFill0x00426F90Fill BigInt limbs from PRNG pool
BigInt_seedTime0x00422730Seed PRNG pool from time()
BigInt_poolInit0x004272F0Allocate and initialize pool of BigInt objects
BigInt_bitTestMod50x00424D40Quick prime screen (mod 5 bit test)

KeyTable_SlotInit @ 0x00409AE0 — Step by Step

Prototype (CONFIRMED @ 0x409AEA): void __fastcall KeyTable_SlotInit(int bit_param, int alt_seed) where EAX = slot pointer; bit_param = 0x400 at all production call sites; alt_seed = 0 in production.
StepVA / CalleeActionStatus
0KeyTable_PreSlotInit @ 0x409750Zero-initializes 11 BigInt objects (FUN_00421DD0 × 11) before cryptoCONFIRMED
1BigInt_ctor @ 0x423430BigInt_poolInit @ 0x4272F0Allocates stack PRNG pool local_14 (32-limb capacity)CONFIRMED
2__time32 @ 0x42D892 + BigInt_seedTime @ 0x422730Seeds pool with time() via KeyTable_SlotAltInit (single-limb store)CONFIRMED
3bitlen = bit_param >> 10x400 → 0x200 (512 bits per half-prime)CONFIRMED @ 0x409B09
4BigInt_randBits @ 0x4233D0 + BigInt_sub @ 0x4225A0Random at slot+0x24 (p): clear bits bitlen−1, bitlen−2, 0CONFIRMED
5BigInt_assign @ 0x422910Force odd; quick prime screen via BigInt_bitTestMod5 @ 0x424D40INFERRED
6Repeat 4–5 at slot+0x30Generate second prime qCONFIRMED
7BigInt_exportCopy @ 0x422100slot+0x3C ← p+1; slot+0x48 ← q+1 (add limb 1)CONFIRMED
8BigInt_modMul @ 0x421EF0N = p·qslot+0x0CCONFIRMED
9aparam_2 == 0 (production)Generate random odd e at slot+0x00; trial-division loop: BigInt_mod while result is zero → e *= 2CONFIRMED @ 0x409BA40x409C68
9bparam_2 != 0KeyTable_SlotAltInit(e, param_2) — inject seed limb from alt_seedCONFIRMED
10BigInt_cmp @ 0x4236B0 ×2Guard comparisons before CRT exponentiation stepCONFIRMED
11BigInt_powMod @ 0x421E50CRT preparation on slot+0x3C, slot+0x48 vs NINFERRED (modexp is opaque)
12BigInt_modMul @ 0x421EF0slot+0x18 ← p·q — overwrites bigint #2 → these become the block_b limbsCONFIRMED
Key generation confidence is approximately 90% based on 16 captured KeyTable slots showing consistent 512-bit half-prime structure and 1024-bit composite results. The full RSA math hypothesis (512-bit primes, 1024-bit N, coprime e) is sound but the exact semantic of the final powMod step is not required for wire replay.

Who Populates the Table

PhaseFunctionVAAction
Process startupKeyTable_GlobalInit0x00406380After INI/session connect @ 0x406578; loop 0x454C74< 0x4554B4, stride 0x84
Per slotKeyTable_SlotInit0x00409AE0KeyTable_SlotInit(0x400, 0) — fills all bigint limbs
Dynamic growthKeyTable_GrowSlots / KeyTable_AllocOne0x406960 / 0x4069D0Extra slots at parent+0x14; KeyTable_PreSlotInit called first @ 0x40F3A5
Per sendSendKeyBlob_A1010x00404DA0KeyDeriv_PRNG @ 0x42D77Eidx = rand() % DAT_454C70; rep movs* limb copy
SlotInit confirmed callers (E8 scan): 0x4065D9 (GlobalInit), 0x406979 / 0x4069B9 / 0x406A09 (growth), 0x40F3B6 (pre-init path).

Supporting Functions

KeyTable_AllocOne @ 0x004069D0

Allocates a single new slot and appends it to the live table. Called from the dynamic growth path when the connection load exceeds the 16 pre-generated slots.

KeyTable_GrowSlots @ 0x00406960

Expands the slot array by calling KeyTable_AllocOne in a loop. Runs KeyTable_PreSlotInit on each new slot before KeyTable_SlotInit.

KeyTable_SlotSerialize @ 0x00409CE0

Serializes a slot to wire format. Currently has zero direct E8 cross-references in the binary — it is a dead/orphaned function. SendKeyBlob_A101 performs inline serialization via rep movsd instead.

KeyTable_SlotAltInit @ 0x00423440

Injects an alternate seed limb into the BigInt e field when alt_seed != 0. Only called in the param_2 != 0 branch of KeyTable_SlotInit.

KeyTable_SetSlot @ 0x0042D771 / KeyDerivPRNG @ 0x0042D77E

KeyDerivPRNG provides the per-send random index: idx = rand() % DAT_454C70. The slot content is not regenerated per send — only the selection index changes.

Login Dispatcher

The ps_login.exe login dispatcher at 0x00403810 handles all inbound client opcodes. The relevant cross-references for the 0xA101 flow:
VARole
0x00403810Login_Dispatcher — top-level inbound packet handler
0x00404375cmp/sub against 0xA101 (and branches for 0xA200, 0xA100)
0x004042F0Login_OpcodeDispatch — switch-table dispatch for all login opcodes
0x00404611, 0x004049A5, 0x004049F2mov word [esp+disp], 0xA102 — outbound error/status replies inside dispatcher
ps_login.exe is the only binary in the server tree that contains a mov word [...], 0xA101 writer. The three occurrences of 0xA101 as byte patterns in ps_game.exe are pointer addresses, not opcode writes.

Emulator Guidance

# Break @ 0x404E3E (just before Connection_Send)
# For each slot index, dump:
#   [slot+0x08] for *(slot+0x04)*4 bytes  → block_a
#   [slot+0x20] for *(slot+0x1C)*4 bytes  → block_b
# Repeat 16 times (once per startup-init slot in GlobalInit loop @ 0x4065D9)

Emulator Approach Comparison

ApproachViable?Notes
Replay fixed 16 slots captured from real ps_login.exe runYes — CONFIRMEDSend path only reads +0x04/+0x08/+0x1C/+0x20; no per-connection re-roll
Reimplement KeyTable_SlotInitOptional — INFERREDRequires GMP-like layer (BigInt_*), time()-seeded pool, matching rand() if growth path activates
Arbitrary random 64+128 bytesNoClient Crypto_ProcessKeyPacket derives session keys and counter from exact octet values
Supplying arbitrary random bytes as block_a / block_b will cause the client’s HMAC-SHA256 derivation to produce a different counter value than the server expects, breaking the stream cipher and dropping the connection. Only captured limb dumps or a faithful reimplementation of KeyTable_SlotInit will produce a wire-compatible 0xA101.

What Still Requires Runtime Capture

The following items in the 0xA101 / counter derivation chain are NOT MAPPED without a live runtime capture:
  • D3: End-to-end 0xA101 counter — the exact bytes at ctx+0xF4 after Crypto_CounterLoad cannot be pre-computed offline because Crypto_PRNGFill state depends on the session time() seed plus all prior PRNG calls. Capture by breaking at 0x401740 and dumping 0x23038E4..0x23038F3.
  • Server-side 0xA101 inbound ack handling — the ps_login.exe branch at 0x00404375 that processes the client’s 131-byte follow-up 0xA101 is mapped at dispatcher level but its full ack body parse and state transition have not been decompiled.

Confidence Summary

ClaimStatus
0xA101 sent by ps_login.exe, not ps_game.exeCONFIRMED (opcode-writer scan)
197-byte payload; push 0xC5 @ 0x404DDECONFIRMED
Send chain 0x404DA00x410AE00x4116C0CONFIRMED
Key table: 16 slots, stride 0x84, base 0x454C74CONFIRMED
Slot init via KeyTable_SlotInit(0x400, 0) at startupCONFIRMED
block_a / block_b are BigInt limb arrays (+0x08, +0x20)CONFIRMED
field_1 = slot[+0x04] × 4, field_2 = slot[+0x1C] × 4CONFIRMED
RSA-like pipeline (512-bit primes, 1024-bit N, coprime e)INFERRED (~90%, 16 captured slots)
Final powMod exact semanticINFERRED (opaque; not required for wire replay)
Emulator can reuse precomputed 16 slots without re-rollingCONFIRMED (no slot mutation on send)
0xA101 counter derivation end-to-end (ctx+0xF4)NOT MAPPED without runtime capture

Cross-References

DocumentContents
0xA101 Body MapByte-accurate server stack build ↔ client PacketRead map
Crypto CounterHMAC-SHA256 derivation, Crypto_CounterLoad permute
Wire CryptoTCP envelope, cipher modes, full handshake sequence

Build docs developers (and LLMs) love