1
0

Initial sync

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

View File

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

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

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

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

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

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

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

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

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