Initial commit

This commit is contained in:
ForeverZer0 2021-08-21 18:04:05 -04:00
commit 7bb273b262
25 changed files with 3094 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
bin/
obj/
/packages/
riderModule.iml
/_ReSharper.Caches/

Binary file not shown.

Binary file not shown.

22
SharpNBT.sln Normal file
View File

@ -0,0 +1,22 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpNBT", "SharpNBT\SharpNBT.csproj", "{370CC658-797C-4DAA-A907-20A4287D9FEF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpNBT.Tests", "SharpNBT.Tests\SharpNBT.Tests.csproj", "{68B8081F-5856-4B90-991C-7E8CB644B1EE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{370CC658-797C-4DAA-A907-20A4287D9FEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{370CC658-797C-4DAA-A907-20A4287D9FEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{370CC658-797C-4DAA-A907-20A4287D9FEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{370CC658-797C-4DAA-A907-20A4287D9FEF}.Release|Any CPU.Build.0 = Release|Any CPU
{68B8081F-5856-4B90-991C-7E8CB644B1EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{68B8081F-5856-4B90-991C-7E8CB644B1EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{68B8081F-5856-4B90-991C-7E8CB644B1EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{68B8081F-5856-4B90-991C-7E8CB644B1EE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

40
SharpNBT/ByteArrayTag.cs Normal file
View File

@ -0,0 +1,40 @@
using System.Collections.Generic;
using JetBrains.Annotations;
namespace SharpNBT
{
/// <summary>
/// A tag that whose value is a contiguous sequence of 8-bit integers.
/// </summary>
/// <remarks>
/// While this class uses the CLS compliant <see cref="byte"/> (0..255), the NBT specification uses a signed value with a range of -128..127, so ensure
/// the bits are equivalent for your values.
/// </remarks>
[PublicAPI]
public class ByteArrayTag : EnumerableTag<byte>
{
/// <summary>
/// Initializes a new instance of the <see cref="ByteArrayTag"/>.
/// </summary>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
public ByteArrayTag([CanBeNull] string name) : base(TagType.ByteArray, name)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ByteArrayTag"/> with the specified <paramref name="values"/>.
/// </summary>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
/// <param name="values">A collection of values to include in this tag.</param>
public ByteArrayTag([CanBeNull] string name, [NotNull] IEnumerable<byte> values) : base(TagType.ByteArray, name, values)
{
}
/// <inheritdoc cref="object.ToString"/>
public override string ToString()
{
var word = Count == 1 ? "element" : "elements";
return $"TAG_Byte_Array({PrettyName}): [{Count} {word}]";
}
}
}

63
SharpNBT/ByteTag.cs Normal file
View File

@ -0,0 +1,63 @@
using System;
using JetBrains.Annotations;
namespace SharpNBT
{
/// <summary>
/// A tag that contains a single 8-bit integer value.
/// </summary>
/// <remarks>
/// While this class uses the CLS compliant <see cref="Byte"/> (0..255), the NBT specification uses a signed value with a range of -128..127. It is
/// recommended to use the <see cref="SignedValue"/> property if your language supports a signed 8-bit value, otherwise simply ensure the bits are
/// equivalent.
/// </remarks>
[PublicAPI]
public class ByteTag : Tag<byte>
{
/// <summary>
/// Gets or sets the value of this tag as an unsigned value.
/// </summary>
/// <remarks>
/// This is only a reinterpretation of the bytes, no actual conversion is performed.
/// </remarks>
[CLSCompliant(false)]
public sbyte SignedValue
{
get => unchecked((sbyte)Value);
set => Value = unchecked((byte)value);
}
/// <summary>
/// Creates a new instance of the <see cref="ByteTag"/> class with the specified <paramref name="value"/>.
/// </summary>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
/// <param name="value">The value to assign to this tag.</param>
public ByteTag([CanBeNull] string name, byte value) : base(TagType.Byte, name, value)
{
}
/// <inheritdoc cref="ByteTag(string,byte)"/>
[CLSCompliant(false)]
public ByteTag([CanBeNull] string name, sbyte value) : base(TagType.Byte, name, unchecked((byte) value))
{
}
/// <inheritdoc cref="object.ToString"/>
public override string ToString() => $"TAG_Byte({PrettyName}): {Value}";
/// <summary>
/// Implicit conversion of this tag to a <see cref="byte"/>.
/// </summary>
/// <param name="tag">The tag to convert.</param>
/// <returns>The tag represented as a <see cref="byte"/>.</returns>
public static implicit operator byte(ByteTag tag) => tag.Value;
/// <summary>
/// Implicit conversion of this tag to a <see cref="sbyte"/>.
/// </summary>
/// <param name="tag">The tag to convert.</param>
/// <returns>The tag represented as a <see cref="sbyte"/>.</returns>
[CLSCompliant(false)]
public static implicit operator sbyte(ByteTag tag) => unchecked((sbyte)tag.Value);
}
}

93
SharpNBT/CompoundTag.cs Normal file
View File

@ -0,0 +1,93 @@
using System.Collections.Generic;
using System.Text;
using JetBrains.Annotations;
namespace SharpNBT
{
/// <summary>
/// Top-level tag that acts as a container for other <b>named</b> tags.
/// </summary>
/// <remarks>
/// This along with the <see cref="ListTag"/> class define the structure of the NBT format. Children are not order-dependent, nor is order guaranteed.
/// </remarks>
[PublicAPI]
public class CompoundTag : EnumerableTag<Tag>
{
/// <summary>
/// Creates a new instance of the <see cref="CompoundTag"/> class.
/// </summary>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
public CompoundTag([CanBeNull] string name) : base(TagType.Compound, name)
{
}
/// <summary>
/// Creates a new instance of the <see cref="CompoundTag"/> class.
/// </summary>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
/// <param name="values">A collection <see cref="Tag"/> objects that are children of this object.</param>
public CompoundTag([CanBeNull] string name, [NotNull] IEnumerable<Tag> values) : base(TagType.Compound, name, values)
{
}
/// <summary>Returns a string that represents the current object.</summary>
/// <returns>A string that represents the current object.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Object.ToString?view=netcore-5.0">`Object.ToString` on docs.microsoft.com</a></footer>
public override string ToString()
{
var word = Count == 1 ? "entry" : "entries";
return $"TAG_Compound({PrettyName}): [{Count} {word}]";
}
/// <summary>
/// Retrieves a "pretty-printed" multiline string representing the complete tree structure of the tag.
/// </summary>
/// <param name="indent">The prefix that will be applied to each indent-level of nested nodes in the tree structure.</param>
/// <returns>The pretty-printed string.</returns>
[NotNull]
public string PrettyPrinted([NotNull] string indent = " ")
{
var buffer = new StringBuilder();
PrettyPrinted(buffer, 0, indent);
return buffer.ToString();
}
/// <summary>
/// Searches the children of this tag, returning the first child with the specified <paramref name="name"/>.
/// </summary>
/// <param name="name">The name of the tag to search for.</param>
/// <param name="deep"><see langword="true"/> to recursively search children, otherwise <see langword="false"/> to only search direct descendants.</param>
/// <returns>The first tag found with <paramref name="name"/>, otherwise <see langword="null"/> if none was found.</returns>
[CanBeNull]
public Tag Find([NotNull] string name, bool deep)
{
foreach (var tag in this)
{
if (name.Equals(tag.Name))
return tag;
if (deep && tag is CompoundTag child)
{
var result = child.Find(name, true);
if (result != null)
return result;
}
}
return null;
}
protected internal override void PrettyPrinted(StringBuilder buffer, int level, string indent)
{
var space = new StringBuilder();
for (var i = 0; i < level; i++)
space.Append(indent);
buffer.AppendLine(space + ToString());
buffer.AppendLine(space + "{");
foreach (var tag in this)
tag.PrettyPrinted(buffer, level + 1, indent);
buffer.AppendLine(space + "}");
}
}
}

30
SharpNBT/DoubleTag.cs Normal file
View File

@ -0,0 +1,30 @@
using JetBrains.Annotations;
namespace SharpNBT
{
/// <summary>
/// A tag that contains a single IEEE-754 double-precision floating point number.
/// </summary>
[PublicAPI]
public class DoubleTag : Tag<double>
{
/// <summary>
/// Creates a new instance of the <see cref="DoubleTag"/> class with the specified <paramref name="value"/>.
/// </summary>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
/// <param name="value">The value to assign to this tag.</param>
public DoubleTag([CanBeNull] string name, double value) : base(TagType.Double, name, value)
{
}
/// <inheritdoc cref="object.ToString"/>
public override string ToString() => $"TAG_Double({PrettyName}): {Value}";
/// <summary>
/// Implicit conversion of this tag to a <see cref="double"/>.
/// </summary>
/// <param name="tag">The tag to convert.</param>
/// <returns>The tag represented as a <see cref="double"/>.</returns>
public static implicit operator double(DoubleTag tag) => tag.Value;
}
}

28
SharpNBT/EndTag.cs Normal file
View File

@ -0,0 +1,28 @@
using System;
using System.Text;
using JetBrains.Annotations;
namespace SharpNBT
{
/// <summary>
/// Represents the end of <see cref="CompoundTag"/>.
/// </summary>
[PublicAPI]
public class EndTag : Tag
{
/// <summary>
/// Creates a new instance of the <see cref="EndTag"/> class.
/// </summary>
public EndTag() : base(TagType.End, null)
{
}
/// <inheritdoc cref="object.ToString"/>
public override string ToString() => $"TAG_End";
protected internal override void PrettyPrinted(StringBuilder buffer, int level, string indent)
{
// Do nothing
}
}
}

193
SharpNBT/EnumerableTag.cs Normal file
View File

@ -0,0 +1,193 @@
using System.Collections;
using System.Collections.Generic;
using System.Text;
using JetBrains.Annotations;
using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute;
namespace SharpNBT
{
/// <summary>
/// Base class for tags that contain a collection of values and can be enumerated.
/// </summary>
/// <typeparam name="T">The type of the item the tag contains.</typeparam>
[PublicAPI]
public abstract class EnumerableTag<T> : Tag, IList<T>
{
/// <summary>
/// Internal list implementation.
/// </summary>
private readonly List<T> list;
/// <summary>
/// Initializes a new instance of the <see cref="EnumerableTag{T}"/>.
/// </summary>
/// <param name="type">A constant describing the NBT type for this tag.</param>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
protected EnumerableTag(TagType type, [CanBeNull] string name) : base(type, name)
{
list = new List<T>();
}
/// <summary>
/// Initializes a new instance of the <see cref="EnumerableTag{T}"/> with the specified <paramref name="values"/>.
/// </summary>
/// <param name="type">A constant describing the NBT type for this tag.</param>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
/// <param name="values">A collection of values to include in this tag.</param>
protected EnumerableTag(TagType type, [CanBeNull] string name, [NotNull] IEnumerable<T> values) : base(type, name)
{
list = new List<T>(values);
}
/// <summary>Returns an enumerator that iterates through the collection.</summary>
/// <returns>An enumerator that can be used to iterate through the collection.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.IEnumerable-1.GetEnumerator?view=netcore-5.0">`IEnumerable.GetEnumerator` on docs.microsoft.com</a></footer>
public IEnumerator<T> GetEnumerator() => list.GetEnumerator();
/// <summary>Returns an enumerator that iterates through a collection.</summary>
/// <returns>An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.IEnumerable.GetEnumerator?view=netcore-5.0">`IEnumerable.GetEnumerator` on docs.microsoft.com</a></footer>
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)list).GetEnumerator();
/// <summary>Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1" />.</summary>
/// <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1" />.</param>
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only.</exception>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.ICollection-1.Add?view=netcore-5.0">`ICollection.Add` on docs.microsoft.com</a></footer>
[SuppressMessage("ReSharper", "AnnotationConflictInHierarchy")]
public void Add([NotNull] T item)
{
if (item is Tag child)
child.Parent = this;
list.Add(item);
}
/// <summary>
/// Adds the elements of the specified collection to the <see cref="EnumerableTag{T}"/>.
/// </summary>
/// <param name="items">A collection containing the items to add.</param>
public void AddRange([NotNull] [ItemNotNull] IEnumerable<T> items)
{
foreach (var item in items)
Add(item);
}
/// <summary>Inserts an item to the <see cref="T:System.Collections.Generic.IList`1" /> at the specified index.</summary>
/// <param name="index">The zero-based index at which <paramref name="item" /> should be inserted.</param>
/// <param name="item">The object to insert into the <see cref="T:System.Collections.Generic.IList`1" />.</param>
/// <exception cref="T:System.ArgumentOutOfRangeException">
/// <paramref name="index" /> is not a valid index in the <see cref="T:System.Collections.Generic.IList`1" />.</exception>
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.IList`1" /> is read-only.</exception>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.IList-1.Insert?view=netcore-5.0">`IList.Insert` on docs.microsoft.com</a></footer>
[SuppressMessage("ReSharper", "AnnotationConflictInHierarchy")]
public void Insert(int index, [NotNull] T item)
{
if (item is Tag child)
child.Parent = this;
list.Insert(index, item);
}
/// <summary>Gets or sets the element at the specified index.</summary>
/// <param name="index">The zero-based index of the element to get or set.</param>
/// <exception cref="T:System.ArgumentOutOfRangeException">
/// <paramref name="index" /> is not a valid index in the <see cref="T:System.Collections.Generic.IList`1" />.</exception>
/// <exception cref="T:System.NotSupportedException">The property is set and the <see cref="T:System.Collections.Generic.IList`1" /> is read-only.</exception>
/// <returns>The element at the specified index.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.IList-1.Item?view=netcore-5.0">`IList.Item` on docs.microsoft.com</a></footer>
[NotNull]
public T this[int index]
{
get => list[index];
set
{
if (value is Tag child)
child.Parent = this;
list[index] = value;
}
}
/// <summary>Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1" />.</summary>
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only.</exception>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.ICollection-1.Clear?view=netcore-5.0">`ICollection.Clear` on docs.microsoft.com</a></footer>
public void Clear()
{
foreach (var item in list)
{
if (item is Tag child)
child.Parent = null;
}
list.Clear();
}
/// <summary>Determines whether the <see cref="T:System.Collections.Generic.ICollection`1" /> contains a specific value.</summary>
/// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1" />.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="item" /> is found in the <see cref="T:System.Collections.Generic.ICollection`1" />; otherwise, <see langword="false" />.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.ICollection-1.Contains?view=netcore-5.0">`ICollection.Contains` on docs.microsoft.com</a></footer>
public bool Contains(T item) => list.Contains(item);
/// <summary>Copies the elements of the <see cref="T:System.Collections.Generic.ICollection`1" /> to an <see cref="T:System.Array" />, starting at a particular <see cref="T:System.Array" /> index.</summary>
/// <param name="array">The one-dimensional <see cref="T:System.Array" /> that is the destination of the elements copied from <see cref="T:System.Collections.Generic.ICollection`1" />. The <see cref="T:System.Array" /> must have zero-based indexing.</param>
/// <param name="arrayIndex">The zero-based index in <paramref name="array" /> at which copying begins.</param>
/// <exception cref="T:System.ArgumentNullException">
/// <paramref name="array" /> is <see langword="null" />.</exception>
/// <exception cref="T:System.ArgumentOutOfRangeException">
/// <paramref name="arrayIndex" /> is less than 0.</exception>
/// <exception cref="T:System.ArgumentException">The number of elements in the source <see cref="T:System.Collections.Generic.ICollection`1" /> is greater than the available space from <paramref name="arrayIndex" /> to the end of the destination <paramref name="array" />.</exception>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.ICollection-1.CopyTo?view=netcore-5.0">`ICollection.CopyTo` on docs.microsoft.com</a></footer>
public void CopyTo(T[] array, int arrayIndex) => list.CopyTo(array, arrayIndex);
/// <summary>Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1" />.</summary>
/// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1" />.</param>
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only.</exception>
/// <returns>
/// <see langword="true" /> if <paramref name="item" /> was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1" />; otherwise, <see langword="false" />. This method also returns <see langword="false" /> if <paramref name="item" /> is not found in the original <see cref="T:System.Collections.Generic.ICollection`1" />.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.ICollection-1.Remove?view=netcore-5.0">`ICollection.Remove` on docs.microsoft.com</a></footer>
public bool Remove(T item)
{
if (list.Remove(item))
{
if (item is Tag child)
child.Parent = null;
return true;
}
return false;
}
/// <summary>Gets the number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1" />.</summary>
/// <returns>The number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1" />.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.ICollection-1.Count?view=netcore-5.0">`ICollection.Count` on docs.microsoft.com</a></footer>
public int Count => list.Count;
/// <summary>Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only.</summary>
/// <returns>
/// <see langword="true" /> if the <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only; otherwise, <see langword="false" />.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.ICollection-1.IsReadOnly?view=netcore-5.0">`ICollection.IsReadOnly` on docs.microsoft.com</a></footer>
public bool IsReadOnly => false;
/// <summary>Determines the index of a specific item in the <see cref="T:System.Collections.Generic.IList`1" />.</summary>
/// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.IList`1" />.</param>
/// <returns>The index of <paramref name="item" /> if found in the list; otherwise, -1.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.IList-1.IndexOf?view=netcore-5.0">`IList.IndexOf` on docs.microsoft.com</a></footer>
public int IndexOf(T item) => list.IndexOf(item);
/// <summary>Removes the <see cref="T:System.Collections.Generic.IList`1" /> item at the specified index.</summary>
/// <param name="index">The zero-based index of the item to remove.</param>
/// <exception cref="T:System.ArgumentOutOfRangeException">
/// <paramref name="index" /> is not a valid index in the <see cref="T:System.Collections.Generic.IList`1" />.</exception>
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.IList`1" /> is read-only.</exception>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.IList-1.RemoveAt?view=netcore-5.0">`IList.RemoveAt` on docs.microsoft.com</a></footer>
public void RemoveAt(int index)
{
if (list[index] is Tag child)
child.Parent = null;
list.RemoveAt(index);
}
protected internal override void PrettyPrinted(StringBuilder buffer, int level, string indent)
{
for (var i = 0; i < level; i++)
buffer.Append(indent);
buffer.AppendLine(ToString());
}
}
}

30
SharpNBT/FloatTag.cs Normal file
View File

@ -0,0 +1,30 @@
using JetBrains.Annotations;
namespace SharpNBT
{
/// <summary>
/// A tag that contains a single IEEE-754 single-precision floating point number.
/// </summary>
[PublicAPI]
public class FloatTag : Tag<float>
{
/// <summary>
/// Creates a new instance of the <see cref="FloatTag"/> class with the specified <paramref name="value"/>.
/// </summary>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
/// <param name="value">The value to assign to this tag.</param>
public FloatTag([CanBeNull] string name, float value) : base(TagType.Float, name, value)
{
}
/// <inheritdoc cref="object.ToString"/>
public override string ToString() => $"TAG_Float({PrettyName}): {Value}";
/// <summary>
/// Implicit conversion of this tag to a <see cref="float"/>.
/// </summary>
/// <param name="tag">The tag to convert.</param>
/// <returns>The tag represented as a <see cref="float"/>.</returns>
public static implicit operator float(FloatTag tag) => tag.Value;
}
}

36
SharpNBT/IntArrayTag.cs Normal file
View File

@ -0,0 +1,36 @@
using System.Collections.Generic;
using JetBrains.Annotations;
namespace SharpNBT
{
/// <summary>
/// A tag that whose value is a contiguous sequence of 32-bit integers.
/// </summary>
[PublicAPI]
public class IntArrayTag : EnumerableTag<int>
{
/// <summary>
/// Initializes a new instance of the <see cref="IntArrayTag"/>.
/// </summary>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
public IntArrayTag([CanBeNull] string name) : base(TagType.IntArray, name)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="IntArrayTag"/> with the specified <paramref name="values"/>.
/// </summary>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
/// <param name="values">A collection of values to include in this tag.</param>
public IntArrayTag([CanBeNull] string name, [NotNull] IEnumerable<int> values) : base(TagType.IntArray, name, values)
{
}
/// <inheritdoc cref="object.ToString"/>
public override string ToString()
{
var word = Count == 1 ? "element" : "elements";
return $"TAG_Int_Array({PrettyName}): [{Count} {word}]";
}
}
}

58
SharpNBT/IntTag.cs Normal file
View File

@ -0,0 +1,58 @@
using System;
using JetBrains.Annotations;
namespace SharpNBT
{
/// <summary>
/// A tag that contains a single 32-bit integer value.
/// </summary>
[PublicAPI]
public class IntTag : Tag<int>
{
/// <summary>
/// Gets or sets the value of this tag as an unsigned value.
/// </summary>
/// <remarks>
/// This is only a reinterpretation of the bytes, no actual conversion is performed.
/// </remarks>
[CLSCompliant(false)]
public uint UnsignedValue
{
get => unchecked((uint)Value);
set => Value = unchecked((int)value);
}
/// <summary>
/// Creates a new instance of the <see cref="IntTag"/> class with the specified <paramref name="value"/>.
/// </summary>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
/// <param name="value">The value to assign to this tag.</param>
public IntTag([CanBeNull] string name, int value) : base(TagType.Int, name, value)
{
}
/// <inheritdoc cref="IntTag(string,int)"/>
[CLSCompliant(false)]
public IntTag([CanBeNull] Tag parent, [CanBeNull] string name, uint value) : base(TagType.Int, name, unchecked((int) value))
{
}
/// <inheritdoc cref="object.ToString"/>
public override string ToString() => $"TAG_Int({PrettyName}): {Value}";
/// <summary>
/// Implicit conversion of this tag to a <see cref="int"/>.
/// </summary>
/// <param name="tag">The tag to convert.</param>
/// <returns>The tag represented as a <see cref="int"/>.</returns>
public static implicit operator int(IntTag tag) => tag.Value;
/// <summary>
/// Implicit conversion of this tag to a <see cref="uint"/>.
/// </summary>
/// <param name="tag">The tag to convert.</param>
/// <returns>The tag represented as a <see cref="uint"/>.</returns>
[CLSCompliant(false)]
public static implicit operator uint(IntTag tag) => unchecked((uint)tag.Value);
}
}

File diff suppressed because it is too large Load Diff

76
SharpNBT/ListTag.cs Normal file
View File

@ -0,0 +1,76 @@
using System.Collections.Generic;
using System.Text;
using JetBrains.Annotations;
namespace SharpNBT
{
/// <summary>
/// Represents a collection of a tags.
/// </summary>
/// <remarks>
/// All child tags <b>must</b> be have the same <see cref="Tag.Type"/> value, and their <see cref="Tag.Name"/> value will be omitted during serialization.
/// </remarks>
[PublicAPI]
public class ListTag : EnumerableTag<Tag>
{
/// <summary>
/// Gets the NBT type of this tag's children.
/// </summary>
public TagType ChildType { get; }
/// <summary>
/// Creates a new instance of the <see cref="ListTag"/> class.
/// </summary>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
/// <param name="childType">A constant describing the NBT type for children in this tag.</param>
public ListTag([CanBeNull] string name, TagType childType) : base(TagType.List, name)
{
ChildType = childType;
}
/// <summary>
/// Creates a new instance of the <see cref="ListTag"/> with the specified <paramref name="children"/>.
/// </summary>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
/// <param name="childType">A constant describing the NBT type for children in this tag.</param>
/// <param name="children">A collection of values to include in this tag.</param>
public ListTag([CanBeNull] string name, TagType childType, [NotNull][ItemNotNull] IEnumerable<Tag> children) : base(TagType.List, name)
{
ChildType = childType;
AddRange(children);
}
/// <inheritdoc cref="object.ToString"/>
public override string ToString()
{
var word = Count == 1 ? "entry" : "entries";
return $"TAG_Compound({PrettyName}): [{Count} {word}]";
}
protected internal override void PrettyPrinted(StringBuilder buffer, int level, string indent)
{
var space = new StringBuilder();
for (var i = 0; i < level; i++)
space.Append(indent);
buffer.AppendLine(space + ToString());
buffer.AppendLine(space + "{");
foreach (var tag in this)
tag.PrettyPrinted(buffer, level + 1, indent);
buffer.AppendLine(space + "}");
}
/// <summary>
/// Retrieves a "pretty-printed" multiline string representing the complete tree structure of the tag.
/// </summary>
/// <param name="indent">The prefix that will be applied to each indent-level of nested nodes in the tree structure.</param>
/// <returns>The pretty-printed string.</returns>
[NotNull]
public string PrettyPrinted([NotNull] string indent = " ")
{
var buffer = new StringBuilder();
PrettyPrinted(buffer, 0, indent);
return buffer.ToString();
}
}
}

36
SharpNBT/LongArrayTag.cs Normal file
View File

@ -0,0 +1,36 @@
using System.Collections.Generic;
using JetBrains.Annotations;
namespace SharpNBT
{
/// <summary>
/// A tag that whose value is a contiguous sequence of 64-bit integers.
/// </summary>
[PublicAPI]
public class LongArrayTag : EnumerableTag<long>
{
/// <summary>
/// Initializes a new instance of the <see cref="LongArrayTag"/>.
/// </summary>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
public LongArrayTag([CanBeNull] string name) : base(TagType.LongArray, name)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="LongArrayTag"/> with the specified <paramref name="values"/>.
/// </summary>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
/// <param name="values">A collection of values to include in this tag.</param>
public LongArrayTag([CanBeNull] string name, [NotNull] IEnumerable<long> values) : base(TagType.LongArray, name, values)
{
}
/// <inheritdoc cref="object.ToString"/>
public override string ToString()
{
var word = Count == 1 ? "element" : "elements";
return $"TAG_Long_Array({PrettyName}): [{Count} {word}]";
}
}
}

58
SharpNBT/LongTag.cs Normal file
View File

@ -0,0 +1,58 @@
using System;
using JetBrains.Annotations;
namespace SharpNBT
{
/// <summary>
/// A tag that contains a single 64-bit integer value.
/// </summary>
[PublicAPI]
public class LongTag : Tag<long>
{
/// <summary>
/// Gets or sets the value of this tag as an unsigned value.
/// </summary>
/// <remarks>
/// This is only a reinterpretation of the bytes, no actual conversion is performed.
/// </remarks>
[CLSCompliant(false)]
public ulong UnsignedValue
{
get => unchecked((ulong)Value);
set => Value = unchecked((long)value);
}
/// <summary>
/// Creates a new instance of the <see cref="LongTag"/> class with the specified <paramref name="value"/>.
/// </summary>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
/// <param name="value">The value to assign to this tag.</param>
public LongTag([CanBeNull] string name, long value) : base(TagType.Long, name, value)
{
}
/// <inheritdoc cref="LongTag(string,long)"/>
[CLSCompliant(false)]
public LongTag([CanBeNull] string name, ulong value) : base(TagType.Long, name, unchecked((long) value))
{
}
/// <inheritdoc cref="object.ToString"/>
public override string ToString() => $"TAG_Long({PrettyName}): {Value}";
/// <summary>
/// Implicit conversion of this tag to a <see cref="long"/>.
/// </summary>
/// <param name="tag">The tag to convert.</param>
/// <returns>The tag represented as a <see cref="long"/>.</returns>
public static implicit operator long(LongTag tag) => tag.Value;
/// <summary>
/// Implicit conversion of this tag to a <see cref="ulong"/>.
/// </summary>
/// <param name="tag">The tag to convert.</param>
/// <returns>The tag represented as a <see cref="ulong"/>.</returns>
[CLSCompliant(false)]
public static implicit operator ulong(LongTag tag) => unchecked((ulong)tag.Value);
}
}

300
SharpNBT/NbtStream.Read.cs Normal file
View File

@ -0,0 +1,300 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using JetBrains.Annotations;
namespace SharpNBT
{
public partial class NbtStream
{
/// <summary>
/// Reads a <see cref="ByteTag"/> from the stream.
/// </summary>
/// <remarks>It is assumed that the stream is positioned at the beginning of the tag payload.</remarks>
/// <returns>The deserialized <see cref="ByteTag"/> instance.</returns>
public new ByteTag ReadByte()
{
var name = named ? ReadPrefixedString() : null;
return new ByteTag(name, (byte)BaseStream.ReadByte());
}
/// <summary>
/// Reads a <see cref="ShortTag"/> from the stream.
/// </summary>
/// <remarks>It is assumed that the stream is positioned at the beginning of the tag payload.</remarks>
/// <returns>The deserialized <see cref="ShortTag"/> instance.</returns>
public ShortTag ReadShort()
{
var name = named ? ReadPrefixedString() : null;
return new ShortTag(name, BitConverter.ToInt16(ReadNumber(sizeof(short))));
}
/// <summary>
/// Reads a <see cref="IntTag"/> from the stream.
/// </summary>
/// <remarks>It is assumed that the stream is positioned at the beginning of the tag payload.</remarks>
/// <returns>The deserialized <see cref="IntTag"/> instance.</returns>
public IntTag ReadInt()
{
var name = named ? ReadPrefixedString() : null;
return new IntTag(name, BitConverter.ToInt32(ReadNumber(sizeof(int))));
}
/// <summary>
/// Reads a <see cref="LongTag"/> from the stream.
/// </summary>
/// <remarks>It is assumed that the stream is positioned at the beginning of the tag payload.</remarks>
/// <returns>The deserialized <see cref="LongTag"/> instance.</returns>
public LongTag ReadLong()
{
var name = named ? ReadPrefixedString() : null;
return new LongTag(name, BitConverter.ToInt64(ReadNumber(sizeof(long))));
}
/// <summary>
/// Reads a <see cref="FloatTag"/> from the stream.
/// </summary>
/// <remarks>It is assumed that the stream is positioned at the beginning of the tag payload.</remarks>
/// <returns>The deserialized <see cref="FloatTag"/> instance.</returns>
public FloatTag ReadFloat()
{
var name = named ? ReadPrefixedString() : null;
return new FloatTag(name, BitConverter.ToSingle(ReadNumber(sizeof(float))));
}
/// <summary>
/// Reads a <see cref="DoubleTag"/> from the stream.
/// </summary>
/// <remarks>It is assumed that the stream is positioned at the beginning of the tag payload.</remarks>
/// <returns>The deserialized <see cref="DoubleTag"/> instance.</returns>
public DoubleTag ReadDouble()
{
var name = named ? ReadPrefixedString() : null;
return new DoubleTag( name, BitConverter.ToDouble(ReadNumber(sizeof(double))));
}
/// <summary>
/// Reads a <see cref="StringTag"/> from the stream.
/// </summary>
/// <remarks>It is assumed that the stream is positioned at the beginning of the tag payload.</remarks>
/// <returns>The deserialized <see cref="StringTag"/> instance.</returns>
public StringTag ReadString()
{
var name = named ? ReadPrefixedString() : null;
var value = ReadPrefixedString();
return new StringTag(name, value);
}
/// <summary>
/// Reads a <see cref="ByteArrayTag"/> from the stream.
/// </summary>
/// <remarks>It is assumed that the stream is positioned at the beginning of the tag payload.</remarks>
/// <returns>The deserialized <see cref="ByteArrayTag"/> instance.</returns>
public ByteArrayTag ReadByteArray()
{
var name = named ? ReadPrefixedString() : null;
var count = BitConverter.ToInt32(ReadNumber(sizeof(int)));
var buffer = new byte[count];
BaseStream.Read(buffer, 0, count);
return new ByteArrayTag(name, buffer);
}
/// <summary>
/// Reads a <see cref="IntArrayTag"/> from the stream.
/// </summary>
/// <remarks>It is assumed that the stream is positioned at the beginning of the tag payload.</remarks>
/// <returns>The deserialized <see cref="IntArrayTag"/> instance.</returns>
public IntArrayTag ReadIntArray()
{
const int INT_SIZE = sizeof(int);
var name = named ? ReadPrefixedString() : null;
var count = BitConverter.ToInt32(ReadNumber(sizeof(int)));
var buffer = new byte[count * INT_SIZE];
BaseStream.Read(buffer, 0, count * INT_SIZE);
var values = new int[count];
var offset = 0;
if (BitConverter.IsLittleEndian)
{
for (var i = 0; i < count; i++, offset += INT_SIZE)
{
Array.Reverse(buffer, offset, INT_SIZE);
values[i] = BitConverter.ToInt32(buffer, offset);
}
}
else
{
for (var i = 0; i < count; i++, offset += INT_SIZE)
values[i] = BitConverter.ToInt32(buffer, offset);
}
return new IntArrayTag(name, values);
}
/// <summary>
/// Reads a <see cref="LongArrayTag"/> from the stream.
/// </summary>
/// <remarks>It is assumed that the stream is positioned at the beginning of the tag payload.</remarks>
/// <returns>The deserialized <see cref="LongArrayTag"/> instance.</returns>
public LongArrayTag ReadLongArray()
{
const int LONG_SIZE = sizeof(long);
var name = named ? ReadPrefixedString() : null;
var count = BitConverter.ToInt32(ReadNumber(sizeof(int)));
var buffer = new byte[count * LONG_SIZE];
BaseStream.Read(buffer, 0, count * LONG_SIZE);
var values = new long[count];
var offset = 0;
if (BitConverter.IsLittleEndian)
{
for (var i = 0; i < count; i++, offset += LONG_SIZE)
{
Array.Reverse(buffer, offset, LONG_SIZE);
values[i] = BitConverter.ToInt64(buffer, offset);
}
}
else
{
for (var i = 0; i < count; i++, offset += LONG_SIZE)
{
values[i] = BitConverter.ToInt64(buffer, offset);
}
}
return new LongArrayTag(name, values);
}
/// <summary>
/// Reads a <see cref="ListTag"/> from the stream.
/// </summary>
/// <remarks>It is assumed that the stream is positioned at the beginning of the tag payload.</remarks>
/// <returns>The deserialized <see cref="ListTag"/> instance.</returns>
public ListTag ReadList()
{
var name = named ? ReadPrefixedString() : null;
var childType = ReadType();
var count = BitConverter.ToInt32(ReadNumber(sizeof(int)));
var list = new ListTag(name, childType);
var previous = named;
named = false;
while (count-- > 0)
{
list.Add(ReadTag(childType));
}
named = previous;
return list;
}
/// <summary>
/// Reads a <see cref="CompoundTag"/> from the stream.
/// </summary>
/// <remarks>It is assumed that the stream is positioned at the beginning of the tag payload.</remarks>
/// <returns>The deserialized <see cref="CompoundTag"/> instance.</returns>
public CompoundTag ReadCompound()
{
var name = named ? ReadPrefixedString() : null;
var compound = new CompoundTag(name);
var previous = named;
named = true;
while (true)
{
var type = ReadType();
if (type == TagType.End)
break;
compound.Add(ReadTag(type));
}
named = previous;
return compound;
}
/// <summary>
/// Reads a <see cref="Tag"/> from the current position in the stream.
/// </summary>
/// <returns></returns>
public Tag ReadTag()
{
var type = ReadType();
return ReadTag(type);
}
public T ReadTag<T>() where T : Tag
{
return (T)ReadTag();
}
protected Tag ReadTag(TagType type)
{
return type switch
{
TagType.End => new EndTag(),
TagType.Byte => ReadByte(),
TagType.Short => ReadShort(),
TagType.Int => ReadInt(),
TagType.Long => ReadLong(),
TagType.Float => ReadFloat(),
TagType.Double => ReadDouble(),
TagType.ByteArray => ReadByteArray(),
TagType.String => ReadString(),
TagType.List => ReadList(),
TagType.Compound => ReadCompound(),
TagType.IntArray => ReadIntArray(),
TagType.LongArray => ReadLongArray(),
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
};
}
public TagType ReadType()
{
try
{
return (TagType)BaseStream.ReadByte();
}
catch (EndOfStreamException)
{
return TagType.End;
}
}
/// <summary>
/// Reads a length-prefixed (unsigned short) UTF-8 string from the <see cref="BaseStream"/>.
/// </summary>
/// <returns>The string instance, or <see langword="null"/> if a length of <c>0</c> was specified.</returns>
[CanBeNull]
protected string ReadPrefixedString()
{
var len = BitConverter.ToUInt16(ReadNumber(sizeof(ushort)));
if (len == 0)
return null;
Span<byte> buffer = stackalloc byte[len];
BaseStream.Read(buffer);
return Encoding.UTF8.GetString(buffer);
}
/// <summary>
/// Reads <paramref name="count"/> bytes from the stream, reversing them if necessary to ensure proper endian format.
/// </summary>
/// <param name="count">The number of bytes to read.</param>
/// <returns>An array of bytes that represent the number.</returns>
[NotNull]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected byte[] ReadNumber(int count)
{
var buffer = new byte[count];
BaseStream.Read(buffer, 0, buffer.Length);
if (BitConverter.IsLittleEndian)
Array.Reverse(buffer);
return buffer;
}
}
}

257
SharpNBT/NbtStream.Write.cs Normal file
View File

@ -0,0 +1,257 @@
using System;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
namespace SharpNBT
{
public partial class NbtStream
{
private bool named = true;
public void WriteType(Tag tag)
{
BaseStream.WriteByte((byte) tag.Type);
}
public void WriteByte(ByteTag tag)
{
BaseStream.WriteByte((byte) tag.Type);
if (named)
WriteString(tag.Name);
BaseStream.WriteByte(tag.Value);
}
public void WriteShort(ShortTag tag)
{
BaseStream.WriteByte((byte) tag.Type);
if (named)
WriteString(tag.Name);
WriteNumber(BitConverter.GetBytes(tag.Value));
}
public void WriteInt(IntTag tag)
{
BaseStream.WriteByte((byte) tag.Type);
if (named)
WriteString(tag.Name);
WriteNumber(BitConverter.GetBytes(tag.Value));
}
public void WriteLong(LongTag tag)
{
BaseStream.WriteByte((byte) tag.Type);
if (named)
WriteString(tag.Name);
WriteNumber(BitConverter.GetBytes(tag.Value));
}
public void WriteFloat(FloatTag tag)
{
BaseStream.WriteByte((byte) tag.Type);
if (named)
WriteString(tag.Name);
WriteNumber(BitConverter.GetBytes(tag.Value));
}
public void WriteDouble(DoubleTag tag)
{
BaseStream.WriteByte((byte) tag.Type);
if (named)
WriteString(tag.Name);
WriteNumber(BitConverter.GetBytes(tag.Value));
}
public void WriteString(StringTag tag)
{
BaseStream.WriteByte((byte) tag.Type);
if (named)
WriteString(tag.Name);
WriteString(tag.Value);
}
public void WriteByteArray(ByteArrayTag tag)
{
BaseStream.WriteByte((byte) tag.Type);
if (named)
WriteString(tag.Name);
WriteNumber(BitConverter.GetBytes(tag.Count));
BaseStream.Write(tag.ToArray(), 0, tag.Count);
}
public void WriteIntArray(IntArrayTag tag)
{
const int INT_SIZE = sizeof(int);
BaseStream.WriteByte((byte) tag.Type);
if (named)
WriteString(tag.Name);
WriteNumber(BitConverter.GetBytes(tag.Count));
var values = tag.ToArray();
var buffer = new byte[tag.Count * INT_SIZE];
byte[] bits;
var offset = 0;
if (BitConverter.IsLittleEndian)
{
for (var i = 0; i < tag.Count; i++, offset += INT_SIZE)
{
bits = BitConverter.GetBytes(values[i]);
Array.Reverse(bits, 0, INT_SIZE);
bits.CopyTo(buffer, offset);
}
}
else
{
for (var i = 0; i < tag.Count; i++, offset += INT_SIZE)
{
bits = BitConverter.GetBytes(values[i]);
bits.CopyTo(buffer, offset);
}
}
BaseStream.Write(buffer, 0, buffer.Length);
}
public void WriteLongArray(LongArrayTag tag)
{
const int LONG_SIZE = sizeof(long);
BaseStream.WriteByte((byte) tag.Type);
if (named)
WriteString(tag.Name);
WriteNumber(BitConverter.GetBytes(tag.Count));
var values = tag.ToArray();
var buffer = new byte[tag.Count * LONG_SIZE];
byte[] bits;
var offset = 0;
if (BitConverter.IsLittleEndian)
{
for (var i = 0; i < tag.Count; i++, offset += LONG_SIZE)
{
bits = BitConverter.GetBytes(values[i]);
Array.Reverse(bits, 0, LONG_SIZE);
bits.CopyTo(buffer, offset);
}
}
else
{
for (var i = 0; i < tag.Count; i++, offset += LONG_SIZE)
{
bits = BitConverter.GetBytes(values[i]);
bits.CopyTo(buffer, offset);
}
}
BaseStream.Write(buffer, 0, buffer.Length);
}
public void WriteList(ListTag tag)
{
BaseStream.WriteByte((byte) tag.Type);
if (named)
WriteString(tag.Name);
BaseStream.WriteByte((byte) tag.ChildType);
WriteNumber(BitConverter.GetBytes(tag.Count));
named = false;
foreach (var child in tag)
WriteTag(child);
named = true;
}
public void WriteCompound(CompoundTag tag)
{
BaseStream.WriteByte((byte) tag.Type);
if (named)
WriteString(tag.Name);
foreach (var child in tag)
{
child.Parent = tag;
WriteTag(child);
}
WriteEndTag();
}
public void WriteEndTag() => BaseStream.WriteByte((byte) TagType.End);
public void WriteTag(Tag tag)
{
switch (tag.Type)
{
case TagType.End:
WriteEndTag();
break;
case TagType.Byte:
WriteByte((ByteTag) tag);
break;
case TagType.Short:
WriteShort((ShortTag) tag);
break;
case TagType.Int:
WriteInt((IntTag) tag);
break;
case TagType.Long:
WriteLong((LongTag)tag);
break;
case TagType.Float:
WriteFloat((FloatTag)tag);
break;
case TagType.Double:
WriteDouble((DoubleTag)tag);
break;
case TagType.ByteArray:
WriteByteArray((ByteArrayTag)tag);
break;
case TagType.String:
WriteString((StringTag)tag);
break;
case TagType.List:
WriteList((ListTag)tag);
break;
case TagType.Compound:
WriteCompound((CompoundTag)tag);
break;
case TagType.IntArray:
WriteIntArray((IntArrayTag)tag);
break;
case TagType.LongArray:
WriteLongArray((LongArrayTag)tag);
break;
default:
throw new ArgumentOutOfRangeException(nameof(tag.Type), "Unknown tag type.");
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void WriteNumber(byte[] data)
{
if (BitConverter.IsLittleEndian)
Array.Reverse(data);
BaseStream.Write(data, 0, data.Length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void WriteString(string value)
{
if (string.IsNullOrEmpty(value))
{
WriteNumber(BitConverter.GetBytes((ushort) 0));
}
else
{
var utf8 = Encoding.UTF8.GetBytes(value);
WriteNumber(BitConverter.GetBytes((ushort) utf8.Length));
BaseStream.Write(utf8, 0, utf8.Length);
}
}
}
}

162
SharpNBT/NbtStream.cs Normal file
View File

@ -0,0 +1,162 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using JetBrains.Annotations;
namespace SharpNBT
{
/// <summary>
/// Provides methods and properties to read and write as a stream using the NBT format specification.
/// </summary>
[PublicAPI]
public partial class NbtStream : Stream
{
protected readonly Stream BaseStream;
private readonly Stack<Tag> topLevel;
// /// <summary>
// /// Opens a <see cref="NbtStream"/> on the specified <paramref name="path"/> with read/write access.
// /// </summary>
// /// <param name="path">The path to a file to open.</param>
// /// <param name="mode">
// /// A value that specified whether a file is created if one does not exist, and determines
// /// whether the contents of an existing file are retained or overwritten.
// /// </param>
// /// <returns>A <see cref="NbtStream"/> opened in the specified mode and path, with read/write access.</returns>
// public static NbtStream Open(string path, FileMode mode) => new(File.Open(path, mode));
//
// public static NbtStream Open(string path, FileMode mode, FileAccess access) => new(File.Open(path, mode, access));
public static NbtStream OpenRead(string path)
{
var compressed = false;
// Look for GZIP magic number
using (var str = File.OpenRead(path))
{
if (str.ReadByte() == 0x1F && str.ReadByte() == 0x8B)
compressed = true;
}
return compressed ? new NbtStream(File.OpenRead(path), CompressionMode.Decompress) : new NbtStream(File.OpenRead(path));
}
public static NbtStream OpenWrite(string path, CompressionLevel level = CompressionLevel.NoCompression)
{
if (level != CompressionLevel.NoCompression)
{
var stream = new GZipStream(File.OpenWrite(path), level, false);
return new NbtStream(stream);
}
return new NbtStream(File.OpenWrite(path));
}
public NbtStream(Stream stream, bool leaveOpen = false)
{
BaseStream = stream ?? throw new ArgumentNullException(nameof(stream));
topLevel = new Stack<Tag>();
}
public NbtStream(Stream stream, CompressionMode compression, bool leaveOpen = false) : this(new GZipStream(stream, compression, leaveOpen), leaveOpen)
{
}
public NbtStream([NotNull] byte[] buffer) : this(new MemoryStream(buffer), false)
{
}
/// <summary>Clears all buffers for this stream and causes any buffered data to be written to the underlying device.</summary>
/// <exception cref="T:System.IO.IOException">An I/O error occurs.</exception>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.IO.Stream.Flush?view=netcore-5.0">`Stream.Flush` on docs.microsoft.com</a></footer>
public override void Flush() => BaseStream.Flush();
/// <summary>Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.</summary>
/// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between <paramref name="offset" /> and (<paramref name="offset" /> + <paramref name="count" /> - 1) replaced by the bytes read from the current source.</param>
/// <param name="offset">The zero-based byte offset in <paramref name="buffer" /> at which to begin storing the data read from the current stream.</param>
/// <param name="count">The maximum number of bytes to be read from the current stream.</param>
/// <exception cref="T:System.ArgumentException">The sum of <paramref name="offset" /> and <paramref name="count" /> is larger than the buffer length.</exception>
/// <exception cref="T:System.ArgumentNullException">
/// <paramref name="buffer" /> is <see langword="null" />.</exception>
/// <exception cref="T:System.ArgumentOutOfRangeException">
/// <paramref name="offset" /> or <paramref name="count" /> is negative.</exception>
/// <exception cref="T:System.IO.IOException">An I/O error occurs.</exception>
/// <exception cref="T:System.NotSupportedException">The stream does not support reading.</exception>
/// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed.</exception>
/// <returns>The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.IO.Stream.Read?view=netcore-5.0">`Stream.Read` on docs.microsoft.com</a></footer>
public override int Read(byte[] buffer, int offset, int count) => BaseStream.Read(buffer, offset, count);
/// <summary>Sets the position within the current stream.</summary>
/// <param name="offset">A byte offset relative to the <paramref name="origin" /> parameter.</param>
/// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin" /> indicating the reference point used to obtain the new position.</param>
/// <exception cref="T:System.IO.IOException">An I/O error occurs.</exception>
/// <exception cref="T:System.NotSupportedException">The stream does not support seeking, such as if the stream is constructed from a pipe or console output.</exception>
/// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed.</exception>
/// <returns>The new position within the current stream.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.IO.Stream.Seek?view=netcore-5.0">`Stream.Seek` on docs.microsoft.com</a></footer>
public override long Seek(long offset, SeekOrigin origin) => BaseStream.Seek(offset, origin);
/// <summary>Sets the length of the current stream.</summary>
/// <param name="value">The desired length of the current stream in bytes.</param>
/// <exception cref="T:System.IO.IOException">An I/O error occurs.</exception>
/// <exception cref="T:System.NotSupportedException">The stream does not support both writing and seeking, such as if the stream is constructed from a pipe or console output.</exception>
/// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed.</exception>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.IO.Stream.SetLength?view=netcore-5.0">`Stream.SetLength` on docs.microsoft.com</a></footer>
public override void SetLength(long value) => BaseStream.SetLength(value);
/// <summary>Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.</summary>
/// <param name="buffer">An array of bytes. This method copies <paramref name="count" /> bytes from <paramref name="buffer" /> to the current stream.</param>
/// <param name="offset">The zero-based byte offset in <paramref name="buffer" /> at which to begin copying bytes to the current stream.</param>
/// <param name="count">The number of bytes to be written to the current stream.</param>
/// <exception cref="T:System.ArgumentException">The sum of <paramref name="offset" /> and <paramref name="count" /> is greater than the buffer length.</exception>
/// <exception cref="T:System.ArgumentNullException">
/// <paramref name="buffer" /> is <see langword="null" />.</exception>
/// <exception cref="T:System.ArgumentOutOfRangeException">
/// <paramref name="offset" /> or <paramref name="count" /> is negative.</exception>
/// <exception cref="T:System.IO.IOException">An I/O error occurred, such as the specified file cannot be found.</exception>
/// <exception cref="T:System.NotSupportedException">The stream does not support writing.</exception>
/// <exception cref="T:System.ObjectDisposedException">
/// <see cref="M:System.IO.Stream.Write(System.Byte[],System.Int32,System.Int32)" /> was called after the stream was closed.</exception>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.IO.Stream.Write?view=netcore-5.0">`Stream.Write` on docs.microsoft.com</a></footer>
public override void Write(byte[] buffer, int offset, int count) => BaseStream.Write(buffer, offset, count);
/// <summary>Gets a value indicating whether the current stream supports reading.</summary>
/// <returns>
/// <see langword="true" /> if the stream supports reading; otherwise, <see langword="false" />.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.IO.Stream.CanRead?view=netcore-5.0">`Stream.CanRead` on docs.microsoft.com</a></footer>
public override bool CanRead => BaseStream.CanRead;
/// <summary>Gets a value indicating whether the current stream supports seeking.</summary>
/// <returns>
/// <see langword="true" /> if the stream supports seeking; otherwise, <see langword="false" />.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.IO.Stream.CanSeek?view=netcore-5.0">`Stream.CanSeek` on docs.microsoft.com</a></footer>
public override bool CanSeek => BaseStream.CanSeek;
/// <summary>Gets a value indicating whether the current stream supports writing.</summary>
/// <returns>
/// <see langword="true" /> if the stream supports writing; otherwise, <see langword="false" />.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.IO.Stream.CanWrite?view=netcore-5.0">`Stream.CanWrite` on docs.microsoft.com</a></footer>
public override bool CanWrite => BaseStream.CanWrite;
/// <summary>Gets the length in bytes of the stream.</summary>
/// <exception cref="T:System.NotSupportedException">A class derived from <see langword="Stream" /> does not support seeking.</exception>
/// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed.</exception>
/// <returns>A long value representing the length of the stream in bytes.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.IO.Stream.Length?view=netcore-5.0">`Stream.Length` on docs.microsoft.com</a></footer>
public override long Length => BaseStream.Length;
/// <summary>Gets or sets the position within the current stream.</summary>
/// <exception cref="T:System.IO.IOException">An I/O error occurs.</exception>
/// <exception cref="T:System.NotSupportedException">The stream does not support seeking.</exception>
/// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed.</exception>
/// <returns>The current position within the stream.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.IO.Stream.Position?view=netcore-5.0">`Stream.Position` on docs.microsoft.com</a></footer>
public override long Position
{
get => BaseStream.Position;
set => BaseStream.Position = value;
}
}
}

9
SharpNBT/SharpNBT.csproj Normal file
View File

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Title>SharpNBT</Title>
<Authors>ForeverZer0</Authors>
</PropertyGroup>
</Project>

58
SharpNBT/ShortTag.cs Normal file
View File

@ -0,0 +1,58 @@
using System;
using JetBrains.Annotations;
namespace SharpNBT
{
/// <summary>
/// A tag that contains a single 16-bit integer value.
/// </summary>
[PublicAPI]
public class ShortTag : Tag<short>
{
/// <summary>
/// Gets or sets the value of this tag as an unsigned value.
/// </summary>
/// <remarks>
/// This is only a reinterpretation of the bytes, no actual conversion is performed.
/// </remarks>
[CLSCompliant(false)]
public ushort UnsignedValue
{
get => unchecked((ushort)Value);
set => Value = unchecked((short)value);
}
/// <summary>
/// Creates a new instance of the <see cref="ShortTag"/> class with the specified <paramref name="value"/>.
/// </summary>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
/// <param name="value">The value to assign to this tag.</param>
public ShortTag([CanBeNull] string name, short value) : base(TagType.Short, name, value)
{
}
/// <inheritdoc cref="ShortTag(string,short)"/>
[CLSCompliant(false)]
public ShortTag([CanBeNull] string name, ushort value) : base(TagType.Short, name, unchecked((short) value))
{
}
/// <inheritdoc cref="object.ToString"/>
public override string ToString() => $"TAG_Short({PrettyName}): {Value}";
/// <summary>
/// Implicit conversion of this tag to a <see cref="short"/>.
/// </summary>
/// <param name="tag">The tag to convert.</param>
/// <returns>The tag represented as a <see cref="short"/>.</returns>
public static implicit operator short(ShortTag tag) => tag.Value;
/// <summary>
/// Implicit conversion of this tag to a <see cref="ushort"/>.
/// </summary>
/// <param name="tag">The tag to convert.</param>
/// <returns>The tag represented as a <see cref="ushort"/>.</returns>
[CLSCompliant(false)]
public static implicit operator ushort(ShortTag tag) => unchecked((ushort)tag.Value);
}
}

30
SharpNBT/StringTag.cs Normal file
View File

@ -0,0 +1,30 @@
using JetBrains.Annotations;
namespace SharpNBT
{
/// <summary>
/// A tag the contains a UTF-8 string.
/// </summary>
[PublicAPI]
public class StringTag : Tag<string>
{
/// <summary>
/// Creates a new instance of the <see cref="StringTag"/> class with the specified <paramref name="value"/>.
/// </summary>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
/// <param name="value">The value to assign to this tag.</param>
public StringTag([CanBeNull] string name, [CanBeNull] string value) : base(TagType.String, name, value)
{
}
/// <inheritdoc cref="object.ToString"/>
public override string ToString() => $"TAG_String({PrettyName}): \"{Value}\"";
/// <summary>
/// Implicit conversion of this tag to a <see cref="string"/>.
/// </summary>
/// <param name="tag">The tag to convert.</param>
/// <returns>The tag represented as a <see cref="string"/>.</returns>
public static implicit operator string(StringTag tag) => tag.Value;
}
}

79
SharpNBT/Tag.cs Normal file
View File

@ -0,0 +1,79 @@
using System;
using System.Text;
using JetBrains.Annotations;
[assembly: CLSCompliant(true)]
namespace SharpNBT
{
/// <summary>
/// Abstract base class that all NBT tags inherit from.
/// </summary>
[PublicAPI]
public abstract class Tag
{
/// <summary>
/// Text applied in a pretty-print sting when a tag has no defined <see cref="Name"/> value.
/// </summary>
protected const string NO_NAME = "None";
/// <summary>
/// Gets a constant describing the NBT type this object represents.
/// </summary>
public TagType Type { get; }
/// <summary>
/// Gets the parent <see cref="Tag"/> this object is a child of.
/// </summary>
[CanBeNull]
public Tag Parent { get; internal set; }
/// <summary>
/// Gets the name assigned to this <see cref="Tag"/>.
/// </summary>
[CanBeNull]
public string Name { get; set; }
protected Tag(TagType type, [CanBeNull] string name)
{
Type = type;
Name = name;
}
protected internal abstract void PrettyPrinted(StringBuilder buffer, int level, string indent);
protected internal string PrettyName => Name is null ? "None" : $"\"{Name}\"";
}
/// <summary>
/// Abstract base class for <see cref="Tag"/> types that contain a single primitive value.
/// </summary>
/// <typeparam name="T">The type of the value the tag represents.</typeparam>
[PublicAPI]
public abstract class Tag<T> : Tag
{
/// <summary>
/// Gets or sets the value of the tag.
/// </summary>
public T Value { get; set; }
/// <summary>
/// Creates a new instance of the <see cref="DoubleTag"/> class with the specified <paramref name="value"/>.
/// </summary>
/// <param name="type">A constant describing the NBT type for this tag.</param>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
/// <param name="value">The value to assign to this tag.</param>
protected Tag(TagType type, [CanBeNull] string name, T value) : base(type, name)
{
Value = value;
}
protected internal override void PrettyPrinted(StringBuilder buffer, int level, string indent)
{
for (var i = 0; i < level; i++)
buffer.Append(indent);
buffer.AppendLine(ToString());
}
}
}

76
SharpNBT/TagType.cs Normal file
View File

@ -0,0 +1,76 @@
using JetBrains.Annotations;
namespace SharpNBT
{
/// <summary>
/// Strongly-typed numerical constants that are prefixed to tags to denote their type.
/// </summary>
[PublicAPI]
public enum TagType : byte
{
/// <summary>
/// Signifies the end of a <see cref="CompoundTag"/>.
/// </summary>
End = 0x00,
/// <summary>
/// A single signed byte,
/// </summary>
Byte = 0x01,
/// <summary>
/// A single signed 16-bit integer.
/// </summary>
Short = 0x02,
/// <summary>
/// A single signed 32-bit integer.
/// </summary>
Int = 0x03,
/// <summary>
/// A single signed 64-bit integer.
/// </summary>
Long = 0x04,
/// <summary>
/// A single IEEE-754 single-precision floating point number.
/// </summary>
Float = 0x05,
/// <summary>
/// A single IEEE-754 double-precision floating point number.
/// </summary>
Double = 0x06,
/// <summary>
/// A length-prefixed array of bytes.
/// </summary>
ByteArray = 0x07,
/// <summary>
/// A length-prefixed UTF-8 string.
/// </summary>
String = 0x08,
/// <summary>
/// A list of nameless tags, all of the same type.
/// </summary>
List = 0x09,
/// <summary>
/// A set of named tags.
/// </summary>
Compound = 0x0a,
/// <summary>
/// A length-prefixed array of signed 32-bit integers.
/// </summary>
IntArray = 0x0b,
/// <summary>
/// A length-prefixed array of signed 64-bit integers.
/// </summary>
LongArray = 0x0c
}
}