From 7bb273b26253d833d3b5793af9f961363bb183e3 Mon Sep 17 00:00:00 2001 From: ForeverZer0 Date: Sat, 21 Aug 2021 18:04:05 -0400 Subject: [PATCH] Initial commit --- .gitignore | 5 + SharpNBT.Tests/Data/bigtest.nbt | Bin 0 -> 507 bytes SharpNBT.Tests/Data/hello_world.nbt | Bin 0 -> 33 bytes SharpNBT.sln | 22 + SharpNBT/ByteArrayTag.cs | 40 + SharpNBT/ByteTag.cs | 63 ++ SharpNBT/CompoundTag.cs | 93 ++ SharpNBT/DoubleTag.cs | 30 + SharpNBT/EndTag.cs | 28 + SharpNBT/EnumerableTag.cs | 193 ++++ SharpNBT/FloatTag.cs | 30 + SharpNBT/IntArrayTag.cs | 36 + SharpNBT/IntTag.cs | 58 ++ SharpNBT/JetBrains.Annotations.cs | 1355 +++++++++++++++++++++++++++ SharpNBT/ListTag.cs | 76 ++ SharpNBT/LongArrayTag.cs | 36 + SharpNBT/LongTag.cs | 58 ++ SharpNBT/NbtStream.Read.cs | 300 ++++++ SharpNBT/NbtStream.Write.cs | 257 +++++ SharpNBT/NbtStream.cs | 162 ++++ SharpNBT/SharpNBT.csproj | 9 + SharpNBT/ShortTag.cs | 58 ++ SharpNBT/StringTag.cs | 30 + SharpNBT/Tag.cs | 79 ++ SharpNBT/TagType.cs | 76 ++ 25 files changed, 3094 insertions(+) create mode 100644 .gitignore create mode 100644 SharpNBT.Tests/Data/bigtest.nbt create mode 100644 SharpNBT.Tests/Data/hello_world.nbt create mode 100644 SharpNBT.sln create mode 100644 SharpNBT/ByteArrayTag.cs create mode 100644 SharpNBT/ByteTag.cs create mode 100644 SharpNBT/CompoundTag.cs create mode 100644 SharpNBT/DoubleTag.cs create mode 100644 SharpNBT/EndTag.cs create mode 100644 SharpNBT/EnumerableTag.cs create mode 100644 SharpNBT/FloatTag.cs create mode 100644 SharpNBT/IntArrayTag.cs create mode 100644 SharpNBT/IntTag.cs create mode 100644 SharpNBT/JetBrains.Annotations.cs create mode 100644 SharpNBT/ListTag.cs create mode 100644 SharpNBT/LongArrayTag.cs create mode 100644 SharpNBT/LongTag.cs create mode 100644 SharpNBT/NbtStream.Read.cs create mode 100644 SharpNBT/NbtStream.Write.cs create mode 100644 SharpNBT/NbtStream.cs create mode 100644 SharpNBT/SharpNBT.csproj create mode 100644 SharpNBT/ShortTag.cs create mode 100644 SharpNBT/StringTag.cs create mode 100644 SharpNBT/Tag.cs create mode 100644 SharpNBT/TagType.cs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..add57be --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +bin/ +obj/ +/packages/ +riderModule.iml +/_ReSharper.Caches/ \ No newline at end of file diff --git a/SharpNBT.Tests/Data/bigtest.nbt b/SharpNBT.Tests/Data/bigtest.nbt new file mode 100644 index 0000000000000000000000000000000000000000..dc3769bcd06cca599974396faaf5ce42ef4143b3 GIT binary patch literal 507 zcmVFFmV&Xw5M#`%wS=YH%|a20v4|EdnhhG* zs4<4PE5m{=yJmN=@zhIuYI4wZzg#H1XOZQ zEDm_fu}zJ5^y6@1J_vgq$EA}P4}wSC?t}tjwW6xWcy?S@%cxZk8_3okYL$kD4Xu7y zdyj+9gHMBR&jS!{TaG@yr8rDv{SfNfbfzOf+-5Fm;kDDdbNY4*DccL+@8~@qI9u-# z2v+spUEd2p;9j@-WVZwWj6qCu#t2nR(;zN=q`=6+5VN}8SPN65?nI7712C~CQ;baU z=@g?=jD_LZpX0OgM1iGzGu_y`$EtM`Unm?1*DldnKd&7dU?ExG`tb$+!Or}hy#T!N zK*{)pLO@3Tp6lullR9XJV7u!wH=`&Dj=S~HX=BPx+v)7)<|{kBCB9@y2|cR2l>Hcf z=+X|_Zxu|jXg(|9o1BE1yo3b_Wmy(Q0RJy2t}pV!vZO8@Aa`0jxs1x^Dc?~+$riQ6 zZJ{cyEX6T@x_Xk1UY%d~5(Oh0(e}4@s?C*hyq@1!T}zj)k{7u|(16JLKEJcvRLci- xZlkt#S(1~f+)+@OYs@v~8vj=#2tv#08`gNj?Ed_E`+a!Rgx}Qir27a4007^G{X+l% literal 0 HcmV?d00001 diff --git a/SharpNBT.Tests/Data/hello_world.nbt b/SharpNBT.Tests/Data/hello_world.nbt new file mode 100644 index 0000000000000000000000000000000000000000..f3f5e21aacfc8ce832a459c6c6d827127109cb5b GIT binary patch literal 33 ocmd;L;Lb?R$;nqJ&o9bJ;b36NOUzAW;B-pNOUx@u%uQqf0G#LvsQ>@~ literal 0 HcmV?d00001 diff --git a/SharpNBT.sln b/SharpNBT.sln new file mode 100644 index 0000000..d41ae92 --- /dev/null +++ b/SharpNBT.sln @@ -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 diff --git a/SharpNBT/ByteArrayTag.cs b/SharpNBT/ByteArrayTag.cs new file mode 100644 index 0000000..c45447b --- /dev/null +++ b/SharpNBT/ByteArrayTag.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace SharpNBT +{ + /// + /// A tag that whose value is a contiguous sequence of 8-bit integers. + /// + /// + /// While this class uses the CLS compliant (0..255), the NBT specification uses a signed value with a range of -128..127, so ensure + /// the bits are equivalent for your values. + /// + [PublicAPI] + public class ByteArrayTag : EnumerableTag + { + /// + /// Initializes a new instance of the . + /// + /// The name of the tag, or if tag has no name. + public ByteArrayTag([CanBeNull] string name) : base(TagType.ByteArray, name) + { + } + + /// + /// Initializes a new instance of the with the specified . + /// + /// The name of the tag, or if tag has no name. + /// A collection of values to include in this tag. + public ByteArrayTag([CanBeNull] string name, [NotNull] IEnumerable values) : base(TagType.ByteArray, name, values) + { + } + + /// + public override string ToString() + { + var word = Count == 1 ? "element" : "elements"; + return $"TAG_Byte_Array({PrettyName}): [{Count} {word}]"; + } + } +} \ No newline at end of file diff --git a/SharpNBT/ByteTag.cs b/SharpNBT/ByteTag.cs new file mode 100644 index 0000000..33bacab --- /dev/null +++ b/SharpNBT/ByteTag.cs @@ -0,0 +1,63 @@ +using System; +using JetBrains.Annotations; + +namespace SharpNBT +{ + /// + /// A tag that contains a single 8-bit integer value. + /// + /// + /// While this class uses the CLS compliant (0..255), the NBT specification uses a signed value with a range of -128..127. It is + /// recommended to use the property if your language supports a signed 8-bit value, otherwise simply ensure the bits are + /// equivalent. + /// + [PublicAPI] + public class ByteTag : Tag + { + /// + /// Gets or sets the value of this tag as an unsigned value. + /// + /// + /// This is only a reinterpretation of the bytes, no actual conversion is performed. + /// + [CLSCompliant(false)] + public sbyte SignedValue + { + get => unchecked((sbyte)Value); + set => Value = unchecked((byte)value); + } + + /// + /// Creates a new instance of the class with the specified . + /// + /// The name of the tag, or if tag has no name. + /// The value to assign to this tag. + public ByteTag([CanBeNull] string name, byte value) : base(TagType.Byte, name, value) + { + } + + /// + [CLSCompliant(false)] + public ByteTag([CanBeNull] string name, sbyte value) : base(TagType.Byte, name, unchecked((byte) value)) + { + } + + /// + public override string ToString() => $"TAG_Byte({PrettyName}): {Value}"; + + /// + /// Implicit conversion of this tag to a . + /// + /// The tag to convert. + /// The tag represented as a . + public static implicit operator byte(ByteTag tag) => tag.Value; + + /// + /// Implicit conversion of this tag to a . + /// + /// The tag to convert. + /// The tag represented as a . + [CLSCompliant(false)] + public static implicit operator sbyte(ByteTag tag) => unchecked((sbyte)tag.Value); + } +} \ No newline at end of file diff --git a/SharpNBT/CompoundTag.cs b/SharpNBT/CompoundTag.cs new file mode 100644 index 0000000..c21f44b --- /dev/null +++ b/SharpNBT/CompoundTag.cs @@ -0,0 +1,93 @@ +using System.Collections.Generic; +using System.Text; +using JetBrains.Annotations; + +namespace SharpNBT +{ + /// + /// Top-level tag that acts as a container for other named tags. + /// + /// + /// This along with the class define the structure of the NBT format. Children are not order-dependent, nor is order guaranteed. + /// + [PublicAPI] + public class CompoundTag : EnumerableTag + { + /// + /// Creates a new instance of the class. + /// + /// The name of the tag, or if tag has no name. + public CompoundTag([CanBeNull] string name) : base(TagType.Compound, name) + { + } + + /// + /// Creates a new instance of the class. + /// + /// The name of the tag, or if tag has no name. + /// A collection objects that are children of this object. + public CompoundTag([CanBeNull] string name, [NotNull] IEnumerable values) : base(TagType.Compound, name, values) + { + } + + /// Returns a string that represents the current object. + /// A string that represents the current object. + /// + public override string ToString() + { + var word = Count == 1 ? "entry" : "entries"; + return $"TAG_Compound({PrettyName}): [{Count} {word}]"; + } + + /// + /// Retrieves a "pretty-printed" multiline string representing the complete tree structure of the tag. + /// + /// The prefix that will be applied to each indent-level of nested nodes in the tree structure. + /// The pretty-printed string. + [NotNull] + public string PrettyPrinted([NotNull] string indent = " ") + { + var buffer = new StringBuilder(); + PrettyPrinted(buffer, 0, indent); + return buffer.ToString(); + } + + /// + /// Searches the children of this tag, returning the first child with the specified . + /// + /// The name of the tag to search for. + /// to recursively search children, otherwise to only search direct descendants. + /// The first tag found with , otherwise if none was found. + [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 + "}"); + } + } +} \ No newline at end of file diff --git a/SharpNBT/DoubleTag.cs b/SharpNBT/DoubleTag.cs new file mode 100644 index 0000000..193ed44 --- /dev/null +++ b/SharpNBT/DoubleTag.cs @@ -0,0 +1,30 @@ +using JetBrains.Annotations; + +namespace SharpNBT +{ + /// + /// A tag that contains a single IEEE-754 double-precision floating point number. + /// + [PublicAPI] + public class DoubleTag : Tag + { + /// + /// Creates a new instance of the class with the specified . + /// + /// The name of the tag, or if tag has no name. + /// The value to assign to this tag. + public DoubleTag([CanBeNull] string name, double value) : base(TagType.Double, name, value) + { + } + + /// + public override string ToString() => $"TAG_Double({PrettyName}): {Value}"; + + /// + /// Implicit conversion of this tag to a . + /// + /// The tag to convert. + /// The tag represented as a . + public static implicit operator double(DoubleTag tag) => tag.Value; + } +} \ No newline at end of file diff --git a/SharpNBT/EndTag.cs b/SharpNBT/EndTag.cs new file mode 100644 index 0000000..ec212a8 --- /dev/null +++ b/SharpNBT/EndTag.cs @@ -0,0 +1,28 @@ +using System; +using System.Text; +using JetBrains.Annotations; + +namespace SharpNBT +{ + /// + /// Represents the end of . + /// + [PublicAPI] + public class EndTag : Tag + { + /// + /// Creates a new instance of the class. + /// + public EndTag() : base(TagType.End, null) + { + } + + /// + public override string ToString() => $"TAG_End"; + + protected internal override void PrettyPrinted(StringBuilder buffer, int level, string indent) + { + // Do nothing + } + } +} \ No newline at end of file diff --git a/SharpNBT/EnumerableTag.cs b/SharpNBT/EnumerableTag.cs new file mode 100644 index 0000000..5f3ae34 --- /dev/null +++ b/SharpNBT/EnumerableTag.cs @@ -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 +{ + /// + /// Base class for tags that contain a collection of values and can be enumerated. + /// + /// The type of the item the tag contains. + [PublicAPI] + public abstract class EnumerableTag : Tag, IList + { + /// + /// Internal list implementation. + /// + private readonly List 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, [CanBeNull] string name) : base(type, name) + { + list = new List(); + } + + /// + /// 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, [CanBeNull] string name, [NotNull] IEnumerable values) : base(type, name) + { + list = new List(values); + } + + /// Returns an enumerator that iterates through the collection. + /// An enumerator that can be used to iterate through the collection. + /// + public IEnumerator GetEnumerator() => list.GetEnumerator(); + + /// Returns an enumerator that iterates through a collection. + /// An object that can be used to iterate through the collection. + /// + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)list).GetEnumerator(); + + /// Adds an item to the . + /// The object to add to the . + /// The is read-only. + /// + [SuppressMessage("ReSharper", "AnnotationConflictInHierarchy")] + public void Add([NotNull] T item) + { + if (item is Tag child) + child.Parent = this; + list.Add(item); + } + + /// + /// Adds the elements of the specified collection to the . + /// + /// A collection containing the items to add. + public void AddRange([NotNull] [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 void Insert(int index, [NotNull] T item) + { + if (item is Tag child) + child.Parent = this; + list.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. + /// + [NotNull] + public T this[int index] + { + get => list[index]; + set + { + if (value is Tag child) + child.Parent = this; + list[index] = value; + } + } + + /// Removes all items from the . + /// The is read-only. + /// + public void Clear() + { + foreach (var item in list) + { + if (item is Tag child) + child.Parent = null; + } + list.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) => list.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) => list.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 bool Remove(T item) + { + if (list.Remove(item)) + { + if (item is Tag child) + child.Parent = null; + return true; + } + return false; + } + + /// Gets the number of elements contained in the . + /// The number of elements contained in the . + /// + public int Count => list.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) => list.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 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()); + } + } +} \ No newline at end of file diff --git a/SharpNBT/FloatTag.cs b/SharpNBT/FloatTag.cs new file mode 100644 index 0000000..272dffb --- /dev/null +++ b/SharpNBT/FloatTag.cs @@ -0,0 +1,30 @@ +using JetBrains.Annotations; + +namespace SharpNBT +{ + /// + /// A tag that contains a single IEEE-754 single-precision floating point number. + /// + [PublicAPI] + public class FloatTag : Tag + { + /// + /// Creates a new instance of the class with the specified . + /// + /// The name of the tag, or if tag has no name. + /// The value to assign to this tag. + public FloatTag([CanBeNull] string name, float value) : base(TagType.Float, name, value) + { + } + + /// + public override string ToString() => $"TAG_Float({PrettyName}): {Value}"; + + /// + /// Implicit conversion of this tag to a . + /// + /// The tag to convert. + /// The tag represented as a . + public static implicit operator float(FloatTag tag) => tag.Value; + } +} \ No newline at end of file diff --git a/SharpNBT/IntArrayTag.cs b/SharpNBT/IntArrayTag.cs new file mode 100644 index 0000000..fd65720 --- /dev/null +++ b/SharpNBT/IntArrayTag.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace SharpNBT +{ + /// + /// A tag that whose value is a contiguous sequence of 32-bit integers. + /// + [PublicAPI] + public class IntArrayTag : EnumerableTag + { + /// + /// Initializes a new instance of the . + /// + /// The name of the tag, or if tag has no name. + public IntArrayTag([CanBeNull] string name) : base(TagType.IntArray, name) + { + } + + /// + /// Initializes a new instance of the with the specified . + /// + /// The name of the tag, or if tag has no name. + /// A collection of values to include in this tag. + public IntArrayTag([CanBeNull] string name, [NotNull] IEnumerable values) : base(TagType.IntArray, name, values) + { + } + + /// + public override string ToString() + { + var word = Count == 1 ? "element" : "elements"; + return $"TAG_Int_Array({PrettyName}): [{Count} {word}]"; + } + } +} \ No newline at end of file diff --git a/SharpNBT/IntTag.cs b/SharpNBT/IntTag.cs new file mode 100644 index 0000000..2888aca --- /dev/null +++ b/SharpNBT/IntTag.cs @@ -0,0 +1,58 @@ +using System; +using JetBrains.Annotations; + +namespace SharpNBT +{ + /// + /// A tag that contains a single 32-bit integer value. + /// + [PublicAPI] + public class IntTag : Tag + { + /// + /// Gets or sets the value of this tag as an unsigned value. + /// + /// + /// This is only a reinterpretation of the bytes, no actual conversion is performed. + /// + [CLSCompliant(false)] + public uint UnsignedValue + { + get => unchecked((uint)Value); + set => Value = unchecked((int)value); + } + + /// + /// Creates a new instance of the class with the specified . + /// + /// The name of the tag, or if tag has no name. + /// The value to assign to this tag. + public IntTag([CanBeNull] string name, int value) : base(TagType.Int, name, value) + { + } + + /// + [CLSCompliant(false)] + public IntTag([CanBeNull] Tag parent, [CanBeNull] string name, uint value) : base(TagType.Int, name, unchecked((int) value)) + { + } + + /// + public override string ToString() => $"TAG_Int({PrettyName}): {Value}"; + + /// + /// Implicit conversion of this tag to a . + /// + /// The tag to convert. + /// The tag represented as a . + public static implicit operator int(IntTag tag) => tag.Value; + + /// + /// Implicit conversion of this tag to a . + /// + /// The tag to convert. + /// The tag represented as a . + [CLSCompliant(false)] + public static implicit operator uint(IntTag tag) => unchecked((uint)tag.Value); + } +} \ No newline at end of file diff --git a/SharpNBT/JetBrains.Annotations.cs b/SharpNBT/JetBrains.Annotations.cs new file mode 100644 index 0000000..a7a2fd4 --- /dev/null +++ b/SharpNBT/JetBrains.Annotations.cs @@ -0,0 +1,1355 @@ +/* MIT License + +Copyright (c) 2016 JetBrains http://www.jetbrains.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. */ + +using System; + +// ReSharper disable UnusedType.Global + +#pragma warning disable 1591 +// ReSharper disable UnusedMember.Global +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +// ReSharper disable IntroduceOptionalParameters.Global +// ReSharper disable MemberCanBeProtected.Global +// ReSharper disable InconsistentNaming + +namespace JetBrains.Annotations +{ + /// + /// Indicates that the value of the marked element could be null sometimes, + /// so checking for null is required before its usage. + /// + /// + /// [CanBeNull] object Test() => null; + /// + /// void UseTest() { + /// var p = Test(); + /// var s = p.ToString(); // Warning: Possible 'System.NullReferenceException' + /// } + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event | + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)] +internal sealed class CanBeNullAttribute : Attribute { } + + /// + /// Indicates that the value of the marked element can never be null. + /// + /// + /// [NotNull] object Foo() { + /// return null; // Warning: Possible 'null' assignment + /// } + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event | + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)] +internal sealed class NotNullAttribute : Attribute { } + + /// + /// Can be applied to symbols of types derived from IEnumerable as well as to symbols of Task + /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property + /// or of the Lazy.Value property can never be null. + /// + /// + /// public void Foo([ItemNotNull]List<string> books) + /// { + /// foreach (var book in books) { + /// if (book != null) // Warning: Expression is always true + /// Console.WriteLine(book.ToUpper()); + /// } + /// } + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field)] +internal sealed class ItemNotNullAttribute : Attribute { } + + /// + /// Can be applied to symbols of types derived from IEnumerable as well as to symbols of Task + /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property + /// or of the Lazy.Value property can be null. + /// + /// + /// public void Foo([ItemCanBeNull]List<string> books) + /// { + /// foreach (var book in books) + /// { + /// // Warning: Possible 'System.NullReferenceException' + /// Console.WriteLine(book.ToUpper()); + /// } + /// } + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field)] +internal sealed class ItemCanBeNullAttribute : Attribute { } + + /// + /// Indicates that the marked method builds string by the format pattern and (optional) arguments. + /// The parameter, which contains the format string, should be given in the constructor. The format string + /// should be in -like form. + /// + /// + /// [StringFormatMethod("message")] + /// void ShowError(string message, params object[] args) { /* do something */ } + /// + /// void Foo() { + /// ShowError("Failed: {0}"); // Warning: Non-existing argument in format string + /// } + /// + [AttributeUsage( + AttributeTargets.Constructor | AttributeTargets.Method | + AttributeTargets.Property | AttributeTargets.Delegate)] +internal sealed class StringFormatMethodAttribute : Attribute + { + /// + /// Specifies which parameter of an annotated method should be treated as the format string + /// + public StringFormatMethodAttribute([NotNull] string formatParameterName) + { + FormatParameterName = formatParameterName; + } + + [NotNull] public string FormatParameterName { get; } + } + + /// + /// Indicates that the marked parameter is a message template where placeholders are to be replaced by the following arguments + /// in the order in which they appear + /// + /// + /// void LogInfo([StructuredMessageTemplate]string message, params object[] args) { /* do something */ } + /// + /// void Foo() { + /// LogInfo("User created: {username}"); // Warning: Non-existing argument in format string + /// } + /// + [AttributeUsage(AttributeTargets.Parameter)] +internal sealed class StructuredMessageTemplateAttribute : Attribute {} + + /// + /// Use this annotation to specify a type that contains static or const fields + /// with values for the annotated property/field/parameter. + /// The specified type will be used to improve completion suggestions. + /// + /// + /// namespace TestNamespace + /// { + /// public class Constants + /// { + /// public static int INT_CONST = 1; + /// public const string STRING_CONST = "1"; + /// } + /// + /// public class Class1 + /// { + /// [ValueProvider("TestNamespace.Constants")] public int myField; + /// public void Foo([ValueProvider("TestNamespace.Constants")] string str) { } + /// + /// public void Test() + /// { + /// Foo(/*try completion here*/);// + /// myField = /*try completion here*/ + /// } + /// } + /// } + /// + [AttributeUsage( + AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field, + AllowMultiple = true)] +internal sealed class ValueProviderAttribute : Attribute + { + public ValueProviderAttribute([NotNull] string name) + { + Name = name; + } + + [NotNull] public string Name { get; } + } + + /// + /// Indicates that the integral value falls into the specified interval. + /// It's allowed to specify multiple non-intersecting intervals. + /// Values of interval boundaries are inclusive. + /// + /// + /// void Foo([ValueRange(0, 100)] int value) { + /// if (value == -1) { // Warning: Expression is always 'false' + /// ... + /// } + /// } + /// + [AttributeUsage( + AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property | + AttributeTargets.Method | AttributeTargets.Delegate, + AllowMultiple = true)] +internal sealed class ValueRangeAttribute : Attribute + { + public object From { get; } + public object To { get; } + + public ValueRangeAttribute(long from, long to) + { + From = from; + To = to; + } + + public ValueRangeAttribute(ulong from, ulong to) + { + From = from; + To = to; + } + + public ValueRangeAttribute(long value) + { + From = To = value; + } + + public ValueRangeAttribute(ulong value) + { + From = To = value; + } + } + + /// + /// Indicates that the integral value never falls below zero. + /// + /// + /// void Foo([NonNegativeValue] int value) { + /// if (value == -1) { // Warning: Expression is always 'false' + /// ... + /// } + /// } + /// + [AttributeUsage( + AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property | + AttributeTargets.Method | AttributeTargets.Delegate)] +internal sealed class NonNegativeValueAttribute : Attribute { } + + /// + /// Indicates that the function argument should be a string literal and match one + /// of the parameters of the caller function. For example, ReSharper annotates + /// the parameter of . + /// + /// + /// void Foo(string param) { + /// if (param == null) + /// throw new ArgumentNullException("par"); // Warning: Cannot resolve symbol + /// } + /// + [AttributeUsage(AttributeTargets.Parameter)] +internal sealed class InvokerParameterNameAttribute : Attribute { } + + /// + /// Indicates that the method is contained in a type that implements + /// System.ComponentModel.INotifyPropertyChanged interface and this method + /// is used to notify that some property value changed. + /// + /// + /// The method should be non-static and conform to one of the supported signatures: + /// + /// NotifyChanged(string) + /// NotifyChanged(params string[]) + /// NotifyChanged{T}(Expression{Func{T}}) + /// NotifyChanged{T,U}(Expression{Func{T,U}}) + /// SetProperty{T}(ref T, T, string) + /// + /// + /// + /// public class Foo : INotifyPropertyChanged { + /// public event PropertyChangedEventHandler PropertyChanged; + /// + /// [NotifyPropertyChangedInvocator] + /// protected virtual void NotifyChanged(string propertyName) { ... } + /// + /// string _name; + /// + /// public string Name { + /// get { return _name; } + /// set { _name = value; NotifyChanged("LastName"); /* Warning */ } + /// } + /// } + /// + /// Examples of generated notifications: + /// + /// NotifyChanged("Property") + /// NotifyChanged(() => Property) + /// NotifyChanged((VM x) => x.Property) + /// SetProperty(ref myField, value, "Property") + /// + /// + [AttributeUsage(AttributeTargets.Method)] +internal sealed class NotifyPropertyChangedInvocatorAttribute : Attribute + { + public NotifyPropertyChangedInvocatorAttribute() { } + public NotifyPropertyChangedInvocatorAttribute([NotNull] string parameterName) + { + ParameterName = parameterName; + } + + [CanBeNull] public string ParameterName { get; } + } + + /// + /// Describes dependency between method input and output. + /// + /// + ///

Function Definition Table syntax:

+ /// + /// FDT ::= FDTRow [;FDTRow]* + /// FDTRow ::= Input => Output | Output <= Input + /// Input ::= ParameterName: Value [, Input]* + /// Output ::= [ParameterName: Value]* {halt|stop|void|nothing|Value} + /// Value ::= true | false | null | notnull | canbenull + /// + /// If the method has a single input parameter, its name could be omitted.
+ /// Using halt (or void/nothing, which is the same) for the method output + /// means that the method doesn't return normally (throws or terminates the process).
+ /// Value canbenull is only applicable for output parameters.
+ /// You can use multiple [ContractAnnotation] for each FDT row, or use single attribute + /// with rows separated by the semicolon. There is no notion of order rows, all rows are checked + /// for applicability and applied per each program state tracked by the analysis engine.
+ ///
+ /// + /// + /// [ContractAnnotation("=> halt")] + /// public void TerminationMethod() + /// + /// + /// [ContractAnnotation("null <= param:null")] // reverse condition syntax + /// public string GetName(string surname) + /// + /// + /// [ContractAnnotation("s:null => true")] + /// public bool IsNullOrEmpty(string s) // string.IsNullOrEmpty() + /// + /// + /// // A method that returns null if the parameter is null, + /// // and not null if the parameter is not null + /// [ContractAnnotation("null => null; notnull => notnull")] + /// public object Transform(object data) + /// + /// + /// [ContractAnnotation("=> true, result: notnull; => false, result: null")] + /// public bool TryParse(string s, out Person result) + /// + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] +internal sealed class ContractAnnotationAttribute : Attribute + { + public ContractAnnotationAttribute([NotNull] string contract) + : this(contract, false) { } + + public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates) + { + Contract = contract; + ForceFullStates = forceFullStates; + } + + [NotNull] public string Contract { get; } + + public bool ForceFullStates { get; } + } + + /// + /// Indicates whether the marked element should be localized. + /// + /// + /// [LocalizationRequiredAttribute(true)] + /// class Foo { + /// string str = "my string"; // Warning: Localizable string + /// } + /// + [AttributeUsage(AttributeTargets.All)] +internal sealed class LocalizationRequiredAttribute : Attribute + { + public LocalizationRequiredAttribute() : this(true) { } + + public LocalizationRequiredAttribute(bool required) + { + Required = required; + } + + public bool Required { get; } + } + + /// + /// Indicates that the value of the marked type (or its derivatives) + /// cannot be compared using '==' or '!=' operators and Equals() + /// should be used instead. However, using '==' or '!=' for comparison + /// with null is always permitted. + /// + /// + /// [CannotApplyEqualityOperator] + /// class NoEquality { } + /// + /// class UsesNoEquality { + /// void Test() { + /// var ca1 = new NoEquality(); + /// var ca2 = new NoEquality(); + /// if (ca1 != null) { // OK + /// bool condition = ca1 == ca2; // Warning + /// } + /// } + /// } + /// + [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct)] +internal sealed class CannotApplyEqualityOperatorAttribute : Attribute { } + + /// + /// When applied to a target attribute, specifies a requirement for any type marked + /// with the target attribute to implement or inherit specific type or types. + /// + /// + /// [BaseTypeRequired(typeof(IComponent)] // Specify requirement + /// class ComponentAttribute : Attribute { } + /// + /// [Component] // ComponentAttribute requires implementing IComponent interface + /// class MyComponent : IComponent { } + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + [BaseTypeRequired(typeof(Attribute))] +internal sealed class BaseTypeRequiredAttribute : Attribute + { + public BaseTypeRequiredAttribute([NotNull] Type baseType) + { + BaseType = baseType; + } + + [NotNull] public Type BaseType { get; } + } + + /// + /// Indicates that the marked symbol is used implicitly (e.g. via reflection, in external library), + /// so this symbol will be ignored by usage-checking inspections.
+ /// You can use and + /// to configure how this attribute is applied. + ///
+ /// + /// [UsedImplicitly] + /// public class TypeConverter {} + /// + /// public class SummaryData + /// { + /// [UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)] + /// public SummaryData() {} + /// } + /// + /// [UsedImplicitly(ImplicitUseTargetFlags.WithInheritors | ImplicitUseTargetFlags.Default)] + /// public interface IService {} + /// + [AttributeUsage(AttributeTargets.All)] +internal sealed class UsedImplicitlyAttribute : Attribute + { + public UsedImplicitlyAttribute() + : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } + + public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags) + : this(useKindFlags, ImplicitUseTargetFlags.Default) { } + + public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) + : this(ImplicitUseKindFlags.Default, targetFlags) { } + + public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) + { + UseKindFlags = useKindFlags; + TargetFlags = targetFlags; + } + + public ImplicitUseKindFlags UseKindFlags { get; } + + public ImplicitUseTargetFlags TargetFlags { get; } + } + + /// + /// Can be applied to attributes, type parameters, and parameters of a type assignable from . + /// When applied to an attribute, the decorated attribute behaves the same as . + /// When applied to a type parameter or to a parameter of type , + /// indicates that the corresponding type is used implicitly. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.GenericParameter | AttributeTargets.Parameter)] +internal sealed class MeansImplicitUseAttribute : Attribute + { + public MeansImplicitUseAttribute() + : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } + + public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags) + : this(useKindFlags, ImplicitUseTargetFlags.Default) { } + + public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags) + : this(ImplicitUseKindFlags.Default, targetFlags) { } + + public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) + { + UseKindFlags = useKindFlags; + TargetFlags = targetFlags; + } + + [UsedImplicitly] public ImplicitUseKindFlags UseKindFlags { get; } + + [UsedImplicitly] public ImplicitUseTargetFlags TargetFlags { get; } + } + + /// + /// Specifies the details of implicitly used symbol when it is marked + /// with or . + /// + [Flags] +internal enum ImplicitUseKindFlags + { + Default = Access | Assign | InstantiatedWithFixedConstructorSignature, + /// Only entity marked with attribute considered used. + Access = 1, + /// Indicates implicit assignment to a member. + Assign = 2, + /// + /// Indicates implicit instantiation of a type with fixed constructor signature. + /// That means any unused constructor parameters won't be reported as such. + /// + InstantiatedWithFixedConstructorSignature = 4, + /// Indicates implicit instantiation of a type. + InstantiatedNoFixedConstructorSignature = 8, + } + + /// + /// Specifies what is considered to be used implicitly when marked + /// with or . + /// + [Flags] +internal enum ImplicitUseTargetFlags + { + Default = Itself, + Itself = 1, + /// Members of the type marked with the attribute are considered used. + Members = 2, + /// Inherited entities are considered used. + WithInheritors = 4, + /// Entity marked with the attribute and all its members considered used. + WithMembers = Itself | Members + } + + /// + /// This attribute is intended to mark publicly available API, + /// which should not be removed and so is treated as used. + /// + [MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)] + [AttributeUsage(AttributeTargets.All, Inherited = false)] +internal sealed class PublicAPIAttribute : Attribute + { + public PublicAPIAttribute() { } + + public PublicAPIAttribute([NotNull] string comment) + { + Comment = comment; + } + + [CanBeNull] public string Comment { get; } + } + + /// + /// Tells the code analysis engine if the parameter is completely handled when the invoked method is on stack. + /// If the parameter is a delegate, indicates that delegate is executed while the method is executed. + /// If the parameter is an enumerable, indicates that it is enumerated while the method is executed. + /// + [AttributeUsage(AttributeTargets.Parameter)] +internal sealed class InstantHandleAttribute : Attribute { } + + /// + /// Indicates that a method does not make any observable state changes. + /// The same as System.Diagnostics.Contracts.PureAttribute. + /// + /// + /// [Pure] int Multiply(int x, int y) => x * y; + /// + /// void M() { + /// Multiply(123, 42); // Warning: Return value of pure method is not used + /// } + /// + [AttributeUsage(AttributeTargets.Method)] +internal sealed class PureAttribute : Attribute { } + + /// + /// Indicates that the return value of the method invocation must be used. + /// + /// + /// Methods decorated with this attribute (in contrast to pure methods) might change state, + /// but make no sense without using their return value.
+ /// Similarly to , this attribute + /// will help to detect usages of the method when the return value is not used. + /// Optionally, you can specify a message to use when showing warnings, e.g. + /// [MustUseReturnValue("Use the return value to...")]. + ///
+ [AttributeUsage(AttributeTargets.Method)] +internal sealed class MustUseReturnValueAttribute : Attribute + { + public MustUseReturnValueAttribute() { } + + public MustUseReturnValueAttribute([NotNull] string justification) + { + Justification = justification; + } + + [CanBeNull] public string Justification { get; } + } + + /// + /// This annotation allows to enforce allocation-less usage patterns of delegates for performance-critical APIs. + /// When this annotation is applied to the parameter of delegate type, IDE checks the input argument of this parameter: + /// * When lambda expression or anonymous method is passed as an argument, IDE verifies that the passed closure + /// has no captures of the containing local variables and the compiler is able to cache the delegate instance + /// to avoid heap allocations. Otherwise the warning is produced. + /// * IDE warns when method name or local function name is passed as an argument as this always results + /// in heap allocation of the delegate instance. + /// + /// + /// In C# 9.0 code IDE would also suggest to annotate the anonymous function with 'static' modifier + /// to make use of the similar analysis provided by the language/compiler. + /// + [AttributeUsage(AttributeTargets.Parameter)] +internal class RequireStaticDelegateAttribute : Attribute + { + public bool IsError { get; set; } + } + + /// + /// Indicates the type member or parameter of some type, that should be used instead of all other ways + /// to get the value of that type. This annotation is useful when you have some "context" value evaluated + /// and stored somewhere, meaning that all other ways to get this value must be consolidated with existing one. + /// + /// + /// class Foo { + /// [ProvidesContext] IBarService _barService = ...; + /// + /// void ProcessNode(INode node) { + /// DoSomething(node, node.GetGlobalServices().Bar); + /// // ^ Warning: use value of '_barService' field + /// } + /// } + /// + [AttributeUsage( + AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.Method | + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.GenericParameter)] +internal sealed class ProvidesContextAttribute : Attribute { } + + /// + /// Indicates that a parameter is a path to a file or a folder within a web project. + /// Path can be relative or absolute, starting from web root (~). + /// + [AttributeUsage(AttributeTargets.Parameter)] +internal sealed class PathReferenceAttribute : Attribute + { + public PathReferenceAttribute() { } + + public PathReferenceAttribute([NotNull, PathReference] string basePath) + { + BasePath = basePath; + } + + [CanBeNull] public string BasePath { get; } + } + + /// + /// An extension method marked with this attribute is processed by code completion + /// as a 'Source Template'. When the extension method is completed over some expression, its source code + /// is automatically expanded like a template at call site. + /// + /// + /// Template method body can contain valid source code and/or special comments starting with '$'. + /// Text inside these comments is added as source code when the template is applied. Template parameters + /// can be used either as additional method parameters or as identifiers wrapped in two '$' signs. + /// Use the attribute to specify macros for parameters. + /// + /// + /// In this example, the 'forEach' method is a source template available over all values + /// of enumerable types, producing ordinary C# 'foreach' statement and placing caret inside block: + /// + /// [SourceTemplate] + /// public static void forEach<T>(this IEnumerable<T> xs) { + /// foreach (var x in xs) { + /// //$ $END$ + /// } + /// } + /// + /// + [AttributeUsage(AttributeTargets.Method)] +internal sealed class SourceTemplateAttribute : Attribute { } + + /// + /// Allows specifying a macro for a parameter of a source template. + /// + /// + /// You can apply the attribute on the whole method or on any of its additional parameters. The macro expression + /// is defined in the property. When applied on a method, the target + /// template parameter is defined in the property. To apply the macro silently + /// for the parameter, set the property value = -1. + /// + /// + /// Applying the attribute on a source template method: + /// + /// [SourceTemplate, Macro(Target = "item", Expression = "suggestVariableName()")] + /// public static void forEach<T>(this IEnumerable<T> collection) { + /// foreach (var item in collection) { + /// //$ $END$ + /// } + /// } + /// + /// Applying the attribute on a template method parameter: + /// + /// [SourceTemplate] + /// public static void something(this Entity x, [Macro(Expression = "guid()", Editable = -1)] string newguid) { + /// /*$ var $x$Id = "$newguid$" + x.ToString(); + /// x.DoSomething($x$Id); */ + /// } + /// + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method, AllowMultiple = true)] +internal sealed class MacroAttribute : Attribute + { + /// + /// Allows specifying a macro that will be executed for a source template + /// parameter when the template is expanded. + /// + [CanBeNull] public string Expression { get; set; } + + /// + /// Allows specifying which occurrence of the target parameter becomes editable when the template is deployed. + /// + /// + /// If the target parameter is used several times in the template, only one occurrence becomes editable; + /// other occurrences are changed synchronously. To specify the zero-based index of the editable occurrence, + /// use values >= 0. To make the parameter non-editable when the template is expanded, use -1. + /// + public int Editable { get; set; } + + /// + /// Identifies the target parameter of a source template if the + /// is applied on a template method. + /// + [CanBeNull] public string Target { get; set; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] +internal sealed class AspMvcAreaMasterLocationFormatAttribute : Attribute + { + public AspMvcAreaMasterLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] public string Format { get; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] +internal sealed class AspMvcAreaPartialViewLocationFormatAttribute : Attribute + { + public AspMvcAreaPartialViewLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] public string Format { get; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] +internal sealed class AspMvcAreaViewLocationFormatAttribute : Attribute + { + public AspMvcAreaViewLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] public string Format { get; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] +internal sealed class AspMvcMasterLocationFormatAttribute : Attribute + { + public AspMvcMasterLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] public string Format { get; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] +internal sealed class AspMvcPartialViewLocationFormatAttribute : Attribute + { + public AspMvcPartialViewLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] public string Format { get; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] +internal sealed class AspMvcViewLocationFormatAttribute : Attribute + { + public AspMvcViewLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] public string Format { get; } + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC action. If applied to a method, the MVC action name is calculated + /// implicitly from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] +internal sealed class AspMvcActionAttribute : Attribute + { + public AspMvcActionAttribute() { } + + public AspMvcActionAttribute([NotNull] string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + [CanBeNull] public string AnonymousProperty { get; } + } + + /// + /// ASP.NET MVC attribute. Indicates that the marked parameter is an MVC area. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] +internal sealed class AspMvcAreaAttribute : Attribute + { + public AspMvcAreaAttribute() { } + + public AspMvcAreaAttribute([NotNull] string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + [CanBeNull] public string AnonymousProperty { get; } + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is + /// an MVC controller. If applied to a method, the MVC controller name is calculated + /// implicitly from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] +internal sealed class AspMvcControllerAttribute : Attribute + { + public AspMvcControllerAttribute() { } + + public AspMvcControllerAttribute([NotNull] string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + [CanBeNull] public string AnonymousProperty { get; } + } + + /// + /// ASP.NET MVC attribute. Indicates that the marked parameter is an MVC Master. Use this attribute + /// for custom wrappers similar to System.Web.Mvc.Controller.View(String, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] +internal sealed class AspMvcMasterAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that the marked parameter is an MVC model type. Use this attribute + /// for custom wrappers similar to System.Web.Mvc.Controller.View(String, Object). + /// + [AttributeUsage(AttributeTargets.Parameter)] +internal sealed class AspMvcModelTypeAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is an MVC + /// partial view. If applied to a method, the MVC partial view name is calculated implicitly + /// from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] +internal sealed class AspMvcPartialViewAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Allows disabling inspections for MVC views within a class or a method. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] +internal sealed class AspMvcSuppressViewErrorAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC display template. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.DisplayExtensions.DisplayForModel(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] +internal sealed class AspMvcDisplayTemplateAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that the marked parameter is an MVC editor template. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.EditorExtensions.EditorForModel(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] +internal sealed class AspMvcEditorTemplateAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that the marked parameter is an MVC template. + /// Use this attribute for custom wrappers similar to + /// System.ComponentModel.DataAnnotations.UIHintAttribute(System.String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] +internal sealed class AspMvcTemplateAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view component. If applied to a method, the MVC view name is calculated implicitly + /// from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Controller.View(Object). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] +internal sealed class AspMvcViewAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view component name. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] +internal sealed class AspMvcViewComponentAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view component view. If applied to a method, the MVC view component view name is default. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] +internal sealed class AspMvcViewComponentViewAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. When applied to a parameter of an attribute, + /// indicates that this parameter is an MVC action name. + /// + /// + /// [ActionName("Foo")] + /// public ActionResult Login(string returnUrl) { + /// ViewBag.ReturnUrl = Url.Action("Foo"); // OK + /// return RedirectToAction("Bar"); // Error: Cannot resolve action + /// } + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] +internal sealed class AspMvcActionSelectorAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field)] +internal sealed class HtmlElementAttributesAttribute : Attribute + { + public HtmlElementAttributesAttribute() { } + + public HtmlElementAttributesAttribute([NotNull] string name) + { + Name = name; + } + + [CanBeNull] public string Name { get; } + } + + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] +internal sealed class HtmlAttributeValueAttribute : Attribute + { + public HtmlAttributeValueAttribute([NotNull] string name) + { + Name = name; + } + + [NotNull] public string Name { get; } + } + + /// + /// Razor attribute. Indicates that the marked parameter or method is a Razor section. + /// Use this attribute for custom wrappers similar to + /// System.Web.WebPages.WebPageBase.RenderSection(String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] +internal sealed class RazorSectionAttribute : Attribute { } + + /// + /// Indicates how method, constructor invocation, or property access + /// over collection type affects the contents of the collection. + /// Use to specify the access type. + /// + /// + /// Using this attribute only makes sense if all collection methods are marked with this attribute. + /// + /// + /// public class MyStringCollection : List<string> + /// { + /// [CollectionAccess(CollectionAccessType.Read)] + /// public string GetFirstString() + /// { + /// return this.ElementAt(0); + /// } + /// } + /// class Test + /// { + /// public void Foo() + /// { + /// // Warning: Contents of the collection is never updated + /// var col = new MyStringCollection(); + /// string x = col.GetFirstString(); + /// } + /// } + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property)] +internal sealed class CollectionAccessAttribute : Attribute + { + public CollectionAccessAttribute(CollectionAccessType collectionAccessType) + { + CollectionAccessType = collectionAccessType; + } + + public CollectionAccessType CollectionAccessType { get; } + } + + /// + /// Provides a value for the to define + /// how the collection method invocation affects the contents of the collection. + /// + [Flags] +internal enum CollectionAccessType + { + /// Method does not use or modify content of the collection. + None = 0, + /// Method only reads content of the collection but does not modify it. + Read = 1, + /// Method can change content of the collection but does not add new elements. + ModifyExistingContent = 2, + /// Method can add new elements to the collection. + UpdatedContent = ModifyExistingContent | 4 + } + + /// + /// Indicates that the marked method is assertion method, i.e. it halts the control flow if + /// one of the conditions is satisfied. To set the condition, mark one of the parameters with + /// attribute. + /// + [AttributeUsage(AttributeTargets.Method)] +internal sealed class AssertionMethodAttribute : Attribute { } + + /// + /// Indicates the condition parameter of the assertion method. The method itself should be + /// marked by attribute. The mandatory argument of + /// the attribute is the assertion type. + /// + [AttributeUsage(AttributeTargets.Parameter)] +internal sealed class AssertionConditionAttribute : Attribute + { + public AssertionConditionAttribute(AssertionConditionType conditionType) + { + ConditionType = conditionType; + } + + public AssertionConditionType ConditionType { get; } + } + + /// + /// Specifies assertion type. If the assertion method argument satisfies the condition, + /// then the execution continues. Otherwise, execution is assumed to be halted. + /// +internal enum AssertionConditionType + { + /// Marked parameter should be evaluated to true. + IS_TRUE = 0, + /// Marked parameter should be evaluated to false. + IS_FALSE = 1, + /// Marked parameter should be evaluated to null value. + IS_NULL = 2, + /// Marked parameter should be evaluated to not null value. + IS_NOT_NULL = 3, + } + + /// + /// Indicates that the marked method unconditionally terminates control flow execution. + /// For example, it could unconditionally throw exception. + /// + [Obsolete("Use [ContractAnnotation('=> halt')] instead")] + [AttributeUsage(AttributeTargets.Method)] +internal sealed class TerminatesProgramAttribute : Attribute { } + + /// + /// Indicates that the method is a pure LINQ method, with postponed enumeration (like Enumerable.Select, + /// .Where). This annotation allows inference of [InstantHandle] annotation for parameters + /// of delegate type by analyzing LINQ method chains. + /// + [AttributeUsage(AttributeTargets.Method)] +internal sealed class LinqTunnelAttribute : Attribute { } + + /// + /// Indicates that IEnumerable passed as a parameter is not enumerated. + /// Use this annotation to suppress the 'Possible multiple enumeration of IEnumerable' inspection. + /// + /// + /// static void ThrowIfNull<T>([NoEnumeration] T v, string n) where T : class + /// { + /// // custom check for null but no enumeration + /// } + /// + /// void Foo(IEnumerable<string> values) + /// { + /// ThrowIfNull(values, nameof(values)); + /// var x = values.ToList(); // No warnings about multiple enumeration + /// } + /// + [AttributeUsage(AttributeTargets.Parameter)] +internal sealed class NoEnumerationAttribute : Attribute { } + + /// + /// Indicates that the marked parameter, field, or property is a regular expression pattern. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] +internal sealed class RegexPatternAttribute : Attribute { } + + /// + /// Prevents the Member Reordering feature from tossing members of the marked class. + /// + /// + /// The attribute must be mentioned in your member reordering patterns. + /// + [AttributeUsage( + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.Enum)] +internal sealed class NoReorderAttribute : Attribute { } + + /// + /// XAML attribute. Indicates the type that has ItemsSource property and should be treated + /// as ItemsControl-derived type, to enable inner items DataContext type resolve. + /// + [AttributeUsage(AttributeTargets.Class)] +internal sealed class XamlItemsControlAttribute : Attribute { } + + /// + /// XAML attribute. Indicates the property of some BindingBase-derived type, that + /// is used to bind some item of ItemsControl-derived type. This annotation will + /// enable the DataContext type resolve for XAML bindings for such properties. + /// + /// + /// Property should have the tree ancestor of the ItemsControl type or + /// marked with the attribute. + /// + [AttributeUsage(AttributeTargets.Property)] +internal sealed class XamlItemBindingOfItemsControlAttribute : Attribute { } + + /// + /// XAML attribute. Indicates the property of some Style-derived type, that + /// is used to style items of ItemsControl-derived type. This annotation will + /// enable the DataContext type resolve for XAML bindings for such properties. + /// + /// + /// Property should have the tree ancestor of the ItemsControl type or + /// marked with the attribute. + /// + [AttributeUsage(AttributeTargets.Property)] +internal sealed class XamlItemStyleOfItemsControlAttribute : Attribute { } + + /// + /// XAML attribute. Indicates that DependencyProperty has OneWay binding mode by default. + /// + /// + /// This attribute must be applied to DependencyProperty's CLR accessor property if it is present, to DependencyProperty descriptor field otherwise. + /// + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] +internal sealed class XamlOneWayBindingModeByDefaultAttribute : Attribute { } + + /// + /// XAML attribute. Indicates that DependencyProperty has TwoWay binding mode by default. + /// + /// + /// This attribute must be applied to DependencyProperty's CLR accessor property if it is present, to DependencyProperty descriptor field otherwise. + /// + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] +internal sealed class XamlTwoWayBindingModeByDefaultAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] +internal sealed class AspChildControlTypeAttribute : Attribute + { + public AspChildControlTypeAttribute([NotNull] string tagName, [NotNull] Type controlType) + { + TagName = tagName; + ControlType = controlType; + } + + [NotNull] public string TagName { get; } + + [NotNull] public Type ControlType { get; } + } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] +internal sealed class AspDataFieldAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] +internal sealed class AspDataFieldsAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Property)] +internal sealed class AspMethodPropertyAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] +internal sealed class AspRequiredAttributeAttribute : Attribute + { + public AspRequiredAttributeAttribute([NotNull] string attribute) + { + Attribute = attribute; + } + + [NotNull] public string Attribute { get; } + } + + [AttributeUsage(AttributeTargets.Property)] +internal sealed class AspTypePropertyAttribute : Attribute + { + public bool CreateConstructorReferences { get; } + + public AspTypePropertyAttribute(bool createConstructorReferences) + { + CreateConstructorReferences = createConstructorReferences; + } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] +internal sealed class RazorImportNamespaceAttribute : Attribute + { + public RazorImportNamespaceAttribute([NotNull] string name) + { + Name = name; + } + + [NotNull] public string Name { get; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] +internal sealed class RazorInjectionAttribute : Attribute + { + public RazorInjectionAttribute([NotNull] string type, [NotNull] string fieldName) + { + Type = type; + FieldName = fieldName; + } + + [NotNull] public string Type { get; } + + [NotNull] public string FieldName { get; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] +internal sealed class RazorDirectiveAttribute : Attribute + { + public RazorDirectiveAttribute([NotNull] string directive) + { + Directive = directive; + } + + [NotNull] public string Directive { get; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] +internal sealed class RazorPageBaseTypeAttribute : Attribute + { + public RazorPageBaseTypeAttribute([NotNull] string baseType) + { + BaseType = baseType; + } + public RazorPageBaseTypeAttribute([NotNull] string baseType, string pageName) + { + BaseType = baseType; + PageName = pageName; + } + + [NotNull] public string BaseType { get; } + [CanBeNull] public string PageName { get; } + } + + [AttributeUsage(AttributeTargets.Method)] +internal sealed class RazorHelperCommonAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Property)] +internal sealed class RazorLayoutAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Method)] +internal sealed class RazorWriteLiteralMethodAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Method)] +internal sealed class RazorWriteMethodAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Parameter)] +internal sealed class RazorWriteMethodParameterAttribute : Attribute { } + + /// + /// Indicates that the marked parameter, field, or property is a route template. + /// + /// + /// This attribute allows IDE to recognize the use of web frameworks' route templates + /// to enable syntax highlighting, code completion, navigation, rename and other features in string literals. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] +internal sealed class RouteTemplateAttribute : Attribute { } + + /// + /// Indicates that the marked type is custom route parameter constraint, + /// which is registered in application's Startup with name ConstraintName + /// + /// + /// You can specify ProposedType if target constraint matches only route parameters of specific type, + /// it will allow IDE to create method's parameter from usage in route template + /// with specified type instead of default System.String + /// and check if constraint's proposed type conflicts with matched parameter's type + /// + [AttributeUsage(AttributeTargets.Class)] +internal sealed class RouteParameterConstraintAttribute : Attribute + { + [NotNull] public string ConstraintName { get; } + [CanBeNull] public Type ProposedType { get; set; } + + public RouteParameterConstraintAttribute([NotNull] string constraintName) + { + ConstraintName = constraintName; + } + } + + /// + /// Indicates that the marked parameter, field, or property is an URI string. + /// + /// + /// This attribute enables code completion, navigation, rename and other features + /// in URI string literals assigned to annotated parameter, field or property. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] +internal sealed class UriStringAttribute : Attribute + { + public UriStringAttribute() { } + + public UriStringAttribute(string httpVerb) + { + HttpVerb = httpVerb; + } + + [CanBeNull] public string HttpVerb { get; } + } +} diff --git a/SharpNBT/ListTag.cs b/SharpNBT/ListTag.cs new file mode 100644 index 0000000..d9e782d --- /dev/null +++ b/SharpNBT/ListTag.cs @@ -0,0 +1,76 @@ +using System.Collections.Generic; +using System.Text; +using JetBrains.Annotations; + +namespace SharpNBT +{ + /// + /// Represents a collection of a tags. + /// + /// + /// All child tags must be have the same value, and their value will be omitted during serialization. + /// + [PublicAPI] + public class ListTag : EnumerableTag + { + /// + /// Gets the NBT type of this tag's children. + /// + public TagType ChildType { get; } + + /// + /// 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. + public ListTag([CanBeNull] string name, TagType childType) : base(TagType.List, name) + { + ChildType = childType; + } + + /// + /// Creates a new instance of the with the specified . + /// + /// The name of the tag, or if tag has no name. + /// A constant describing the NBT type for children in this tag. + /// A collection of values to include in this tag. + public ListTag([CanBeNull] string name, TagType childType, [NotNull][ItemNotNull] IEnumerable children) : base(TagType.List, name) + { + ChildType = childType; + AddRange(children); + } + + /// + 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 + "}"); + } + + /// + /// Retrieves a "pretty-printed" multiline string representing the complete tree structure of the tag. + /// + /// The prefix that will be applied to each indent-level of nested nodes in the tree structure. + /// The pretty-printed string. + [NotNull] + public string PrettyPrinted([NotNull] string indent = " ") + { + var buffer = new StringBuilder(); + PrettyPrinted(buffer, 0, indent); + return buffer.ToString(); + } + } +} \ No newline at end of file diff --git a/SharpNBT/LongArrayTag.cs b/SharpNBT/LongArrayTag.cs new file mode 100644 index 0000000..4dcf26e --- /dev/null +++ b/SharpNBT/LongArrayTag.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace SharpNBT +{ + /// + /// A tag that whose value is a contiguous sequence of 64-bit integers. + /// + [PublicAPI] + public class LongArrayTag : EnumerableTag + { + /// + /// Initializes a new instance of the . + /// + /// The name of the tag, or if tag has no name. + public LongArrayTag([CanBeNull] string name) : base(TagType.LongArray, name) + { + } + + /// + /// Initializes a new instance of the with the specified . + /// + /// The name of the tag, or if tag has no name. + /// A collection of values to include in this tag. + public LongArrayTag([CanBeNull] string name, [NotNull] IEnumerable values) : base(TagType.LongArray, name, values) + { + } + + /// + public override string ToString() + { + var word = Count == 1 ? "element" : "elements"; + return $"TAG_Long_Array({PrettyName}): [{Count} {word}]"; + } + } +} \ No newline at end of file diff --git a/SharpNBT/LongTag.cs b/SharpNBT/LongTag.cs new file mode 100644 index 0000000..055c06b --- /dev/null +++ b/SharpNBT/LongTag.cs @@ -0,0 +1,58 @@ +using System; +using JetBrains.Annotations; + +namespace SharpNBT +{ + /// + /// A tag that contains a single 64-bit integer value. + /// + [PublicAPI] + public class LongTag : Tag + { + /// + /// Gets or sets the value of this tag as an unsigned value. + /// + /// + /// This is only a reinterpretation of the bytes, no actual conversion is performed. + /// + [CLSCompliant(false)] + public ulong UnsignedValue + { + get => unchecked((ulong)Value); + set => Value = unchecked((long)value); + } + + /// + /// Creates a new instance of the class with the specified . + /// + /// The name of the tag, or if tag has no name. + /// The value to assign to this tag. + public LongTag([CanBeNull] string name, long value) : base(TagType.Long, name, value) + { + } + + /// + [CLSCompliant(false)] + public LongTag([CanBeNull] string name, ulong value) : base(TagType.Long, name, unchecked((long) value)) + { + } + + /// + public override string ToString() => $"TAG_Long({PrettyName}): {Value}"; + + /// + /// Implicit conversion of this tag to a . + /// + /// The tag to convert. + /// The tag represented as a . + public static implicit operator long(LongTag tag) => tag.Value; + + /// + /// Implicit conversion of this tag to a . + /// + /// The tag to convert. + /// The tag represented as a . + [CLSCompliant(false)] + public static implicit operator ulong(LongTag tag) => unchecked((ulong)tag.Value); + } +} \ No newline at end of file diff --git a/SharpNBT/NbtStream.Read.cs b/SharpNBT/NbtStream.Read.cs new file mode 100644 index 0000000..5d30e47 --- /dev/null +++ b/SharpNBT/NbtStream.Read.cs @@ -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 + { + /// + /// Reads a from the stream. + /// + /// It is assumed that the stream is positioned at the beginning of the tag payload. + /// The deserialized instance. + public new ByteTag ReadByte() + { + var name = named ? ReadPrefixedString() : null; + return new ByteTag(name, (byte)BaseStream.ReadByte()); + } + + /// + /// Reads a from the stream. + /// + /// It is assumed that the stream is positioned at the beginning of the tag payload. + /// The deserialized instance. + public ShortTag ReadShort() + { + var name = named ? ReadPrefixedString() : null; + return new ShortTag(name, BitConverter.ToInt16(ReadNumber(sizeof(short)))); + } + + /// + /// Reads a from the stream. + /// + /// It is assumed that the stream is positioned at the beginning of the tag payload. + /// The deserialized instance. + public IntTag ReadInt() + { + var name = named ? ReadPrefixedString() : null; + return new IntTag(name, BitConverter.ToInt32(ReadNumber(sizeof(int)))); + } + + /// + /// Reads a from the stream. + /// + /// It is assumed that the stream is positioned at the beginning of the tag payload. + /// The deserialized instance. + public LongTag ReadLong() + { + var name = named ? ReadPrefixedString() : null; + return new LongTag(name, BitConverter.ToInt64(ReadNumber(sizeof(long)))); + } + + /// + /// Reads a from the stream. + /// + /// It is assumed that the stream is positioned at the beginning of the tag payload. + /// The deserialized instance. + public FloatTag ReadFloat() + { + var name = named ? ReadPrefixedString() : null; + return new FloatTag(name, BitConverter.ToSingle(ReadNumber(sizeof(float)))); + } + + /// + /// Reads a from the stream. + /// + /// It is assumed that the stream is positioned at the beginning of the tag payload. + /// The deserialized instance. + public DoubleTag ReadDouble() + { + var name = named ? ReadPrefixedString() : null; + return new DoubleTag( name, BitConverter.ToDouble(ReadNumber(sizeof(double)))); + } + + /// + /// Reads a from the stream. + /// + /// It is assumed that the stream is positioned at the beginning of the tag payload. + /// The deserialized instance. + public StringTag ReadString() + { + var name = named ? ReadPrefixedString() : null; + var value = ReadPrefixedString(); + return new StringTag(name, value); + } + + /// + /// Reads a from the stream. + /// + /// It is assumed that the stream is positioned at the beginning of the tag payload. + /// The deserialized instance. + 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); + } + + /// + /// Reads a from the stream. + /// + /// It is assumed that the stream is positioned at the beginning of the tag payload. + /// The deserialized instance. + 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); + } + + /// + /// Reads a from the stream. + /// + /// It is assumed that the stream is positioned at the beginning of the tag payload. + /// The deserialized instance. + 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); + } + + /// + /// Reads a from the stream. + /// + /// It is assumed that the stream is positioned at the beginning of the tag payload. + /// The deserialized instance. + 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; + } + + /// + /// Reads a from the stream. + /// + /// It is assumed that the stream is positioned at the beginning of the tag payload. + /// The deserialized instance. + 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; + } + + /// + /// Reads a from the current position in the stream. + /// + /// + public Tag ReadTag() + { + var type = ReadType(); + return ReadTag(type); + } + + public T ReadTag() 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; + } + } + + /// + /// Reads a length-prefixed (unsigned short) UTF-8 string from the . + /// + /// The string instance, or if a length of 0 was specified. + [CanBeNull] + protected string ReadPrefixedString() + { + var len = BitConverter.ToUInt16(ReadNumber(sizeof(ushort))); + if (len == 0) + return null; + + Span buffer = stackalloc byte[len]; + BaseStream.Read(buffer); + return Encoding.UTF8.GetString(buffer); + } + + /// + /// Reads bytes from the stream, reversing them if necessary to ensure proper endian format. + /// + /// The number of bytes to read. + /// An array of bytes that represent the number. + [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; + } + } +} \ No newline at end of file diff --git a/SharpNBT/NbtStream.Write.cs b/SharpNBT/NbtStream.Write.cs new file mode 100644 index 0000000..c0dbe43 --- /dev/null +++ b/SharpNBT/NbtStream.Write.cs @@ -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); + } + } + } +} \ No newline at end of file diff --git a/SharpNBT/NbtStream.cs b/SharpNBT/NbtStream.cs new file mode 100644 index 0000000..b95a71f --- /dev/null +++ b/SharpNBT/NbtStream.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using JetBrains.Annotations; + +namespace SharpNBT +{ + /// + /// Provides methods and properties to read and write as a stream using the NBT format specification. + /// + [PublicAPI] + public partial class NbtStream : Stream + { + protected readonly Stream BaseStream; + private readonly Stack topLevel; + + // /// + // /// Opens a on the specified with read/write access. + // /// + // /// The path to a file to open. + // /// + // /// 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. + // /// + // /// A opened in the specified mode and path, with read/write access. + // 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(); + } + + 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) + { + } + + /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device. + /// An I/O error occurs. + /// + public override void Flush() => BaseStream.Flush(); + + /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. + /// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between and ( + - 1) replaced by the bytes read from the current source. + /// The zero-based byte offset in at which to begin storing the data read from the current stream. + /// The maximum number of bytes to be read from the current stream. + /// The sum of and is larger than the buffer length. + /// + /// is . + /// + /// or is negative. + /// An I/O error occurs. + /// The stream does not support reading. + /// Methods were called after the stream was closed. + /// 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. + /// + public override int Read(byte[] buffer, int offset, int count) => BaseStream.Read(buffer, offset, count); + + /// Sets the position within the current stream. + /// A byte offset relative to the parameter. + /// A value of type indicating the reference point used to obtain the new position. + /// An I/O error occurs. + /// The stream does not support seeking, such as if the stream is constructed from a pipe or console output. + /// Methods were called after the stream was closed. + /// The new position within the current stream. + /// + public override long Seek(long offset, SeekOrigin origin) => BaseStream.Seek(offset, origin); + + /// Sets the length of the current stream. + /// The desired length of the current stream in bytes. + /// An I/O error occurs. + /// The stream does not support both writing and seeking, such as if the stream is constructed from a pipe or console output. + /// Methods were called after the stream was closed. + /// + public override void SetLength(long value) => BaseStream.SetLength(value); + + /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. + /// An array of bytes. This method copies bytes from to the current stream. + /// The zero-based byte offset in at which to begin copying bytes to the current stream. + /// The number of bytes to be written to the current stream. + /// The sum of and is greater than the buffer length. + /// + /// is . + /// + /// or is negative. + /// An I/O error occurred, such as the specified file cannot be found. + /// The stream does not support writing. + /// + /// was called after the stream was closed. + /// + public override void Write(byte[] buffer, int offset, int count) => BaseStream.Write(buffer, offset, count); + + /// Gets a value indicating whether the current stream supports reading. + /// + /// if the stream supports reading; otherwise, . + /// + public override bool CanRead => BaseStream.CanRead; + + /// Gets a value indicating whether the current stream supports seeking. + /// + /// if the stream supports seeking; otherwise, . + /// + public override bool CanSeek => BaseStream.CanSeek; + + /// Gets a value indicating whether the current stream supports writing. + /// + /// if the stream supports writing; otherwise, . + /// + public override bool CanWrite => BaseStream.CanWrite; + + /// Gets the length in bytes of the stream. + /// A class derived from does not support seeking. + /// Methods were called after the stream was closed. + /// A long value representing the length of the stream in bytes. + /// + public override long Length => BaseStream.Length; + + /// Gets or sets the position within the current stream. + /// An I/O error occurs. + /// The stream does not support seeking. + /// Methods were called after the stream was closed. + /// The current position within the stream. + /// + public override long Position + { + get => BaseStream.Position; + set => BaseStream.Position = value; + } + + } +} \ No newline at end of file diff --git a/SharpNBT/SharpNBT.csproj b/SharpNBT/SharpNBT.csproj new file mode 100644 index 0000000..284330a --- /dev/null +++ b/SharpNBT/SharpNBT.csproj @@ -0,0 +1,9 @@ + + + + netstandard2.1 + SharpNBT + ForeverZer0 + + + diff --git a/SharpNBT/ShortTag.cs b/SharpNBT/ShortTag.cs new file mode 100644 index 0000000..35a4ae0 --- /dev/null +++ b/SharpNBT/ShortTag.cs @@ -0,0 +1,58 @@ +using System; +using JetBrains.Annotations; + +namespace SharpNBT +{ + /// + /// A tag that contains a single 16-bit integer value. + /// + [PublicAPI] + public class ShortTag : Tag + { + /// + /// Gets or sets the value of this tag as an unsigned value. + /// + /// + /// This is only a reinterpretation of the bytes, no actual conversion is performed. + /// + [CLSCompliant(false)] + public ushort UnsignedValue + { + get => unchecked((ushort)Value); + set => Value = unchecked((short)value); + } + + /// + /// Creates a new instance of the class with the specified . + /// + /// The name of the tag, or if tag has no name. + /// The value to assign to this tag. + public ShortTag([CanBeNull] string name, short value) : base(TagType.Short, name, value) + { + } + + /// + [CLSCompliant(false)] + public ShortTag([CanBeNull] string name, ushort value) : base(TagType.Short, name, unchecked((short) value)) + { + } + + /// + public override string ToString() => $"TAG_Short({PrettyName}): {Value}"; + + /// + /// Implicit conversion of this tag to a . + /// + /// The tag to convert. + /// The tag represented as a . + public static implicit operator short(ShortTag tag) => tag.Value; + + /// + /// Implicit conversion of this tag to a . + /// + /// The tag to convert. + /// The tag represented as a . + [CLSCompliant(false)] + public static implicit operator ushort(ShortTag tag) => unchecked((ushort)tag.Value); + } +} \ No newline at end of file diff --git a/SharpNBT/StringTag.cs b/SharpNBT/StringTag.cs new file mode 100644 index 0000000..b207d98 --- /dev/null +++ b/SharpNBT/StringTag.cs @@ -0,0 +1,30 @@ +using JetBrains.Annotations; + +namespace SharpNBT +{ + /// + /// A tag the contains a UTF-8 string. + /// + [PublicAPI] + public class StringTag : Tag + { + /// + /// Creates a new instance of the class with the specified . + /// + /// The name of the tag, or if tag has no name. + /// The value to assign to this tag. + public StringTag([CanBeNull] string name, [CanBeNull] string value) : base(TagType.String, name, value) + { + } + + /// + public override string ToString() => $"TAG_String({PrettyName}): \"{Value}\""; + + /// + /// Implicit conversion of this tag to a . + /// + /// The tag to convert. + /// The tag represented as a . + public static implicit operator string(StringTag tag) => tag.Value; + } +} \ No newline at end of file diff --git a/SharpNBT/Tag.cs b/SharpNBT/Tag.cs new file mode 100644 index 0000000..9b27741 --- /dev/null +++ b/SharpNBT/Tag.cs @@ -0,0 +1,79 @@ +using System; +using System.Text; +using JetBrains.Annotations; + +[assembly: CLSCompliant(true)] + +namespace SharpNBT +{ + /// + /// Abstract base class that all NBT tags inherit from. + /// + [PublicAPI] + public abstract class Tag + { + /// + /// Text applied in a pretty-print sting when a tag has no defined value. + /// + protected const string NO_NAME = "None"; + + /// + /// Gets a constant describing the NBT type this object represents. + /// + public TagType Type { get; } + + /// + /// Gets the parent this object is a child of. + /// + [CanBeNull] + public Tag Parent { get; internal set; } + + /// + /// Gets the name assigned to this . + /// + [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}\""; + } + + /// + /// Abstract base class for types that contain a single primitive value. + /// + /// The type of the value the tag represents. + [PublicAPI] + public abstract class Tag : Tag + { + /// + /// Gets or sets the value of the tag. + /// + public T Value { get; set; } + + /// + /// Creates a new instance of the class with the specified . + /// + /// A constant describing the NBT type for this tag. + /// The name of the tag, or if tag has no name. + /// The value to assign to this tag. + 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()); + } + } + +} \ No newline at end of file diff --git a/SharpNBT/TagType.cs b/SharpNBT/TagType.cs new file mode 100644 index 0000000..ee3c4f5 --- /dev/null +++ b/SharpNBT/TagType.cs @@ -0,0 +1,76 @@ +using JetBrains.Annotations; + +namespace SharpNBT +{ + /// + /// Strongly-typed numerical constants that are prefixed to tags to denote their type. + /// + [PublicAPI] + public enum TagType : byte + { + /// + /// Signifies the end of a . + /// + End = 0x00, + + /// + /// A single signed byte, + /// + Byte = 0x01, + + /// + /// A single signed 16-bit integer. + /// + Short = 0x02, + + /// + /// A single signed 32-bit integer. + /// + Int = 0x03, + + /// + /// A single signed 64-bit integer. + /// + Long = 0x04, + + /// + /// A single IEEE-754 single-precision floating point number. + /// + Float = 0x05, + + /// + /// A single IEEE-754 double-precision floating point number. + /// + Double = 0x06, + + /// + /// A length-prefixed array of bytes. + /// + ByteArray = 0x07, + + /// + /// A length-prefixed UTF-8 string. + /// + String = 0x08, + + /// + /// A list of nameless tags, all of the same type. + /// + List = 0x09, + + /// + /// A set of named tags. + /// + Compound = 0x0a, + + /// + /// A length-prefixed array of signed 32-bit integers. + /// + IntArray = 0x0b, + + /// + /// A length-prefixed array of signed 64-bit integers. + /// + LongArray = 0x0c + } +} \ No newline at end of file