From 22f0404561c998e4bcc84052331a7b74b71c4341 Mon Sep 17 00:00:00 2001 From: ForeverZer0 Date: Mon, 23 Aug 2021 05:11:22 -0400 Subject: [PATCH] Implemented VarInt class Included some additional annotations to NbtFile class --- SharpNBT/EndianUtils.cs | 26 +++--- SharpNBT/NbtFile.cs | 6 +- SharpNBT/TagWriter.cs | 22 ++--- SharpNBT/VarInt.cs | 181 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 210 insertions(+), 25 deletions(-) create mode 100644 SharpNBT/VarInt.cs diff --git a/SharpNBT/EndianUtils.cs b/SharpNBT/EndianUtils.cs index 08dd72c..c76b5ba 100644 --- a/SharpNBT/EndianUtils.cs +++ b/SharpNBT/EndianUtils.cs @@ -16,19 +16,20 @@ namespace SharpNBT /// The value to convert. /// An array of bytes representing the value in big-endian format. /// The endianness of the host machine is accounted for. - internal static byte[] GetBytes(this short n) => BitConverter.GetBytes(BitConverter.IsLittleEndian ? SwapEndian(n) : n); + public static byte[] BigEndianBytes(this short n) => BitConverter.GetBytes(BitConverter.IsLittleEndian ? SwapEndian(n) : n); - /// - internal static byte[] GetBytes(this int n) => BitConverter.GetBytes(BitConverter.IsLittleEndian ? SwapEndian(n) : n); + /// + public static byte[] BigEndianBytes(this int n) => BitConverter.GetBytes(BitConverter.IsLittleEndian ? SwapEndian(n) : n); - /// - internal static byte[] GetBytes(this long n) => BitConverter.GetBytes(BitConverter.IsLittleEndian ? SwapEndian(n) : n); + /// + public static byte[] BigEndianBytes(this long n) => BitConverter.GetBytes(BitConverter.IsLittleEndian ? SwapEndian(n) : n); - /// - internal static byte[] GetBytes(this ushort n) => BitConverter.GetBytes(BitConverter.IsLittleEndian ? SwapEndian(n) : n); + /// + [CLSCompliant(false)] + internal static byte[] BigEndianBytes(this ushort n) => BitConverter.GetBytes(BitConverter.IsLittleEndian ? SwapEndian(n) : n); - /// - internal static byte[] GetBytes(this float n) + /// + internal static byte[] BigEndianBytes(this float n) { var bytes = BitConverter.GetBytes(n); if (BitConverter.IsLittleEndian) @@ -36,8 +37,8 @@ namespace SharpNBT return bytes; } - /// - internal static byte[] GetBytes(this double n) + /// + internal static byte[] BigEndianBytes(this double n) { var bytes = BitConverter.GetBytes(n); if (BitConverter.IsLittleEndian) @@ -57,6 +58,7 @@ namespace SharpNBT } /// + [CLSCompliant(false)] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ushort SwapEndian(this ushort value) { @@ -68,6 +70,7 @@ namespace SharpNBT public static int SwapEndian(this int value) => unchecked((int) SwapEndian(unchecked((uint)value))); /// + [CLSCompliant(false)] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint SwapEndian(this uint value) { @@ -76,6 +79,7 @@ namespace SharpNBT } /// + [CLSCompliant(false)] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ulong SwapEndian(this ulong val) { diff --git a/SharpNBT/NbtFile.cs b/SharpNBT/NbtFile.cs index 2565b93..2698b06 100644 --- a/SharpNBT/NbtFile.cs +++ b/SharpNBT/NbtFile.cs @@ -60,7 +60,7 @@ namespace SharpNBT /// /// The path of the file to query. /// if GZip compression was detected, otherwise . - public static bool IsCompressed(string path) + public static bool IsCompressed([NotNull] string path) { using var str = File.OpenRead(path); return str.ReadByte() == 0x1F && str.ReadByte() == 0x8B; @@ -72,7 +72,7 @@ namespace SharpNBT /// The path of the file to query write. /// A instance for the file stream. /// File compression will be automatically detected and used handled when necessary. - public static TagReader OpenRead(string path) + public static TagReader OpenRead([NotNull] string path) { var compressed = IsCompressed(path); var stream = File.OpenRead(path); @@ -85,7 +85,7 @@ namespace SharpNBT /// The path of the file to query write. /// A flag indicating the compression strategy that will be used, if any. /// A instance for the file stream. - public static TagWriter OpenWrite(string path, CompressionLevel compression = CompressionLevel.NoCompression) + public static TagWriter OpenWrite([NotNull] string path, CompressionLevel compression = CompressionLevel.NoCompression) { var stream = File.OpenWrite(path); if (compression == CompressionLevel.NoCompression) diff --git a/SharpNBT/TagWriter.cs b/SharpNBT/TagWriter.cs index d0a7982..dd2cf66 100644 --- a/SharpNBT/TagWriter.cs +++ b/SharpNBT/TagWriter.cs @@ -70,7 +70,7 @@ namespace SharpNBT public virtual void WriteShort(ShortTag tag) { WriteTypeAndName(tag); - BaseStream.Write(tag.Value.GetBytes(), 0, sizeof(short)); + BaseStream.Write(tag.Value.BigEndianBytes(), 0, sizeof(short)); } /// @@ -80,7 +80,7 @@ namespace SharpNBT public virtual void WriteInt(IntTag tag) { WriteTypeAndName(tag); - BaseStream.Write(tag.Value.GetBytes(), 0, sizeof(int)); + BaseStream.Write(tag.Value.BigEndianBytes(), 0, sizeof(int)); } /// @@ -90,7 +90,7 @@ namespace SharpNBT public virtual void WriteLong(LongTag tag) { WriteTypeAndName(tag); - BaseStream.Write(tag.Value.GetBytes(), 0, sizeof(long)); + BaseStream.Write(tag.Value.BigEndianBytes(), 0, sizeof(long)); } /// @@ -100,7 +100,7 @@ namespace SharpNBT public virtual void WriteFloat(FloatTag tag) { WriteTypeAndName(tag); - BaseStream.Write(tag.Value.GetBytes(), 0, sizeof(float)); + BaseStream.Write(tag.Value.BigEndianBytes(), 0, sizeof(float)); } /// @@ -110,7 +110,7 @@ namespace SharpNBT public virtual void WriteDouble(DoubleTag tag) { WriteTypeAndName(tag); - BaseStream.Write(tag.Value.GetBytes(), 0, sizeof(double)); + BaseStream.Write(tag.Value.BigEndianBytes(), 0, sizeof(double)); } /// @@ -130,7 +130,7 @@ namespace SharpNBT public virtual void WriteByteArray(ByteArrayTag tag) { WriteTypeAndName(tag); - BaseStream.Write(tag.Count.GetBytes(), 0, sizeof(int)); + BaseStream.Write(tag.Count.BigEndianBytes(), 0, sizeof(int)); BaseStream.Write(tag.ToArray(), 0, tag.Count); } @@ -141,7 +141,7 @@ namespace SharpNBT public virtual void WriteIntArray(IntArrayTag tag) { WriteTypeAndName(tag); - BaseStream.Write(tag.Count.GetBytes(), 0, sizeof(int)); + BaseStream.Write(tag.Count.BigEndianBytes(), 0, sizeof(int)); var values = new Span(tag.ToArray()); if (BitConverter.IsLittleEndian) @@ -161,7 +161,7 @@ namespace SharpNBT { WriteTypeAndName(tag); - BaseStream.Write(tag.Count.GetBytes(), 0, sizeof(int)); + BaseStream.Write(tag.Count.BigEndianBytes(), 0, sizeof(int)); var values = new Span(tag.ToArray()); if (BitConverter.IsLittleEndian) @@ -181,7 +181,7 @@ namespace SharpNBT { WriteTypeAndName(tag); BaseStream.WriteByte((byte) tag.ChildType); - BaseStream.Write(tag.Count.GetBytes(), 0, sizeof(int)); + BaseStream.Write(tag.Count.BigEndianBytes(), 0, sizeof(int)); foreach (var child in tag) WriteTag(child); @@ -311,12 +311,12 @@ namespace SharpNBT { if (string.IsNullOrEmpty(value)) { - BaseStream.Write(((ushort) 0).GetBytes(), 0, sizeof(ushort)); + BaseStream.Write(((ushort) 0).BigEndianBytes(), 0, sizeof(ushort)); } else { var utf8 = Encoding.UTF8.GetBytes(value); - BaseStream.Write(((ushort) utf8.Length).GetBytes(), 0, sizeof(ushort)); + BaseStream.Write(((ushort) utf8.Length).BigEndianBytes(), 0, sizeof(ushort)); BaseStream.Write(utf8, 0, utf8.Length); } } diff --git a/SharpNBT/VarInt.cs b/SharpNBT/VarInt.cs new file mode 100644 index 0000000..dd9edfa --- /dev/null +++ b/SharpNBT/VarInt.cs @@ -0,0 +1,181 @@ +using System; +using System.IO; +using JetBrains.Annotations; + +namespace SharpNBT +{ + /// + /// Provides static methods for reading/writing the VarInt implementation commonly used by Minecraft to/from streams and buffers. + /// + /// This is not part of the NBT API, and is merely included for convenience. + [PublicAPI] + public class VarInt + { + private const int INT_SIZE = sizeof(int); + + /// + /// Reads a VarInt from the given . + /// + /// The object to read from. + /// The VarInt value as a 32-bit integer. + public static int Read([NotNull] Stream stream) => Read(stream, out var dummy); + + /// + /// Reads a VarInt from the given . + /// + /// A buffer containing the VarInt bytes. + /// The VarInt value as a 32-bit integer. + public static int Read(ReadOnlySpan buffer) => Read(buffer, out var dummy); + + /// + /// Reads a VarInt from the given . + /// + /// A buffer containing the VarInt bytes. + /// The start index of the to begin reading at. + /// The maximum number of bytes to read from the . + /// The VarInt value as a 32-bit integer. + public static int Read([NotNull] byte[] buffer, int start, int length) => Read(new ReadOnlySpan(buffer, start, length), out var dummy); + + /// + /// Reads a VarInt from the given . + /// + /// A buffer containing the VarInt bytes. + /// The start index of the to begin reading at. + /// The maximum number of bytes to read from the . + /// A variable to store the number of bytes read from the . + /// The VarInt value as a 32-bit integer. + public static int Read([NotNull] byte[] buffer, int start, int length, out int count) => Read(new ReadOnlySpan(buffer, start, length), out count); + + /// + /// Reads a VarInt from the given . + /// + /// The object to read from. + /// A variable to store the number of bytes read from the . + /// The VarInt value as a 32-bit integer. + public static int Read([NotNull] Stream stream, out int count) + { + uint value = 0; + count = 0; + + while (true) + { + if (count == INT_SIZE) + throw new OverflowException($"A VarInt cannot exceed {INT_SIZE} bytes."); + + var currentByte = (byte)stream.ReadByte(); + value |= unchecked((uint) (currentByte & 0x7F) << (count * 7)); + if ((currentByte & 0x80) == 0) + break; + count++; + } + + return unchecked((int) value); + } + + /// + /// Reads a VarInt from the given . + /// + /// A buffer containing the VarInt bytes. + /// A variable to store the number of bytes read from the . + /// The VarInt value as a 32-bit integer. + public static int Read(ReadOnlySpan buffer, out int count) + { + uint value = 0; + count = 0; + + while (true) + { + if (count == INT_SIZE) + throw new OverflowException($"A VarInt cannot exceed {INT_SIZE} bytes."); + if (count >= buffer.Length) + throw new ArgumentException("Not enough data provided in buffer.", nameof(buffer)); + + var currentByte = buffer[count]; + value |= unchecked((uint) (currentByte & 0x7F) << (count * 7)); + if ((currentByte & 0x80) == 0) + break; + count++; + } + + return unchecked((int) value); + } + + /// + /// Writes the given to the as a VarInt. + /// + /// The object to write to. + /// The value to be written. + /// The number of bytes that were written to the . + /// Thrown when the is too large for a VarInt. + public static int Write([NotNull] Stream stream, int value) + { + Span buffer = stackalloc byte[INT_SIZE]; + var count = 0; + + var unsigned = unchecked((uint)value); + while (true) + { + if (count == INT_SIZE) + throw new OverflowException("Int32 value exceeds the size of a VarInt."); + + var currentByte = (byte) (unsigned & 0x7F); + unsigned >>= 7; + if (unsigned != 0) + currentByte |= 0x80; + buffer[count] = currentByte; + + if (unsigned == 0) + break; + count++; + } + + stream.Write(buffer[..count]); + return count; + } + + /// + /// Writes the given to the as a VarInt. + /// + /// A buffer to write to.. + /// The value to be written. + /// The number of bytes that were written to the . + /// Thrown when the is too large for a VarInt. + /// Thrown when the is not large enough to contain the data. + public static int Write(Span buffer, int value) + { + var count = 0; + var unsigned = unchecked((uint)value); + while (true) + { + if (count == INT_SIZE) + throw new OverflowException("Int32 value exceeds the size of a VarInt."); + if (count >= buffer.Length) + throw new ArgumentException("Buffer is not large enough to contain data.", nameof(buffer)); + + var currentByte = (byte) (unsigned & 0x7F); + unsigned >>= 7; + if (unsigned != 0) + currentByte |= 0x80; + buffer[count] = currentByte; + + if (unsigned == 0) + break; + count++; + } + + return count; + } + + /// + /// Writes the given to the as a VarInt. + /// + /// A buffer to write to.. + /// The start index of the to begin writing to. + /// The maximum number of bytes to write to the . + /// The value to be written. + /// The number of bytes that were written to the . + /// Thrown when the is too large for a VarInt. + /// Thrown when the is not large enough to contain the data. + public static int Write([NotNull] byte[] buffer, int start, int length, int value) => Write(new Span(buffer, start, length), value); + } +} \ No newline at end of file