using AcDream.Core.Net.Packets; namespace AcDream.Core.Net.Messages; /// /// Helper to wrap a single GameMessage (opcode + body) in one /// . Only handles the common "small message /// fits in one fragment" case — the >448-byte multi-fragment split is /// deferred until we actually need it for outbound traffic. /// /// /// Callers build the message body via /// (starting with a 4-byte opcode, then the fields), pick a /// for the queue, pick a fragment sequence /// number for this message, and receive a fully-formed /// ready to embed in a packet body. /// /// public static class GameMessageFragment { /// /// Constant Id used on every outbound fragment. ACE's server-side /// code uses the same literal — the per-message uniqueness lives in /// the Sequence field, not the Id field. /// public const uint OutboundFragmentId = 0x80000000u; /// /// Build a single fragment that carries an entire GameMessage in its /// payload. Throws if the payload exceeds the max single-fragment /// body size (448 bytes). /// public static MessageFragment BuildSingleFragment( uint fragmentSequence, GameMessageGroup queue, ReadOnlySpan gameMessageBytes) { if (gameMessageBytes.Length > MessageFragmentHeader.MaxFragmentDataSize) throw new ArgumentException( $"game message body ({gameMessageBytes.Length} bytes) exceeds single-fragment capacity " + $"({MessageFragmentHeader.MaxFragmentDataSize} bytes). Multi-fragment split TBD.", nameof(gameMessageBytes)); var header = new MessageFragmentHeader { Sequence = fragmentSequence, Id = OutboundFragmentId, Count = 1, TotalSize = (ushort)(MessageFragmentHeader.Size + gameMessageBytes.Length), Index = 0, Queue = (ushort)queue, }; return new MessageFragment(header, gameMessageBytes.ToArray()); } /// /// Concatenate a fragment's header + payload into the bytes that go /// into a packet's body. Use when building the full body span /// passed to . /// public static byte[] Serialize(in MessageFragment fragment) { byte[] buffer = new byte[MessageFragmentHeader.Size + fragment.Payload.Length]; fragment.Header.Pack(buffer); fragment.Payload.CopyTo(buffer.AsSpan(MessageFragmentHeader.Size)); return buffer; } } /// /// AC's per-queue routing for GameMessages. Matches ACE's GameMessageGroup /// enum byte-for-byte so ported handlers are unambiguous. /// public enum GameMessageGroup : ushort { InvalidQueue = 0x00, EventQueue = 0x01, ControlQueue = 0x02, WeenieQueue = 0x03, LoginQueue = 0x04, DatabaseQueue = 0x05, SecureControlQueue = 0x06, SecureWeenieQueue = 0x07, SecureLoginQueue = 0x08, UIQueue = 0x09, SmartboxQueue = 0x0A, ObserverQueue = 0x0B, }