fix(net): honor retail graceful logout handshake

Send the active character id, drain until the authoritative server confirmation, then emit retail's zero-sequence connection disconnect with the negotiated receiver iteration. The connected gate now waits for ACE to remove the exact UDP session before reconnecting, eliminating fixed-delay races.
This commit is contained in:
Erik 2026-07-20 23:31:23 +02:00
parent b03371c03d
commit 68578fa5fa
10 changed files with 372 additions and 41 deletions

View file

@ -0,0 +1,36 @@
using System.Buffers.Binary;
using AcDream.Core.Net.Messages;
namespace AcDream.Core.Net.Tests.Messages;
public sealed class CharacterLogOffTests
{
[Fact]
public void BuildRequestBody_MatchesRetailOpcodeThenCharacterId()
{
byte[] body = CharacterLogOff.BuildRequestBody(0x5000000Au);
Assert.Equal(8, body.Length);
Assert.Equal(
CharacterLogOff.Opcode,
BinaryPrimitives.ReadUInt32LittleEndian(body));
Assert.Equal(
0x5000000Au,
BinaryPrimitives.ReadUInt32LittleEndian(body.AsSpan(4)));
}
[Fact]
public void IsConfirmation_AcceptsServerOpcodeOnlyBody()
{
byte[] body = BitConverter.GetBytes(CharacterLogOff.Opcode);
Assert.True(CharacterLogOff.IsConfirmation(body));
}
[Fact]
public void IsConfirmation_RejectsTruncatedOrDifferentMessage()
{
Assert.False(CharacterLogOff.IsConfirmation([0x53, 0xF6, 0x00]));
Assert.False(CharacterLogOff.IsConfirmation(BitConverter.GetBytes(0xF654u)));
}
}

View file

@ -0,0 +1,26 @@
using AcDream.Core.Net.Packets;
namespace AcDream.Core.Net.Tests.Packets;
public sealed class TransportDisconnectTests
{
[Fact]
public void Build_MatchesRetailOptionalHeaderShape()
{
byte[] datagram = TransportDisconnect.Build(0x1234, 0x0001);
Assert.Equal(PacketHeader.Size, datagram.Length);
PacketHeader header = PacketHeader.Unpack(datagram);
Assert.Equal(0u, header.Sequence);
Assert.Equal(PacketHeaderFlags.Disconnect, header.Flags);
Assert.Equal((ushort)0x1234, header.Id);
Assert.Equal((ushort)0x0001, header.Iteration);
Assert.Equal((ushort)0, header.DataSize);
PacketCodec.PacketDecodeResult decoded = PacketCodec.TryDecode(
datagram,
inboundIsaac: null);
Assert.True(decoded.IsOk);
}
}