using System; using System.Collections.Generic; using System.Numerics; using System.Text; using BasicServer.Management; namespace BasicServer.Arch { public sealed class Packet : IDisposable { private List _buffer; private bool _disposed; private byte[] _readableBuffer; private int _readPos; /// Creates a new empty packet (without an ID). public Packet() { _buffer = new List(); // Initialize buffer _readPos = 0; // Set readPos to 0 } /// Creates a new packet with a given ID. Used for sending. /// The packetType ID. /// The packetAction ID. public Packet(int packetTypeId, int packetActionId) { _buffer = new List(); // Initialize buffer _readPos = 0; // Set readPos to 0 Write(packetTypeId); // Write packet ids to the buffer Write(packetActionId); } /// Creates a packet from which data can be read. Used for receiving. /// The bytes to add to the packet. public Packet(byte[] data) { _buffer = new List(); // Initialize buffer _readPos = 0; // Set readPos to 0 SetBytes(data); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (_disposed) return; if (disposing) { _buffer = null; _readableBuffer = null; _readPos = 0; } _disposed = true; } #region Functions /// Sets the packet's content and prepares it to be read. /// The bytes to add to the packet. public void SetBytes(byte[] data) { Write(data); _readableBuffer = _buffer.ToArray(); } /// Inserts the length of the packet's content at the start of the buffer. public void WriteLength() { _buffer.InsertRange(0, BitConverter.GetBytes(_buffer.Count)); // Insert the byte length of the packet at the very beginning } /// Inserts the given int at the start of the buffer. /// The int to insert. public void InsertInt(int value) { _buffer.InsertRange(0, BitConverter.GetBytes(value)); // Insert the int at the start of the buffer } /// Gets the packet's content in array form. public byte[] ToArray() { _readableBuffer = _buffer.ToArray(); return _readableBuffer; } /// Gets the length of the packet's content. public int Length() { return _buffer.Count; // Return the length of buffer } /// Gets the length of the unread data contained in the packet. public int UnreadLength() { return Length() - _readPos; // Return the remaining length (unread) } /// Resets the packet instance to allow it to be reused. /// Whether or not to reset the packet. public void Reset(bool shouldReset = true) { if (shouldReset) { _buffer.Clear(); // Clear buffer _readableBuffer = null; _readPos = 0; // Reset readPos } else { _readPos -= 4; // "Unread" the last read int } } #endregion #region Write Data /// Adds a byte to the packet. /// The byte to add. public void Write(byte value) { _buffer.Add(value); } /// Adds an array of bytes to the packet. /// The byte array to add. public void Write(IEnumerable value) { _buffer.AddRange(value); } /// Adds a short to the packet. /// The short to add. public void Write(short value) { _buffer.AddRange(BitConverter.GetBytes(value)); } /// Adds an int to the packet. /// The int to add. public void Write(int value) { _buffer.AddRange(BitConverter.GetBytes(value)); } /// Adds a long to the packet. /// The long to add. public void Write(long value) { _buffer.AddRange(BitConverter.GetBytes(value)); } /// Adds a float to the packet. /// The float to add. public void Write(float value) { _buffer.AddRange(BitConverter.GetBytes(value)); } /// Adds a bool to the packet. /// The bool to add. public void Write(bool value) { _buffer.AddRange(BitConverter.GetBytes(value)); } /// Adds a string to the packet. /// The string to add. public void Write(string value) { Write(value.Length); // Add the length of the string to the packet _buffer.AddRange(Encoding.ASCII.GetBytes(value)); // Add the string itself } /// Adds a Vector3 to the packet. /// The Vector3 to add. public void Write(Vector3 value) { Write(value.X); Write(value.Y); Write(value.Z); } /// Adds a Quaternion to the packet. /// The Quaternion to add. public void Write(Quaternion value) { Write(value.X); Write(value.Y); Write(value.Z); Write(value.W); } /// Adds a Room to the packet. /// The Room to add. public void Write(Room value) { Write(value.Id); Write(value.Name); Write(value.IsLocked); Write(value.Leader.Id); Write(value.MaxPlayers); Write(value.CurrentPlayers); foreach (var client in value.Clients) { Write(client.Id); Write(client.Name); } } /// Adds ClientProperties to the packet. /// ClientProperties to add. public void Write(Dictionary value) { Write(value.Count); foreach (var (key, clientProperties) in value) { Write(key); Write(clientProperties.IsLeader); Write(clientProperties.IsReady); Write(clientProperties.ColorId); } } #endregion #region Read Data /// Reads a byte from the packet. /// Whether or not to move the buffer's read position. public byte ReadByte(bool moveReadPos = true) { if (_buffer.Count > _readPos) { // If there are unread bytes var value = _readableBuffer[_readPos]; // Get the byte at readPos' position if (moveReadPos) // If _moveReadPos is true _readPos += 1; // Increase readPos by 1 return value; // Return the byte } throw new Exception("Could not read value of type 'byte'!"); } /// Reads an array of bytes from the packet. /// The length of the byte array. /// Whether or not to move the buffer's read position. public byte[] ReadBytes(int length, bool moveReadPos = true) { if (_buffer.Count > _readPos) { // If there are unread bytes var value = _buffer.GetRange(_readPos, length).ToArray(); // Get the bytes at readPos' position with a range of _length if (moveReadPos) // If _moveReadPos is true _readPos += length; // Increase readPos by _length return value; // Return the bytes } throw new Exception("Could not read value of type 'byte[]'!"); } /// Reads a short from the packet. /// Whether or not to move the buffer's read position. public short ReadShort(bool moveReadPos = true) { if (_buffer.Count > _readPos) { // If there are unread bytes var value = BitConverter.ToInt16(_readableBuffer, _readPos); // Convert the bytes to a short if (moveReadPos) // If _moveReadPos is true and there are unread bytes _readPos += 2; // Increase readPos by 2 return value; // Return the short } throw new Exception("Could not read value of type 'short'!"); } /// Reads an int from the packet. /// Whether or not to move the buffer's read position. public int ReadInt(bool moveReadPos = true) { if (_buffer.Count > _readPos) { // If there are unread bytes var value = BitConverter.ToInt32(_readableBuffer, _readPos); // Convert the bytes to an int if (moveReadPos) // If _moveReadPos is true _readPos += 4; // Increase readPos by 4 return value; // Return the int } throw new Exception("Could not read value of type 'int'!"); } /// Reads a long from the packet. /// Whether or not to move the buffer's read position. public long ReadLong(bool moveReadPos = true) { if (_buffer.Count > _readPos) { // If there are unread bytes var value = BitConverter.ToInt64(_readableBuffer, _readPos); // Convert the bytes to a long if (moveReadPos) // If _moveReadPos is true _readPos += 8; // Increase readPos by 8 return value; // Return the long } throw new Exception("Could not read value of type 'long'!"); } /// Reads a float from the packet. /// Whether or not to move the buffer's read position. public float ReadFloat(bool moveReadPos = true) { if (_buffer.Count > _readPos) { // If there are unread bytes var value = BitConverter.ToSingle(_readableBuffer, _readPos); // Convert the bytes to a float if (moveReadPos) // If _moveReadPos is true _readPos += 4; // Increase readPos by 4 return value; // Return the float } throw new Exception("Could not read value of type 'float'!"); } /// Reads a bool from the packet. /// Whether or not to move the buffer's read position. public bool ReadBool(bool moveReadPos = true) { if (_buffer.Count > _readPos) { // If there are unread bytes var value = BitConverter.ToBoolean(_readableBuffer, _readPos); // Convert the bytes to a bool if (moveReadPos) // If _moveReadPos is true _readPos += 1; // Increase readPos by 1 return value; // Return the bool } throw new Exception("Could not read value of type 'bool'!"); } /// Reads a string from the packet. /// Whether or not to move the buffer's read position. public string ReadString(bool moveReadPos = true) { try { var length = ReadInt(); // Get the length of the string var value = Encoding.ASCII.GetString(_readableBuffer, _readPos, length); // Convert the bytes to a string if (moveReadPos && value.Length > 0) // If _moveReadPos is true string is not empty _readPos += length; // Increase readPos by the length of the string return value; // Return the string } catch { throw new Exception("Could not read value of type 'string'!"); } } /// Reads a Vector3 from the packet. /// Whether or not to move the buffer's read position. public Vector3 ReadVector3(bool moveReadPos = true) { return new(ReadFloat(moveReadPos), ReadFloat(moveReadPos), ReadFloat(moveReadPos)); } /// Reads a Quaternion from the packet. /// Whether or not to move the buffer's read position. public Quaternion ReadQuaternion(bool moveReadPos = true) { return new(ReadFloat(moveReadPos), ReadFloat(moveReadPos), ReadFloat(moveReadPos), ReadFloat(moveReadPos)); } #endregion } }