Implemented VarInt class

Included some additional annotations to NbtFile class
This commit is contained in:
ForeverZer0 2021-08-23 05:11:22 -04:00
parent 9440a9bff3
commit 22f0404561
4 changed files with 210 additions and 25 deletions

View File

@ -16,19 +16,20 @@ namespace SharpNBT
/// <param name="n">The value to convert.</param>
/// <returns>An array of bytes representing the value in big-endian format.</returns>
/// <remarks>The endianness of the host machine is accounted for.</remarks>
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);
/// <inheritdoc cref="GetBytes(short)"/>
internal static byte[] GetBytes(this int n) => BitConverter.GetBytes(BitConverter.IsLittleEndian ? SwapEndian(n) : n);
/// <inheritdoc cref="BigEndianBytes(short)"/>
public static byte[] BigEndianBytes(this int n) => BitConverter.GetBytes(BitConverter.IsLittleEndian ? SwapEndian(n) : n);
/// <inheritdoc cref="GetBytes(short)"/>
internal static byte[] GetBytes(this long n) => BitConverter.GetBytes(BitConverter.IsLittleEndian ? SwapEndian(n) : n);
/// <inheritdoc cref="BigEndianBytes(short)"/>
public static byte[] BigEndianBytes(this long n) => BitConverter.GetBytes(BitConverter.IsLittleEndian ? SwapEndian(n) : n);
/// <inheritdoc cref="GetBytes(short)"/>
internal static byte[] GetBytes(this ushort n) => BitConverter.GetBytes(BitConverter.IsLittleEndian ? SwapEndian(n) : n);
/// <inheritdoc cref="BigEndianBytes(short)"/>
[CLSCompliant(false)]
internal static byte[] BigEndianBytes(this ushort n) => BitConverter.GetBytes(BitConverter.IsLittleEndian ? SwapEndian(n) : n);
/// <inheritdoc cref="GetBytes(short)"/>
internal static byte[] GetBytes(this float n)
/// <inheritdoc cref="BigEndianBytes(short)"/>
internal static byte[] BigEndianBytes(this float n)
{
var bytes = BitConverter.GetBytes(n);
if (BitConverter.IsLittleEndian)
@ -36,8 +37,8 @@ namespace SharpNBT
return bytes;
}
/// <inheritdoc cref="GetBytes(short)"/>
internal static byte[] GetBytes(this double n)
/// <inheritdoc cref="BigEndianBytes"/>
internal static byte[] BigEndianBytes(this double n)
{
var bytes = BitConverter.GetBytes(n);
if (BitConverter.IsLittleEndian)
@ -57,6 +58,7 @@ namespace SharpNBT
}
/// <inheritdoc cref="SwapEndian(short)"/>
[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)));
/// <inheritdoc cref="SwapEndian(short)"/>
[CLSCompliant(false)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint SwapEndian(this uint value)
{
@ -76,6 +79,7 @@ namespace SharpNBT
}
/// <inheritdoc cref="SwapEndian(short)"/>
[CLSCompliant(false)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong SwapEndian(this ulong val)
{

View File

@ -60,7 +60,7 @@ namespace SharpNBT
/// </summary>
/// <param name="path">The path of the file to query.</param>
/// <returns><see langword="true"/> if GZip compression was detected, otherwise <see langword="false"/>.</returns>
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
/// <param name="path">The path of the file to query write.</param>
/// <returns>A <see cref="TagReader"/> instance for the file stream.</returns>
/// <remarks>File compression will be automatically detected and used handled when necessary.</remarks>
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
/// <param name="path">The path of the file to query write.</param>
/// <param name="compression">A flag indicating the compression strategy that will be used, if any.</param>
/// <returns>A <see cref="TagWriter"/> instance for the file stream.</returns>
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)

View File

@ -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));
}
/// <summary>
@ -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));
}
/// <summary>
@ -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));
}
/// <summary>
@ -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));
}
/// <summary>
@ -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));
}
/// <summary>
@ -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<int>(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<long>(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);
}
}

181
SharpNBT/VarInt.cs Normal file
View File

@ -0,0 +1,181 @@
using System;
using System.IO;
using JetBrains.Annotations;
namespace SharpNBT
{
/// <summary>
/// Provides static methods for reading/writing the VarInt implementation commonly used by Minecraft to/from streams and buffers.
/// </summary>
/// <remarks>This is not part of the NBT API, and is merely included for convenience.</remarks>
[PublicAPI]
public class VarInt
{
private const int INT_SIZE = sizeof(int);
/// <summary>
/// Reads a VarInt from the given <paramref name="stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> object to read from.</param>
/// <returns>The VarInt value as a 32-bit integer.</returns>
public static int Read([NotNull] Stream stream) => Read(stream, out var dummy);
/// <summary>
/// Reads a VarInt from the given <paramref name="buffer"/>.
/// </summary>
/// <param name="buffer">A buffer containing the VarInt bytes.</param>
/// <returns>The VarInt value as a 32-bit integer.</returns>
public static int Read(ReadOnlySpan<byte> buffer) => Read(buffer, out var dummy);
/// <summary>
/// Reads a VarInt from the given <paramref name="buffer"/>.
/// </summary>
/// <param name="buffer">A buffer containing the VarInt bytes.</param>
/// <param name="start">The start index of the <paramref name="buffer"/> to begin reading at.</param>
/// <param name="length">The maximum number of bytes to read from the <paramref name="buffer"/>.</param>
/// <returns>The VarInt value as a 32-bit integer.</returns>
public static int Read([NotNull] byte[] buffer, int start, int length) => Read(new ReadOnlySpan<byte>(buffer, start, length), out var dummy);
/// <summary>
/// Reads a VarInt from the given <paramref name="buffer"/>.
/// </summary>
/// <param name="buffer">A buffer containing the VarInt bytes.</param>
/// <param name="start">The start index of the <paramref name="buffer"/> to begin reading at.</param>
/// <param name="length">The maximum number of bytes to read from the <paramref name="buffer"/>.</param>
/// <param name="count">A variable to store the number of bytes read from the <paramref name="buffer"/>.</param>
/// <returns>The VarInt value as a 32-bit integer.</returns>
public static int Read([NotNull] byte[] buffer, int start, int length, out int count) => Read(new ReadOnlySpan<byte>(buffer, start, length), out count);
/// <summary>
/// Reads a VarInt from the given <paramref name="stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> object to read from.</param>
/// <param name="count">A variable to store the number of bytes read from the <paramref name="stream"/>.</param>
/// <returns>The VarInt value as a 32-bit integer.</returns>
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);
}
/// <summary>
/// Reads a VarInt from the given <paramref name="buffer"/>.
/// </summary>
/// <param name="buffer">A buffer containing the VarInt bytes.</param>
/// <param name="count">A variable to store the number of bytes read from the <paramref name="buffer"/>.</param>
/// <returns>The VarInt value as a 32-bit integer.</returns>
public static int Read(ReadOnlySpan<byte> 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);
}
/// <summary>
/// Writes the given <paramref name="value"/> to the <paramref name="stream"/> as a VarInt.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> object to write to.</param>
/// <param name="value">The value to be written.</param>
/// <returns>The number of bytes that were written to the <paramref name="stream"/>.</returns>
/// <exception cref="OverflowException">Thrown when the <paramref name="value"/> is too large for a VarInt.</exception>
public static int Write([NotNull] Stream stream, int value)
{
Span<byte> 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;
}
/// <summary>
/// Writes the given <paramref name="value"/> to the <paramref name="buffer"/> as a VarInt.
/// </summary>
/// <param name="buffer">A buffer to write to..</param>
/// <param name="value">The value to be written.</param>
/// <returns>The number of bytes that were written to the <paramref name="buffer"/>.</returns>
/// <exception cref="OverflowException">Thrown when the <paramref name="value"/> is too large for a VarInt.</exception>
/// <exception cref="ArgumentException">Thrown when the <paramref name="buffer"/> is not large enough to contain the data.</exception>
public static int Write(Span<byte> 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;
}
/// <summary>
/// Writes the given <paramref name="value"/> to the <paramref name="buffer"/> as a VarInt.
/// </summary>
/// <param name="buffer">A buffer to write to..</param>
/// <param name="start">The start index of the <paramref name="buffer"/> to begin writing to.</param>
/// <param name="length">The maximum number of bytes to write to the <paramref name="buffer"/>.</param>
/// <param name="value">The value to be written.</param>
/// <returns>The number of bytes that were written to the <paramref name="buffer"/>.</returns>
/// <exception cref="OverflowException">Thrown when the <paramref name="value"/> is too large for a VarInt.</exception>
/// <exception cref="ArgumentException">Thrown when the <paramref name="buffer"/> is not large enough to contain the data.</exception>
public static int Write([NotNull] byte[] buffer, int start, int length, int value) => Write(new Span<byte>(buffer, start, length), value);
}
}