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:
parent
b03371c03d
commit
68578fa5fa
10 changed files with 372 additions and 41 deletions
38
src/AcDream.Core.Net/Messages/CharacterLogOff.cs
Normal file
38
src/AcDream.Core.Net/Messages/CharacterLogOff.cs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
using System.Buffers.Binary;
|
||||
using AcDream.Core.Net.Packets;
|
||||
|
||||
namespace AcDream.Core.Net.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// Retail character-logoff request and server confirmation (opcode
|
||||
/// <c>0xF653</c>).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Retail <c>Proto_UI::LogOffCharacter</c> at <c>0x00546A20</c> sends the
|
||||
/// opcode followed by the active character id. The server confirmation uses
|
||||
/// the same opcode with no trailing payload. <c>CPlayerSystem::RequestLogOff</c>
|
||||
/// at <c>0x00562DD0</c> waits for that confirmation; its inbound dispatch calls
|
||||
/// <c>CPlayerSystem::ExecuteLogOff</c> at <c>0x0055D780</c>, which only then
|
||||
/// disconnects the world connection.
|
||||
/// </remarks>
|
||||
public static class CharacterLogOff
|
||||
{
|
||||
public const uint Opcode = 0xF653u;
|
||||
|
||||
/// <summary>Builds retail's eight-byte client request.</summary>
|
||||
public static byte[] BuildRequestBody(uint characterId)
|
||||
{
|
||||
var writer = new PacketWriter(8);
|
||||
writer.WriteUInt32(Opcode);
|
||||
writer.WriteUInt32(characterId);
|
||||
return writer.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether a complete game-message body is the server's logoff
|
||||
/// confirmation. ACE emits the canonical four-byte opcode-only form.
|
||||
/// </summary>
|
||||
public static bool IsConfirmation(ReadOnlySpan<byte> body) =>
|
||||
body.Length >= sizeof(uint) &&
|
||||
BinaryPrimitives.ReadUInt32LittleEndian(body) == Opcode;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue