Initial sync
This commit is contained in:
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
/.vs
|
||||
/.vscode
|
||||
|
||||
bin/
|
||||
obj/
|
||||
|
||||
/Nbt/Properties
|
||||
31
NamedBinaryTag.sln
Executable file
31
NamedBinaryTag.sln
Executable 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
10
Nbt/Exceptions/NbtException.cs
Executable 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) { }
|
||||
}
|
||||
22
Nbt/Exceptions/TagNotFoundException.cs
Executable file
22
Nbt/Exceptions/TagNotFoundException.cs
Executable 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; }
|
||||
// }
|
||||
16
Nbt/Exceptions/UnknownCompressionSchemeException.cs
Executable file
16
Nbt/Exceptions/UnknownCompressionSchemeException.cs
Executable 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;
|
||||
}
|
||||
}
|
||||
16
Nbt/Exceptions/UnknownTagTypeException.cs
Executable file
16
Nbt/Exceptions/UnknownTagTypeException.cs
Executable 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; }
|
||||
}
|
||||
8
Nbt/Exceptions/UnsupportedPathElementException.cs
Executable file
8
Nbt/Exceptions/UnsupportedPathElementException.cs
Executable file
@@ -0,0 +1,8 @@
|
||||
namespace Nbt;
|
||||
|
||||
public class UnsupportedPathElementException : NbtException
|
||||
{
|
||||
public UnsupportedPathElementException() : base() { }
|
||||
|
||||
public UnsupportedPathElementException(string? message) : base(message) { }
|
||||
}
|
||||
8
Nbt/Exceptions/WrongTagTypeException.cs
Executable file
8
Nbt/Exceptions/WrongTagTypeException.cs
Executable 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
19
Nbt/Nbt.csproj
Executable 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
139
Nbt/NbtPath.cs
Executable 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
19
Nbt/NbtTagType.cs
Executable 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
160
Nbt/NbtUtils.cs
Executable 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;
|
||||
}
|
||||
}
|
||||
11
Nbt/Serialization/Compression.cs
Executable file
11
Nbt/Serialization/Compression.cs
Executable 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
640
Nbt/Serialization/JsonNbt.cs
Executable 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
249
Nbt/Serialization/NbtReader.cs
Executable 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
185
Nbt/Serialization/NbtWriter.cs
Executable 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
401
Nbt/Serialization/SNbt.cs
Executable 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
8
Nbt/Tag/Array/NbtByteArray.cs
Executable 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
8
Nbt/Tag/Array/NbtIntArray.cs
Executable 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
8
Nbt/Tag/Array/NbtLongArray.cs
Executable 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
3
Nbt/Tag/NamedTag.cs
Executable file
@@ -0,0 +1,3 @@
|
||||
namespace Nbt.Tag;
|
||||
|
||||
public readonly record struct NamedTag(string Name, INbtTag Tag) { }
|
||||
28
Nbt/Tag/NbtArray.cs
Executable file
28
Nbt/Tag/NbtArray.cs
Executable 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
117
Nbt/Tag/NbtCompound.cs
Executable 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
19
Nbt/Tag/NbtEnd.cs
Executable 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
227
Nbt/Tag/NbtList.cs
Executable 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
68
Nbt/Tag/NbtTag.cs
Executable 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
64
Nbt/Tag/NbtValue.cs
Executable 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
8
Nbt/Tag/Value/NbtByte.cs
Executable 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
8
Nbt/Tag/Value/NbtDouble.cs
Executable 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
8
Nbt/Tag/Value/NbtFloat.cs
Executable 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
8
Nbt/Tag/Value/NbtInt.cs
Executable 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
8
Nbt/Tag/Value/NbtLong.cs
Executable 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
8
Nbt/Tag/Value/NbtShort.cs
Executable 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
8
Nbt/Tag/Value/NbtString.cs
Executable 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);
|
||||
}
|
||||
14
Nbt/Type/Array/NbtByteArrayType.cs
Executable file
14
Nbt/Type/Array/NbtByteArrayType.cs
Executable 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);
|
||||
}
|
||||
14
Nbt/Type/Array/NbtIntArrayType.cs
Executable file
14
Nbt/Type/Array/NbtIntArrayType.cs
Executable 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
14
Nbt/Type/Array/NbtLongArray.cs
Executable 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
43
Nbt/Type/NbtArrayType.cs
Executable 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
45
Nbt/Type/NbtCompoundType.cs
Executable 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
17
Nbt/Type/NbtEndType.cs
Executable 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
38
Nbt/Type/NbtListType.cs
Executable 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
98
Nbt/Type/NbtRegistry.cs
Executable 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
33
Nbt/Type/NbtType.cs
Executable 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
15
Nbt/Type/NbtValueType.cs
Executable 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
19
Nbt/Type/Value/NbtByteType.cs
Executable 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
19
Nbt/Type/Value/NbtDoubleType.cs
Executable 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
19
Nbt/Type/Value/NbtFloatType.cs
Executable 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
19
Nbt/Type/Value/NbtIntType.cs
Executable 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
19
Nbt/Type/Value/NbtLongType.cs
Executable 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
19
Nbt/Type/Value/NbtShortType.cs
Executable 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
19
Nbt/Type/Value/NbtStringType.cs
Executable 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
91
Nbt/Util/IndentedWriter.cs
Executable 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
128
Nbt/Util/QuotedWriter.cs
Executable 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
251
Nbt/Util/Utils.cs
Executable 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
144
Test/MapReader.cs
Executable 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
128
Test/Program.cs
Executable 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
6
Test/StreamExtensions.cs
Executable 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
9
Test/StreamMark.cs
Executable 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
15
Test/Test.csproj
Executable 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>
|
||||
Reference in New Issue
Block a user