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,31 @@
namespace AcDream.Core.Net.Packets;
/// <summary>
/// Builds retail's connection-level disconnect packet.
/// </summary>
/// <remarks>
/// <c>ClientNet::LogOffServer</c> at <c>0x00543EF0</c> creates the
/// <c>0x8000</c> optional header and sends it through
/// <c>SharedNet::SendOptionalHeader</c> at <c>0x00543160</c>. That path
/// zero-initializes the packet sequence and copies the receiver's network id
/// and iteration into the fixed header. The disconnect is cleartext and has
/// no body.
/// </remarks>
public static class TransportDisconnect
{
public static byte[] Build(ushort networkId, ushort iteration)
{
var header = new PacketHeader
{
Sequence = 0,
Flags = PacketHeaderFlags.Disconnect,
Id = networkId,
Iteration = iteration,
};
return PacketCodec.Encode(
header,
ReadOnlySpan<byte>.Empty,
outboundIsaac: null);
}
}