1
0

Initial sync

This commit is contained in:
2024-03-15 14:44:21 +01:00
commit d2a1cabe35
59 changed files with 3783 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
/.vs
/.vscode
bin/
obj/
/Nbt/Properties

31
NamedBinaryTag.sln Executable file
View File

@@ -0,0 +1,31 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.1.32228.430
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test", "Test\Test.csproj", "{B064142B-B2BD-4F0A-B37C-EF8DEF542368}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nbt", "Nbt\Nbt.csproj", "{431C4EC2-37FD-4DBD-8966-DF757431346B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B064142B-B2BD-4F0A-B37C-EF8DEF542368}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B064142B-B2BD-4F0A-B37C-EF8DEF542368}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B064142B-B2BD-4F0A-B37C-EF8DEF542368}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B064142B-B2BD-4F0A-B37C-EF8DEF542368}.Release|Any CPU.Build.0 = Release|Any CPU
{431C4EC2-37FD-4DBD-8966-DF757431346B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{431C4EC2-37FD-4DBD-8966-DF757431346B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{431C4EC2-37FD-4DBD-8966-DF757431346B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{431C4EC2-37FD-4DBD-8966-DF757431346B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0B021450-A24B-44BB-A85D-D334EE7F9BA1}
EndGlobalSection
EndGlobal

10
Nbt/Exceptions/NbtException.cs Executable file
View File

@@ -0,0 +1,10 @@
namespace Nbt;
public class NbtException : Exception
{
public NbtException() { }
public NbtException(string? message) : base(message) { }
public NbtException(string? message, Exception? innerException) : base(message, innerException) { }
}

View File

@@ -0,0 +1,22 @@
// using Nbt.Tag;
// namespace Nbt;
// public class TagNotFoundException : NbtException
// {
// public TagNotFoundException(INbtTag parentTag, INbtPathElement tagPathElement)
// {
// ParentTag = parentTag;
// TagPathElement = tagPathElement;
// }
// public TagNotFoundException(INbtTag parentTag, INbtPathElement tagPathElement, string? message) : base(message)
// {
// ParentTag = parentTag;
// TagPathElement = tagPathElement;
// }
// public INbtTag ParentTag { get; }
// public INbtPathElement TagPathElement { get; }
// }

View File

@@ -0,0 +1,16 @@
namespace Nbt;
public class UnknownCompressionSchemeException : NbtException
{
public int CompressionMode { get; }
public UnknownCompressionSchemeException(int mode)
{
CompressionMode = mode;
}
public UnknownCompressionSchemeException(int mode, string? message) : base(message)
{
CompressionMode = mode;
}
}

View File

@@ -0,0 +1,16 @@
namespace Nbt;
public class UnknownTagTypeException : NbtException
{
public UnknownTagTypeException(NbtTagType unknownTagType) : base()
{
UnknownTagType = unknownTagType;
}
public UnknownTagTypeException(NbtTagType unknownTagType, string? message) : base(message)
{
UnknownTagType = unknownTagType;
}
public NbtTagType UnknownTagType { get; }
}

View File

@@ -0,0 +1,8 @@
namespace Nbt;
public class UnsupportedPathElementException : NbtException
{
public UnsupportedPathElementException() : base() { }
public UnsupportedPathElementException(string? message) : base(message) { }
}

View File

@@ -0,0 +1,8 @@
namespace Nbt;
public class WrongTagTypeException : NbtException
{
public WrongTagTypeException() : base() { }
public WrongTagTypeException(string? message) : base(message) { }
}

19
Nbt/Nbt.csproj Executable file
View File

@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackageId>Nbt</PackageId>
<Version>1.0.0</Version>
<Authors>Hervé BECHER</Authors>
<Company>HbShare</Company>
<Product>Nbt</Product>
<Description>Nbt library</Description>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.3.7-beta" />
</ItemGroup>
</Project>

139
Nbt/NbtPath.cs Executable file
View File

@@ -0,0 +1,139 @@
using System.Collections;
namespace Nbt;
public static class NbtPath
{
public static readonly INbtPath Empty = new EmptyNbtPathImpl();
public static INbtPath CreatePath(INbtPathElement pathElement) => new NbtPathImpl([pathElement]);
public static INbtPath CreatePath(params INbtPathElement[] pathElements) => CreatePath(pathElements.AsEnumerable());
public static INbtPath CreatePath(IEnumerable<INbtPathElement> pathElements) => new NbtPathImpl(pathElements);
public static INbtPath CreatePath(params object[] pathElements) => CreatePath(pathElements.AsEnumerable());
public static INbtPath CreatePath(IEnumerable<object> pathElements) => CreatePath(pathElements.Select(WrapPathElement));
private static INbtPathElement WrapPathElement(object pathElement) => pathElement switch
{
null => throw new ArgumentNullException(nameof(pathElement)),
string name => new NbtName(name),
int index => new NbtIndex(index),
_ => throw new ArgumentException($"Invalid path element type: {pathElement.GetType().Name}", nameof(pathElement))
};
}
public interface INbtPath : IEnumerable<INbtPathElement>
{
int Count { get; }
bool IsEmpty { get; }
INbtPathElement PopFirst(out INbtPath rest);
INbtPathElement this[int index] { get; }
INbtPath Combine(INbtPath other);
INbtPath Append(INbtPathElement pathElement);
INbtPath Insert(int index, INbtPathElement pathElement);
INbtPath Replace(int index, INbtPathElement pathElement);
}
file class NbtPathImpl(IEnumerable<INbtPathElement> pathElements) : INbtPath
{
private readonly IList<INbtPathElement> _pathElements = pathElements.AsIList();
public int Count => _pathElements.Count;
public bool IsEmpty => Count == 0;
public INbtPathElement PopFirst(out INbtPath rest)
{
var count = Count;
if (count < 1)
{
throw new IndexOutOfRangeException();
}
rest = count > 1 ? new NbtPathImpl(_pathElements.Skip(1)) : NbtPath.Empty;
return _pathElements[0];
}
public INbtPathElement this[int index] => _pathElements[index];
public INbtPath Append(INbtPathElement pathElement) => new NbtPathImpl(_pathElements.Append(pathElement));
public INbtPath Insert(int index, INbtPathElement pathElement) => new NbtPathImpl(Utils.Insert(_pathElements, index, pathElement));
public INbtPath Replace(int index, INbtPathElement pathElement) => new NbtPathImpl(Utils.Replace(_pathElements, index, pathElement));
public INbtPath Combine(INbtPath other) => new NbtPathImpl(_pathElements.Concat(other));
public IEnumerator<INbtPathElement> GetEnumerator() => _pathElements.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
file class EmptyNbtPathImpl : INbtPath
{
public int Count => 0;
public bool IsEmpty => true;
public INbtPathElement PopFirst(out INbtPath rest) => throw new IndexOutOfRangeException();
public INbtPathElement this[int index] => throw new IndexOutOfRangeException();
public INbtPath Append(INbtPathElement pathElement) => new NbtPathImpl(pathElement.Yield());
public INbtPath Insert(int index, INbtPathElement pathElement)
{
ArgumentOutOfRangeException.ThrowIfNotEqual(index, 0);
return new NbtPathImpl(pathElement.Yield());
}
public INbtPath Replace(int index, INbtPathElement pathElement)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
public INbtPath Combine(INbtPath other) => other;
public IEnumerator<INbtPathElement> GetEnumerator() => Enumerable.Empty<INbtPathElement>().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
public interface INbtPathElement
{
object Key { get; }
}
public readonly struct NbtName(string name) : INbtPathElement
{
public string Name { get; } = name;
object INbtPathElement.Key => Name;
public static implicit operator NbtName(string tagName) => new(tagName);
public static implicit operator string(NbtName nbtName) => nbtName.Name;
}
public readonly struct NbtIndex(int index) : INbtPathElement
{
public int Index { get; } = index;
object INbtPathElement.Key => Index;
public static implicit operator NbtIndex(int tagIndex) => new(tagIndex);
public static implicit operator int(NbtIndex nbtIndex) => nbtIndex.Index;
}

19
Nbt/NbtTagType.cs Executable file
View File

@@ -0,0 +1,19 @@
namespace Nbt;
public enum NbtTagType : byte
{
Unknown = 0xFF,
End = 0x0,
Byte = 0x1,
Short = 0x2,
Int = 0x3,
Long = 0x4,
Float = 0x5,
Double = 0x6,
ByteArray = 0x7,
String = 0x8,
List = 0x9,
Compound = 0xA,
IntArray = 0xB,
LongArray = 0xC
}

160
Nbt/NbtUtils.cs Executable file
View File

@@ -0,0 +1,160 @@
using System.Collections;
using System.Text;
using Nbt.Type;
namespace Nbt;
internal static class NbtUtils
{
public const char ESCAPE_CHAR = '\\';
public const char SINGLE_QUOTE_CHAR = '\'';
public const char DOUBLE_QUOTE_CHAR = '"';
public static readonly INbtRegistry Registry = new NbtRegistry();
static NbtUtils()
{
Registry.AddAll(NbtByteType.Value, NbtShortType.Value, NbtIntType.Value, NbtLongType.Value, NbtFloatType.Value, NbtDoubleType.Value, NbtStringType.Value, NbtListType.Value, NbtCompoundType.Value, NbtEndType.Value, NbtByteArrayType.Value, NbtIntArrayType.Value, NbtLongArrayType.Value);
}
public static INbtType GetTagType(NbtTagType tagType) => Registry.TryGet(tagType, out var value) ? value : throw new UnknownTagTypeException(tagType, $"Unknown tag type: {tagType}");
public static NbtTagType GetValueTagType<T>() => GetValueTagType(typeof(T));
public static NbtTagType GetValueTagType(System.Type type) =>
type == typeof(sbyte) ? NbtTagType.Byte :
type == typeof(short) ? NbtTagType.Short :
type == typeof(int) ? NbtTagType.Int :
type == typeof(long) ? NbtTagType.Long :
type == typeof(float) ? NbtTagType.Float :
type == typeof(double) ? NbtTagType.Double :
type == typeof(string) ? NbtTagType.String :
NbtTagType.Unknown;
public static NbtTagType GetArrayTagType<T>() => GetArrayTagType(typeof(T));
public static NbtTagType GetArrayTagType(System.Type type) =>
type == typeof(sbyte) ? NbtTagType.ByteArray :
type == typeof(int) ? NbtTagType.IntArray :
type == typeof(long) ? NbtTagType.LongArray :
NbtTagType.Unknown;
public static NbtTagType EnsureValueType<T>()
{
var type = typeof(T);
var tagType = GetValueTagType(type);
return tagType is NbtTagType.Unknown ? throw new UnknownTagTypeException(tagType, $"Unknown value type: {type.Name}") : tagType;
}
public static NbtTagType EnsureArrayType<T>()
{
var type = typeof(T);
var tagType = GetArrayTagType(type);
return tagType is NbtTagType.Unknown ? throw new UnknownTagTypeException(tagType, $"Unknown array type: {type.Name}") : tagType;
}
public static NbtTagType ToArrayType(this NbtTagType tagType) => tagType switch
{
NbtTagType.Byte => NbtTagType.ByteArray,
NbtTagType.Int => NbtTagType.IntArray,
NbtTagType.Long => NbtTagType.LongArray,
_ => NbtTagType.Unknown
};
public static NbtTagType ToValueType(this NbtTagType tagType) => tagType switch
{
NbtTagType.ByteArray => NbtTagType.Byte,
NbtTagType.IntArray => NbtTagType.Int,
NbtTagType.LongArray => NbtTagType.Long,
_ => NbtTagType.Unknown
};
public static string QuoteString(string s, bool onlyIfNeeded = false)
{
if (s.Length == 0)
{
return $"{DOUBLE_QUOTE_CHAR}{DOUBLE_QUOTE_CHAR}";
}
var sb = new StringBuilder();
var needQuotes = false;
var quoteChar = '\0';
foreach (var c in s)
{
needQuotes |= QuoteString(sb, c, ref quoteChar);
}
if (!onlyIfNeeded || needQuotes)
{
quoteChar = quoteChar == DOUBLE_QUOTE_CHAR ? SINGLE_QUOTE_CHAR : DOUBLE_QUOTE_CHAR;
sb.Insert(0, quoteChar);
sb.Append(quoteChar);
}
return sb.ToString();
}
private static bool QuoteString(StringBuilder sb, char c, ref char quoteChar)
{
var needQuotes = false;
var escape = false;
switch (c)
{
case '\t':
escape = true;
c = 't';
break;
case '\r':
escape = true;
c = 'r';
break;
case '\n':
escape = true;
c = 'n';
break;
case ESCAPE_CHAR:
escape = true;
break;
case SINGLE_QUOTE_CHAR:
case DOUBLE_QUOTE_CHAR:
if (quoteChar == '\0')
{
quoteChar = c;
}
else if (c != quoteChar)
{
escape = true;
}
needQuotes = true;
break;
case '.':
case '_':
case '-':
case '+':
case >= 'a' and <= 'z' or >= 'A' and <= 'Z' or >= '0' and <= '9':
break;
default:
needQuotes = true;
break;
}
if (escape)
{
sb.Append(ESCAPE_CHAR);
}
sb.Append(c);
return needQuotes || escape;
}
}

View File

@@ -0,0 +1,11 @@
namespace Nbt.Serialization;
public enum Compression : byte
{
Auto = byte.MaxValue,
None = 0,
Uncompressed = None,
GZip,
ZLib,
LZ4
}

640
Nbt/Serialization/JsonNbt.cs Executable file
View File

@@ -0,0 +1,640 @@
using System.Text;
using System.Text.Json;
using Nbt.Tag;
namespace Nbt.Serialization;
public static class JsonNbt
{
private static readonly JsonEncodedText TypeProperty = JsonEncodedText.Encode("type");
private static readonly JsonEncodedText ValueProperty = JsonEncodedText.Encode("value");
#region Serialize lossless
public static string Serialize(INbtTag tag) => Serialize(serializer => serializer.Serialize(tag));
public static string Serialize(INbtList tag) => Serialize(serializer => serializer.Serialize(tag));
public static string Serialize(INbtCompound tag) => Serialize(serializer => serializer.Serialize(tag));
public static string Serialize(INbtArray tag) => Serialize(serializer => serializer.Serialize(tag));
public static string Serialize(INbtValue tag) => Serialize(serializer => serializer.Serialize(tag));
public static string Serialize(INbtArray<sbyte> tag) => Serialize(serializer => serializer.Serialize(tag));
public static string Serialize(INbtArray<int> tag) => Serialize(serializer => serializer.Serialize(tag));
public static string Serialize(INbtArray<long> tag) => Serialize(serializer => serializer.Serialize(tag));
public static string Serialize(INbtValue<sbyte> tag) => Serialize(serializer => serializer.Serialize(tag));
public static string Serialize(INbtValue<short> tag) => Serialize(serializer => serializer.Serialize(tag));
public static string Serialize(INbtValue<int> tag) => Serialize(serializer => serializer.Serialize(tag));
public static string Serialize(INbtValue<long> tag) => Serialize(serializer => serializer.Serialize(tag));
public static string Serialize(INbtValue<float> tag) => Serialize(serializer => serializer.Serialize(tag));
public static string Serialize(INbtValue<double> tag) => Serialize(serializer => serializer.Serialize(tag));
public static string Serialize(INbtValue<string> tag) => Serialize(serializer => serializer.Serialize(tag));
public static void Serialize(TextWriter writer, INbtTag tag) => Serialize(writer, serializer => serializer.Serialize(tag));
public static void Serialize(TextWriter writer, INbtList tag) => Serialize(writer, serializer => serializer.Serialize(tag));
public static void Serialize(TextWriter writer, INbtCompound tag) => Serialize(writer, serializer => serializer.Serialize(tag));
public static void Serialize(TextWriter writer, INbtArray tag) => Serialize(writer, serializer => serializer.Serialize(tag));
public static void Serialize(TextWriter writer, INbtValue tag) => Serialize(writer, serializer => serializer.Serialize(tag));
public static void Serialize(TextWriter writer, INbtArray<sbyte> tag) => Serialize(writer, serializer => serializer.Serialize(tag));
public static void Serialize(TextWriter writer, INbtArray<int> tag) => Serialize(writer, serializer => serializer.Serialize(tag));
public static void Serialize(TextWriter writer, INbtArray<long> tag) => Serialize(writer, serializer => serializer.Serialize(tag));
public static void Serialize(TextWriter writer, INbtValue<sbyte> tag) => Serialize(writer, serializer => serializer.Serialize(tag));
public static void Serialize(TextWriter writer, INbtValue<short> tag) => Serialize(writer, serializer => serializer.Serialize(tag));
public static void Serialize(TextWriter writer, INbtValue<int> tag) => Serialize(writer, serializer => serializer.Serialize(tag));
public static void Serialize(TextWriter writer, INbtValue<long> tag) => Serialize(writer, serializer => serializer.Serialize(tag));
public static void Serialize(TextWriter writer, INbtValue<float> tag) => Serialize(writer, serializer => serializer.Serialize(tag));
public static void Serialize(TextWriter writer, INbtValue<double> tag) => Serialize(writer, serializer => serializer.Serialize(tag));
public static void Serialize(TextWriter writer, INbtValue<string> tag) => Serialize(writer, serializer => serializer.Serialize(tag));
public static void Serialize(Stream stream, INbtTag tag) => Serialize(stream, serializer => serializer.Serialize(tag));
public static void Serialize(Stream stream, INbtList tag) => Serialize(stream, serializer => serializer.Serialize(tag));
public static void Serialize(Stream stream, INbtCompound tag) => Serialize(stream, serializer => serializer.Serialize(tag));
public static void Serialize(Stream stream, INbtArray tag) => Serialize(stream, serializer => serializer.Serialize(tag));
public static void Serialize(Stream stream, INbtValue tag) => Serialize(stream, serializer => serializer.Serialize(tag));
public static void Serialize(Stream stream, INbtArray<sbyte> tag) => Serialize(stream, serializer => serializer.Serialize(tag));
public static void Serialize(Stream stream, INbtArray<int> tag) => Serialize(stream, serializer => serializer.Serialize(tag));
public static void Serialize(Stream stream, INbtArray<long> tag) => Serialize(stream, serializer => serializer.Serialize(tag));
public static void Serialize(Stream stream, INbtValue<sbyte> tag) => Serialize(stream, serializer => serializer.Serialize(tag));
public static void Serialize(Stream stream, INbtValue<short> tag) => Serialize(stream, serializer => serializer.Serialize(tag));
public static void Serialize(Stream stream, INbtValue<int> tag) => Serialize(stream, serializer => serializer.Serialize(tag));
public static void Serialize(Stream stream, INbtValue<long> tag) => Serialize(stream, serializer => serializer.Serialize(tag));
public static void Serialize(Stream stream, INbtValue<float> tag) => Serialize(stream, serializer => serializer.Serialize(tag));
public static void Serialize(Stream stream, INbtValue<double> tag) => Serialize(stream, serializer => serializer.Serialize(tag));
public static void Serialize(Stream stream, INbtValue<string> tag) => Serialize(stream, serializer => serializer.Serialize(tag));
private static string Serialize(Action<Serializer> serialize)
{
using var writer = new StringWriter();
Serialize(writer, serialize);
return writer.ToString();
}
private static void Serialize(TextWriter writer, Action<Serializer> serialize)
{
using var stream = new MemoryStream();
Serialize(stream, serialize);
stream.Position = 0L;
using var reader = new StreamReader(stream, encoding: Encoding.UTF8, leaveOpen: true);
while (true)
{
var i = reader.Read();
if (i < 0)
{
break;
}
writer.Write((char)i);
}
}
private static void Serialize(Stream stream, Action<Serializer> serialize)
{
using var serializer = new Serializer(stream);
serialize(serializer);
}
private sealed class Serializer(Stream stream) : IDisposable
{
private readonly Utf8JsonWriter writer = new(stream, new() { Indented = true, SkipValidation = true });
public void Serialize(INbtTag tag)
{
switch (tag)
{
case INbtArray arrayTag:
Serialize(arrayTag);
break;
case INbtValue valueTag:
Serialize(valueTag);
break;
case INbtList listTag:
Serialize(listTag);
break;
case INbtCompound compoundTag:
Serialize(compoundTag);
break;
}
}
public void Serialize(INbtList tag)
{
using (new NbtObjectMeta(writer, tag.Type))
{
writer.WriteStartArray();
foreach (var entry in tag)
{
Serialize(entry);
}
writer.WriteEndArray();
}
}
public void Serialize(INbtCompound tag)
{
using (new NbtObjectMeta(writer, tag.Type))
{
writer.WriteStartObject();
foreach (var entry in tag as IEnumerable<NamedTag>)
{
writer.WritePropertyName(entry.Name);
Serialize(entry.Tag);
}
writer.WriteEndObject();
}
}
public void Serialize(INbtArray tag)
{
switch (tag)
{
case INbtArray<sbyte> byteArrayTag:
Serialize(byteArrayTag);
break;
case INbtArray<int> intArrayTag:
Serialize(intArrayTag);
break;
case INbtArray<long> longArrayTag:
Serialize(longArrayTag);
break;
}
}
public void Serialize(INbtValue valueTag)
{
switch (valueTag)
{
case INbtValue<sbyte> tag:
Serialize(tag);
break;
case INbtValue<short> tag:
Serialize(tag);
break;
case INbtValue<int> tag:
Serialize(tag);
break;
case INbtValue<long> tag:
Serialize(tag);
break;
case INbtValue<float> tag:
Serialize(tag);
break;
case INbtValue<double> tag:
Serialize(tag);
break;
case INbtValue<string> tag:
Serialize(tag);
break;
}
}
public void Serialize(INbtArray<sbyte> tag) => Serialize(tag, Serialize);
public void Serialize(INbtArray<int> tag) => Serialize(tag, Serialize);
public void Serialize(INbtArray<long> tag) => Serialize(tag, Serialize);
public void Serialize(INbtValue<sbyte> tag) => Serialize(tag, Serialize);
public void Serialize(INbtValue<short> tag) => Serialize(tag, Serialize);
public void Serialize(INbtValue<int> tag) => Serialize(tag, Serialize);
public void Serialize(INbtValue<long> tag) => Serialize(tag, Serialize);
public void Serialize(INbtValue<float> tag) => Serialize(tag, Serialize);
public void Serialize(INbtValue<double> tag) => Serialize(tag, Serialize);
public void Serialize(INbtValue<string> tag) => Serialize(tag, Serialize);
private void Serialize<T>(INbtArray<T> tag, Action<T> writeValue) where T : notnull
{
using (new NbtObjectMeta(writer, tag.Type))
{
writer.WriteStartArray();
foreach (var e in tag)
{
writeValue(e);
}
writer.WriteEndArray();
}
}
private void Serialize<T>(INbtValue<T> tag, Action<T> writeValue) where T : notnull
{
using (new NbtObjectMeta(writer, tag.Type))
{
writeValue(tag.Value);
}
}
private void Serialize(sbyte value) => writer.WriteNumberValue(value);
private void Serialize(short value) => writer.WriteNumberValue(value);
private void Serialize(int value) => writer.WriteNumberValue(value);
private void Serialize(long value) => writer.WriteNumberValue(value);
private void Serialize(float value) => writer.WriteNumberValue(value);
private void Serialize(double value) => writer.WriteNumberValue(value);
private void Serialize(string value) => writer.WriteStringValue(value);
public void Dispose()
{
writer.Dispose();
GC.SuppressFinalize(this);
}
private readonly struct NbtObjectMeta : IDisposable
{
private readonly Utf8JsonWriter writer;
public NbtObjectMeta(Utf8JsonWriter writer, NbtTagType tagType)
{
this.writer = writer;
writer.WriteStartObject();
writer.WriteNumber(TypeProperty, (int)tagType);
writer.WritePropertyName(ValueProperty);
}
public void Dispose()
{
writer.WriteEndObject();
}
}
}
#endregion
#region Serialize lossy
public static string ToJson(INbtTag tag) => ToJson(serializer => serializer.ToJson(tag));
public static string ToJson(INbtList tag) => ToJson(serializer => serializer.ToJson(tag));
public static string ToJson(INbtCompound tag) => ToJson(serializer => serializer.ToJson(tag));
public static string ToJson(INbtArray tag) => ToJson(serializer => serializer.ToJson(tag));
public static string ToJson(INbtValue tag) => ToJson(serializer => serializer.ToJson(tag));
public static string ToJson(INbtArray<sbyte> tag) => ToJson(serializer => serializer.ToJson(tag));
public static string ToJson(INbtArray<int> tag) => ToJson(serializer => serializer.ToJson(tag));
public static string ToJson(INbtArray<long> tag) => ToJson(serializer => serializer.ToJson(tag));
public static string ToJson(INbtValue<sbyte> tag) => ToJson(serializer => serializer.ToJson(tag));
public static string ToJson(INbtValue<short> tag) => ToJson(serializer => serializer.ToJson(tag));
public static string ToJson(INbtValue<int> tag) => ToJson(serializer => serializer.ToJson(tag));
public static string ToJson(INbtValue<long> tag) => ToJson(serializer => serializer.ToJson(tag));
public static string ToJson(INbtValue<float> tag) => ToJson(serializer => serializer.ToJson(tag));
public static string ToJson(INbtValue<double> tag) => ToJson(serializer => serializer.ToJson(tag));
public static string ToJson(INbtValue<string> tag) => ToJson(serializer => serializer.ToJson(tag));
public static void ToJson(TextWriter writer, INbtTag tag) => ToJson(writer, serializer => serializer.ToJson(tag));
public static void ToJson(TextWriter writer, INbtList tag) => ToJson(writer, serializer => serializer.ToJson(tag));
public static void ToJson(TextWriter writer, INbtCompound tag) => ToJson(writer, serializer => serializer.ToJson(tag));
public static void ToJson(TextWriter writer, INbtArray tag) => ToJson(writer, serializer => serializer.ToJson(tag));
public static void ToJson(TextWriter writer, INbtValue tag) => ToJson(writer, serializer => serializer.ToJson(tag));
public static void ToJson(TextWriter writer, INbtArray<sbyte> tag) => ToJson(writer, serializer => serializer.ToJson(tag));
public static void ToJson(TextWriter writer, INbtArray<int> tag) => ToJson(writer, serializer => serializer.ToJson(tag));
public static void ToJson(TextWriter writer, INbtArray<long> tag) => ToJson(writer, serializer => serializer.ToJson(tag));
public static void ToJson(TextWriter writer, INbtValue<sbyte> tag) => ToJson(writer, serializer => serializer.ToJson(tag));
public static void ToJson(TextWriter writer, INbtValue<short> tag) => ToJson(writer, serializer => serializer.ToJson(tag));
public static void ToJson(TextWriter writer, INbtValue<int> tag) => ToJson(writer, serializer => serializer.ToJson(tag));
public static void ToJson(TextWriter writer, INbtValue<long> tag) => ToJson(writer, serializer => serializer.ToJson(tag));
public static void ToJson(TextWriter writer, INbtValue<float> tag) => ToJson(writer, serializer => serializer.ToJson(tag));
public static void ToJson(TextWriter writer, INbtValue<double> tag) => ToJson(writer, serializer => serializer.ToJson(tag));
public static void ToJson(TextWriter writer, INbtValue<string> tag) => ToJson(writer, serializer => serializer.ToJson(tag));
public static void ToJson(Stream stream, INbtTag tag) => ToJson(stream, serializer => serializer.ToJson(tag));
public static void ToJson(Stream stream, INbtList tag) => ToJson(stream, serializer => serializer.ToJson(tag));
public static void ToJson(Stream stream, INbtCompound tag) => ToJson(stream, serializer => serializer.ToJson(tag));
public static void ToJson(Stream stream, INbtArray tag) => ToJson(stream, serializer => serializer.ToJson(tag));
public static void ToJson(Stream stream, INbtValue tag) => ToJson(stream, serializer => serializer.ToJson(tag));
public static void ToJson(Stream stream, INbtArray<sbyte> tag) => ToJson(stream, serializer => serializer.ToJson(tag));
public static void ToJson(Stream stream, INbtArray<int> tag) => ToJson(stream, serializer => serializer.ToJson(tag));
public static void ToJson(Stream stream, INbtArray<long> tag) => ToJson(stream, serializer => serializer.ToJson(tag));
public static void ToJson(Stream stream, INbtValue<sbyte> tag) => ToJson(stream, serializer => serializer.ToJson(tag));
public static void ToJson(Stream stream, INbtValue<short> tag) => ToJson(stream, serializer => serializer.ToJson(tag));
public static void ToJson(Stream stream, INbtValue<int> tag) => ToJson(stream, serializer => serializer.ToJson(tag));
public static void ToJson(Stream stream, INbtValue<long> tag) => ToJson(stream, serializer => serializer.ToJson(tag));
public static void ToJson(Stream stream, INbtValue<float> tag) => ToJson(stream, serializer => serializer.ToJson(tag));
public static void ToJson(Stream stream, INbtValue<double> tag) => ToJson(stream, serializer => serializer.ToJson(tag));
public static void ToJson(Stream stream, INbtValue<string> tag) => ToJson(stream, serializer => serializer.ToJson(tag));
private static string ToJson(Action<LossySerializer> serialize)
{
using var writer = new StringWriter();
ToJson(writer, serialize);
return writer.ToString();
}
private static void ToJson(TextWriter writer, Action<LossySerializer> serialize)
{
using var stream = new MemoryStream();
ToJson(stream, serialize);
stream.Position = 0L;
using var reader = new StreamReader(stream, encoding: Encoding.UTF8, leaveOpen: true);
while (true)
{
var i = reader.Read();
if (i < 0)
{
break;
}
writer.Write((char)i);
}
}
private static void ToJson(Stream stream, Action<LossySerializer> serialize)
{
using var serializer = new LossySerializer(stream);
serialize(serializer);
}
private sealed class LossySerializer(Stream stream) : IDisposable
{
private readonly Utf8JsonWriter writer = new(stream, new() { Indented = true, SkipValidation = true });
public void ToJson(INbtTag tag)
{
switch (tag)
{
case INbtArray arrayTag:
ToJson(arrayTag);
break;
case INbtValue valueTag:
ToJson(valueTag);
break;
case INbtList listTag:
ToJson(listTag);
break;
case INbtCompound compoundTag:
ToJson(compoundTag);
break;
}
}
public void ToJson(INbtList tag)
{
writer.WriteStartArray();
foreach (var entry in tag)
{
ToJson(entry);
}
writer.WriteEndArray();
}
public void ToJson(INbtCompound tag)
{
writer.WriteStartObject();
foreach (var entry in tag as IEnumerable<NamedTag>)
{
writer.WritePropertyName(entry.Name);
ToJson(entry.Tag);
}
writer.WriteEndObject();
}
public void ToJson(INbtArray tag)
{
switch (tag)
{
case INbtArray<sbyte> byteArrayTag:
ToJson(byteArrayTag);
break;
case INbtArray<int> intArrayTag:
ToJson(intArrayTag);
break;
case INbtArray<long> longArrayTag:
ToJson(longArrayTag);
break;
}
}
public void ToJson(INbtValue valueTag)
{
switch (valueTag)
{
case INbtValue<sbyte> tag:
ToJson(tag);
break;
case INbtValue<short> tag:
ToJson(tag);
break;
case INbtValue<int> tag:
ToJson(tag);
break;
case INbtValue<long> tag:
ToJson(tag);
break;
case INbtValue<float> tag:
ToJson(tag);
break;
case INbtValue<double> tag:
ToJson(tag);
break;
case INbtValue<string> tag:
ToJson(tag);
break;
}
}
public void ToJson(INbtArray<sbyte> tag) => ToJson(tag, ToJson);
public void ToJson(INbtArray<int> tag) => ToJson(tag, ToJson);
public void ToJson(INbtArray<long> tag) => ToJson(tag, ToJson);
public void ToJson(INbtValue<sbyte> tag) => ToJson(tag, ToJson);
public void ToJson(INbtValue<short> tag) => ToJson(tag, ToJson);
public void ToJson(INbtValue<int> tag) => ToJson(tag, ToJson);
public void ToJson(INbtValue<long> tag) => ToJson(tag, ToJson);
public void ToJson(INbtValue<float> tag) => ToJson(tag, ToJson);
public void ToJson(INbtValue<double> tag) => ToJson(tag, ToJson);
public void ToJson(INbtValue<string> tag) => ToJson(tag, ToJson);
private void ToJson<T>(INbtArray<T> tag, Action<T> writeValue) where T : notnull
{
writer.WriteStartArray();
foreach (var e in tag)
{
writeValue(e);
}
writer.WriteEndArray();
}
private static void ToJson<T>(INbtValue<T> tag, Action<T> writeValue) where T : notnull => writeValue(tag.Value);
private void ToJson(sbyte value) => writer.WriteNumberValue(value);
private void ToJson(short value) => writer.WriteNumberValue(value);
private void ToJson(int value) => writer.WriteNumberValue(value);
private void ToJson(long value) => writer.WriteNumberValue(value);
private void ToJson(float value) => writer.WriteNumberValue(value);
private void ToJson(double value) => writer.WriteNumberValue(value);
private void ToJson(string value) => writer.WriteStringValue(value);
public void Dispose()
{
writer.Dispose();
GC.SuppressFinalize(this);
}
}
#endregion
#region Deserialize
public static INbtTag Deserialize(string s)
{
using var reader = new StringReader(s);
return Deserialize(reader);
}
public static INbtTag Deserialize(Stream stream)
{
var doc = JsonDocument.Parse(stream);
return Deserialize(doc.RootElement);
}
public static INbtTag Deserialize(TextReader reader)
{
using var stream = new MemoryStream();
using (var writer = new StreamWriter(stream, encoding: Encoding.UTF8, leaveOpen: true))
{
while (true)
{
var i = reader.Read();
if (i < 0)
{
break;
}
writer.Write((char)i);
}
}
stream.Position = 0L;
return Deserialize(stream);
}
private static INbtTag Deserialize(JsonElement serialized)
{
var tagType = (NbtTagType)serialized.GetProperty(TypeProperty.Value).GetByte();
var value = serialized.GetProperty(ValueProperty.Value);
switch (tagType)
{
case NbtTagType.End:
throw new NbtException($"Invalid tag type: {Enum.GetName(tagType)}");
case NbtTagType.Byte:
return new NbtByte(value.GetSByte());
case NbtTagType.Short:
return new NbtShort(value.GetInt16());
case NbtTagType.Int:
return new NbtInt(value.GetInt32());
case NbtTagType.Long:
return new NbtLong(value.GetInt64());
case NbtTagType.Float:
return new NbtFloat(value.GetSingle());
case NbtTagType.Double:
return new NbtDouble(value.GetDouble());
case NbtTagType.String:
return new NbtString(value.GetString() ?? throw new NbtException("Null string value"));
case NbtTagType.ByteArray:
{
var bytes = new sbyte[value.GetArrayLength()];
var i = 0;
foreach (var element in value.EnumerateArray())
{
bytes[i++] = element.GetSByte();
}
return new NbtByteArray(bytes);
}
case NbtTagType.IntArray:
{
var ints = new int[value.GetArrayLength()];
var i = 0;
foreach (var element in value.EnumerateArray())
{
ints[i++] = element.GetInt32();
}
return new NbtIntArray(ints);
}
case NbtTagType.LongArray:
{
var longs = new long[value.GetArrayLength()];
var i = 0;
foreach (var element in value.EnumerateArray())
{
longs[i++] = element.GetInt64();
}
return new NbtLongArray(longs);
}
case NbtTagType.List:
{
var list = new NbtList();
foreach (var element in value.EnumerateArray())
{
list.Add(Deserialize(element));
}
return list;
}
case NbtTagType.Compound:
{
var compound = new NbtCompound();
foreach (var property in value.EnumerateObject())
{
compound.Add(property.Name, Deserialize(property.Value));
}
return compound;
}
default:
throw new UnknownTagTypeException(tagType);
}
}
#endregion
}

249
Nbt/Serialization/NbtReader.cs Executable file
View File

@@ -0,0 +1,249 @@
using System.Buffers;
using System.Text;
using Nbt.Tag;
namespace Nbt.Serialization;
public interface INbtReader : IDisposable
{
INbtTag ReadTag();
NamedTag ReadNamedTag();
NbtTagType ReadTagType();
sbyte ReadSByte();
short ReadShort();
int ReadInt();
long ReadLong();
float ReadFloat();
double ReadDouble();
string ReadString();
}
public sealed class NbtReader(Stream stream, bool leaveOpen = false) : INbtReader
{
private readonly Stream stream = stream;
private readonly bool leaveOpen = leaveOpen;
public Stream BaseStream => stream;
public static NbtReader Create(Stream stream, Compression compression = Compression.Auto, bool leaveOpen = false) => compression switch
{
Compression.Auto => CreateInflater(stream, leaveOpen),
Compression.GZip => new(Utils.CreateGZipInflater(stream, leaveOpen), false),
Compression.ZLib => new(Utils.CreateZLibInflater(stream, leaveOpen), false),
Compression.LZ4 => new(Utils.CreateLZ4Deflater(stream, leaveOpen), false),
_ => new(stream, leaveOpen)
};
private static NbtReader CreateInflater(Stream stream, bool leaveOpen)
{
var cmf = stream.ReadByte();
if (cmf < 0)
{
throw new EndOfStreamException();
}
stream.Seek(-1, SeekOrigin.Current);
return cmf switch
{
0x1F => new(Utils.CreateGZipInflater(stream, leaveOpen), false),
0x78 => new(Utils.CreateZLibInflater(stream, leaveOpen), false),
_ => throw new UnknownCompressionSchemeException(cmf)
};
}
private int ReadBytes(byte[] buffer, int offset, int count) => stream.Read(buffer, offset, count);
private int ReadBytes(byte[] buffer) => ReadBytes(buffer, 0, buffer.Length);
private int ReadBytes(Span<byte> buffer) => stream.Read(buffer);
private void ReadAllBytes(byte[] buffer, int offset, int count)
{
if (count == 0)
{
return;
}
var bytesLeft = count;
do
{
var bytesRead = ReadBytes(buffer, offset + count - bytesLeft, bytesLeft);
if (bytesRead == 0)
{
throw new EndOfStreamException();
}
bytesLeft -= bytesRead;
} while (bytesLeft > 0);
}
private void ReadAllBytes(byte[] buffer) => ReadAllBytes(buffer, 0, buffer.Length);
private void ReadAllBytes(Span<byte> buffer)
{
var count = buffer.Length;
if (count == 0)
{
return;
}
var bytesLeft = count;
do
{
var bytesRead = ReadBytes(buffer.Slice(count - bytesLeft, bytesLeft));
if (bytesRead == 0)
{
throw new EndOfStreamException();
}
bytesLeft -= bytesRead;
} while (bytesLeft > 0);
}
private int Read() => stream.ReadByte();
private int ReadAndThrowIfEndOfStream()
{
var b = Read();
return b < 0 ? throw new EndOfStreamException() : b;
}
private void ReadEndian(Span<byte> buffer)
{
ReadAllBytes(buffer);
if (BitConverter.IsLittleEndian)
{
buffer.Reverse();
}
}
private byte ReadByte() => (byte)ReadAndThrowIfEndOfStream();
public sbyte ReadSByte() => (sbyte)ReadAndThrowIfEndOfStream();
private ushort ReadUShort()
{
Span<byte> buffer = stackalloc byte[sizeof(ushort)];
ReadEndian(buffer);
return BitConverter.ToUInt16(buffer);
}
public short ReadShort()
{
Span<byte> buffer = stackalloc byte[sizeof(short)];
ReadEndian(buffer);
return BitConverter.ToInt16(buffer);
}
public int ReadInt()
{
Span<byte> buffer = stackalloc byte[sizeof(int)];
ReadEndian(buffer);
return BitConverter.ToInt32(buffer);
}
public long ReadLong()
{
Span<byte> buffer = stackalloc byte[sizeof(long)];
ReadEndian(buffer);
return BitConverter.ToInt64(buffer);
}
public float ReadFloat()
{
Span<byte> buffer = stackalloc byte[sizeof(float)];
ReadEndian(buffer);
return BitConverter.ToSingle(buffer);
}
public double ReadDouble()
{
Span<byte> buffer = stackalloc byte[sizeof(double)];
ReadEndian(buffer);
return BitConverter.ToDouble(buffer);
}
public string ReadString()
{
var len = ReadUShort();
if (len == 0)
{
return string.Empty;
}
var buffer = ArrayPool<byte>.Shared.Rent(len);
try
{
ReadAllBytes(buffer, 0, len);
return Encoding.UTF8.GetString(buffer, 0, len);
}
finally
{
Array.Fill(buffer, (byte)0, 0, len);
ArrayPool<byte>.Shared.Return(buffer);
}
}
public NbtTagType ReadTagType() => (NbtTagType)ReadByte();
public INbtTag ReadTag()
{
var tagType = NbtUtils.GetTagType(ReadTagType());
return tagType.Read(this);
}
public NamedTag ReadNamedTag()
{
var tagType = NbtUtils.GetTagType(ReadTagType());
var tagName = ReadString();
var tag = tagType.Read(this);
return new(tagName, tag);
}
#region IDisposable
private bool _disposedValue;
private void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
if (!leaveOpen)
{
stream.Dispose();
}
}
_disposedValue = true;
}
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
#endregion
}

185
Nbt/Serialization/NbtWriter.cs Executable file
View File

@@ -0,0 +1,185 @@
using System.Text;
using Nbt.Tag;
namespace Nbt.Serialization;
public interface INbtWriter : IDisposable
{
void WriteNamedTag(NamedTag namedTag);
void WriteTag(INbtTag tag);
void WriteTagType(NbtTagType tagType);
void WriteSByte(sbyte b);
void WriteShort(short s);
void WriteInt(int i);
void WriteLong(long l);
void WriteFloat(float f);
void WriteDouble(double d);
void WriteString(string s);
}
public sealed class NbtWriter(Stream stream, bool leaveOpen = false) : INbtWriter
{
private readonly Stream stream = stream;
private readonly bool leaveOpen = leaveOpen;
public Stream BaseStream => stream;
public static NbtWriter Create(Stream stream, Compression compression = Compression.None, bool leaveOpen = false) => compression switch
{
Compression.GZip => new(Utils.CreateGZipDeflater(stream, leaveOpen), false),
Compression.ZLib => new(Utils.CreateZLibDeflater(stream, leaveOpen), false),
Compression.LZ4 => new(Utils.CreateLZ4Deflater(stream, leaveOpen), false),
Compression.Auto => throw new ArgumentException($"{nameof(Compression.Auto)} is invalid for a writer", nameof(compression)),
_ => new(stream, leaveOpen)
};
public void Flush() => stream.Flush();
public void WriteBytes(byte[] buffer, int offset, int count) => stream.Write(buffer, offset, count);
public void WriteBytes(byte[] buffer) => stream.Write(buffer, 0, buffer.Length);
public void WriteBytes(ReadOnlySpan<byte> buffer) => stream.Write(buffer);
public void WriteByte(byte b) => stream.WriteByte(b);
private void WriteEndian(Span<byte> buffer)
{
if (BitConverter.IsLittleEndian)
{
for (var i = buffer.Length - 1; i >= 0; i--)
{
WriteByte(buffer[i]);
}
}
else
{
WriteBytes(buffer);
}
}
public void WriteSByte(sbyte b)
{
WriteByte((byte)b);
}
public void WriteUShort(ushort s)
{
Span<byte> buffer = stackalloc byte[sizeof(ushort)];
BitConverter.TryWriteBytes(buffer, s);
WriteEndian(buffer);
}
public void WriteShort(short s)
{
Span<byte> buffer = stackalloc byte[sizeof(short)];
BitConverter.TryWriteBytes(buffer, s);
WriteEndian(buffer);
}
public void WriteUInt(uint i)
{
Span<byte> buffer = stackalloc byte[sizeof(uint)];
BitConverter.TryWriteBytes(buffer, i);
WriteEndian(buffer);
}
public void WriteInt(int i)
{
Span<byte> buffer = stackalloc byte[sizeof(int)];
BitConverter.TryWriteBytes(buffer, i);
WriteEndian(buffer);
}
public void WriteULong(ulong l)
{
Span<byte> buffer = stackalloc byte[sizeof(ulong)];
BitConverter.TryWriteBytes(buffer, l);
WriteEndian(buffer);
}
public void WriteLong(long l)
{
Span<byte> buffer = stackalloc byte[sizeof(long)];
BitConverter.TryWriteBytes(buffer, l);
WriteEndian(buffer);
}
public void WriteFloat(float f)
{
Span<byte> buffer = stackalloc byte[sizeof(float)];
BitConverter.TryWriteBytes(buffer, f);
WriteEndian(buffer);
}
public void WriteDouble(double d)
{
Span<byte> buffer = stackalloc byte[sizeof(double)];
BitConverter.TryWriteBytes(buffer, d);
WriteEndian(buffer);
}
public void WriteString(string s)
{
WriteUShort((ushort)s.Length);
var buffer = Encoding.UTF8.GetBytes(s);
WriteBytes(buffer);
}
public void WriteNamedTag(NamedTag namedTag) => WriteNamedTag(namedTag.Name, namedTag.Tag);
public void WriteNamedTag(string tagName, INbtTag tag)
{
var tagType = NbtUtils.GetTagType(tag.Type);
WriteTagType(tag.Type);
WriteString(tagName);
tagType.Write(this, tag);
}
public void WriteTag(INbtTag tag)
{
var tagType = NbtUtils.GetTagType(tag.Type);
WriteTagType(tag.Type);
tagType.Write(this, tag);
}
public void WriteTagType(NbtTagType tagType) => WriteByte((byte)tagType);
#region IDisposable
private bool _disposedValue;
private void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
if (!leaveOpen)
{
stream.Dispose();
}
}
_disposedValue = true;
}
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
#endregion
}

401
Nbt/Serialization/SNbt.cs Executable file
View File

@@ -0,0 +1,401 @@
namespace Nbt.Serialization;
using System.Globalization;
using Nbt.Tag;
public static class SNbt
{
#region Serialize
public enum SerializationStyle
{
Compact, Spaced, Indented
}
public class SerializerOptions
{
public static readonly SerializerOptions Default = new();
public SerializationStyle Style { get; init; } = SerializationStyle.Compact;
public bool AlwaysQuoteTagNames { get; init; } = false;
// public bool AlwaysQuoteStringTags { get; init; } = false;
}
public static string Serialize(INbtTag tag, SerializerOptions? options = null) => Serialize(options, serializer => serializer.Serialize(tag));
public static string Serialize(NamedTag tag, SerializerOptions? options = null) => Serialize(options, serializer => serializer.Serialize(tag));
public static string Serialize(INbtList tag, SerializerOptions? options = null) => Serialize(options, serializer => serializer.Serialize(tag));
public static string Serialize(INbtCompound tag, SerializerOptions? options = null) => Serialize(options, serializer => serializer.Serialize(tag));
public static string Serialize(INbtArray tag, SerializerOptions? options = null) => Serialize(options, serializer => serializer.Serialize(tag));
public static string Serialize(INbtValue tag, SerializerOptions? options = null) => Serialize(options, serializer => serializer.Serialize(tag));
public static string Serialize(INbtArray<sbyte> tag, SerializerOptions? options = null) => Serialize(options, serializer => serializer.Serialize(tag));
public static string Serialize(INbtArray<int> tag, SerializerOptions? options = null) => Serialize(options, serializer => serializer.Serialize(tag));
public static string Serialize(INbtArray<long> tag, SerializerOptions? options = null) => Serialize(options, serializer => serializer.Serialize(tag));
public static string Serialize(INbtValue<sbyte> tag, SerializerOptions? options = null) => Serialize(options, serializer => serializer.Serialize(tag));
public static string Serialize(INbtValue<short> tag, SerializerOptions? options = null) => Serialize(options, serializer => serializer.Serialize(tag));
public static string Serialize(INbtValue<int> tag, SerializerOptions? options = null) => Serialize(options, serializer => serializer.Serialize(tag));
public static string Serialize(INbtValue<long> tag, SerializerOptions? options = null) => Serialize(options, serializer => serializer.Serialize(tag));
public static string Serialize(INbtValue<float> tag, SerializerOptions? options = null) => Serialize(options, serializer => serializer.Serialize(tag));
public static string Serialize(INbtValue<double> tag, SerializerOptions? options = null) => Serialize(options, serializer => serializer.Serialize(tag));
public static string Serialize(INbtValue<string> tag, SerializerOptions? options = null) => Serialize(options, serializer => serializer.Serialize(tag));
public static void Serialize(TextWriter writer, INbtTag tag, SerializerOptions? options = null) => Serialize(writer, options, serializer => serializer.Serialize(tag));
public static void Serialize(TextWriter writer, NamedTag tag, SerializerOptions? options = null) => Serialize(writer, options, serializer => serializer.Serialize(tag));
public static void Serialize(TextWriter writer, INbtList tag, SerializerOptions? options = null) => Serialize(writer, options, serializer => serializer.Serialize(tag));
public static void Serialize(TextWriter writer, INbtCompound tag, SerializerOptions? options = null) => Serialize(writer, options, serializer => serializer.Serialize(tag));
public static void Serialize(TextWriter writer, INbtArray tag, SerializerOptions? options = null) => Serialize(writer, options, serializer => serializer.Serialize(tag));
public static void Serialize(TextWriter writer, INbtValue tag, SerializerOptions? options = null) => Serialize(writer, options, serializer => serializer.Serialize(tag));
public static void Serialize(TextWriter writer, INbtArray<sbyte> tag, SerializerOptions? options = null) => Serialize(writer, options, serializer => serializer.Serialize(tag));
public static void Serialize(TextWriter writer, INbtArray<int> tag, SerializerOptions? options = null) => Serialize(writer, options, serializer => serializer.Serialize(tag));
public static void Serialize(TextWriter writer, INbtArray<long> tag, SerializerOptions? options = null) => Serialize(writer, options, serializer => serializer.Serialize(tag));
public static void Serialize(TextWriter writer, INbtValue<sbyte> tag, SerializerOptions? options = null) => Serialize(writer, options, serializer => serializer.Serialize(tag));
public static void Serialize(TextWriter writer, INbtValue<short> tag, SerializerOptions? options = null) => Serialize(writer, options, serializer => serializer.Serialize(tag));
public static void Serialize(TextWriter writer, INbtValue<int> tag, SerializerOptions? options = null) => Serialize(writer, options, serializer => serializer.Serialize(tag));
public static void Serialize(TextWriter writer, INbtValue<long> tag, SerializerOptions? options = null) => Serialize(writer, options, serializer => serializer.Serialize(tag));
public static void Serialize(TextWriter writer, INbtValue<float> tag, SerializerOptions? options = null) => Serialize(writer, options, serializer => serializer.Serialize(tag));
public static void Serialize(TextWriter writer, INbtValue<double> tag, SerializerOptions? options = null) => Serialize(writer, options, serializer => serializer.Serialize(tag));
public static void Serialize(TextWriter writer, INbtValue<string> tag, SerializerOptions? options = null) => Serialize(writer, options, serializer => serializer.Serialize(tag));
public static void Serialize(Stream stream, INbtTag tag, SerializerOptions? options = null) => Serialize(stream, options, serializer => serializer.Serialize(tag));
public static void Serialize(Stream stream, NamedTag tag, SerializerOptions? options = null) => Serialize(stream, options, serializer => serializer.Serialize(tag));
public static void Serialize(Stream stream, INbtList tag, SerializerOptions? options = null) => Serialize(stream, options, serializer => serializer.Serialize(tag));
public static void Serialize(Stream stream, INbtCompound tag, SerializerOptions? options = null) => Serialize(stream, options, serializer => serializer.Serialize(tag));
public static void Serialize(Stream stream, INbtArray tag, SerializerOptions? options = null) => Serialize(stream, options, serializer => serializer.Serialize(tag));
public static void Serialize(Stream stream, INbtValue tag, SerializerOptions? options = null) => Serialize(stream, options, serializer => serializer.Serialize(tag));
public static void Serialize(Stream stream, INbtArray<sbyte> tag, SerializerOptions? options = null) => Serialize(stream, options, serializer => serializer.Serialize(tag));
public static void Serialize(Stream stream, INbtArray<int> tag, SerializerOptions? options = null) => Serialize(stream, options, serializer => serializer.Serialize(tag));
public static void Serialize(Stream stream, INbtArray<long> tag, SerializerOptions? options = null) => Serialize(stream, options, serializer => serializer.Serialize(tag));
public static void Serialize(Stream stream, INbtValue<sbyte> tag, SerializerOptions? options = null) => Serialize(stream, options, serializer => serializer.Serialize(tag));
public static void Serialize(Stream stream, INbtValue<short> tag, SerializerOptions? options = null) => Serialize(stream, options, serializer => serializer.Serialize(tag));
public static void Serialize(Stream stream, INbtValue<int> tag, SerializerOptions? options = null) => Serialize(stream, options, serializer => serializer.Serialize(tag));
public static void Serialize(Stream stream, INbtValue<long> tag, SerializerOptions? options = null) => Serialize(stream, options, serializer => serializer.Serialize(tag));
public static void Serialize(Stream stream, INbtValue<float> tag, SerializerOptions? options = null) => Serialize(stream, options, serializer => serializer.Serialize(tag));
public static void Serialize(Stream stream, INbtValue<double> tag, SerializerOptions? options = null) => Serialize(stream, options, serializer => serializer.Serialize(tag));
public static void Serialize(Stream stream, INbtValue<string> tag, SerializerOptions? options = null) => Serialize(stream, options, serializer => serializer.Serialize(tag));
private static void Serialize(TextWriter writer, SerializerOptions? options, Action<Serializer> serialize)
{
var serializer = new Serializer(writer, options);
serialize(serializer);
}
private static void Serialize(Stream stream, SerializerOptions? options, Action<Serializer> serialize)
{
using var writer = new StreamWriter(stream, leaveOpen: true);
Serialize(writer, options, serialize);
}
private static string Serialize(SerializerOptions? options, Action<Serializer> serialize)
{
using var writer = new StringWriter();
Serialize(writer, options, serialize);
return writer.ToString();
}
private sealed class Serializer(TextWriter writer, SerializerOptions? options)
{
private readonly TextWriter writer = writer;
private readonly SerializerOptions options = options ?? SerializerOptions.Default;
private uint depth = 0u;
private void WriteIndent()
{
for (var i = 0u; i < depth; i++)
{
writer.Write(" ");
}
}
public void Serialize(INbtTag tag)
{
switch (tag)
{
case INbtArray arrayTag:
Serialize(arrayTag);
break;
case INbtValue valueTag:
Serialize(valueTag);
break;
case INbtList listTag:
Serialize(listTag);
break;
case INbtCompound compoundTag:
Serialize(compoundTag);
break;
}
}
public void Serialize(NamedTag namedTag)
{
if (!string.IsNullOrEmpty(namedTag.Name))
{
writer.Write(NbtUtils.QuoteString(namedTag.Name, onlyIfNeeded: !options.AlwaysQuoteTagNames));
writer.Write(':');
if (options.Style is not SerializationStyle.Compact)
{
writer.Write(' ');
}
}
Serialize(namedTag.Tag);
}
public void Serialize(INbtList tag)
{
writer.Write('[');
depth++;
var style = options.Style;
var isSpaced = style is SerializationStyle.Spaced;
var isIndented = style is SerializationStyle.Indented;
var b = false;
foreach (var entry in tag)
{
if (b)
{
writer.Write(',');
if (isSpaced)
{
writer.Write(' ');
}
}
else
{
b = true;
}
if (isIndented)
{
writer.WriteLine();
WriteIndent();
}
Serialize(entry);
}
depth--;
if (b && isIndented)
{
writer.WriteLine();
WriteIndent();
}
writer.Write(']');
}
public void Serialize(INbtCompound tag)
{
writer.Write('{');
depth++;
var style = options.Style;
var isSpaced = style is SerializationStyle.Spaced;
var isIndented = style is SerializationStyle.Indented;
var b = false;
foreach (var entry in tag as IEnumerable<NamedTag>)
{
if (b)
{
writer.Write(',');
if (isSpaced)
{
writer.Write(' ');
}
}
else
{
b = true;
}
if (isIndented)
{
writer.WriteLine();
WriteIndent();
}
Serialize(entry);
}
depth--;
if (b && isIndented)
{
writer.WriteLine();
WriteIndent();
}
writer.Write('}');
}
public void Serialize(INbtArray tag)
{
switch (tag)
{
case INbtArray<sbyte> byteArrayTag:
Serialize(byteArrayTag);
break;
case INbtArray<int> intArrayTag:
Serialize(intArrayTag);
break;
case INbtArray<long> longArrayTag:
Serialize(longArrayTag);
break;
}
}
public void Serialize(INbtValue valueTag)
{
switch (valueTag)
{
case INbtValue<sbyte> tag:
Serialize(tag);
break;
case INbtValue<short> tag:
Serialize(tag);
break;
case INbtValue<int> tag:
Serialize(tag);
break;
case INbtValue<long> tag:
Serialize(tag);
break;
case INbtValue<float> tag:
Serialize(tag);
break;
case INbtValue<double> tag:
Serialize(tag);
break;
case INbtValue<string> tag:
Serialize(tag);
break;
}
}
public void Serialize(INbtArray<sbyte> tag) => Serialize(tag, 'B', Serialize);
public void Serialize(INbtArray<int> tag) => Serialize(tag, 'I', Serialize);
public void Serialize(INbtArray<long> tag) => Serialize(tag, 'L', Serialize);
public void Serialize(INbtValue<sbyte> tag) => Serialize(tag.Value);
public void Serialize(INbtValue<short> tag) => Serialize(tag.Value);
public void Serialize(INbtValue<int> tag) => Serialize(tag.Value);
public void Serialize(INbtValue<long> tag) => Serialize(tag.Value);
public void Serialize(INbtValue<float> tag) => Serialize(tag.Value);
public void Serialize(INbtValue<double> tag) => Serialize(tag.Value);
public void Serialize(INbtValue<string> tag) => Serialize(tag.Value);
private void Serialize<T>(INbtArray<T> tag, char decorator, Action<T> writeValue) where T : notnull
{
writer.Write('[');
writer.Write(decorator);
writer.Write(';');
if (tag.Value.Length > 0)
{
var isNotCompact = options.Style is not SerializationStyle.Compact;
if (isNotCompact)
{
writer.Write(' ');
}
var b = false;
foreach (var e in tag)
{
if (b)
{
writer.Write(',');
if (isNotCompact)
{
writer.Write(' ');
}
}
else
{
b = true;
}
writeValue(e);
}
}
writer.Write(']');
}
private void Serialize(sbyte value)
{
writer.Write(value.ToString(CultureInfo.InvariantCulture));
writer.Write('b');
}
private void Serialize(short value)
{
writer.Write(value.ToString(CultureInfo.InvariantCulture));
writer.Write('s');
}
private void Serialize(int value)
{
writer.Write(value.ToString(CultureInfo.InvariantCulture));
}
private void Serialize(long value)
{
writer.Write(value.ToString(CultureInfo.InvariantCulture));
writer.Write('L');
}
private void Serialize(float value)
{
writer.Write(value.ToString(CultureInfo.InvariantCulture));
if (float.IsFinite(value))
{
writer.Write('F');
}
}
private void Serialize(double value)
{
writer.Write(value.ToString(CultureInfo.InvariantCulture));
if (double.IsFinite(value))
{
writer.Write('D');
}
}
private void Serialize(string value)
{
// Utils.Quote(writer, value, NbtUtils.DOUBLE_QUOTE_CHAR, NbtUtils.ESCAPE_CHAR);
writer.Write(NbtUtils.QuoteString(value, onlyIfNeeded: false));
}
}
#endregion
}

8
Nbt/Tag/Array/NbtByteArray.cs Executable file
View File

@@ -0,0 +1,8 @@
namespace Nbt.Tag;
public class NbtByteArray(sbyte[] value) : NbtArray<sbyte>(value)
{
public override NbtTagType Type => NbtTagType.ByteArray;
protected override NbtByteArray NewInstance(sbyte[] value) => new(value);
}

8
Nbt/Tag/Array/NbtIntArray.cs Executable file
View File

@@ -0,0 +1,8 @@
namespace Nbt.Tag;
public class NbtIntArray(int[] value) : NbtArray<int>(value)
{
public override NbtTagType Type => NbtTagType.IntArray;
protected override NbtIntArray NewInstance(int[] value) => new(value);
}

8
Nbt/Tag/Array/NbtLongArray.cs Executable file
View File

@@ -0,0 +1,8 @@
namespace Nbt.Tag;
public class NbtLongArray(long[] value) : NbtArray<long>(value)
{
public override NbtTagType Type => NbtTagType.LongArray;
protected override NbtLongArray NewInstance(long[] value) => new(value);
}

3
Nbt/Tag/NamedTag.cs Executable file
View File

@@ -0,0 +1,3 @@
namespace Nbt.Tag;
public readonly record struct NamedTag(string Name, INbtTag Tag) { }

28
Nbt/Tag/NbtArray.cs Executable file
View File

@@ -0,0 +1,28 @@
using System.Collections;
namespace Nbt.Tag;
public interface INbtArray : INbtValue, IEnumerable
{
new Array Value { get; set; }
}
public interface INbtArray<T> : INbtArray, INbtValue<T[]>, IEnumerable<T> where T : notnull
{
new T[] Value { get; set; }
}
public abstract class NbtArray<T>(T[] value) : NbtValue<T[]>(value), INbtArray<T> where T : notnull
{
Array INbtArray.Value
{
get => Value;
set => Value = (T[])value;
}
protected override T[] CopyValue() => (T[])value.Clone();
IEnumerator<T> IEnumerable<T>.GetEnumerator() => ((IEnumerable<T>)Value).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => Value.GetEnumerator();
}

117
Nbt/Tag/NbtCompound.cs Executable file
View File

@@ -0,0 +1,117 @@
using System.Collections;
using System.Diagnostics.CodeAnalysis;
namespace Nbt.Tag;
public interface INbtCompound : INbtTag, IEnumerable<NamedTag>
{
int Count { get; }
new INbtTag this[string tagName] { get; set; }
void Add(NamedTag namedTag);
void Add(string tagName, INbtTag tag);
void Set(NamedTag namedTag);
void Set(string tagName, INbtTag tag);
INbtTag Get(string tagName);
T Get<T>(string tagName) where T : INbtTag;
bool TryGet(string tagName, [MaybeNullWhen(false)] out INbtTag tag);
bool TryGet<T>(string tagName, [MaybeNullWhen(false)] out T tag) where T : INbtTag;
bool ContainsKey(string tagName);
bool Contains(NamedTag namedTag);
bool Contains(string tagName, INbtTag tag);
bool Remove(string tagName);
bool Remove(NamedTag namedTag);
bool Remove(string tagName, INbtTag tag);
bool Remove(string tagName, [MaybeNullWhen(false)] out INbtTag tag);
void Clear();
}
public class NbtCompound : INbtCompound
{
private readonly Dictionary<string, INbtTag> entries = [];
private static string EnsureName(string tagName) => tagName ?? throw new ArgumentNullException(nameof(tagName));
private static INbtTag EnsureValue(INbtTag tag) => tag ?? throw new ArgumentNullException(nameof(tag));
public NbtTagType Type => NbtTagType.Compound;
INbtCompound INbtTag.AsCompound() => this;
INbtTag INbtTag.Copy() => Copy();
public NbtCompound Copy()
{
var copy = new NbtCompound();
foreach (var e in entries)
{
copy.entries[e.Key] = e.Value.Copy();
}
return copy;
}
public int Count => entries.Count;
public INbtTag this[string tagName]
{
get => entries[tagName];
set => entries[EnsureName(tagName)] = EnsureValue(value);
}
INbtTag INbtTag.this[INbtPathElement tagPath]
{
get => tagPath is NbtName tagName ? this[tagName] : throw new UnsupportedPathElementException();
set => this[tagPath is NbtName tagName ? tagName : throw new UnsupportedPathElementException()] = value;
}
public void Add(NamedTag namedTag) => Add(namedTag.Name, namedTag.Tag);
public void Add(string tagName, INbtTag tag) => entries.Add(EnsureName(tagName), EnsureValue(tag));
public void Set(NamedTag namedTag) => Set(namedTag.Name, namedTag.Tag);
public void Set(string tagName, INbtTag tag) => this[tagName] = tag;
public INbtTag Get(string tagName) => this[tagName];
public T Get<T>(string tagName) where T : INbtTag => (T)Get(tagName);
public bool TryGet(string tagName, [MaybeNullWhen(false)] out INbtTag tag) => entries.TryGetValue(tagName, out tag);
public bool TryGet<T>(string tagName, [MaybeNullWhen(false)] out T tag) where T : INbtTag
{
if (TryGet(tagName, out var t))
{
tag = (T)t;
return true;
}
tag = default;
return false;
}
public bool ContainsKey(string tagName) => entries.ContainsKey(tagName);
public bool Contains(NamedTag namedTag) => Contains(namedTag.Name, namedTag.Tag);
public bool Contains(string tagName, INbtTag tag) => entries.Contains(KeyValuePair.Create(tagName, tag));
public bool Remove(string tagName) => entries.Remove(tagName);
public bool Remove(NamedTag namedTag) => Remove(namedTag.Name, namedTag.Tag);
public bool Remove(string tagName, INbtTag tag) => ((IDictionary<string, INbtTag>)entries).Remove(KeyValuePair.Create(tagName, tag));
public bool Remove(string tagName, [MaybeNullWhen(false)] out INbtTag tag) => entries.Remove(tagName, out tag);
public void Clear() => entries.Clear();
public bool Equals(INbtTag? other) => ReferenceEquals(other, this) || other is NbtCompound o && Utils.DictionaryEquals(entries, o.entries);
public override bool Equals(object? obj) => ReferenceEquals(obj, this) || obj is INbtCompound other && Equals(other);
public override int GetHashCode() => entries.GetHashCode();
public IEnumerator<NamedTag> GetEnumerator() => entries.Select(e => new NamedTag(e.Key, e.Value)).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)entries).GetEnumerator();
}

19
Nbt/Tag/NbtEnd.cs Executable file
View File

@@ -0,0 +1,19 @@
namespace Nbt.Tag;
public sealed class NbtEnd : INbtTag
{
public static readonly NbtEnd Value = new();
private NbtEnd() { }
public NbtTagType Type => NbtTagType.End;
INbtTag INbtTag.Copy() => this;
public bool Equals(INbtTag? other) => this == other;
public override bool Equals(object? obj) => this == obj;
public override int GetHashCode() => 0;
}

227
Nbt/Tag/NbtList.cs Executable file
View File

@@ -0,0 +1,227 @@
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using Nbt.Type;
namespace Nbt.Tag;
public interface INbtList : INbtTag, IEnumerable<INbtTag>
{
NbtTagType ElementType { get; }
bool IsElementTypeLocked { get; }
int Count { get; }
new INbtTag this[int tagIndex] { get; set; }
void Add(INbtTag tag);
void Insert(int tagIndex, INbtTag tag);
void Set(int tagIndex, INbtTag tag);
INbtTag GetAt(int tagIndex);
T GetAt<T>(int tagIndex) where T : INbtTag;
bool TryGetAt(int tagIndex, [MaybeNullWhen(false)] out INbtTag tag);
bool TryGetAt<T>(int tagIndex, [MaybeNullWhen(false)] out T tag) where T : INbtTag;
int IndexOf(INbtTag tag);
bool Contains(INbtTag tag);
INbtTag RemoveAt(int tagIndex);
bool Remove(INbtTag tag);
void Clear();
}
public class NbtList : INbtList
{
private readonly List<INbtTag> entries = [];
public NbtList() : this(NbtTagType.Unknown, false) { }
public NbtList(NbtTagType elementType) : this(elementType, elementType is not NbtTagType.Unknown or NbtTagType.End) { }
private NbtList(NbtTagType elementType, bool lockedType)
{
ElementType = elementType;
IsElementTypeLocked = lockedType;
}
public NbtTagType Type => NbtTagType.List;
INbtList INbtTag.AsList() => this;
INbtTag INbtTag.Copy() => Copy();
public NbtList Copy()
{
var copy = new NbtList(ElementType, IsElementTypeLocked);
foreach (var e in entries)
{
copy.entries.Add(e.Copy());
}
return copy;
}
private INbtTag EnsureType(INbtTag tag)
{
ArgumentNullException.ThrowIfNull(tag);
if (tag.Type is NbtTagType.End)
{
throw new WrongTagTypeException($"The {nameof(NbtTagType.End)} tag cannot appear in a list");
}
if ((Count > 0 || IsElementTypeLocked) && tag.Type != ElementType)
{
throw new WrongTagTypeException();
}
return tag;
}
private void OnItemAdded(INbtTag tag)
{
if (!IsElementTypeLocked && Count == 1)
{
ElementType = tag.Type;
}
}
private void OnItemRemoved()
{
if (!IsElementTypeLocked && Count == 0)
{
ElementType = NbtTagType.Unknown;
}
}
public NbtTagType ElementType { get; private set; }
public bool IsElementTypeLocked { get; }
public int Count => entries.Count;
public INbtTag this[int tagIndex]
{
get => entries[tagIndex];
set => entries[tagIndex] = EnsureType(value);
}
INbtTag INbtTag.this[INbtPathElement tagPath]
{
get => tagPath is NbtIndex tagIndex ? this[tagIndex] : throw new UnsupportedPathElementException();
set => this[tagPath is NbtIndex tagIndex ? tagIndex : throw new UnsupportedPathElementException()] = value;
}
public void Add(INbtTag tag)
{
entries.Add(EnsureType(tag));
OnItemAdded(tag);
}
public void Insert(int index, INbtTag tag)
{
entries.Insert(index, EnsureType(tag));
OnItemAdded(tag);
}
public void Set(int tagIndex, INbtTag tag) => this[tagIndex] = tag;
public INbtTag GetAt(int tagIndex) => this[tagIndex];
public T GetAt<T>(int tagIndex) where T : INbtTag => (T)this[tagIndex];
public bool TryGetAt(int tagIndex, [MaybeNullWhen(false)] out INbtTag tag)
{
if (tagIndex >= 0 && tagIndex < Count)
{
tag = this[tagIndex];
return true;
}
tag = default;
return false;
}
public bool TryGetAt<T>(int tagIndex, [MaybeNullWhen(false)] out T tag) where T : INbtTag
{
if (TryGetAt(tagIndex, out var result))
{
tag = (T)result;
return true;
}
tag = default;
return false;
}
public int IndexOf(INbtTag tag) => entries.IndexOf(tag);
public bool Contains(INbtTag tag) => entries.Contains(tag);
public INbtTag RemoveAt(int index)
{
var e = entries[index];
entries.RemoveAt(index);
OnItemRemoved();
return e;
}
public bool Remove(INbtTag tag)
{
var b = entries.Remove(tag);
if (b)
{
OnItemRemoved();
}
return b;
}
public void Clear()
{
entries.Clear();
OnItemRemoved();
}
public bool Equals(INbtTag? other) => ReferenceEquals(other, this) || other is NbtList o && Utils.EnumerableEquals(entries, o.entries);
public override bool Equals(object? obj) => ReferenceEquals(obj, this) || obj is INbtList other && Equals(other);
public override int GetHashCode() => entries.GetHashCode();
IEnumerator<INbtTag> IEnumerable<INbtTag>.GetEnumerator() => entries.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)entries).GetEnumerator();
public static INbtList FromValues<T>(params T[] values) where T : notnull
{
var elementType = NbtUtils.EnsureValueType<T>();
var t = (NbtValueType<T>)NbtUtils.GetTagType(elementType);
var result = new NbtList(elementType);
foreach (var value in values)
{
result.Add(t.CreateTag(value));
}
return result;
}
public static INbtList FromTags<T>(NbtTagType elementType, params T[] tags) where T : INbtTag
{
var result = new NbtList(elementType);
foreach (var tag in tags)
{
result.Add(tag);
}
return result;
}
}

68
Nbt/Tag/NbtTag.cs Executable file
View File

@@ -0,0 +1,68 @@
namespace Nbt.Tag;
public interface INbtTag : IEquatable<INbtTag>
{
NbtTagType Type { get; }
INbtTag this[string tagName] { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
INbtTag this[int tagIndex] { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
INbtTag this[INbtPathElement tagPath] { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
INbtTag this[INbtPath tagPath]
{
get
{
var count = tagPath.Count;
if (count == 0)
{
return this;
}
var pathElt = tagPath.PopFirst(out var pathRest);
var childTag = this[pathElt];
return childTag[pathRest];
}
set
{
var count = tagPath.Count;
if (count == 0)
{
throw new ArgumentException("Path cannot be empty", nameof(value));
}
var pathElt = tagPath.PopFirst(out var pathRest);
if (count == 1)
{
this[pathElt] = value;
}
else
{
var childTag = this[pathElt];
childTag[pathRest] = value;
}
}
}
INbtList AsList() => (INbtList)this;
INbtCompound AsCompound() => (INbtCompound)this;
INbtValue AsValue() => (INbtValue)this;
INbtValue<T> AsValue<T>() where T : notnull => (INbtValue<T>)this;
INbtArray AsArray() => (INbtArray)this;
INbtArray<T> AsArray<T>() where T : notnull => (INbtArray<T>)this;
T As<T>() where T : INbtTag => (T)this;
INbtTag Copy();
}

64
Nbt/Tag/NbtValue.cs Executable file
View File

@@ -0,0 +1,64 @@
namespace Nbt.Tag;
public interface INbtValue : INbtTag
{
object Value { get; set; }
}
public interface INbtValue<T> : INbtValue where T : notnull
{
new T Value { get; set; }
}
public abstract class NbtValue<T> : INbtValue<T> where T : notnull
{
private static readonly EqualityComparer<T> ValueComparer = EqualityComparer<T>.Default;
protected T value;
internal protected NbtValue(T value) : base() => this.value = value;
public abstract NbtTagType Type { get; }
INbtTag INbtTag.this[string tagName] { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
INbtTag INbtTag.this[int tagIndex] { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
INbtTag INbtTag.this[INbtPathElement tagPath] { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
INbtList INbtTag.AsList() => throw new NotSupportedException();
INbtCompound INbtTag.AsCompound() => throw new NotSupportedException();
INbtValue INbtTag.AsValue() => this;
INbtValue<U> INbtTag.AsValue<U>() => (INbtValue<U>)this;
INbtArray INbtTag.AsArray() => (INbtArray)this;
INbtArray<U> INbtTag.AsArray<U>() => (INbtArray<U>)this;
INbtTag INbtTag.Copy() => Copy();
public NbtValue<T> Copy() => NewInstance(CopyValue());
object INbtValue.Value
{
get => Value;
set => Value = (T)Convert.ChangeType(value, typeof(T));
}
public T Value
{
get => value;
set => this.value = value ?? throw new ArgumentNullException(nameof(value));
}
protected virtual T CopyValue() => value;
protected abstract NbtValue<T> NewInstance(T value);
public bool Equals(INbtTag? other) => ReferenceEquals(other, this) || other is NbtValue<T> o && ValueComparer.Equals(value, o.value);
public override bool Equals(object? obj) => ReferenceEquals(obj, this) || obj is NbtValue<T> other && ValueComparer.Equals(value, other.value);
public override int GetHashCode() => value.GetHashCode();
}

8
Nbt/Tag/Value/NbtByte.cs Executable file
View File

@@ -0,0 +1,8 @@
namespace Nbt.Tag;
public class NbtByte(sbyte value) : NbtValue<sbyte>(value)
{
public override NbtTagType Type => NbtTagType.Byte;
protected override NbtByte NewInstance(sbyte value) => new(value);
}

8
Nbt/Tag/Value/NbtDouble.cs Executable file
View File

@@ -0,0 +1,8 @@
namespace Nbt.Tag;
public class NbtDouble(double value) : NbtValue<double>(value)
{
public override NbtTagType Type => NbtTagType.Double;
protected override NbtDouble NewInstance(double value) => new(value);
}

8
Nbt/Tag/Value/NbtFloat.cs Executable file
View File

@@ -0,0 +1,8 @@
namespace Nbt.Tag;
public class NbtFloat(float value) : NbtValue<float>(value)
{
public override NbtTagType Type => NbtTagType.Float;
protected override NbtFloat NewInstance(float value) => new(value);
}

8
Nbt/Tag/Value/NbtInt.cs Executable file
View File

@@ -0,0 +1,8 @@
namespace Nbt.Tag;
public class NbtInt(int value) : NbtValue<int>(value)
{
public override NbtTagType Type => NbtTagType.Int;
protected override NbtInt NewInstance(int value) => new(value);
}

8
Nbt/Tag/Value/NbtLong.cs Executable file
View File

@@ -0,0 +1,8 @@
namespace Nbt.Tag;
public class NbtLong(long value) : NbtValue<long>(value)
{
public override NbtTagType Type => NbtTagType.Long;
protected override NbtLong NewInstance(long value) => new(value);
}

8
Nbt/Tag/Value/NbtShort.cs Executable file
View File

@@ -0,0 +1,8 @@
namespace Nbt.Tag;
public class NbtShort(short value) : NbtValue<short>(value)
{
public override NbtTagType Type => NbtTagType.Short;
protected override NbtShort NewInstance(short value) => new(value);
}

8
Nbt/Tag/Value/NbtString.cs Executable file
View File

@@ -0,0 +1,8 @@
namespace Nbt.Tag;
public class NbtString(string value) : NbtValue<string>(value)
{
public override NbtTagType Type => NbtTagType.String;
protected override NbtString NewInstance(string value) => new(value);
}

View File

@@ -0,0 +1,14 @@
using Nbt.Tag;
namespace Nbt.Type;
internal class NbtByteArrayType : NbtArrayType<sbyte>
{
public static readonly NbtByteArrayType Value = new();
private NbtByteArrayType() : base(NbtByteType.Value) { }
public override NbtTagType Type => NbtTagType.ByteArray;
public override NbtByteArray CreateTag(sbyte[] values) => new(values);
}

View File

@@ -0,0 +1,14 @@
using Nbt.Tag;
namespace Nbt.Type;
internal class NbtIntArrayType : NbtArrayType<int>
{
public static readonly NbtIntArrayType Value = new();
private NbtIntArrayType() : base(NbtIntType.Value) { }
public override NbtTagType Type => NbtTagType.IntArray;
public override NbtIntArray CreateTag(int[] values) => new(values);
}

14
Nbt/Type/Array/NbtLongArray.cs Executable file
View File

@@ -0,0 +1,14 @@
using Nbt.Tag;
namespace Nbt.Type;
internal class NbtLongArrayType : NbtArrayType<long>
{
public static readonly NbtLongArrayType Value = new();
private NbtLongArrayType() : base(NbtLongType.Value) { }
public override NbtTagType Type => NbtTagType.LongArray;
public override NbtLongArray CreateTag(long[] values) => new(values);
}

43
Nbt/Type/NbtArrayType.cs Executable file
View File

@@ -0,0 +1,43 @@
using Nbt.Serialization;
using Nbt.Tag;
namespace Nbt.Type;
internal abstract class NbtArrayType<T>(NbtValueType<T> elementType) : NbtType<INbtArray<T>> where T : notnull
{
private readonly NbtValueType<T> elementType = elementType;
protected virtual T ReadValue(INbtReader reader) => elementType.ReadValue(reader);
public abstract INbtArray<T> CreateTag(T[] values);
public virtual INbtArray<T> CreateEmptyTag() => CreateTag([]);
protected virtual void WriteValue(INbtWriter writer, T value) => elementType.WriteValue(writer, value);
public override INbtArray<T> Read(INbtReader reader)
{
var length = reader.ReadInt();
if (length == 0)
{
return CreateEmptyTag();
}
var arr = new T[length];
for (var i = 0; i < length; i++)
{
arr[i] = ReadValue(reader);
}
return CreateTag(arr);
}
public override void Write(INbtWriter writer, INbtArray<T> tag)
{
writer.WriteInt(tag.Value.Length);
foreach (var e in tag)
{
WriteValue(writer, e);
}
}
}

45
Nbt/Type/NbtCompoundType.cs Executable file
View File

@@ -0,0 +1,45 @@
using Nbt.Serialization;
using Nbt.Tag;
namespace Nbt.Type;
internal class NbtCompoundType : NbtType<INbtCompound>
{
public static readonly NbtCompoundType Value = new();
private NbtCompoundType() { }
public override NbtTagType Type => NbtTagType.Compound;
public override INbtCompound Read(INbtReader reader)
{
var compound = new NbtCompound();
while (true)
{
var tagType = NbtUtils.GetTagType(reader.ReadTagType());
if (tagType == NbtEndType.Value)
{
break;
}
var tagName = reader.ReadString();
var tag = tagType.Read(reader);
compound.Add(tagName, tag);
}
return compound;
}
public override void Write(INbtWriter writer, INbtCompound tag)
{
foreach (var entry in (IEnumerable<NamedTag>)tag)
{
writer.WriteNamedTag(entry);
}
writer.WriteTag(NbtEnd.Value);
}
}

17
Nbt/Type/NbtEndType.cs Executable file
View File

@@ -0,0 +1,17 @@
using Nbt.Serialization;
using Nbt.Tag;
namespace Nbt.Type;
internal class NbtEndType : NbtType<NbtEnd>
{
public static readonly NbtEndType Value = new();
private NbtEndType() { }
public override NbtTagType Type => NbtTagType.End;
public override NbtEnd Read(INbtReader reader) => NbtEnd.Value;
public override void Write(INbtWriter writer, NbtEnd tag) { }
}

38
Nbt/Type/NbtListType.cs Executable file
View File

@@ -0,0 +1,38 @@
using Nbt.Serialization;
using Nbt.Tag;
namespace Nbt.Type;
internal class NbtListType : NbtType<INbtList>
{
public static readonly NbtListType Value = new();
private NbtListType() { }
public override NbtTagType Type => NbtTagType.List;
public override NbtList Read(INbtReader reader)
{
var tagType = NbtUtils.GetTagType(reader.ReadTagType());
var length = reader.ReadInt();
var list = new NbtList(tagType.Type);
for (var i = 0; i < length; i++)
{
var tag = tagType.Read(reader);
list.Add(tag);
}
return list;
}
public override void Write(INbtWriter writer, INbtList tag)
{
foreach (var entry in tag)
{
writer.WriteTag(entry);
}
}
}

98
Nbt/Type/NbtRegistry.cs Executable file
View File

@@ -0,0 +1,98 @@
using System.Diagnostics.CodeAnalysis;
using Nbt.Tag;
namespace Nbt.Type;
internal interface INbtRegistry
{
int Count { get; }
IEnumerable<INbtType> Entries { get; }
bool Contains(NbtTagType type);
bool Contains(INbtType type);
INbtType Get(NbtTagType type);
INbtType<T> Get<T>(NbtTagType type) where T : INbtTag;
bool TryGet(NbtTagType type, [MaybeNullWhen(false)] out INbtType value);
bool TryGet<T>(NbtTagType type, [MaybeNullWhen(false)] out INbtType<T> value) where T : INbtTag;
void Clear();
void Add(INbtType t);
void AddAll(params INbtType[] t);
void AddRange(IEnumerable<INbtType> t);
bool Remove(NbtTagType type);
bool Remove(INbtType type);
INbtType this[NbtTagType type] { get; set; }
}
internal class NbtRegistry : INbtRegistry
{
private readonly Dictionary<NbtTagType, INbtType> _registry = [];
public int Count => _registry.Count;
public IEnumerable<INbtType> Entries => _registry.Values;
public void Clear() => _registry.Clear();
public void Add(INbtType t) => _registry.Add(t.Type, t);
public void AddAll(params INbtType[] ts)
{
foreach (var t in ts)
{
Add(t);
}
}
public void AddRange(IEnumerable<INbtType> e)
{
foreach (var t in e)
{
Add(t);
}
}
public bool Remove(NbtTagType type) => _registry.Remove(type);
public bool Remove(INbtType type) => _registry.Remove(type.Type);
public bool Contains(NbtTagType type) => _registry.ContainsKey(type);
public bool Contains(INbtType type) => Contains(type.Type);
public INbtType Get(NbtTagType type) => _registry[type];
public INbtType<T> Get<T>(NbtTagType type) where T : INbtTag => (INbtType<T>)Get(type);
public bool TryGet(NbtTagType type, [MaybeNullWhen(false)] out INbtType value) => _registry.TryGetValue(type, out value);
public bool TryGet<T>(NbtTagType type, [MaybeNullWhen(false)] out INbtType<T> value) where T : INbtTag
{
if (_registry.TryGetValue(type, out var v))
{
value = (INbtType<T>)v;
return true;
}
value = default;
return false;
}
public INbtType this[NbtTagType type]
{
get => _registry[type];
set => _registry[type] = value ?? throw new ArgumentNullException(nameof(value));
}
}

33
Nbt/Type/NbtType.cs Executable file
View File

@@ -0,0 +1,33 @@
using Nbt.Serialization;
using Nbt.Tag;
namespace Nbt.Type;
internal interface INbtType
{
NbtTagType Type { get; }
INbtTag Read(INbtReader reader);
void Write(INbtWriter writer, INbtTag tag);
}
internal interface INbtType<T> : INbtType where T : INbtTag
{
new T Read(INbtReader reader);
void Write(INbtWriter writer, T tag);
}
internal abstract class NbtType<T> : INbtType<T> where T : INbtTag
{
public abstract NbtTagType Type { get; }
public abstract T Read(INbtReader reader);
public abstract void Write(INbtWriter writer, T tag);
INbtTag INbtType.Read(INbtReader reader) => Read(reader);
void INbtType.Write(INbtWriter writer, INbtTag tag) => Write(writer, (T)tag);
}

15
Nbt/Type/NbtValueType.cs Executable file
View File

@@ -0,0 +1,15 @@
using Nbt.Serialization;
using Nbt.Tag;
namespace Nbt.Type;
internal abstract class NbtValueType<T> : NbtType<INbtValue<T>> where T : notnull
{
protected internal abstract T ReadValue(INbtReader reader);
public abstract INbtValue<T> CreateTag(T value);
protected internal abstract void WriteValue(INbtWriter writer, T value);
public override INbtValue<T> Read(INbtReader reader) => CreateTag(ReadValue(reader));
public override void Write(INbtWriter writer, INbtValue<T> tag) => WriteValue(writer, tag.Value);
}

19
Nbt/Type/Value/NbtByteType.cs Executable file
View File

@@ -0,0 +1,19 @@
using Nbt.Serialization;
using Nbt.Tag;
namespace Nbt.Type;
internal class NbtByteType : NbtValueType<sbyte>
{
public static readonly NbtByteType Value = new();
private NbtByteType() { }
public override NbtTagType Type => NbtTagType.Byte;
protected internal override sbyte ReadValue(INbtReader reader) => reader.ReadSByte();
public override NbtByte CreateTag(sbyte value) => new(value);
protected internal override void WriteValue(INbtWriter writer, sbyte value) => writer.WriteSByte(value);
}

19
Nbt/Type/Value/NbtDoubleType.cs Executable file
View File

@@ -0,0 +1,19 @@
using Nbt.Serialization;
using Nbt.Tag;
namespace Nbt.Type;
internal class NbtDoubleType : NbtValueType<double>
{
public static readonly NbtDoubleType Value = new();
private NbtDoubleType() { }
public override NbtTagType Type => NbtTagType.Double;
protected internal override double ReadValue(INbtReader reader) => reader.ReadDouble();
public override NbtDouble CreateTag(double value) => new(value);
protected internal override void WriteValue(INbtWriter writer, double value) => writer.WriteDouble(value);
}

19
Nbt/Type/Value/NbtFloatType.cs Executable file
View File

@@ -0,0 +1,19 @@
using Nbt.Serialization;
using Nbt.Tag;
namespace Nbt.Type;
internal class NbtFloatType : NbtValueType<float>
{
public static readonly NbtFloatType Value = new();
private NbtFloatType() { }
public override NbtTagType Type => NbtTagType.Float;
protected internal override float ReadValue(INbtReader reader) => reader.ReadFloat();
public override NbtFloat CreateTag(float value) => new(value);
protected internal override void WriteValue(INbtWriter writer, float value) => writer.WriteFloat(value);
}

19
Nbt/Type/Value/NbtIntType.cs Executable file
View File

@@ -0,0 +1,19 @@
using Nbt.Serialization;
using Nbt.Tag;
namespace Nbt.Type;
internal class NbtIntType : NbtValueType<int>
{
public static readonly NbtIntType Value = new();
private NbtIntType() { }
public override NbtTagType Type => NbtTagType.Int;
protected internal override int ReadValue(INbtReader reader) => reader.ReadInt();
public override NbtInt CreateTag(int value) => new(value);
protected internal override void WriteValue(INbtWriter writer, int value) => writer.WriteInt(value);
}

19
Nbt/Type/Value/NbtLongType.cs Executable file
View File

@@ -0,0 +1,19 @@
using Nbt.Serialization;
using Nbt.Tag;
namespace Nbt.Type;
internal class NbtLongType : NbtValueType<long>
{
public static readonly NbtLongType Value = new();
private NbtLongType() { }
public override NbtTagType Type => NbtTagType.Long;
protected internal override long ReadValue(INbtReader reader) => reader.ReadLong();
public override NbtLong CreateTag(long value) => new(value);
protected internal override void WriteValue(INbtWriter writer, long value) => writer.WriteLong(value);
}

19
Nbt/Type/Value/NbtShortType.cs Executable file
View File

@@ -0,0 +1,19 @@
using Nbt.Serialization;
using Nbt.Tag;
namespace Nbt.Type;
internal class NbtShortType : NbtValueType<short>
{
public static readonly NbtShortType Value = new();
private NbtShortType() { }
public override NbtTagType Type => NbtTagType.Short;
protected internal override short ReadValue(INbtReader reader) => reader.ReadShort();
public override NbtShort CreateTag(short value) => new(value);
protected internal override void WriteValue(INbtWriter writer, short value) => writer.WriteShort(value);
}

19
Nbt/Type/Value/NbtStringType.cs Executable file
View File

@@ -0,0 +1,19 @@
using Nbt.Serialization;
using Nbt.Tag;
namespace Nbt.Type;
internal class NbtStringType : NbtValueType<string>
{
public static readonly NbtStringType Value = new();
private NbtStringType() { }
public override NbtTagType Type => NbtTagType.String;
protected internal override string ReadValue(INbtReader reader) => reader.ReadString();
public override NbtString CreateTag(string value) => new(value);
protected internal override void WriteValue(INbtWriter writer, string value) => writer.WriteString(value);
}

91
Nbt/Util/IndentedWriter.cs Executable file
View File

@@ -0,0 +1,91 @@
using System.Text;
namespace Nbt;
public sealed class IndentedWriter(TextWriter baseWriter, string indent, bool leaveOpen = false) : TextWriter
{
private readonly TextWriter baseWriter = baseWriter;
private readonly string indent = indent;
private readonly bool leaveOpen = leaveOpen;
private uint depth = 0u;
public IndentedWriter(StringBuilder sb, string indent) : this(new StringWriter(sb), indent) { }
public override Encoding Encoding => baseWriter.Encoding;
public IndentationLevel NewIndentationLevel() => new(this);
public override void Write(char value) => baseWriter.Write(value);
public override void WriteLine()
{
baseWriter.WriteLine();
for (var i = 0u; i < depth; i++)
{
baseWriter.Write(indent);
}
}
public override async Task WriteAsync(char value) => await baseWriter.WriteAsync(value);
public override async Task WriteLineAsync()
{
await baseWriter.WriteLineAsync();
for (var i = 0u; i < depth; i++)
{
await baseWriter.WriteAsync(indent);
}
}
protected override void Dispose(bool disposing)
{
try
{
if (disposing)
{
if (!leaveOpen)
{
baseWriter.Dispose();
}
}
}
finally
{
base.Dispose(disposing);
}
}
public override async ValueTask DisposeAsync()
{
try
{
if (!leaveOpen)
{
await baseWriter.DisposeAsync();
}
}
finally
{
await base.DisposeAsync();
}
}
public readonly struct IndentationLevel : IDisposable
{
private readonly IndentedWriter writer;
public IndentationLevel(IndentedWriter writer)
{
this.writer = writer;
writer.depth++;
}
public void Dispose()
{
writer.depth--;
}
}
}

128
Nbt/Util/QuotedWriter.cs Executable file
View File

@@ -0,0 +1,128 @@
using System.Text;
namespace Nbt;
public sealed class QuotedWriter(TextWriter baseWriter, char quoteChar, char escapeChar, bool leaveOpen = false) : TextWriter
{
private readonly TextWriter baseWriter = baseWriter;
private readonly char quoteChar = quoteChar;
private readonly char escapeChar = escapeChar;
private readonly bool leaveOpen = leaveOpen;
private bool hasWrittenOneChar = false;
public QuotedWriter(StringBuilder sb, char quoteChar, char escapeChar) : this(new StringWriter(sb), quoteChar, escapeChar) { }
public override Encoding Encoding => baseWriter.Encoding;
public override void Write(char value)
{
if (!hasWrittenOneChar)
{
baseWriter.Write(quoteChar);
hasWrittenOneChar = true;
}
foreach (var c in Escape(value))
{
baseWriter.Write(c);
}
}
public override async Task WriteAsync(char value)
{
if (!hasWrittenOneChar)
{
await baseWriter.WriteAsync(quoteChar);
hasWrittenOneChar = true;
}
foreach (var c in Escape(value))
{
await baseWriter.WriteAsync(c);
}
}
private IEnumerable<char> Escape(char c)
{
var escape = false;
switch (c)
{
case '\t':
escape = true;
c = 't';
break;
case '\r':
escape = true;
c = 'r';
break;
case '\n':
escape = true;
c = 'n';
break;
default:
if (c == quoteChar || c == escapeChar)
{
escape = true;
}
break;
}
if (escape)
{
yield return escapeChar;
}
yield return c;
}
protected override void Dispose(bool disposing)
{
try
{
if (disposing)
{
if (!hasWrittenOneChar)
{
baseWriter.Write(quoteChar);
}
baseWriter.Write(quoteChar);
if (!leaveOpen)
{
baseWriter.Dispose();
}
}
}
finally
{
base.Dispose(disposing);
}
}
public override async ValueTask DisposeAsync()
{
try
{
if (!hasWrittenOneChar)
{
await baseWriter.WriteAsync(quoteChar);
}
await baseWriter.WriteAsync(quoteChar);
if (!leaveOpen)
{
await baseWriter.DisposeAsync();
}
}
finally
{
await base.DisposeAsync();
}
}
}

251
Nbt/Util/Utils.cs Executable file
View File

@@ -0,0 +1,251 @@
using System.IO.Compression;
using System.Text;
using K4os.Compression.LZ4.Streams;
namespace Nbt;
internal static class Utils
{
#region conversion stuff
public static TTo Convert<TFrom, TTo>(this TFrom value, Converter<TFrom, TTo> converter) => value is TTo sameValue ? sameValue : converter(value);
#endregion
#region enumerable stuff
public static T[] AsArray<T>(this IEnumerable<T> enumerable) => Convert(enumerable, Enumerable.ToArray);
public static IList<T> AsIList<T>(this IEnumerable<T> enumerable) => Convert<IEnumerable<T>, IList<T>>(enumerable, Enumerable.ToList);
public static List<T> AsList<T>(this IEnumerable<T> enumerable) => Convert(enumerable, Enumerable.ToList);
public static IEnumerable<T> Insert<T>(this IEnumerable<T> e, int index, T value)
{
ArgumentOutOfRangeException.ThrowIfNegative(index);
if (e.TryGetNonEnumeratedCount(out var count))
{
ArgumentOutOfRangeException.ThrowIfGreaterThan(index, count);
}
var i = 0;
foreach (var o in e)
{
if (i == index)
{
yield return value;
}
yield return o;
i++;
}
if (i == index)
{
yield return value;
}
}
public static IEnumerable<T> Replace<T>(this IEnumerable<T> e, int index, T value)
{
ArgumentOutOfRangeException.ThrowIfNegative(index);
if (e.TryGetNonEnumeratedCount(out var count))
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, count);
}
var i = 0;
foreach (var o in e)
{
yield return i == index ? value : o;
i++;
}
}
public static IEnumerable<T> Yield<T>(this T value)
{
yield return value;
}
#endregion
#region streams, readers and writers
public static GZipStream CreateGZipDeflater(Stream stream, bool leaveOpen) => new(stream, CompressionMode.Compress, leaveOpen);
public static ZLibStream CreateZLibDeflater(Stream stream, bool leaveOpen) => new(stream, CompressionMode.Compress, leaveOpen);
public static LZ4EncoderStream CreateLZ4Deflater(Stream stream, bool leaveOpen) => LZ4Stream.Encode(stream, leaveOpen: leaveOpen);
public static GZipStream CreateGZipInflater(Stream stream, bool leaveOpen) => new(stream, CompressionMode.Decompress, leaveOpen);
public static ZLibStream CreateZLibInflater(Stream stream, bool leaveOpen) => new(stream, CompressionMode.Decompress, leaveOpen);
public static LZ4DecoderStream CreateLZ4Inflater(Stream stream, bool leaveOpen) => LZ4Stream.Decode(stream, leaveOpen: leaveOpen);
public static char SkipWhiteSpaces(TextReader reader)
{
while (true)
{
var i = reader.Read();
if (i < 0)
{
throw new EndOfStreamException();
// return i;
}
var c = (char)i;
if (char.IsWhiteSpace(c))
{
continue;
}
return c;
}
}
#endregion
#region string and char quotation and whatnot
public static string Repeat(this string s, int c) => c switch
{
< 0 => throw new ArgumentException("Count must be positive.", nameof(c)),
0 => string.Empty,
1 => s,
_ => string.IsNullOrEmpty(s) ? s : string.Create(s.Length * c, (s, c), static (span, state) =>
{
var (s, c) = state;
var n = s.Length;
for (var i = 0; i < c; i++)
{
var ss = span.Slice(n * i, n);
s.CopyTo(ss);
}
})
};
public static string Unquote(string s, char quoteChar, char escapeChar)
{
var length = s.Length;
if (length < 2 || s[0] != quoteChar || s[^1] != quoteChar)
{
throw new ArgumentException("Invalid quoted string", nameof(s));
}
var sb = new StringBuilder();
var escape = false;
for (var i = 1; i < length - 1; i++)
{
var c = s[i];
if (escape)
{
escape = false;
switch (c)
{
case 't':
c = '\t';
break;
case 'r':
c = '\r';
break;
case 'n':
c = '\n';
break;
}
}
else if (c == quoteChar)
{
throw new ArgumentException("Invalid quoted string", nameof(s));
}
else if (c == escapeChar)
{
escape = true;
continue;
}
sb.Append(c);
}
return sb.ToString();
}
public static string Quote(string s, char quoteChar, char escapeChar)
{
using var writer = new StringWriter();
Quote(writer, s, quoteChar, escapeChar);
return writer.ToString();
}
public static void Quote(TextWriter writer, string s, char quoteChar, char escapeChar)
{
if (s.Length == 0)
{
writer.Write(quoteChar);
writer.Write(quoteChar);
return;
}
using var quoter = new QuotedWriter(writer, quoteChar, escapeChar, true);
quoter.Write(s);
}
#endregion
#region equalities and comparisons
public static bool EnumerableEquals<T>(IEnumerable<T> a, IEnumerable<T> b, IEqualityComparer<T>? elementComparer = null) => ReferenceEquals(a, b) || a is not null && b is not null && a.SequenceEqual(b, elementComparer);
public static bool DictionaryEquals<K, V>(IDictionary<K, V> a, IDictionary<K, V> b, IEqualityComparer<V>? valueComparer = null)
{
if (ReferenceEquals(a, b))
{
return true;
}
if (a is null || b is null)
{
return false;
}
var n = a.Count;
if (n != b.Count)
{
return false;
}
if (n is 0)
{
return true;
}
valueComparer ??= EqualityComparer<V>.Default;
foreach (var entry in a)
{
if (!b.TryGetValue(entry.Key, out var value) || !valueComparer.Equals(entry.Value, value))
{
return false;
}
}
return true;
}
#endregion
}

144
Test/MapReader.cs Executable file
View File

@@ -0,0 +1,144 @@
// using System.Drawing;
// using Nbt.Serialization;
// using Nbt.Tag;
// namespace Nbt.Test;
// public static class MapReader
// {
// private static readonly Color[] colorTable = [
// Color.Transparent,
// Color.FromArgb(127, 178, 56),
// Color.FromArgb(247, 233, 163),
// Color.FromArgb(199, 199, 199),
// Color.FromArgb(255, 0, 0),
// Color.FromArgb(160, 160, 255),
// Color.FromArgb(167, 167, 167),
// Color.FromArgb(0, 124, 0),
// Color.FromArgb(255, 255, 255),
// Color.FromArgb(164, 168, 184),
// Color.FromArgb(151, 109, 77),
// Color.FromArgb(112, 112, 112),
// Color.FromArgb(64, 64, 255),
// Color.FromArgb(143, 119, 72),
// Color.FromArgb(255, 252, 245),
// Color.FromArgb(216, 127, 51),
// Color.FromArgb(178, 76, 216),
// Color.FromArgb(102, 153, 216),
// Color.FromArgb(229, 229, 51),
// Color.FromArgb(127, 204, 25),
// Color.FromArgb(242, 127, 165),
// Color.FromArgb(76, 76, 76),
// Color.FromArgb(153, 153, 153),
// Color.FromArgb(76, 127, 153),
// Color.FromArgb(127, 63, 178),
// Color.FromArgb(51, 76, 178),
// Color.FromArgb(102, 76, 51),
// Color.FromArgb(102, 127, 51),
// Color.FromArgb(153, 51, 51),
// Color.FromArgb(25, 25, 25),
// Color.FromArgb(250, 238, 77),
// Color.FromArgb(92, 219, 213),
// Color.FromArgb(74, 128, 255),
// Color.FromArgb(0, 217, 58),
// Color.FromArgb(129, 86, 49),
// Color.FromArgb(112, 2, 0),
// Color.FromArgb(209, 177, 161),
// Color.FromArgb(159, 82, 36),
// Color.FromArgb(149, 87, 108),
// Color.FromArgb(112, 108, 138),
// Color.FromArgb(186, 133, 36),
// Color.FromArgb(103, 117, 53),
// Color.FromArgb(160, 77, 78),
// Color.FromArgb(57, 41, 35),
// Color.FromArgb(135, 107, 98),
// Color.FromArgb(87, 92, 92),
// Color.FromArgb(122, 73, 88),
// Color.FromArgb(76, 62, 92),
// Color.FromArgb(76, 50, 35),
// Color.FromArgb(76, 82, 42),
// Color.FromArgb(142, 60, 46),
// Color.FromArgb(37, 22, 16),
// Color.FromArgb(189, 48, 49),
// Color.FromArgb(148, 63, 97),
// Color.FromArgb(92, 25, 29),
// Color.FromArgb(22, 126, 134),
// Color.FromArgb(58, 142, 140),
// Color.FromArgb(86, 44, 62),
// Color.FromArgb(20, 180, 133),
// Color.FromArgb(100, 100, 100),
// Color.FromArgb(216, 175, 147),
// Color.FromArgb(127, 167, 150)
// ];
// private static readonly double[] multiplierTable = [
// 180.0 / 255.0,
// 220.0 / 255.0,
// 1.0,
// 135.0 / 255.0
// ];
// public static void Main(string[] args)
// {
// const string inputDir = @"C:\Users\hb68\Desktop\a";
// const string outputDir = @"C:\Users\hb68\Desktop\b";
// const int width = 128, height = 128;
// const int pixelSize = 8;
// Parallel.ForEach(Directory.EnumerateFiles(inputDir), static file =>
// {
// INbtTag rootTag;
// using (var inputStream = File.OpenRead(file))
// {
// var reader = NbtReader.Create(inputStream);
// rootTag = reader.ReadNamedTag().Tag;
// }
// var colors = rootTag["data"]["colors"].AsArray<sbyte>().Value;
// using var image = new Bitmap(width * pixelSize, height * pixelSize);
// var k = 0;
// for (var i = 0; i < height; i++)
// {
// for (var j = 0; j < width; j++)
// {
// var colorId = (byte)colors[k];
// var baseColorId = colorId / 4;
// var colorOffset = colorId % 4;
// var baseColor = colorTable[baseColorId];
// var multiplier = multiplierTable[colorOffset];
// var color = Color.FromArgb(baseColor.A, (int)(baseColor.R * multiplier), (int)(baseColor.G * multiplier), (int)(baseColor.B * multiplier));
// if (pixelSize > 1)
// {
// var imgI = i * pixelSize;
// var imgJ = j * pixelSize;
// for (var xOff = 0; xOff < pixelSize; xOff++)
// {
// for (var yOff = 0; yOff < pixelSize; yOff++)
// {
// image.SetPixel(imgJ + xOff, imgI + yOff, color);
// }
// }
// }
// else
// {
// image.SetPixel(j, i, color);
// }
// k++;
// }
// }
// var fileName = Path.GetFileName(file);
// image.Save(Path.Combine(outputDir, $"{fileName}.png"), System.Drawing.Imaging.ImageFormat.Png);
// Console.WriteLine(fileName);
// });
// }
// }

128
Test/Program.cs Executable file
View File

@@ -0,0 +1,128 @@
namespace Nbt.Test;
using System.IO.Compression;
using K4os.Compression.LZ4.Streams;
using Nbt.Serialization;
using Nbt.Tag;
public static class Program
{
public static void Main(string[] args)
{
WorldFileReading();
}
private static void Test()
{
const string filePath = @"D:\Minecraft\game\profiles\test\saves\creatif\players\hbdu68.dat";
INbtTag rootTag;
using (var inputStream = File.OpenRead(filePath))
{
using var reader = NbtReader.Create(inputStream);
rootTag = reader.ReadNamedTag().Tag;
}
// SNbt.Serialize(Console.Out, rootTag, new() { Style = SNbt.SerializationStyle.Indented });
var s = JsonNbt.Serialize(rootTag);
var t = JsonNbt.Deserialize(s);
Console.WriteLine(rootTag.Equals(t));
}
private static void WorldFileReading()
{
const string inputFile = @"/home/hbecher/Téléchargements/sc-murder/region/r.-3.0.mca";
// chunk coordinates and last modified timestamps
// two 4kiB tables (1024 ints)
var allOfTheShite = new NbtCompound();
Span<byte> buf4 = stackalloc byte[4];
using var inputStream = File.OpenRead(inputFile);
for (var chunkX = 0; chunkX < 32; chunkX++)
{
for (var chunkZ = 0; chunkZ < 32; chunkZ++)
{
var chunkOffset = (chunkX + (chunkZ << 5)) << 2;
inputStream.Position = chunkOffset;
inputStream.Read(buf4);
buf4.Reverse();
var rawLoc = BitConverter.ToUInt32(buf4);
var offset = (int)((rawLoc & ~0xFFu) << 4);
var size = (int)((rawLoc & 0xFFu) << 12);
if (offset == 0 && size == 0)
{
continue;
}
using (inputStream.Mark())
{
inputStream.Position += 1024 * 4;
inputStream.Read(buf4);
}
buf4.Reverse();
var timestamp = BitConverter.ToInt32(buf4);
using (inputStream.Mark())
{
inputStream.Position = offset;
inputStream.Read(buf4);
buf4.Reverse();
var length = BitConverter.ToInt32(buf4);
var compressionMode = inputStream.ReadByte();
Stream inflaterStream;
bool leaveOpen = false;
switch (compressionMode)
{
case 1:
inflaterStream = new GZipStream(inputStream, CompressionMode.Decompress, true);
break;
case 2:
inflaterStream = new ZLibStream(inputStream, CompressionMode.Decompress, true);
break;
case 3:
inflaterStream = inputStream;
leaveOpen = true;
break;
case 4:
inflaterStream = LZ4Stream.Decode(inputStream, leaveOpen: true);
break;
default:
continue;
}
INbtTag tag;
using (var nbtReader = new NbtReader(inflaterStream, leaveOpen))
{
tag = nbtReader.ReadNamedTag().Tag;
}
allOfTheShite[$"{chunkX}-{chunkZ}"] = tag;
}
}
}
using var outputStream = File.CreateText(@"/home/hbecher/Téléchargements/a.snbt");
// SNbt.Serialize(outputStream, allOfTheShite, new() { Style = SNbt.SerializationStyle.Indented });
JsonNbt.ToJson(outputStream, allOfTheShite);
}
}

6
Test/StreamExtensions.cs Executable file
View File

@@ -0,0 +1,6 @@
namespace Nbt.Test;
public static class StreamExtensions
{
public static StreamMark Mark(this Stream stream) => new(stream);
}

9
Test/StreamMark.cs Executable file
View File

@@ -0,0 +1,9 @@
namespace Nbt.Test;
public readonly struct StreamMark(Stream stream) : IDisposable
{
private readonly Stream _stream = stream;
private readonly long _pos = stream.Position;
public void Dispose() => _stream.Position = _pos;
}

15
Test/Test.csproj Executable file
View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<StartupObject></StartupObject>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Nbt\Nbt.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Drawing.Common" Version="8.0.2" />
</ItemGroup>
</Project>