/* * MIT License * * Copyright (c) 2020 Tom Weiland * Copyright (c) 2022 Vincent Dowling * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ using System; using System.Collections.Generic; using System.Text; using UnityEngine; // ReSharper disable once CheckNamespace namespace Scripts.CustomServer { public sealed class Packet : IDisposable { private List buffer; 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 packet ID. public Packet(int id) { buffer = new List(); // Initialize buffer readPos = 0; // Set readPos to 0 Write(id); // Write packet id to the buffer } /// 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); } #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. // ReSharper disable once MemberCanBePrivate.Global 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); } #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 } else { 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 } else { 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 } else { 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 } else { 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 } else { 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 } else { 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 } else { 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 Vector3(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 Quaternion(ReadFloat(moveReadPos), ReadFloat(moveReadPos), ReadFloat(moveReadPos), ReadFloat(moveReadPos)); } #endregion private bool disposed; private void Dispose(bool disposing) { if (disposed) return; if (disposing) { buffer = null; readableBuffer = null; readPos = 0; } disposed = true; } public void Dispose() { Dispose(true); // ReSharper disable once GCSuppressFinalizeForTypeWithoutDestructor GC.SuppressFinalize(this); } } }