using System; using System.Buffers.Binary; namespace AcDream.Core.Net.Messages; /// /// Parsed header of a retail AC 0xF7B0 GameEvent envelope (S→C). /// /// /// Wire layout /// (references/ACE/Source/ACE.Server/Network/GameEvent/GameEventMessage.cs): /// /// u32 0xF7B0 // GameMessage opcode (GameEvent envelope) /// u32 guid // session's player guid (0 if none yet) /// u32 gameEventSequence // per-session, incremented by server /// u32 eventType // GameEventType sub-opcode (94 values) /// <payload> // variable; type-dispatched /// /// /// /// /// Payload is returned as a view into the /// original message body — no copy. Per-event parsers slice from this. /// /// public readonly record struct GameEventEnvelope( uint PlayerGuid, uint Sequence, GameEventType EventType, ReadOnlyMemory Payload) { /// GameMessage opcode of the outer envelope. public const uint Opcode = 0xF7B0u; /// Header size (4×u32 = 16 bytes before payload). public const int HeaderSize = 16; /// /// Parse a raw 0xF7B0 GameMessage body into the header + payload view. /// Returns null if the body is shorter than the header or the outer /// opcode doesn't match. /// public static GameEventEnvelope? TryParse(byte[] body) { if (body is null || body.Length < HeaderSize) return null; uint outer = BinaryPrimitives.ReadUInt32LittleEndian(body); if (outer != Opcode) return null; uint guid = BinaryPrimitives.ReadUInt32LittleEndian(body.AsSpan(4)); uint sequence = BinaryPrimitives.ReadUInt32LittleEndian(body.AsSpan(8)); uint eventType = BinaryPrimitives.ReadUInt32LittleEndian(body.AsSpan(12)); var payload = body.AsMemory(HeaderSize); return new GameEventEnvelope(guid, sequence, (GameEventType)eventType, payload); } }