using BattleBitAPI.Common.Extentions; using System.Net; namespace BattleBitAPI.Common.Serialization { public class Stream : IDisposable { public const int DefaultBufferSize = 1024 * 512; #if BIGENDIAN public static readonly bool IsLittleEndian = false; #else public static readonly bool IsLittleEndian = true; #endif public byte[] Buffer; public int WritePosition; public int ReadPosition; public bool InPool; public bool CanRead(int size) { int readableLenght = WritePosition - ReadPosition; return (readableLenght >= size); } public void EnsureWriteBufferSize(int requiredSize) { int bufferLenght = Buffer.Length; int leftSpace = bufferLenght - WritePosition; if (leftSpace < requiredSize) { int newSize = bufferLenght + Math.Max(requiredSize, 1024); System.Array.Resize(ref Buffer, newSize); } } // -------- Write ------ public void Write(byte value) { EnsureWriteBufferSize(1); Buffer[WritePosition] = value; WritePosition += 1; } public void Write(bool value) { EnsureWriteBufferSize(1); Buffer[WritePosition] = value ? (byte)1 : (byte)0; WritePosition += 1; } public unsafe void Write(short value) { EnsureWriteBufferSize(2); fixed (byte* ptr = &Buffer[WritePosition]) *((short*)ptr) = value; WritePosition += 2; } public unsafe void Write(ushort value) { EnsureWriteBufferSize(2); fixed (byte* ptr = &Buffer[WritePosition]) *((ushort*)ptr) = value; WritePosition += 2; } public unsafe void Write(int value) { EnsureWriteBufferSize(4); fixed (byte* ptr = &Buffer[WritePosition]) *((int*)ptr) = value; WritePosition += 4; } public unsafe void Write(uint value) { EnsureWriteBufferSize(4); fixed (byte* ptr = &Buffer[WritePosition]) *((uint*)ptr) = value; WritePosition += 4; } public unsafe void Write(long value) { EnsureWriteBufferSize(8); fixed (byte* ptr = &Buffer[WritePosition]) *((long*)ptr) = value; WritePosition += 8; } public unsafe void Write(ulong value) { EnsureWriteBufferSize(8); fixed (byte* ptr = &Buffer[WritePosition]) *((ulong*)ptr) = value; WritePosition += 8; } public unsafe void Write(decimal value) { EnsureWriteBufferSize(16); fixed (byte* ptr = &Buffer[WritePosition]) *((decimal*)ptr) = value; WritePosition += 16; } public unsafe void Write(double value) { EnsureWriteBufferSize(16); fixed (byte* ptr = &Buffer[WritePosition]) *((double*)ptr) = value; WritePosition += 16; } public unsafe void Write(float value) { int intValue = *((int*)&value); EnsureWriteBufferSize(4); fixed (byte* ptr = &Buffer[WritePosition]) *((int*)ptr) = intValue; WritePosition += 4; } public unsafe void Write(string value) { int charCount = value.Length; fixed (char* strPtr = value) { int size = System.Text.Encoding.UTF8.GetByteCount(strPtr, charCount); EnsureWriteBufferSize(size + 2); fixed (byte* buffPtr = &Buffer[WritePosition]) { *((ushort*)buffPtr) = (ushort)size; System.Text.Encoding.UTF8.GetBytes(strPtr, charCount, buffPtr + 2, size); } WritePosition += size + 2; } } public unsafe void Write(DateTime value) { var utc = value.ToUniversalTime(); EnsureWriteBufferSize(8); fixed (byte* ptr = &Buffer[WritePosition]) *((long*)ptr) = utc.Ticks; WritePosition += 8; } public unsafe void Write(IPAddress value) { uint ip = value.ToUInt(); Write(ip); } public unsafe void Write(IPEndPoint value) { uint ip = value.Address.ToUInt(); Write(ip); Write((ushort)value.Port); } public unsafe void WriteRaw(string value) { int charCount = value.Length; fixed (char* strPtr = value) { int size = System.Text.Encoding.UTF8.GetByteCount(strPtr, charCount); EnsureWriteBufferSize(size); fixed (byte* buffPtr = &Buffer[WritePosition]) System.Text.Encoding.UTF8.GetBytes(strPtr, charCount, buffPtr, size); WritePosition += size; } } public unsafe void Write(T value) where T : IStreamSerializable { value.Write(this); } public void Write(byte[] source, int sourceIndex, int length) { if (length == 0) return; EnsureWriteBufferSize(length); System.Array.Copy(source, sourceIndex, this.Buffer, this.WritePosition, length); this.WritePosition += length; } public void Write(Stream source) { EnsureWriteBufferSize(source.WritePosition); System.Array.Copy(source.Buffer, 0, this.Buffer, this.WritePosition, source.WritePosition); this.WritePosition += source.WritePosition; } public unsafe void WriteAt(byte value, int position) { Buffer[position] = value; } public unsafe void WriteAt(short value, int position) { fixed (byte* ptr = &Buffer[position]) *((short*)ptr) = value; } public unsafe void WriteAt(ushort value, int position) { fixed (byte* ptr = &Buffer[position]) *((ushort*)ptr) = value; } public unsafe void WriteAt(int value, int position) { fixed (byte* ptr = &Buffer[position]) *((int*)ptr) = value; } public unsafe void WriteAt(uint value, int position) { fixed (byte* ptr = &Buffer[position]) *((uint*)ptr) = value; } public unsafe void WriteAt(long value, int position) { fixed (byte* ptr = &Buffer[position]) *((long*)ptr) = value; } public unsafe void WriteAt(ulong value, int position) { fixed (byte* ptr = &Buffer[position]) *((ulong*)ptr) = value; } // -------- Read ------ public byte ReadInt8() { var value = Buffer[ReadPosition]; ReadPosition++; return value; } public bool ReadBool() { var value = Buffer[ReadPosition]; ReadPosition++; return value == 1; } public unsafe short ReadInt16() { short value = 0; fixed (byte* pbyte = &Buffer[ReadPosition]) { if (ReadPosition % 2 == 0) { value = *((short*)pbyte); } else { if (IsLittleEndian) { value = (short)((*pbyte) | (*(pbyte + 1) << 8)); } else { value = (short)((*pbyte << 8) | (*(pbyte + 1))); } } } ReadPosition += 2; return value; } public unsafe ushort ReadUInt16() { ushort value = 0; fixed (byte* pbyte = &Buffer[ReadPosition]) { if (ReadPosition % 2 == 0) { value = *((ushort*)pbyte); } else { if (IsLittleEndian) { value = (ushort)((*pbyte) | (*(pbyte + 1) << 8)); } else { value = (ushort)((*pbyte << 8) | (*(pbyte + 1))); } } } ReadPosition += 2; return value; } public unsafe int ReadInt32() { int value = 0; fixed (byte* pbyte = &Buffer[ReadPosition]) { if (ReadPosition % 4 == 0) { value = *((int*)pbyte); } else { if (IsLittleEndian) { value = (int)((*pbyte) | (*(pbyte + 1) << 8) | (*(pbyte + 2) << 16) | (*(pbyte + 3) << 24)); } else { value = (int)((*pbyte << 24) | (*(pbyte + 1) << 16) | (*(pbyte + 2) << 8) | (*(pbyte + 3))); } } } ReadPosition += 4; return value; } public unsafe uint ReadUInt32() { uint value = 0; fixed (byte* pbyte = &Buffer[ReadPosition]) { if (ReadPosition % 4 == 0) { value = *((uint*)pbyte); } else { if (IsLittleEndian) { value = (uint)((*pbyte) | (*(pbyte + 1) << 8) | (*(pbyte + 2) << 16) | (*(pbyte + 3) << 24)); } else { value = (uint)((*pbyte << 24) | (*(pbyte + 1) << 16) | (*(pbyte + 2) << 8) | (*(pbyte + 3))); } } } ReadPosition += 4; return value; } public unsafe long ReadInt64() { long value = 0; fixed (byte* pbyte = &Buffer[ReadPosition]) { if (ReadPosition % 8 == 0) { value = *((long*)pbyte); } else { if (IsLittleEndian) { int i1 = (*pbyte) | (*(pbyte + 1) << 8) | (*(pbyte + 2) << 16) | (*(pbyte + 3) << 24); int i2 = (*(pbyte + 4)) | (*(pbyte + 5) << 8) | (*(pbyte + 6) << 16) | (*(pbyte + 7) << 24); value = (uint)i1 | ((long)i2 << 32); } else { int i1 = (*pbyte << 24) | (*(pbyte + 1) << 16) | (*(pbyte + 2) << 8) | (*(pbyte + 3)); int i2 = (*(pbyte + 4) << 24) | (*(pbyte + 5) << 16) | (*(pbyte + 6) << 8) | (*(pbyte + 7)); value = (uint)i2 | ((long)i1 << 32); } } } ReadPosition += 8; return value; } public unsafe ulong ReadUInt64() { ulong value = 0; fixed (byte* pbyte = &Buffer[ReadPosition]) { if (ReadPosition % 8 == 0) { value = *((ulong*)pbyte); } else { if (IsLittleEndian) { int i1 = (*pbyte) | (*(pbyte + 1) << 8) | (*(pbyte + 2) << 16) | (*(pbyte + 3) << 24); int i2 = (*(pbyte + 4)) | (*(pbyte + 5) << 8) | (*(pbyte + 6) << 16) | (*(pbyte + 7) << 24); value = (uint)i1 | ((ulong)i2 << 32); } else { int i1 = (*pbyte << 24) | (*(pbyte + 1) << 16) | (*(pbyte + 2) << 8) | (*(pbyte + 3)); int i2 = (*(pbyte + 4) << 24) | (*(pbyte + 5) << 16) | (*(pbyte + 6) << 8) | (*(pbyte + 7)); value = (uint)i2 | ((ulong)i1 << 32); } } } ReadPosition += 8; return value; } public unsafe decimal ReadInt128() { decimal value = 0; fixed (byte* ptr = &Buffer[ReadPosition]) { value = *((decimal*)ptr); } ReadPosition += 16; return value; } public unsafe double ReadDouble() { double value = 0; fixed (byte* ptr = &Buffer[ReadPosition]) { value = *((double*)ptr); } ReadPosition += 16; return value; } public unsafe float ReadFloat() { int value = 0; fixed (byte* pbyte = &Buffer[ReadPosition]) { if (ReadPosition % 4 == 0) { value = *((int*)pbyte); } else { if (IsLittleEndian) { value = (int)((*pbyte) | (*(pbyte + 1) << 8) | (*(pbyte + 2) << 16) | (*(pbyte + 3) << 24)); } else { value = (int)((*pbyte << 24) | (*(pbyte + 1) << 16) | (*(pbyte + 2) << 8) | (*(pbyte + 3))); } } } ReadPosition += 4; return *(float*)&value; } public DateTime ReadDateTime() { long value = ReadInt64(); try { return new DateTime(value, DateTimeKind.Utc); } catch { return DateTime.MinValue; } } public bool TryReadDateTime(out DateTime time) { long value = ReadInt64(); try { time = new DateTime(value, DateTimeKind.Utc); return true; } catch { } time = default; return false; } public unsafe IPAddress ReadIPAddress() { uint ip = ReadUInt32(); return new IPAddress(ip); } public unsafe IPEndPoint ReadIPEndPoint() { uint ip = ReadUInt32(); ushort port = ReadUInt16(); return new IPEndPoint(ip, port); } public T Read() where T : IStreamSerializable { T value = default; value.Read(this); return value; } public byte[] ReadByteArray(int lenght) { if (lenght == 0) return new byte[0]; byte[] newBuffer = new byte[lenght]; System.Array.Copy(this.Buffer, this.ReadPosition, newBuffer, 0, lenght); this.ReadPosition += lenght; return newBuffer; } public void ReadTo(byte[] buffer, int offset, int size) { System.Array.Copy(this.Buffer, this.ReadPosition, buffer, offset, size); this.ReadPosition += size; } public unsafe string ReadString(int size) { string str; #if NETCOREAPP fixed (byte* ptr = &Buffer[ReadPosition]) str = System.Text.Encoding.UTF8.GetString(ptr, size); #else str = System.Text.Encoding.UTF8.GetString(Buffer, ReadPosition, size); #endif ReadPosition += size; return str; } public unsafe bool TryReadString(out string str) { if (!CanRead(2)) { str = null; return false; } int size = 0; fixed (byte* ptr = &Buffer[ReadPosition]) size = *((ushort*)ptr); ReadPosition += 2; if (!CanRead(size)) { str = null; return false; } #if NETCOREAPP fixed (byte* ptr = &Buffer[ReadPosition]) str = System.Text.Encoding.UTF8.GetString(ptr, size); #else str = System.Text.Encoding.UTF8.GetString(Buffer, ReadPosition, size); #endif ReadPosition += size; return true; } public unsafe bool TryReadString(out string str, int maximumSize = ushort.MaxValue) { if (!CanRead(2)) { str = null; return false; } int size = 0; fixed (byte* ptr = &Buffer[ReadPosition]) size = *((ushort*)ptr); ReadPosition += 2; if (size > maximumSize) { str = null; return false; } if (!CanRead(size)) { str = null; return false; } #if NETCOREAPP fixed (byte* ptr = &Buffer[ReadPosition]) str = System.Text.Encoding.UTF8.GetString(ptr, size); #else str = System.Text.Encoding.UTF8.GetString(Buffer, ReadPosition, size); #endif ReadPosition += size; return true; } public unsafe bool TrySkipString() { if (!CanRead(2)) return false; int size = 0; fixed (byte* ptr = &Buffer[ReadPosition]) size = *((ushort*)ptr); ReadPosition += 2; if (!CanRead(size)) return false; ReadPosition += size; return true; } public int NumberOfBytesReadable { get => WritePosition - ReadPosition; } public void SkipWriting(int size) { EnsureWriteBufferSize(size); WritePosition += size; } public void SkipReading(int size) { ReadPosition += size; } // -------- Finalizing ------ public byte[] AsByteArrayData() { var data = new byte[this.WritePosition]; System.Array.Copy(this.Buffer, 0, data, 0, this.WritePosition); return data; } public void Reset() { this.ReadPosition = 0; this.WritePosition = 0; } public void Dispose() { if (InPool) return; InPool = true; lock (mPool) mPool.Enqueue(this); } // ------- Pool ----- private static Queue mPool = new Queue(1024 * 256); public static Stream Get() { lock (mPool) { if (mPool.Count > 0) { var item = mPool.Dequeue(); item.WritePosition = 0; item.ReadPosition = 0; item.InPool = false; return item; } } return new Stream() { Buffer = new byte[DefaultBufferSize], InPool = false, ReadPosition = 0, WritePosition = 0, }; } } }