namespace SilkroadBot.Core.Networking;
///
/// Represents a Silkroad packet with opcode and payload.
///
public class Packet
{
public ushort Opcode { get; }
public byte[] Payload { get; }
public bool IsEncrypted { get; }
public DateTime Timestamp { get; }
public PacketDirection Direction { get; }
public Packet(ushort opcode, byte[] payload, PacketDirection direction, bool isEncrypted = false)
{
Opcode = opcode;
Payload = payload ?? Array.Empty();
IsEncrypted = isEncrypted;
Timestamp = DateTime.UtcNow;
Direction = direction;
}
/// Create a client-to-server packet.
public static Packet ToServer(ushort opcode, byte[] payload, bool encrypted = false)
=> new(opcode, payload, PacketDirection.ClientToServer, encrypted);
/// Create a server-to-client packet.
public static Packet FromServer(ushort opcode, byte[] payload, bool encrypted = false)
=> new(opcode, payload, PacketDirection.ServerToClient, encrypted);
public override string ToString() => $"[{Direction}] 0x{Opcode:X4} ({Payload.Length} bytes)";
}
public enum PacketDirection
{
ClientToServer,
ServerToClient
}
///
/// Packet reader utility for extracting data from packet payloads.
///
public class PacketReader
{
private readonly byte[] _data;
private int _position;
public PacketReader(byte[] data)
{
_data = data;
_position = 0;
}
public int Remaining => _data.Length - _position;
public int Position => _position;
public byte ReadByte() => _data[_position++];
public ushort ReadUInt16()
{
var value = BitConverter.ToUInt16(_data, _position);
_position += 2;
return value;
}
public uint ReadUInt32()
{
var value = BitConverter.ToUInt32(_data, _position);
_position += 4;
return value;
}
public ulong ReadUInt64()
{
var value = BitConverter.ToUInt64(_data, _position);
_position += 8;
return value;
}
public float ReadSingle()
{
var value = BitConverter.ToSingle(_data, _position);
_position += 4;
return value;
}
public string ReadAscii()
{
ushort length = ReadUInt16();
var text = System.Text.Encoding.ASCII.GetString(_data, _position, length);
_position += length;
return text;
}
public string ReadUnicode()
{
ushort length = ReadUInt16();
var text = System.Text.Encoding.Unicode.GetString(_data, _position, length * 2);
_position += length * 2;
return text;
}
public byte[] ReadBytes(int count)
{
var bytes = new byte[count];
Array.Copy(_data, _position, bytes, 0, count);
_position += count;
return bytes;
}
public void Skip(int count) => _position += count;
}
///
/// Packet writer utility for constructing packet payloads.
///
public class PacketWriter
{
private readonly MemoryStream _stream;
public PacketWriter(int initialCapacity = 64)
{
_stream = new MemoryStream(initialCapacity);
}
public PacketWriter WriteByte(byte value) { _stream.WriteByte(value); return this; }
public PacketWriter WriteUInt16(ushort value)
{
_stream.Write(BitConverter.GetBytes(value));
return this;
}
public PacketWriter WriteUInt32(uint value)
{
_stream.Write(BitConverter.GetBytes(value));
return this;
}
public PacketWriter WriteUInt64(ulong value)
{
_stream.Write(BitConverter.GetBytes(value));
return this;
}
public PacketWriter WriteSingle(float value)
{
_stream.Write(BitConverter.GetBytes(value));
return this;
}
public PacketWriter WriteAscii(string text)
{
var bytes = System.Text.Encoding.ASCII.GetBytes(text);
WriteUInt16((ushort)bytes.Length);
_stream.Write(bytes);
return this;
}
public PacketWriter WriteUnicode(string text)
{
var bytes = System.Text.Encoding.Unicode.GetBytes(text);
WriteUInt16((ushort)(bytes.Length / 2));
_stream.Write(bytes);
return this;
}
public PacketWriter WriteBytes(byte[] data) { _stream.Write(data); return this; }
public byte[] ToArray() => _stream.ToArray();
}