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