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,
}