/************************************************************************* * ModernUO * * Copyright 2019-2025 - ModernUO Development Team * * Email: hi@modernuo.com * * File: SpanReader.cs * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * *************************************************************************/ using System.Buffers.Binary; using System.IO; using System.Runtime.CompilerServices; using System.Text; using Server; using Server.Text; namespace System.Buffers; public ref struct SpanReader { private readonly ReadOnlySpan _buffer; public int Length { get; } public int Position { get; private set; } public int Remaining => Length - Position; public ReadOnlySpan Buffer => _buffer; public SpanReader(ReadOnlySpan span) { _buffer = span; Position = 0; Length = span.Length; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public byte ReadByte() { if (Position >= Length) { throw new EndOfStreamException("Cannot read past the end of the buffer."); } return _buffer[Position++]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ReadBoolean() => ReadByte() > 0; [MethodImpl(MethodImplOptions.AggressiveInlining)] public sbyte ReadSByte() => (sbyte)ReadByte(); [MethodImpl(MethodImplOptions.AggressiveInlining)] public short ReadInt16() { if (!BinaryPrimitives.TryReadInt16BigEndian(_buffer[Position..], out var value)) { throw new EndOfStreamException("Cannot read past the end of the buffer."); } Position += 2; return value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public short ReadInt16LE() { if (!BinaryPrimitives.TryReadInt16LittleEndian(_buffer[Position..], out var value)) { throw new EndOfStreamException("Cannot read past the end of the buffer."); } Position += 2; return value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public ushort ReadUInt16() { if (!BinaryPrimitives.TryReadUInt16BigEndian(_buffer[Position..], out var value)) { throw new EndOfStreamException("Cannot read past the end of the buffer."); } Position += 2; return value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public ushort ReadUInt16LE() { if (!BinaryPrimitives.TryReadUInt16LittleEndian(_buffer[Position..], out var value)) { throw new EndOfStreamException("Cannot read past the end of the buffer."); } Position += 2; return value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public int ReadInt32() { if (!BinaryPrimitives.TryReadInt32BigEndian(_buffer[Position..], out var value)) { throw new EndOfStreamException("Cannot read past the end of the buffer."); } Position += 4; return value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public uint ReadUInt32() { if (!BinaryPrimitives.TryReadUInt32BigEndian(_buffer[Position..], out var value)) { throw new EndOfStreamException("Cannot read past the end of the buffer."); } Position += 4; return value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public uint ReadUInt32LE() { if (!BinaryPrimitives.TryReadUInt32LittleEndian(_buffer[Position..], out var value)) { throw new EndOfStreamException("Cannot read past the end of the buffer."); } Position += 4; return value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public long ReadInt64() { if (!BinaryPrimitives.TryReadInt64BigEndian(_buffer[Position..], out var value)) { throw new EndOfStreamException("Cannot read past the end of the buffer."); } Position += 8; return value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public ulong ReadUInt64() { if (!BinaryPrimitives.TryReadUInt64BigEndian(_buffer[Position..], out var value)) { throw new EndOfStreamException("Cannot read past the end of the buffer."); } Position += 8; return value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public string ReadString(Encoding encoding, bool safeString = false, int fixedLength = -1) { if (fixedLength == 0) { return ""; } var byteLength = encoding.GetByteLengthForEncoding(); var isFixedLength = fixedLength > -1; var remaining = Remaining; int size; if (isFixedLength) { size = fixedLength * byteLength; if (size > Remaining) { throw new EndOfStreamException("Cannot read past the end of the buffer."); } } else { // In case the remaining is not evenly divisible size = remaining - (remaining & (byteLength - 1)); } var span = _buffer.Slice(Position, size); var index = span.IndexOfTerminator(byteLength); if (index > -1) { span = _buffer.Slice(Position, index); } Position += isFixedLength || index < 0 ? size : index + 1; // The string is either as long as the first terminator character, remaining buffer size, or fixed length. return TextEncoding.GetString(span, encoding, safeString); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public string ReadLittleUniSafe(int fixedLength) => ReadString(TextEncoding.UnicodeLE, true, fixedLength); [MethodImpl(MethodImplOptions.AggressiveInlining)] public string ReadLittleUniSafe() => ReadString(TextEncoding.UnicodeLE, true); [MethodImpl(MethodImplOptions.AggressiveInlining)] public string ReadLittleUni(int fixedLength) => ReadString(TextEncoding.UnicodeLE, false, fixedLength); [MethodImpl(MethodImplOptions.AggressiveInlining)] public string ReadLittleUni() => ReadString(TextEncoding.UnicodeLE); [MethodImpl(MethodImplOptions.AggressiveInlining)] public string ReadBigUniSafe(int fixedLength) => ReadString(TextEncoding.Unicode, true, fixedLength); [MethodImpl(MethodImplOptions.AggressiveInlining)] public string ReadBigUniSafe() => ReadString(TextEncoding.Unicode, true); [MethodImpl(MethodImplOptions.AggressiveInlining)] public string ReadBigUni(int fixedLength) => ReadString(TextEncoding.Unicode, false, fixedLength); [MethodImpl(MethodImplOptions.AggressiveInlining)] public string ReadBigUni() => ReadString(TextEncoding.Unicode); [MethodImpl(MethodImplOptions.AggressiveInlining)] public string ReadUTF8Safe(int fixedLength) => ReadString(TextEncoding.UTF8, true, fixedLength); [MethodImpl(MethodImplOptions.AggressiveInlining)] public string ReadUTF8Safe() => ReadString(TextEncoding.UTF8, true); [MethodImpl(MethodImplOptions.AggressiveInlining)] public string ReadUTF8() => ReadString(TextEncoding.UTF8); [MethodImpl(MethodImplOptions.AggressiveInlining)] public string ReadAsciiSafe(int fixedLength) => ReadString(Encoding.ASCII, true, fixedLength); [MethodImpl(MethodImplOptions.AggressiveInlining)] public string ReadAsciiSafe() => ReadString(Encoding.ASCII, true); [MethodImpl(MethodImplOptions.AggressiveInlining)] public string ReadAscii(int fixedLength) => ReadString(Encoding.ASCII, false, fixedLength); [MethodImpl(MethodImplOptions.AggressiveInlining)] public string ReadAscii() => ReadString(Encoding.ASCII); [MethodImpl(MethodImplOptions.AggressiveInlining)] public int Seek(int offset, SeekOrigin origin) { var newPosition = origin switch { SeekOrigin.Current => Position + offset, SeekOrigin.End => _buffer.Length + offset, _ => offset // Begin }; if (newPosition < 0) { throw new ArgumentOutOfRangeException(nameof(offset), "Seek operation would result in a negative position."); } if (newPosition > _buffer.Length) { throw new ArgumentOutOfRangeException(nameof(offset), $"Cannot seek to position {newPosition} beyond buffer length {_buffer.Length}."); } return Position = newPosition; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public int Read(scoped Span bytes) { if (bytes.Length == 0) { return 0; } var bytesWritten = Math.Min(bytes.Length, Remaining); _buffer.Slice(Position, bytesWritten).CopyTo(bytes); Position += bytesWritten; return bytesWritten; } }