3.9 KiB
Follow-up request: the RecRoomClientEncryptLayer 9-byte format
Your Tachyon findings resolved the handshake failure. Reconciled against the server side:
- The client's inbound layer strips 9 bytes off every datagram before LiteNetLib parses it.
- Our stock server sent a 15-byte ConnectAccept; after the client strips 9 it is 6 bytes, so
NetConnectAcceptPacket.FromDatafailsSize != 15and returns null — the exact silent ignore-and-retry-to-timeout we observed. - Confirmed on the server: a stock server absorbs the client's over-long ConnectRequest (the 9 extra bytes fold into variable-length connect data), which is why the server logged "peer connected" while the client never did.
So the server needs the matching layer. We have built it as a configurable
PacketLayerBase with ExtraPacketSizeForLayer = 9, and proved the plumbing: with the same
9-byte framing on both ends the handshake completes; with it on the server only, a stock
client fails identically to the game today.
The one thing blocking a live test is the exact 9-byte format and the null-key behaviour. This is §3 / step 1 of your own task doc, and it is now the whole critical path.
Q1 — RecRoomClientEncryptLayer.ProcessOutBoundPacket (RVA 0x735D760)
Full disassembly / decompile. Specifically:
- Are the 9 bytes prepended (written at the front, payload shifted) or appended (written after the existing payload)? The offset math in the method answers this.
- What are the 9 bytes when the session key field (
DAKIIEIEKNI,NetPacket+0x18) is null — the state during the whole LiteNetLib + NGO handshake, beforeRecRoom_SetEncryptionInforuns? Zero fill? A counter? A magic constant? Copied from somewhere in the packet? - When the key is non-null, what transforms the payload — AES over the body, and the 9 bytes carry a nonce/tag? We need the outbound path, but the null-key branch is what unblocks the handshake.
- Does it branch on packet property (e.g. treat ConnectRequest/Accept differently from Channeled data), or is it uniform for every datagram?
Q2 — RecRoomClientEncryptLayer.ProcessInboundPacket (RVA 0x735DF90)
Full disassembly / decompile. Specifically:
- Does it validate the 9 bytes (compare a MAC/magic/counter and drop on mismatch), or just strip them? This is the single most important question: if it only strips when the key is null, a zero-fill server reply connects immediately; if it validates, we must reproduce the exact bytes.
- Same prepend-vs-append question from the read side (which 9 bytes are removed, front or back).
- The null-key branch specifically — what happens to a datagram that arrives before a key is set (which is every handshake packet). Does it early-out, strip-only, or reject?
- Any length guard (e.g. drop if
size < 9).
Q3 — the two unidentified layer methods
0x735DC20 and 0x735E140 are listed as unidentified members of the layer. One-line summary
of each — likely a key setter (the sink for RecRoom_SetEncryptionInfo) and/or a
nonce/counter helper. If one installs the key into DAKIIEIEKNI, note its signature.
Q4 — the .cctor at 0x735E220
If the layer has static state (a fixed magic, a base nonce, an RNG seed), it is initialised here. Dump any constants it writes.
Why this precise scoping
We are one bounded read from a live handshake. If Q2's null-key path is strip-only, the layer
we have built connects the real client as-is and we move straight to the NGO ConnectionRequest
(where your Tachyon JSON / "Carrot" findings take over). If it validates, we need the byte
recipe before any wire test is meaningful — a guess here is the "plausible-but-wrong costs more
than not-found" trap you flagged.
Report bytes and offsets, not prose. Where the null-key path is genuinely absent (i.e. the layer assumes a key is always set), say so — that itself would be a finding, implying the key is established out-of-band before the first datagram.