From acc55f6cf7fcfb9a64b703c79e5f51c7ac42afc4 Mon Sep 17 00:00:00 2001 From: FUTURE-SL Date: Thu, 19 Mar 2026 18:18:41 +0300 Subject: [PATCH 1/4] Refactor NetDataReader for safer, faster reads Rework NetDataReader to improve safety, performance and API clarity. Adds bounds-checked helpers (EnsureAvailable, ThrowNotEnoughData), validates SetSource/SetPosition inputs and changes SetSource to use an endOffset. Replaces BitConverter/Buffer.BlockCopy usages with Span/MemoryMarshal-based GetUnmanaged/PeekUnmanaged/TryGetUnmanaged helpers and uses ReadOnlySpan/ReadOnlyMemory for remaining bytes. Streamlines Get/Peek/TryGet methods (value-returning overloads, generic unmanaged array handling), improves string and IPEndPoint parsing, and introduces typed TryGet and Peek methods for common primitives. --- LiteNetLib/Utils/NetDataReader.cs | 616 ++++++++++++++---------------- 1 file changed, 285 insertions(+), 331 deletions(-) diff --git a/LiteNetLib/Utils/NetDataReader.cs b/LiteNetLib/Utils/NetDataReader.cs index b8599e43..30ff5c34 100644 --- a/LiteNetLib/Utils/NetDataReader.cs +++ b/LiteNetLib/Utils/NetDataReader.cs @@ -1,6 +1,7 @@ using System; using System.Net; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace LiteNetLib.Utils { @@ -11,6 +12,9 @@ public class NetDataReader protected int _dataSize; protected int _offset; + private const int IPv4Size = 4; + private const int IPv6Size = 16; + public byte[] RawData { [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -55,14 +59,30 @@ public int AvailableBytes [MethodImpl(MethodImplOptions.AggressiveInlining)] private void EnsureAvailable(int count) { - if (count < 0 || count > AvailableBytes) - throw new InvalidOperationException( - $"Not enough data to read {count} byte(s). Position={_position}, DataSize={_dataSize}"); + if ((uint)count > (uint)AvailableBytes) + ThrowNotEnoughData(count); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void ThrowNotEnoughData(int count) + { + throw new InvalidOperationException( + $"Not enough data to read {count} byte(s). Position={_position}, DataSize={_dataSize}"); + } + + public void SkipBytes(int count) + { + EnsureAvailable(count); + _position += count; } - public void SkipBytes(int count) => _position += count; + public void SetPosition(int position) + { + if ((uint)position > (uint)_dataSize) + throw new ArgumentOutOfRangeException(nameof(position)); - public void SetPosition(int position) => _position = position; + _position = position; + } public void SetSource(NetDataWriter dataWriter) { @@ -80,12 +100,17 @@ public void SetSource(byte[] source) _dataSize = source.Length; } - public void SetSource(byte[] source, int offset, int maxSize) + public void SetSource(byte[] source, int offset, int endOffset) { + if ((uint)offset > (uint)source.Length) + throw new ArgumentOutOfRangeException(nameof(offset)); + if ((uint)endOffset > (uint)source.Length || endOffset < offset) + throw new ArgumentOutOfRangeException(nameof(endOffset)); + _data = source; _position = offset; _offset = offset; - _dataSize = maxSize; + _dataSize = endOffset; } public NetDataReader() { } @@ -94,196 +119,139 @@ public NetDataReader() { } public NetDataReader(byte[] source) => SetSource(source); - public NetDataReader(byte[] source, int offset, int maxSize) => SetSource(source, offset, maxSize); + public NetDataReader(byte[] source, int offset, int endOffset) => SetSource(source, offset, endOffset); #region GetMethods + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Get(out T result) where T : struct, INetSerializable + => result = Get(); + + public T Get() where T : struct, INetSerializable { - result = default(T); - result.Deserialize(this); + var obj = default(T); + obj.Deserialize(this); + return obj; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Get(out T result, Func constructor) where T : class, INetSerializable + => result = Get(constructor); + + public T Get(Func constructor) where T : class, INetSerializable { - result = constructor(); - result.Deserialize(this); + var obj = constructor(); + obj.Deserialize(this); + return obj; } public void Get(out IPEndPoint result) => result = GetIPEndPoint(); public IPEndPoint GetIPEndPoint() { - IPAddress address; - //IPv4 - if (GetByte() == 0) - { - EnsureAvailable(4); - address = new IPAddress(new ReadOnlySpan(_data, _position, 4)); - _position += 4; - } - //IPv6 - else - { - EnsureAvailable(16); - address = new IPAddress(new ReadOnlySpan(_data, _position, 16)); - _position += 16; - } - return new IPEndPoint(address, GetUShort()); - } + bool isIPv4 = GetByte() == 0; - public void Get(out byte result) => result = GetByte(); - public void Get(out sbyte result) => result = (sbyte)GetByte(); - public void Get(out bool result) => result = GetBool(); - public void Get(out char result) => result = GetChar(); - public void Get(out ushort result) => result = GetUShort(); - public void Get(out short result) => result = GetShort(); - public void Get(out ulong result) => result = GetULong(); - public void Get(out long result) => result = GetLong(); - public void Get(out uint result) => result = GetUInt(); - public void Get(out int result) => result = GetInt(); - public void Get(out double result) => result = GetDouble(); - public void Get(out float result) => result = GetFloat(); - public void Get(out string result) => result = GetString(); - public void Get(out string result, int maxLength) => result = GetString(maxLength); - public void Get(out Guid result) => result = GetGuid(); - - public byte GetByte() => _data[_position++]; - - public sbyte GetSByte() => (sbyte)GetByte(); - - public T[] GetArray(ushort size) - { - ushort length = BitConverter.ToUInt16(_data, _position); - _position += 2; - - int byteLength = checked(length * size); - EnsureAvailable(byteLength); - - T[] result = new T[length]; - if (byteLength > 0) - Buffer.BlockCopy(_data, _position, result, 0, byteLength); + int size = isIPv4 ? IPv4Size : IPv6Size; + EnsureAvailable(size); - _position += byteLength; - return result; + IPAddress address = new IPAddress(new ReadOnlySpan(_data, _position, size)); + _position += size; + return new IPEndPoint(address, GetUShort()); } public T[] GetArray() where T : INetSerializable, new() { - ushort length = BitConverter.ToUInt16(_data, _position); - _position += 2; + ushort length = GetUShort(); + T[] result = new T[length]; for (int i = 0; i < length; i++) { - var item = new T(); - item.Deserialize(this); - result[i] = item; + result[i] = new T(); + result[i].Deserialize(this); } + return result; } public T[] GetArray(Func constructor) where T : class, INetSerializable { - ushort length = BitConverter.ToUInt16(_data, _position); - _position += 2; + ushort length = GetUShort(); + T[] result = new T[length]; for (int i = 0; i < length; i++) + { Get(out result[i], constructor); + } + return result; } - public bool[] GetBoolArray() => GetArray(1); - public ushort[] GetUShortArray() => GetArray(2); - public short[] GetShortArray() => GetArray(2); - public int[] GetIntArray() => GetArray(4); - public uint[] GetUIntArray() => GetArray(4); - public float[] GetFloatArray() => GetArray(4); - public double[] GetDoubleArray() => GetArray(8); - public long[] GetLongArray() => GetArray(8); - public ulong[] GetULongArray() => GetArray(8); + public bool GetBool() => GetByte() == 1; + public void Get(out bool result) => result = GetBool(); + public bool[] GetBoolArray() => GetUnmanagedArray(); - public string[] GetStringArray() - { - ushort length = GetUShort(); - string[] arr = new string[length]; - for (int i = 0; i < length; i++) - { - arr[i] = GetString(); - } - return arr; - } + public byte GetByte() => GetUnmanaged(); + public void Get(out byte result) => result = GetByte(); + public byte[] GetBytesWithLength() => GetUnmanagedArray(); - /// - /// Note that "maxStringLength" only limits the number of characters in a string, not its size in bytes. - /// Strings that exceed this parameter are returned as empty - /// - public string[] GetStringArray(int maxStringLength) - { - ushort length = GetUShort(); - string[] arr = new string[length]; - for (int i = 0; i < length; i++) - { - arr[i] = GetString(maxStringLength); - } - return arr; - } + public sbyte GetSByte() => GetUnmanaged(); + public void Get(out sbyte result) => result = GetSByte(); + public sbyte[] GetSBytesWithLength() => GetUnmanagedArray(); - public bool GetBool() => GetByte() == 1; - public char GetChar() => (char)GetUShort(); + public char GetChar() => GetUnmanaged(); + public void Get(out char result) => result = GetChar(); + public char[] GetCharArray() => GetUnmanagedArray(); - public ushort GetUShort() - { - ushort result = BitConverter.ToUInt16(_data, _position); - _position += 2; - return result; - } + public short GetShort() => GetUnmanaged(); + public void Get(out short result) => result = GetShort(); + public short[] GetShortArray() => GetUnmanagedArray(); - public short GetShort() - { - short result = BitConverter.ToInt16(_data, _position); - _position += 2; - return result; - } + public ushort GetUShort() => GetUnmanaged(); + public void Get(out ushort result) => result = GetUShort(); + public ushort[] GetUShortArray() => GetUnmanagedArray(); - public long GetLong() - { - long result = BitConverter.ToInt64(_data, _position); - _position += 8; - return result; - } + public int GetInt() => GetUnmanaged(); + public void Get(out int result) => result = GetInt(); + public int[] GetIntArray() => GetUnmanagedArray(); - public ulong GetULong() - { - ulong result = BitConverter.ToUInt64(_data, _position); - _position += 8; - return result; - } + public uint GetUInt() => GetUnmanaged(); + public void Get(out uint result) => result = GetUInt(); + public uint[] GetUIntArray() => GetUnmanagedArray(); - public int GetInt() - { - int result = BitConverter.ToInt32(_data, _position); - _position += 4; - return result; - } + public float GetFloat() => GetUnmanaged(); + public void Get(out float result) => result = GetFloat(); + public float[] GetFloatArray() => GetUnmanagedArray(); - public uint GetUInt() - { - uint result = BitConverter.ToUInt32(_data, _position); - _position += 4; - return result; - } + public long GetLong() => GetUnmanaged(); + public void Get(out long result) => result = GetLong(); + public long[] GetLongArray() => GetUnmanagedArray(); - public float GetFloat() - { - float result = BitConverter.ToSingle(_data, _position); - _position += 4; - return result; - } + public ulong GetULong() => GetUnmanaged(); + public void Get(out ulong result) => result = GetULong(); + public ulong[] GetULongArray() => GetUnmanagedArray(); + + public double GetDouble() => GetUnmanaged(); + public void Get(out double result) => result = GetDouble(); + public double[] GetDoubleArray() => GetUnmanagedArray(); + + public Guid GetGuid() => GetUnmanaged(); + public void Get(out Guid result) => result = GetGuid(); + public Guid[] GetGuidArray() => GetUnmanagedArray(); - public double GetDouble() + public void Get(out string result) => result = GetString(); + public void Get(out string result, int maxLength) => result = GetString(maxLength); + + public string GetString() { - double result = BitConverter.ToDouble(_data, _position); - _position += 8; + ushort size = GetUShort(); + if (size == 0) + return string.Empty; + + int actualSize = size - 1; + EnsureAvailable(actualSize); + + string result = NetDataWriter.uTF8Encoding.GetString(_data, _position, actualSize); + _position += actualSize; return result; } @@ -309,17 +277,35 @@ public string GetString(int maxLength) return result; } - public string GetString() + public string[] GetStringArray() { - ushort size = GetUShort(); - if (size == 0) - return string.Empty; + ushort length = GetUShort(); + EnsureAvailable(checked(length * sizeof(ushort))); // 2 bytes (ushort) for string length - int actualSize = size - 1; - EnsureAvailable(actualSize); + string[] result = new string[length]; + for (int i = 0; i < length; i++) + { + result[i] = GetString(); + } + + return result; + } + + /// + /// Note that "maxStringLength" only limits the number of characters in a string, not its size in bytes. + /// Strings that exceed this parameter are returned as empty + /// + public string[] GetStringArray(int maxStringLength) + { + ushort length = GetUShort(); + EnsureAvailable(checked(length * sizeof(ushort))); // 2 bytes (ushort) for string length + + string[] result = new string[length]; + for (int i = 0; i < length; i++) + { + result[i] = GetString(maxStringLength); + } - string result = NetDataWriter.uTF8Encoding.GetString(_data, _position, actualSize); - _position += actualSize; return result; } @@ -336,14 +322,6 @@ public string GetLargeString() return result; } - public Guid GetGuid() - { - EnsureAvailable(16); - var result = new Guid(_data.AsSpan(_position, 16)); - _position += 16; - return result; - } - public ArraySegment GetBytesSegment(int count) { EnsureAvailable(count); @@ -359,68 +337,64 @@ public ArraySegment GetRemainingBytesSegment() return segment; } - public T Get() where T : struct, INetSerializable + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan GetRemainingBytesSpan() { - var obj = default(T); - obj.Deserialize(this); - return obj; + ReadOnlySpan span = new ReadOnlySpan(_data, _position, AvailableBytes); + _position = _dataSize; + return span; } - public T Get(Func constructor) where T : class, INetSerializable + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyMemory GetRemainingBytesMemory() { - var obj = constructor(); - obj.Deserialize(this); - return obj; + ReadOnlyMemory memory = new ReadOnlyMemory(_data, _position, AvailableBytes); + _position = _dataSize; + return memory; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan GetRemainingBytesSpan() => - new ReadOnlySpan(_data, _position, _dataSize - _position); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlyMemory GetRemainingBytesMemory() => - new ReadOnlyMemory(_data, _position, _dataSize - _position); - public byte[] GetRemainingBytes() { - byte[] outgoingData = new byte[AvailableBytes]; - Buffer.BlockCopy(_data, _position, outgoingData, 0, AvailableBytes); + byte[] result = _data.AsSpan(_position, AvailableBytes).ToArray(); _position = _dataSize; - return outgoingData; + return result; } public void GetBytes(byte[] destination, int start, int count) { EnsureAvailable(count); - Buffer.BlockCopy(_data, _position, destination, start, count); + _data.AsSpan(_position, count).CopyTo(destination.AsSpan(start, count)); _position += count; } public void GetBytes(byte[] destination, int count) { EnsureAvailable(count); - Buffer.BlockCopy(_data, _position, destination, 0, count); + _data.AsSpan(_position, count).CopyTo(destination.AsSpan(0, count)); _position += count; } - public sbyte[] GetSBytesWithLength() => GetArray(1); - public byte[] GetBytesWithLength() => GetArray(1); #endregion #region PeekMethods - public byte PeekByte() => _data[_position]; - public sbyte PeekSByte() => (sbyte)_data[_position]; - public bool PeekBool() => _data[_position] == 1; - public char PeekChar() => (char)PeekUShort(); - public ushort PeekUShort() => BitConverter.ToUInt16(_data, _position); - public short PeekShort() => BitConverter.ToInt16(_data, _position); - public long PeekLong() => BitConverter.ToInt64(_data, _position); - public ulong PeekULong() => BitConverter.ToUInt64(_data, _position); - public int PeekInt() => BitConverter.ToInt32(_data, _position); - public uint PeekUInt() => BitConverter.ToUInt32(_data, _position); - public float PeekFloat() => BitConverter.ToSingle(_data, _position); - public double PeekDouble() => BitConverter.ToDouble(_data, _position); + public bool PeekBool() => PeekByte() == 1; + public byte PeekByte() => PeekUnmanaged(); + public sbyte PeekSByte() => PeekUnmanaged(); + + public char PeekChar() => PeekUnmanaged(); + public short PeekShort() => PeekUnmanaged(); + public ushort PeekUShort() => PeekUnmanaged(); + + public int PeekInt() => PeekUnmanaged(); + public uint PeekUInt() => PeekUnmanaged(); + public float PeekFloat() => PeekUnmanaged(); + + public long PeekLong() => PeekUnmanaged(); + public ulong PeekULong() => PeekUnmanaged(); + public double PeekDouble() => PeekUnmanaged(); + + public Guid PeekGuid() => PeekUnmanaged(); /// /// Note that "maxLength" only limits the number of characters in a string, not its size in bytes. @@ -432,9 +406,12 @@ public string PeekString(int maxLength) return string.Empty; int actualSize = size - 1; - return (maxLength > 0 && NetDataWriter.uTF8Encoding.GetCharCount(_data, _position + 2, actualSize) > maxLength) + EnsureAvailable(sizeof(ushort) + actualSize); + + return maxLength > 0 && + NetDataWriter.uTF8Encoding.GetCharCount(_data, _position + sizeof(ushort), actualSize) > maxLength ? string.Empty - : NetDataWriter.uTF8Encoding.GetString(_data, _position + 2, actualSize); + : NetDataWriter.uTF8Encoding.GetString(_data, _position + sizeof(ushort), actualSize); } public string PeekString() @@ -444,195 +421,172 @@ public string PeekString() return string.Empty; int actualSize = size - 1; - return NetDataWriter.uTF8Encoding.GetString(_data, _position + 2, actualSize); + EnsureAvailable(sizeof(ushort) + actualSize); + + return NetDataWriter.uTF8Encoding.GetString(_data, _position + sizeof(ushort), actualSize); } + #endregion #region TryGetMethods - public bool TryGetByte(out byte result) - { - if (AvailableBytes >= 1) - { - result = GetByte(); - return true; - } - result = 0; - return false; - } - public bool TryGetSByte(out sbyte result) + public bool TryGetBool(out bool result) { - if (AvailableBytes >= 1) + if (!TryGetByte(out byte value)) { - result = GetSByte(); - return true; + result = default; + return false; } - result = 0; - return false; + + result = value == 1; + return true; } - public bool TryGetBool(out bool result) + public bool TryGetByte(out byte result) => TryGetUnmanaged(out result); + public bool TryGetSByte(out sbyte result) => TryGetUnmanaged(out result); + + public bool TryGetChar(out char result) => TryGetUnmanaged(out result); + public bool TryGetShort(out short result) => TryGetUnmanaged(out result); + public bool TryGetUShort(out ushort result) => TryGetUnmanaged(out result); + + public bool TryGetInt(out int result) => TryGetUnmanaged(out result); + public bool TryGetUInt(out uint result) => TryGetUnmanaged(out result); + public bool TryGetFloat(out float result) => TryGetUnmanaged(out result); + + public bool TryGetLong(out long result) => TryGetUnmanaged(out result); + public bool TryGetULong(out ulong result) => TryGetUnmanaged(out result); + public bool TryGetDouble(out double result) => TryGetUnmanaged(out result); + + public bool TryGetGuid(out Guid result) => TryGetUnmanaged(out result); + + public bool TryGetString(out string result) { - if (AvailableBytes >= 1) + if (AvailableBytes < sizeof(ushort)) { - result = GetBool(); - return true; + result = null; + return false; } - result = false; - return false; - } - public bool TryGetChar(out char result) - { - if (!TryGetUShort(out ushort uShortValue)) + ushort size = PeekUShort(); + int actualSize = size == 0 ? 0 : size - 1; + + if (AvailableBytes < sizeof(ushort) + actualSize) { - result = '\0'; + result = null; return false; } - result = (char)uShortValue; + + result = GetString(); return true; } - public bool TryGetShort(out short result) + public bool TryGetStringArray(out string[] result) { - if (AvailableBytes >= 2) + if (AvailableBytes < sizeof(ushort)) { - result = GetShort(); - return true; + result = null; + return false; } - result = 0; - return false; - } - public bool TryGetUShort(out ushort result) - { - if (AvailableBytes >= 2) + int startPosition = _position; + + ushort length = PeekUShort(); + if (AvailableBytes < sizeof(ushort) + checked(length * sizeof(ushort))) { - result = GetUShort(); - return true; + result = null; + return false; } - result = 0; - return false; - } + _position += sizeof(ushort); - public bool TryGetInt(out int result) - { - if (AvailableBytes >= 4) + string[] values = new string[length]; + for (int i = 0; i < length; i++) { - result = GetInt(); - return true; + if (!TryGetString(out values[i])) + { + _position = startPosition; + result = null; + return false; + } } - result = 0; - return false; + + result = values; + return true; } - public bool TryGetUInt(out uint result) + public bool TryGetBytesWithLength(out byte[] result) { - if (AvailableBytes >= 4) + if (AvailableBytes < sizeof(ushort)) { - result = GetUInt(); - return true; + result = null; + return false; } - result = 0; - return false; - } - public bool TryGetLong(out long result) - { - if (AvailableBytes >= 8) + ushort length = PeekUShort(); + if (AvailableBytes < sizeof(ushort) + length) { - result = GetLong(); - return true; + result = null; + return false; } - result = 0; - return false; + + result = GetBytesWithLength(); + return true; } - public bool TryGetULong(out ulong result) + #endregion + + #region Helpers + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe T PeekUnmanaged() where T : unmanaged { - if (AvailableBytes >= 8) - { - result = GetULong(); - return true; - } - result = 0; - return false; + int size = sizeof(T); + EnsureAvailable(size); + return MemoryMarshal.Read(_data.AsSpan(_position)); } - public bool TryGetFloat(out float result) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe T GetUnmanaged() where T : unmanaged { - if (AvailableBytes >= 4) - { - result = GetFloat(); - return true; - } - result = 0; - return false; + int size = sizeof(T); + EnsureAvailable(size); + + T value = MemoryMarshal.Read(_data.AsSpan(_position)); + _position += size; + return value; } - public bool TryGetDouble(out double result) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe bool TryGetUnmanaged(out T result) where T : unmanaged { - if (AvailableBytes >= 8) + if (MemoryMarshal.TryRead(_data.AsSpan(_position, AvailableBytes), out result)) { - result = GetDouble(); + _position += sizeof(T); return true; } - result = 0; - return false; - } - - public bool TryGetString(out string result) - { - if (AvailableBytes >= 2) - { - ushort strSize = PeekUShort(); - int actualSize = strSize == 0 ? 0 : strSize - 1; - if (AvailableBytes >= 2 + actualSize) - { - result = GetString(); - return true; - } - } - result = null; + result = default; return false; } - public bool TryGetStringArray(out string[] result) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe T[] GetUnmanagedArray() where T : unmanaged { - if (!TryGetUShort(out ushort strArrayLength)) - { - result = null; - return false; - } + ushort length = GetUShort(); + + int byteLength = checked(length * sizeof(T)); + EnsureAvailable(byteLength); - result = new string[strArrayLength]; - for (int i = 0; i < strArrayLength; i++) + T[] result = new T[length]; + if (byteLength != 0) { - if (!TryGetString(out result[i])) - { - result = null; - return false; - } + MemoryMarshal.Cast(_data.AsSpan(_position, byteLength)) + .CopyTo(result.AsSpan()); } - return true; + _position += byteLength; + return result; } - public bool TryGetBytesWithLength(out byte[] result) - { - if (AvailableBytes >= 2) - { - ushort length = PeekUShort(); - if (AvailableBytes >= 2 + length) - { - result = GetBytesWithLength(); - return true; - } - } - result = null; - return false; - } #endregion public void Clear() From 10dcffaf7d1fe3393405c1479cbabd088db784da Mon Sep 17 00:00:00 2001 From: FUTURE-SL Date: Thu, 19 Mar 2026 23:40:42 +0300 Subject: [PATCH 2/4] Use Unsafe.ReadUnaligned; add Unsafe package Add System.Runtime.CompilerServices.Unsafe (v6.0.0) to the project and update NetDataReader.cs to use Unsafe.ReadUnaligned for unmanaged reads instead of MemoryMarshal.Read/TryRead. Also adjust TryGetUnmanaged to validate size and advance position correctly, and fix the generic Deserialize loop to create/deserialize the item before assigning it to the result array. Changes touch LiteNetLib.csproj and Utils/NetDataReader.cs. --- LiteNetLib/LiteNetLib.csproj | 4 ++++ LiteNetLib/Utils/NetDataReader.cs | 18 ++++++++++-------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/LiteNetLib/LiteNetLib.csproj b/LiteNetLib/LiteNetLib.csproj index a896bb08..c4e3af8e 100644 --- a/LiteNetLib/LiteNetLib.csproj +++ b/LiteNetLib/LiteNetLib.csproj @@ -46,6 +46,10 @@ README.md + + + + True diff --git a/LiteNetLib/Utils/NetDataReader.cs b/LiteNetLib/Utils/NetDataReader.cs index 30ff5c34..f2a808b7 100644 --- a/LiteNetLib/Utils/NetDataReader.cs +++ b/LiteNetLib/Utils/NetDataReader.cs @@ -166,8 +166,9 @@ public IPEndPoint GetIPEndPoint() T[] result = new T[length]; for (int i = 0; i < length; i++) { - result[i] = new T(); - result[i].Deserialize(this); + var item = new T(); + item.Deserialize(this); + result[i] = item; } return result; @@ -539,9 +540,8 @@ public bool TryGetBytesWithLength(out byte[] result) [MethodImpl(MethodImplOptions.AggressiveInlining)] private unsafe T PeekUnmanaged() where T : unmanaged { - int size = sizeof(T); - EnsureAvailable(size); - return MemoryMarshal.Read(_data.AsSpan(_position)); + EnsureAvailable(sizeof(T)); + return Unsafe.ReadUnaligned(ref _data[_position]); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -550,7 +550,7 @@ private unsafe T GetUnmanaged() where T : unmanaged int size = sizeof(T); EnsureAvailable(size); - T value = MemoryMarshal.Read(_data.AsSpan(_position)); + T value = Unsafe.ReadUnaligned(ref _data[_position]); _position += size; return value; } @@ -558,9 +558,11 @@ private unsafe T GetUnmanaged() where T : unmanaged [MethodImpl(MethodImplOptions.AggressiveInlining)] private unsafe bool TryGetUnmanaged(out T result) where T : unmanaged { - if (MemoryMarshal.TryRead(_data.AsSpan(_position, AvailableBytes), out result)) + int size = sizeof(T); + if (size <= AvailableBytes) { - _position += sizeof(T); + result = Unsafe.ReadUnaligned(ref _data[_position]); + _position += size; return true; } From 8ff0b1b6f816277e4f430975e7581fc9d795f05b Mon Sep 17 00:00:00 2001 From: FUTURE-SL Date: Fri, 20 Mar 2026 00:24:12 +0300 Subject: [PATCH 3/4] Update Source setter --- LiteNetLib/Utils/NetDataReader.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/LiteNetLib/Utils/NetDataReader.cs b/LiteNetLib/Utils/NetDataReader.cs index f2a808b7..7853b56b 100644 --- a/LiteNetLib/Utils/NetDataReader.cs +++ b/LiteNetLib/Utils/NetDataReader.cs @@ -102,11 +102,6 @@ public void SetSource(byte[] source) public void SetSource(byte[] source, int offset, int endOffset) { - if ((uint)offset > (uint)source.Length) - throw new ArgumentOutOfRangeException(nameof(offset)); - if ((uint)endOffset > (uint)source.Length || endOffset < offset) - throw new ArgumentOutOfRangeException(nameof(endOffset)); - _data = source; _position = offset; _offset = offset; From a42a87f20efb540183f36dd5abba1b3250c555a7 Mon Sep 17 00:00:00 2001 From: FUTURE-SL Date: Fri, 20 Mar 2026 00:59:20 +0300 Subject: [PATCH 4/4] Replace Unsafe.ReadUnaligned and remove package Remove the System.Runtime.CompilerServices.Unsafe package reference from the csproj and replace uses of Unsafe.ReadUnaligned in NetDataReader with a new private unsafe ReadUnmanaged() method (uses fixed pointer dereference and AggressiveInlining). This preserves the previous unaligned reads while eliminating the external Unsafe dependency and keeping behavior unchanged. --- LiteNetLib/LiteNetLib.csproj | 4 ---- LiteNetLib/Utils/NetDataReader.cs | 13 ++++++++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/LiteNetLib/LiteNetLib.csproj b/LiteNetLib/LiteNetLib.csproj index c4e3af8e..a896bb08 100644 --- a/LiteNetLib/LiteNetLib.csproj +++ b/LiteNetLib/LiteNetLib.csproj @@ -46,10 +46,6 @@ README.md - - - - True diff --git a/LiteNetLib/Utils/NetDataReader.cs b/LiteNetLib/Utils/NetDataReader.cs index 7853b56b..8b9863ed 100644 --- a/LiteNetLib/Utils/NetDataReader.cs +++ b/LiteNetLib/Utils/NetDataReader.cs @@ -532,11 +532,18 @@ public bool TryGetBytesWithLength(out byte[] result) #region Helpers + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe T ReadUnmanaged() where T : unmanaged + { + fixed (byte* ptr = &_data[_position]) + return *(T*)ptr; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private unsafe T PeekUnmanaged() where T : unmanaged { EnsureAvailable(sizeof(T)); - return Unsafe.ReadUnaligned(ref _data[_position]); + return ReadUnmanaged(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -545,7 +552,7 @@ private unsafe T GetUnmanaged() where T : unmanaged int size = sizeof(T); EnsureAvailable(size); - T value = Unsafe.ReadUnaligned(ref _data[_position]); + T value = ReadUnmanaged(); _position += size; return value; } @@ -556,7 +563,7 @@ private unsafe bool TryGetUnmanaged(out T result) where T : unmanaged int size = sizeof(T); if (size <= AvailableBytes) { - result = Unsafe.ReadUnaligned(ref _data[_position]); + result = ReadUnmanaged(); _position += size; return true; }