using System;
using System.Collections.Generic;
using System.Text;
namespace AcDream.Core.Net.Tests.Messages;
///
/// Test-only faithful mirror of ACE's server-side wire writer, so golden-byte
/// conformance tests generate their fixtures the same way the authoritative
/// server generates them. Hand-rolled hex would only prove that our parser
/// agrees with whoever typed the hex; generating from the oracle's own
/// algorithm proves it agrees with the server.
///
///
/// Every primitive below is a line-for-line port of
/// ACE/Source/ACE.Server/Network/Extensions.cs (read 2026-07-29 from
/// the ACE checkout). Cited line numbers are that file's:
///
///
/// - CalculatePadMultiple — Extensions.cs:10
/// - WriteString16L — Extensions.cs:12-21
/// - WritePackedDword — Extensions.cs:23-34
/// - Pad — Extensions.cs:51
/// - Align — Extensions.cs:55-58
/// - WriteGuid — Extensions.cs:121 (writes guid.Full, a u32)
///
///
///
/// ACE's underlying is little-endian for
/// the fixed-width overloads, which is what Write(uint),
/// Write(ushort) and Write(float) reproduce here.
///
///
internal sealed class AceWireWriter
{
private readonly List _buffer = new();
public int Length => _buffer.Count;
/// ACE Extensions.cs:10.
private static uint CalculatePadMultiple(uint length, uint multiple)
=> multiple * ((length + multiple - 1u) / multiple) - length;
/// BinaryWriter.Write(uint) — little-endian.
public AceWireWriter Write(uint value)
{
_buffer.Add((byte)(value & 0xFF));
_buffer.Add((byte)((value >> 8) & 0xFF));
_buffer.Add((byte)((value >> 16) & 0xFF));
_buffer.Add((byte)((value >> 24) & 0xFF));
return this;
}
/// BinaryWriter.Write(ushort) — little-endian.
public AceWireWriter Write(ushort value)
{
_buffer.Add((byte)(value & 0xFF));
_buffer.Add((byte)((value >> 8) & 0xFF));
return this;
}
/// BinaryWriter.Write(int) — little-endian, same bit pattern as Write(uint).
public AceWireWriter Write(int value) => Write(unchecked((uint)value));
/// BinaryWriter.Write(byte).
public AceWireWriter Write(byte value)
{
_buffer.Add(value);
return this;
}
/// BinaryWriter.Write(bool) — one byte, 0 or 1 (used via Convert.ToUInt32 sites as a plain u32 instead; kept for completeness).
public AceWireWriter Write(bool value) => Write(value ? (byte)1 : (byte)0);
/// BinaryWriter.Write(ulong) — little-endian.
public AceWireWriter Write(ulong value) =>
Write((uint)(value & 0xFFFFFFFFu)).Write((uint)(value >> 32));
/// BinaryWriter.Write(float) — little-endian IEEE-754.
public AceWireWriter Write(float value)
=> Write((uint)BitConverter.SingleToInt32Bits(value));
///
/// ACE Extensions.cs:121 — WriteGuid writes the full 32-bit
/// guid with no packing.
///
public AceWireWriter WriteGuid(uint guid) => Write(guid);
///
/// ACE Extensions.cs:12-21. Writes a u16 length, the CP1252 bytes,
/// then pads so that (2 + length) is a multiple of 4 — the comment in ACE
/// reads "client expects string length to be a multiple of 4 including the
/// 2 bytes for length".
///
public AceWireWriter WriteString16L(string? data)
{
data ??= "";
byte[] bytes = Encoding.GetEncoding(1252).GetBytes(data);
Write((ushort)data.Length);
_buffer.AddRange(bytes);
return Pad(CalculatePadMultiple(sizeof(ushort) + (uint)data.Length, 4u));
}
/// ACE Extensions.cs:23-34.
public AceWireWriter WritePackedDword(uint value)
{
if (value <= 32767)
return Write((ushort)value);
uint packed = (value << 16) | ((value >> 16) | 0x8000);
return Write(packed);
}
/// ACE Extensions.cs:51.
public AceWireWriter Pad(uint pad)
{
for (uint i = 0; i < pad; i++)
_buffer.Add(0);
return this;
}
/// ACE Extensions.cs:55-58 — pad the stream to a 4-byte boundary.
public AceWireWriter Align() => Pad(CalculatePadMultiple((uint)_buffer.Count, 4u));
public byte[] ToArray() => _buffer.ToArray();
///
/// Start a top-level GameMessage body: the 4-byte opcode, exactly as
/// ACE.Server.Network.GameMessages.GameMessage's constructor writes
/// it before the per-message payload.
///
public static AceWireWriter GameMessage(uint opcode)
=> new AceWireWriter().Write(opcode);
///
/// Start a GameEvent body (top-level opcode 0xF7B0), mirroring
/// ACE/Source/ACE.Server/Network/GameEvent/GameEventMessage.cs:21-25:
/// WriteGuid(guid), Write(session.GameEventSequence++),
/// Write((uint)EventType).
///
public static AceWireWriter GameEvent(uint guid, uint eventSequence, uint eventType)
=> new AceWireWriter()
.Write(0xF7B0u)
.WriteGuid(guid)
.Write(eventSequence)
.Write(eventType);
}