using System.Buffers.Binary; namespace AcDream.Core.Net.Packets; /// /// The 16-byte header that prefixes every message fragment inside a UDP /// packet's body. A single UDP packet can contain one or more fragments, /// and a single logical GameMessage can be split across multiple /// fragments that share the same and /// but differ in . /// /// /// Layout (byte offsets): /// /// 0 Sequence uint32 Fragment-level sequence /// 4 Id uint32 Message id — fragments with the same Id belong /// to the same logical GameMessage. Outbound /// messages use the high bit set (0x80000000+). /// 8 Count uint16 Total fragments in the logical message (1 if /// the message fits in a single fragment) /// 10 Size uint16 Total fragment size including this 16-byte /// header — max 464 bytes (448 bytes of payload) /// 12 Index uint16 0-based position of this fragment in the /// logical message /// 14 Queue uint16 GameMessageGroup — used by the server to /// sequence related messages /// /// /// /// Reimplemented from ACE's AGPL reference; see NOTICE.md. /// public struct MessageFragmentHeader { public const int Size = 16; /// Max total fragment size on the wire (including this header). public const int MaxFragmentSize = 464; /// Max payload bytes per fragment (= MaxFragmentSize - Size). public const int MaxFragmentDataSize = MaxFragmentSize - Size; // 448 public uint Sequence; public uint Id; public ushort Count; public ushort TotalSize; // total bytes of this fragment including header public ushort Index; public ushort Queue; public readonly void Pack(Span destination) { if (destination.Length < Size) throw new ArgumentException($"destination must be at least {Size} bytes", nameof(destination)); BinaryPrimitives.WriteUInt32LittleEndian(destination.Slice(0), Sequence); BinaryPrimitives.WriteUInt32LittleEndian(destination.Slice(4), Id); BinaryPrimitives.WriteUInt16LittleEndian(destination.Slice(8), Count); BinaryPrimitives.WriteUInt16LittleEndian(destination.Slice(10), TotalSize); BinaryPrimitives.WriteUInt16LittleEndian(destination.Slice(12), Index); BinaryPrimitives.WriteUInt16LittleEndian(destination.Slice(14), Queue); } public static MessageFragmentHeader Unpack(ReadOnlySpan source) { if (source.Length < Size) throw new ArgumentException($"source must be at least {Size} bytes", nameof(source)); return new MessageFragmentHeader { Sequence = BinaryPrimitives.ReadUInt32LittleEndian(source.Slice(0)), Id = BinaryPrimitives.ReadUInt32LittleEndian(source.Slice(4)), Count = BinaryPrimitives.ReadUInt16LittleEndian(source.Slice(8)), TotalSize = BinaryPrimitives.ReadUInt16LittleEndian(source.Slice(10)), Index = BinaryPrimitives.ReadUInt16LittleEndian(source.Slice(12)), Queue = BinaryPrimitives.ReadUInt16LittleEndian(source.Slice(14)), }; } }