diff --git a/SharpNBT.Tests/ConversionTest.cs b/SharpNBT.Tests/ConversionTest.cs
index e0510f3..86bae4e 100644
--- a/SharpNBT.Tests/ConversionTest.cs
+++ b/SharpNBT.Tests/ConversionTest.cs
@@ -1,3 +1,6 @@
+using System;
+using System.Linq;
+using System.Text;
using Xunit;
using Xunit.Abstractions;
@@ -19,5 +22,24 @@ namespace SharpNBT.Tests
{
output.WriteLine(tag.ToJsonString(true));
}
+
+ [Fact]
+ public unsafe void PinTest()
+ {
+ const int length = 69;
+ var ary = Enumerable.Range(420, length);
+ var intTag = new IntArrayTag("Foobar", ary);
+
+ var sb = new StringBuilder();
+ fixed (int* ptr = &intTag.GetPinnableReference())
+ {
+ for (var i = 0; i < length; i++)
+ {
+ sb.Append(ptr[i]);
+ sb.Append(',');
+ }
+ }
+ output.WriteLine(sb.ToString());
+ }
}
}
\ No newline at end of file
diff --git a/SharpNBT.Tests/SharpNBT.Tests.csproj b/SharpNBT.Tests/SharpNBT.Tests.csproj
index 9fb8c29..5655d2e 100644
--- a/SharpNBT.Tests/SharpNBT.Tests.csproj
+++ b/SharpNBT.Tests/SharpNBT.Tests.csproj
@@ -8,6 +8,8 @@
latestmajor
enable
+
+ true
diff --git a/SharpNBT.Tests/StringifiedTest.cs b/SharpNBT.Tests/StringifiedTest.cs
index 8b60ab9..20e4968 100644
--- a/SharpNBT.Tests/StringifiedTest.cs
+++ b/SharpNBT.Tests/StringifiedTest.cs
@@ -32,6 +32,14 @@ namespace SharpNBT.Tests
output.WriteLine(tag.Stringify(true));
}
+ [Fact]
+ public void ParseBugged()
+ {
+ var testString = "{Count:1b,id:\"minecraft:netherite_sword\",tag:{Damage:0,Enchantments:[{id:\"minecraft:looting\",lvl:3s},{id:\"minecraft:smite\",lvl:5s},{id:\"minecraft:sweeping\",lvl:3s}],RepairCost:7,display:{Name:'{\"extra\":[{\"text\":\"我是修改的名字\"}],\"text\":\"\"}'}}}";
+ var tag = StringNbt.Parse(testString);
+ output.WriteLine(tag.PrettyPrinted());
+ }
+
[Fact]
public void ParseSmall()
{
diff --git a/SharpNBT/NbtFile.cs b/SharpNBT/NbtFile.cs
index 36606a4..a6dae4f 100644
--- a/SharpNBT/NbtFile.cs
+++ b/SharpNBT/NbtFile.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
@@ -21,7 +22,7 @@ public static class NbtFile
/// Bitwise flags to configure how data should be handled for compatibility between different specifications.
/// The type of tag to deserialize.
/// The deserialized instance.
- public static T Read(string path, FormatOptions options, CompressionType compression = CompressionType.AutoDetect) where T : TagContainer
+ public static T Read(string path, FormatOptions options, CompressionType compression = CompressionType.AutoDetect) where T : Tag, ICollection
{
using var reader = new TagReader(GetReadStream(path, compression), options);
return reader.ReadTag();
@@ -47,7 +48,7 @@ public static class NbtFile
/// Indicates the compression algorithm used to compress the file.
/// Bitwise flags to configure how data should be handled for compatibility between different specifications.
/// The deserialized instance.
- public static async Task ReadAsync(string path, FormatOptions options, CompressionType compression = CompressionType.AutoDetect) where T : TagContainer
+ public static async Task ReadAsync(string path, FormatOptions options, CompressionType compression = CompressionType.AutoDetect) where T : Tag, ICollection
{
await using var reader = new TagReader(GetReadStream(path, compression), options);
return await reader.ReadTagAsync();
diff --git a/SharpNBT/Tags/EnumerableTag.cs b/SharpNBT/Tags/EnumerableTag.cs
deleted file mode 100644
index 3ba7e64..0000000
--- a/SharpNBT/Tags/EnumerableTag.cs
+++ /dev/null
@@ -1,203 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Runtime.Serialization;
-using System.Text;
-using JetBrains.Annotations;
-using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute;
-
-namespace SharpNBT;
-
-///
-/// Base class for tags that contain a collection of values and can be enumerated.
-///
-/// The type of the item the tag contains.
-[PublicAPI][Serializable]
-public abstract class EnumerableTag : Tag, IList
-{
- ///
- /// Internal list implementation.
- ///
- [ItemNotNull]
- private readonly List internalList = new List();
-
- ///
- /// Initializes a new instance of the .
- ///
- /// A constant describing the NBT type for this tag.
- /// The name of the tag, or if tag has no name.
- protected EnumerableTag(TagType type, string? name) : base(type, name)
- {
- }
-
- ///
- /// Initializes a new instance of the with the specified .
- ///
- /// A constant describing the NBT type for this tag.
- /// The name of the tag, or if tag has no name.
- /// A collection of values to include in this tag.
- protected EnumerableTag(TagType type, string? name, T[] values) : base(type, name)
- {
- internalList.AddRange(values);
- }
-
- ///
- /// Initializes a new instance of the with the specified .
- ///
- /// A constant describing the NBT type for this tag.
- /// The name of the tag, or if tag has no name.
- /// A collection of values to include in this tag.
- protected EnumerableTag(TagType type, string? name, IEnumerable values) : base(type, name)
- {
- internalList.AddRange(values);
- }
-
- ///
- /// Initializes a new instance of the with the specified .
- ///
- /// A constant describing the NBT type for this tag.
- /// The name of the tag, or if tag has no name.
- /// A collection of values to include in this tag.
- protected EnumerableTag(TagType type, string? name, ReadOnlySpan values) : base(type, name)
- {
- internalList.AddRange(values.ToArray());
- }
-
- ///
- /// Required constructor for ISerializable implementation.
- ///
- /// The to describing this instance.
- /// The destination (see ) for this serialization.
- protected EnumerableTag(SerializationInfo info, StreamingContext context) : base(info, context)
- {
- var dummy = info.GetInt32("count");
- var obj = info.GetValue("values", typeof(T[])) as T[];
- if (obj is IEnumerable e)
- internalList.AddRange(e);
- }
-
- /// Populates a with the data needed to serialize the target object.
- /// The to populate with data.
- /// The destination (see ) for this serialization.
- /// The caller does not have the required permission.
- public override void GetObjectData(SerializationInfo info, StreamingContext context)
- {
- base.GetObjectData(info, context);
- info.AddValue("count", Count);
- info.AddValue("values", internalList.ToArray(), typeof(T[]));
- }
-
- /// Returns an enumerator that iterates through the collection.
- /// An enumerator that can be used to iterate through the collection.
- ///
- public IEnumerator GetEnumerator() => internalList.GetEnumerator();
-
- /// Returns an enumerator that iterates through a collection.
- /// An object that can be used to iterate through the collection.
- ///
- IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)internalList).GetEnumerator();
-
- /// Adds an item to the .
- /// The object to add to the .
- /// The is read-only.
- ///
- [SuppressMessage("ReSharper", "AnnotationConflictInHierarchy")]
- public virtual void Add(T item) => internalList.Add(item);
-
- ///
- /// Adds the elements of the specified collection to the .
- ///
- /// A collection containing the items to add.
- public void AddRange([ItemNotNull] IEnumerable items)
- {
- foreach (var item in items)
- Add(item!);
- }
-
- /// Inserts an item to the at the specified index.
- /// The zero-based index at which should be inserted.
- /// The object to insert into the .
- ///
- /// is not a valid index in the .
- /// The is read-only.
- ///
- [SuppressMessage("ReSharper", "AnnotationConflictInHierarchy")]
- public virtual void Insert(int index, T item) => internalList.Insert(index, item);
-
- /// Gets or sets the element at the specified index.
- /// The zero-based index of the element to get or set.
- ///
- /// is not a valid index in the .
- /// The property is set and the is read-only.
- /// The element at the specified index.
- ///
- public virtual T this[int index]
- {
- get => internalList[index];
- set => internalList[index] = value;
- }
-
- /// Removes all items from the .
- /// The is read-only.
- ///
- public virtual void Clear() => internalList.Clear();
-
- /// Determines whether the contains a specific value.
- /// The object to locate in the .
- ///
- /// if is found in the ; otherwise, .
- ///
- public bool Contains(T item) => internalList.Contains(item);
-
- /// Copies the elements of the to an , starting at a particular index.
- /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing.
- /// The zero-based index in at which copying begins.
- ///
- /// is .
- ///
- /// is less than 0.
- /// The number of elements in the source is greater than the available space from to the end of the destination .
- ///
- public void CopyTo(T[] array, int arrayIndex) => internalList.CopyTo(array, arrayIndex);
-
- /// Removes the first occurrence of a specific object from the .
- /// The object to remove from the .
- /// The is read-only.
- ///
- /// if was successfully removed from the ; otherwise, . This method also returns if is not found in the original .
- ///
- public virtual bool Remove(T item) => internalList.Remove(item);
-
- /// Gets the number of elements contained in the .
- /// The number of elements contained in the .
- ///
- public int Count => internalList.Count;
-
- /// Gets a value indicating whether the is read-only.
- ///
- /// if the is read-only; otherwise, .
- ///
- public bool IsReadOnly => false;
-
- /// Determines the index of a specific item in the .
- /// The object to locate in the .
- /// The index of if found in the list; otherwise, -1.
- ///
- public int IndexOf(T item) => internalList.IndexOf(item);
-
- /// Removes the item at the specified index.
- /// The zero-based index of the item to remove.
- ///
- /// is not a valid index in the .
- /// The is read-only.
- ///
- public virtual void RemoveAt(int index) => internalList.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());
- }
-}
\ No newline at end of file
diff --git a/SharpNBT/Tags/ListTag.cs b/SharpNBT/Tags/ListTag.cs
index a6a12c1..c152055 100644
--- a/SharpNBT/Tags/ListTag.cs
+++ b/SharpNBT/Tags/ListTag.cs
@@ -1,5 +1,7 @@
using System;
+using System.Collections;
using System.Collections.Generic;
+using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Text;
using JetBrains.Annotations;
@@ -13,7 +15,7 @@ namespace SharpNBT;
/// All child tags must be have the same value, and their value will be omitted during serialization.
///
[PublicAPI][Serializable]
-public class ListTag : TagContainer
+public class ListTag : Tag, IList
{
///
/// Gets the NBT type of this tag's children.
@@ -27,9 +29,20 @@ public class ListTag : TagContainer
/// A constant describing the NBT type for children in this tag.
public ListTag(string? name, TagType childType) : base(TagType.List, name)
{
- RequiredType = childType;
- NamedChildren = false;
ChildType = childType;
+ list = new List();
+ }
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ /// The name of the tag, or if tag has no name.
+ /// A constant describing the NBT type for children in this tag.
+ /// The initial capacity of the list.
+ public ListTag(string? name, TagType childType, int capacity) : base(TagType.List, name)
+ {
+ ChildType = childType;
+ list = new List(capacity);
}
///
@@ -40,7 +53,8 @@ public class ListTag : TagContainer
/// A collection of values to include in this tag.
public ListTag(string? name, TagType childType, IEnumerable children) : this(name, childType)
{
- AddRange(children);
+ foreach (var item in children)
+ list.Add(AssertType(item));
}
///
@@ -50,15 +64,98 @@ public class ListTag : TagContainer
/// The destination (see ) for this serialization.
protected ListTag(SerializationInfo info, StreamingContext context) : base(info, context)
{
+ ChildType = (TagType)info.GetByte("child_type");
+ var count = info.GetInt32("count");
+ list = new List(count);
+ if (info.GetValue("values", typeof(Tag[])) is Tag[] ary)
+ AddRange(ary);
}
+ public override void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ base.GetObjectData(info, context);
+ info.AddValue("child_type", (byte) ChildType);
+ info.AddValue("count", list.Count);
+ info.AddValue("values", list.ToArray());
+ }
+
+ ///
+ public IEnumerator GetEnumerator() => list.GetEnumerator();
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator() => list.GetEnumerator();
+
+ ///
+ public void Add(Tag item) => list.Add(AssertType(item));
+
+ public void AddRange(IEnumerable items)
+ {
+ foreach (var item in items)
+ list.Add(AssertType(item));
+ }
+
+ ///
+ public void Clear() => list.Clear();
+
+ ///
+ public bool Contains(Tag item) => list.Contains(item);
+
+ ///
+ public void CopyTo(Tag[] array, int arrayIndex) => list.CopyTo(array, arrayIndex);
+
+ ///
+ public bool Remove(Tag item) => list.Remove(item);
+
+ ///
+ public int Count => list.Count;
+
+ ///
+ bool ICollection.IsReadOnly => false;
+
+ ///
+ public int IndexOf(Tag item) => list.IndexOf(item);
+
+ ///
+ public void Insert(int index, Tag item) => list.Insert(index, AssertType(item));
+
+ ///
+ public void RemoveAt(int index) => list.RemoveAt(index);
+
+ ///
+ public Tag this[int index]
+ {
+ get => list[index];
+ set => list[index] = AssertType(value);
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
///
public override string ToString()
{
var word = Count == 1 ? Strings.WordEntry : Strings.WordEntries;
return $"TAG_List({PrettyName}): [{Count} {word}]";
}
-
+
+
+
///
protected internal override void PrettyPrinted(StringBuilder buffer, int level, string indent)
{
@@ -100,4 +197,16 @@ public class ListTag : TagContainer
return $"{StringifyName}:[{string.Join(',', strings)}]";
}
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private Tag AssertType(Tag tag)
+ {
+ if (tag.Type != ChildType)
+ throw new ArrayTypeMismatchException(Strings.ChildWrongType);
+ return tag;
+ }
+
+ private readonly List list;
+
+
}
\ No newline at end of file
diff --git a/SharpNBT/Tags/Tag.cs b/SharpNBT/Tags/Tag.cs
index fb4984f..151069a 100644
--- a/SharpNBT/Tags/Tag.cs
+++ b/SharpNBT/Tags/Tag.cs
@@ -35,8 +35,6 @@ public abstract class Tag : IEquatable, ISerializable, ICloneable
typeof(NumericTag<>),
typeof(ArrayTag<>),
typeof(Tag[]),
- typeof(EnumerableTag<>),
- typeof(TagContainer),
typeof(ByteTag),
typeof(ShortTag),
typeof(IntTag),
diff --git a/SharpNBT/Tags/TagContainer.cs b/SharpNBT/Tags/TagContainer.cs
deleted file mode 100644
index 5bce5fc..0000000
--- a/SharpNBT/Tags/TagContainer.cs
+++ /dev/null
@@ -1,173 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Runtime.CompilerServices;
-using System.Runtime.Serialization;
-using JetBrains.Annotations;
-using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute;
-
-namespace SharpNBT;
-
-///
-/// Base class for tags that contain a collection of other objects and can be enumerated.
-///
-[PublicAPI][Serializable]
-public abstract class TagContainer : EnumerableTag
-{
- ///
- /// A value indicating if children of this container are required to have be named.
- ///
- protected bool NamedChildren;
-
- ///
- /// When not , indicates that a child must be of a specific type to be added.
- ///
- protected TagType? RequiredType;
-
- ///
- /// Initializes a new instance of the .
- ///
- /// A constant describing the NBT type for this tag.
- /// The name of the tag, or if tag has no name.
- protected TagContainer(TagType type, string? name) : base(type, name)
- {
- }
-
- ///
- /// Initializes a new instance of the with the specified .
- ///
- /// A constant describing the NBT type for this tag.
- /// The name of the tag, or if tag has no name.
- /// A collection of values to include in this tag.
- protected TagContainer(TagType type, string? name, IEnumerable values) : base(type, name, values)
- {
- }
-
- ///
- /// Required constructor for ISerializable implementation.
- ///
- /// The to describing this instance.
- /// The destination (see ) for this serialization.
- protected TagContainer(SerializationInfo info, StreamingContext context) : base(info, context)
- {
- RequiredType = (TagType?) info.GetValue("child_type", typeof(TagType?));
- NamedChildren = !RequiredType.HasValue;
- }
-
- /// Populates a with the data needed to serialize the target object.
- /// The to populate with data.
- /// The destination (see ) for this serialization.
- /// The caller does not have the required permission.
- public override void GetObjectData(SerializationInfo info, StreamingContext context)
- {
- base.GetObjectData(info, context);
- if (RequiredType.HasValue)
- info.AddValue("child_type", RequiredType.Value);
- }
-
- /// Adds an item to the .
- /// The object to add to the .
- /// The is read-only.
- ///
- [SuppressMessage("ReSharper", "AnnotationConflictInHierarchy")]
- public sealed override void Add(Tag item)
- {
- base.Add(AssertConventions(item));
- item.Parent = this;
- }
-
- /// Inserts an item to the at the specified index.
- /// The zero-based index at which should be inserted.
- /// The object to insert into the .
- ///
- /// is not a valid index in the .
- /// The is read-only.
- ///
- [SuppressMessage("ReSharper", "AnnotationConflictInHierarchy")]
- public sealed override void Insert(int index, Tag item)
- {
- base.Insert(index, AssertConventions(item));
- item.Parent = this;
- }
-
- /// Gets or sets the element at the specified index.
- /// The zero-based index of the element to get or set.
- ///
- /// is not a valid index in the .
- /// The property is set and the is read-only.
- /// The element at the specified index.
- ///
- public sealed override Tag this[int index]
- {
- get => base[index];
- set
- {
- base[index] = AssertConventions(value);
- value.Parent = this;
- }
- }
-
- /// Removes all items from the .
- /// The is read-only.
- ///
- public sealed override void Clear()
- {
- foreach (var item in this)
- item.Parent = null;
- base.Clear();
- }
-
- /// Removes the first occurrence of a specific object from the .
- /// The object to remove from the .
- /// The is read-only.
- ///
- /// if was successfully removed from the ; otherwise, . This method also returns if is not found in the original .
- ///
- public sealed override bool Remove(Tag item)
- {
- if (item is null || !base.Remove(item))
- return false;
-
- item.Parent = null;
- return true;
- }
-
- /// Removes the item at the specified index.
- /// The zero-based index of the item to remove.
- ///
- /// is not a valid index in the .
- /// The is read-only.
- ///
- public sealed override void RemoveAt(int index)
- {
- this[index].Parent = null;
- base.RemoveAt(index);
- }
-
- ///
- /// Performs routine checks to ensure that the given complies with the NBT standard for this collection type.
- ///
- /// A instance to validate.
- /// Returns the instance.
- ///
- ///
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- protected Tag AssertConventions(Tag? tag)
- {
- if (tag is null)
- throw new ArgumentNullException(nameof(tag), Strings.ChildCannotBeNull);
-
- switch (NamedChildren)
- {
- case true when tag.Name is null:
- throw new FormatException(Strings.ChildrenMustBeNamed);
- case false when tag.Name != null:
- throw new FormatException(Strings.ChildrenMustNotBeNamed);
- }
-
- if (RequiredType.HasValue && RequiredType.Value != tag.Type)
- throw new ArrayTypeMismatchException(Strings.ChildWrongType);
-
- return tag;
- }
-}
\ No newline at end of file