From 0aea24c78e0ca840c431bb76f2a184ea92a50fe9 Mon Sep 17 00:00:00 2001 From: Erik Date: Sat, 11 Apr 2026 14:53:08 +0200 Subject: [PATCH] feat(net): extract GameMessage opcodes from live fragment stream (Phase 4.6f) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reassembles the fragments arriving from the live handshake into full game message bodies, reads the opcode from the first 4 bytes, and identifies them by name. On the live wire we now see exactly the sequence ACE sends right after HandleConnectResponse: GameMessage assembled: opcode=0xF7E5 (DDDInterrogation), body=28 bytes GameMessage assembled: opcode=0xF658 (CharacterList), body=80 bytes GameMessage assembled: opcode=0xF7E1 (ServerName), body=20 bytes summary: 5 packets received, 5 decoded OK, 0 checksum failures, 3 GameMessages assembled Every layer of the net stack is now proven live: * NetClient send/receive on both ports 9000 and 9001 * PacketCodec.Encode building LoginRequest + ConnectResponse with correct unencrypted CRC * IsaacRandom byte-compatible with ACE's ISAAC (3 EncryptedChecksum packets decoded, zero mismatches) * PacketHeaderOptional parsing ConnectRequest, TimeSync, AckSequence * MessageFragment.TryParse walking a body tail of back-to-back fragments (the 152-byte packet had TWO messages: CharacterList and ServerName packed into one datagram) * FragmentAssembler reassembling by index The CharacterList body has our test character +Acdream inside it but we're not decoding its fields yet — that's Phase 4.7 where we actually pick a character and send CharacterLogin to enter the game world. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../LiveHandshakeTests.cs | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/tests/AcDream.Core.Net.Tests/LiveHandshakeTests.cs b/tests/AcDream.Core.Net.Tests/LiveHandshakeTests.cs index c5d3f66..44f4258 100644 --- a/tests/AcDream.Core.Net.Tests/LiveHandshakeTests.cs +++ b/tests/AcDream.Core.Net.Tests/LiveHandshakeTests.cs @@ -197,12 +197,15 @@ public class LiveHandshakeTests $"inbound.next=0x{new IsaacRandom(serverSeedBytes).Next():X8}, " + $"outbound.next=0x{new IsaacRandom(clientSeedBytes).Next():X8}"); - // Step 4: receive post-handshake traffic. We expect EncryptedChecksum - // packets containing CharacterList and friends. If our ISAAC seed is - // wrong or our CRC math is off, TryDecode will return ChecksumMismatch. + // Step 4: receive post-handshake traffic. Run fragments through a + // FragmentAssembler so multi-packet game messages reassemble, then + // read the opcode (first 4 bytes of the assembled body) to prove + // we're looking at real GameMessage opcodes. + var assembler = new FragmentAssembler(); int postHandshakePackets = 0; int successfullyDecoded = 0; int checksumFailures = 0; + var seenOpcodes = new List(); var postDeadline = DateTime.UtcNow + TimeSpan.FromSeconds(5); while (DateTime.UtcNow < postDeadline) { @@ -214,13 +217,34 @@ public class LiveHandshakeTests $"decode={decoded.Error}, flags={decoded.Packet?.Header.Flags}, " + $"seq={decoded.Packet?.Header.Sequence}"); if (decoded.IsOk) + { successfullyDecoded++; + foreach (var frag in decoded.Packet!.Fragments) + { + var completeBody = assembler.Ingest(frag, out _); + if (completeBody is not null && completeBody.Length >= 4) + { + uint opcode = BinaryPrimitives.ReadUInt32LittleEndian(completeBody); + seenOpcodes.Add(opcode); + string name = opcode switch + { + 0xF658 => "CharacterList", + 0xF7E1 => "ServerName", + 0xF7E5 => "DDDInterrogation", + _ => "unknown", + }; + Console.WriteLine($"[live] GameMessage assembled: opcode=0x{opcode:X8} ({name}), " + + $"body={completeBody.Length} bytes"); + } + } + } else if (decoded.Error == PacketCodec.DecodeError.ChecksumMismatch) checksumFailures++; } Console.WriteLine($"[live] step 4 summary: {postHandshakePackets} packets received, " + - $"{successfullyDecoded} decoded OK, {checksumFailures} checksum failures"); + $"{successfullyDecoded} decoded OK, {checksumFailures} checksum failures, " + + $"{seenOpcodes.Count} GameMessages assembled"); // The contract of Phase 4.6e is "server accepted our ConnectResponse // and started streaming". Any post-handshake traffic at all proves