Port NBT impl from LibAC Dart

This commit is contained in:
zontreck 2024-12-16 04:25:24 -07:00
parent 0a022634c1
commit ad7b619706
55 changed files with 3226 additions and 2983 deletions

358
NBT/API/ByteLayer.cs Normal file
View file

@ -0,0 +1,358 @@
using System;
using System.IO;
using System.Text;
using System.Linq;
using System.IO.Compression;
using System.Collections.Generic;
public class ByteLayer
{
private byte[] _byteBuffer;
private int _position;
public ByteLayer()
{
_byteBuffer = new byte[0];
_position = 0;
}
public int Length => _byteBuffer.Length;
public int CurrentPosition => _position;
public byte[] Bytes => _byteBuffer.Take(_position).ToArray();
private void EnsureCapacity(int additionalBytes)
{
int requiredCapacity = _position + additionalBytes;
if (requiredCapacity > _byteBuffer.Length)
{
Array.Resize(ref _byteBuffer, requiredCapacity);
}
}
public void WriteInt(int value)
{
EnsureCapacity(4);
Array.Copy(BitConverter.GetBytes(value), 0, _byteBuffer, _position, 4);
if (BitConverter.IsLittleEndian)
Array.Reverse(_byteBuffer, _position, 4);
_position += 4;
}
public int ReadInt()
{
if (_position + 4 > _byteBuffer.Length) throw new InvalidOperationException("Buffer overflow.");
var value = _byteBuffer.Skip(_position).Take(4).ToArray();
if (BitConverter.IsLittleEndian) Array.Reverse(value);
_position += 4;
return BitConverter.ToInt32(value, 0);
}
public void WriteDouble(double value)
{
EnsureCapacity(8);
var bytes = BitConverter.GetBytes(value);
if (BitConverter.IsLittleEndian)
Array.Reverse(bytes);
Array.Copy(bytes, 0, _byteBuffer, _position, 8);
_position += 8;
}
public double ReadDouble()
{
if (_position + 8 > _byteBuffer.Length) throw new InvalidOperationException("Buffer overflow.");
var value = _byteBuffer.Skip(_position).Take(8).ToArray();
if (BitConverter.IsLittleEndian) Array.Reverse(value);
_position += 8;
return BitConverter.ToDouble(value, 0);
}
public void WriteFloat(float value)
{
EnsureCapacity(4);
var bytes = BitConverter.GetBytes(value);
if(BitConverter.IsLittleEndian)
Array.Reverse(bytes);
Array.Copy(bytes, 0, _byteBuffer, _position, 4);
_position += 4;
}
public float ReadFloat()
{
if (_position + 4 > _byteBuffer.Length) throw new InvalidOperationException("Buffer overflow.");
var value = _byteBuffer.Skip(_position).Take(4).ToArray();
if (BitConverter.IsLittleEndian) Array.Reverse(value);
_position += 4;
return BitConverter.ToSingle(value, 0);
}
public void WriteString(string value)
{
var encoded = Encoding.UTF8.GetBytes(value);
WriteShort((short)encoded.Length);
EnsureCapacity(encoded.Length);
Array.Copy(encoded, 0, _byteBuffer, _position, encoded.Length);
_position += encoded.Length;
}
public string ReadString()
{
int length = ReadShort();
if (_position + length > _byteBuffer.Length) throw new InvalidOperationException("Buffer overflow.");
var value = Encoding.UTF8.GetString(_byteBuffer, _position, length);
_position += length;
return value;
}
public void WriteShort(short value)
{
EnsureCapacity(2);
var bytes = BitConverter.GetBytes((short)value);
if (BitConverter.IsLittleEndian)
Array.Reverse(bytes);
Array.Copy(bytes, 0, _byteBuffer, _position, 2);
_position += 2;
}
public short ReadShort()
{
if (_position + 2 > _byteBuffer.Length) throw new InvalidOperationException("Buffer overflow.");
var value = _byteBuffer.Skip(_position).Take(2).ToArray();
if (BitConverter.IsLittleEndian) Array.Reverse(value);
_position += 2;
return BitConverter.ToInt16(value, 0);
}
public void WriteByte(byte value)
{
EnsureCapacity(1);
_byteBuffer[_position] = value;
_position++;
}
public void WriteBytes(List<byte> bytes)
{
EnsureCapacity(bytes.Count);
Array.Copy(bytes.ToArray(), 0, _byteBuffer, _position, bytes.Count);
_position += bytes.Count;
}
public byte ReadByte()
{
if (_position >= _byteBuffer.Length) throw new InvalidOperationException("Buffer overflow.");
return _byteBuffer[_position++];
}
public void ResetPosition() => _position = 0;
public void RestorePosition(int position)
{
if (position < 0 || position > _byteBuffer.Length)
throw new ArgumentOutOfRangeException(nameof(position));
_position = position;
}
public void Clear()
{
_position = 0;
_byteBuffer = new byte[0];
}
public void WriteToFile(string filePath)
{
File.WriteAllBytes(filePath, Bytes);
}
public void ReadFromFile(string filePath)
{
if (!File.Exists(filePath)) throw new FileNotFoundException("File does not exist.", filePath);
_byteBuffer = File.ReadAllBytes(filePath);
ResetPosition();
}
public void Compress()
{
using var memoryStream = new MemoryStream();
using (var gzipStream = new GZipStream(memoryStream, CompressionMode.Compress))
{
gzipStream.Write(_byteBuffer, 0, _byteBuffer.Length);
}
_byteBuffer = memoryStream.ToArray();
_position = _byteBuffer.Length;
}
public void Decompress()
{
using var memoryStream = new MemoryStream(_byteBuffer);
using var gzipStream = new GZipStream(memoryStream, CompressionMode.Decompress);
using var decompressedStream = new MemoryStream();
gzipStream.CopyTo(decompressedStream);
_byteBuffer = decompressedStream.ToArray();
_position = _byteBuffer.Length;
}
public void WriteVarInt(int value)
{
while ((value & ~0x7F) != 0)
{
WriteByte((byte)((value & 0x7F) | 0x80));
value >>= 7;
}
WriteByte((byte)(value & 0x7F));
}
public int ReadVarInt()
{
int result = 0;
int shift = 0;
int byteRead;
do
{
byteRead = ReadByte();
result |= (byteRead & 0x7F) << shift;
shift += 7;
} while ((byteRead & 0x80) != 0);
return result;
}
public void WriteVarIntNoZigZag(int value)
{
while ((value & ~0x7F) != 0)
{
WriteByte((byte)((value & 0x7F) | 0x80));
value >>= 7;
}
WriteByte((byte)(value & 0x7F));
}
public int ReadVarIntNoZigZag()
{
int result = 0;
int shift = 0;
int byteRead;
do
{
byteRead = ReadByte();
result |= (byteRead & 0x7F) << shift;
shift += 7;
} while ((byteRead & 0x80) != 0);
return result;
}
public void WriteLong(long value)
{
EnsureCapacity(8);
Array.Copy(BitConverter.GetBytes(value), 0, _byteBuffer, _position, 8);
if (BitConverter.IsLittleEndian)
Array.Reverse(_byteBuffer, _position, 8);
_position += 8;
}
public long ReadLong()
{
if (_position + 8 > _byteBuffer.Length) throw new InvalidOperationException("Buffer overflow.");
var value = _byteBuffer.Skip(_position).Take(8).ToArray();
if (BitConverter.IsLittleEndian) Array.Reverse(value);
_position += 8;
return BitConverter.ToInt64(value, 0);
}
public void WriteVarLongZigZag(long value)
{
value = (value << 1) ^ (value >> 63);
WriteVarLongNoZigZag(value);
}
public void WriteVarLongNoZigZag(long value)
{
while ((value & ~0x7F) != 0)
{
WriteByte((byte)((value & 0x7F) | 0x80));
value >>= 7;
}
WriteByte((byte)(value & 0x7F));
}
public long ReadVarLongZigZag()
{
long result = ReadVarLongNoZigZag();
return (result >> 1) ^ -(result & 1);
}
public long ReadVarLongNoZigZag()
{
long result = 0;
int shift = 0;
int byteRead;
do
{
byteRead = ReadByte();
result |= (byteRead & 0x7F) << shift;
shift += 7;
} while ((byteRead & 0x80) != 0);
return result;
}
public void SetBit(int position, byte maskToSet)
{
if (position < _byteBuffer.Length)
{
Seek(position);
byte current = ReadByte();
Seek(position);
current |= maskToSet;
WriteByte(current);
}
}
public void ClearBit(int position, byte maskToClear)
{
if (position < _byteBuffer.Length)
{
Seek(position);
byte current = ReadByte();
current &= (byte)~maskToClear;
Seek(position);
WriteByte(current);
}
}
public bool CheckBit(int position, byte mask)
{
if (position < _byteBuffer.Length)
{
Seek(position);
byte current = ReadByte();
return (current & mask) == mask;
}
return false;
}
public byte GetBit(int position)
{
if (position < _byteBuffer.Length)
{
Seek(position);
return ReadByte();
}
return 0;
}
public void Seek(int position)
{
if (position < 0 || position > _byteBuffer.Length)
throw new ArgumentOutOfRangeException(nameof(position));
_position = position;
}
public void InsertRandomBytes(int count)
{
Random rng = new Random();
for (int i = 0; i < count; i++)
{
WriteByte((byte)rng.Next(256));
}
}
}

View file

@ -0,0 +1,8 @@
using System;
namespace LibAC.NBT.API
{
public class InvalidNbtDataException : Exception
{
}
}

32
NBT/API/NBTAccountant.cs Normal file
View file

@ -0,0 +1,32 @@
namespace LibAC.NBT.API;
using System;
public class NBTAccountant
{
private static int _prettyIndex = 0;
public static void PrintRead(Tag tag)
{
tag.PrettyPrint(_prettyIndex, false);
}
public static void VisitTag()
{
_prettyIndex++;
}
public static void LeaveTag(Tag tag)
{
if (tag is CompoundTag ct)
{
ct.EndPrettyPrint(_prettyIndex);
}
else if (tag is ListTag lt)
{
lt.EndPrettyPrint(_prettyIndex);
}
_prettyIndex--;
}
}

132
NBT/API/NbtIo.cs Normal file
View file

@ -0,0 +1,132 @@
using System;
using System.IO;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
using LibAC.NBT;
namespace LibAC.NBT.API
{
public class NbtIo
{
private static ByteLayer _io = new ByteLayer();
private static void _Read(string file)
{
_io = new ByteLayer();
_io.ReadFromFile(file);
}
public static CompoundTag Read(string file)
{
_Read(file);
if (_io.ReadByte() == (byte)TagType.Compound)
{
_io.ResetPosition();
return (CompoundTag)Tag.ReadNamedTag(_io);
}
else
{
return ReadCompressed(file);
}
}
public static CompoundTag ReadCompressed(string file)
{
_io = new ByteLayer();
_io.ReadFromFile(file);
_io.Decompress();
_io.ResetPosition();
return (CompoundTag)Tag.ReadNamedTag(_io);
}
public static void Write(string file, CompoundTag tag)
{
_io = new ByteLayer();
Tag.WriteNamedTag(tag, _io);
_io.WriteToFile(file);
}
public static void WriteCompressedAsync(string file, CompoundTag tag)
{
_io = new ByteLayer();
Tag.WriteNamedTag(tag, _io);
_io.Compress();
_io.WriteToFile(file);
}
public static ByteLayer GetStream()
{
return _io;
}
public static string WriteBase64String(CompoundTag tag)
{
_io = new ByteLayer();
Tag.WriteNamedTag(tag, _io);
_io.Compress();
return Convert.ToBase64String(_io.Bytes);
}
public static CompoundTag ReadBase64String(string encoded)
{
_io = new ByteLayer();
byte[] bytes = Convert.FromBase64String(encoded);
foreach (byte b in bytes)
{
_io.WriteByte(b);
}
_io.Decompress();
_io.ResetPosition();
return (CompoundTag)Tag.ReadNamedTag(_io);
}
public static byte[] WriteToStreamCompressed(CompoundTag tag)
{
_io = new ByteLayer();
Tag.WriteNamedTag(tag, _io);
_io.Compress();
return _io.Bytes;
}
public static CompoundTag ReadFromStreamCompressed(byte[] list)
{
_io = new ByteLayer();
try
{
_io.WriteBytes(list.ToList());
_io.ResetPosition();
_io.Decompress();
_io.ResetPosition();
}
catch (Exception e)
{
Console.WriteLine(e);
}
return (CompoundTag)Tag.ReadNamedTag(_io);
}
public static async Task<byte[]> WriteToStreamAsync(CompoundTag tag)
{
_io = new ByteLayer();
Tag.WriteNamedTag(tag, _io);
return _io.Bytes;
}
public static CompoundTag ReadFromStream(byte[] list)
{
_io = new ByteLayer();
try
{
_io.WriteBytes(list.ToList());
_io.ResetPosition();
}
catch (Exception e)
{
Console.WriteLine(e);
}
return (CompoundTag)Tag.ReadNamedTag(_io);
}
}
}

43
NBT/API/NbtUUID.cs Normal file
View file

@ -0,0 +1,43 @@
using System;
using LibAC.Utilities;
namespace LibAC.NBT.API
{
public class NbtUUID
{
public long MSB { get; }
public long LSB { get; }
public static readonly NbtUUID ZERO = new NbtUUID(UUID.ZERO);
public NbtUUID(long msb, long lsb)
{
MSB = msb;
LSB = lsb;
}
public NbtUUID(UUID uuid)
{
byte[] uuidBytes = uuid.GetBytes();
MSB = BitConverter.ToInt64(uuidBytes, 0);
LSB = BitConverter.ToInt64(uuidBytes, 8);
}
public long GetMostSignificantBits() => MSB;
public long GetLeastSignificantBits() => LSB;
public UUID ToUUID()
{
byte[] bytes = new byte[16];
BitConverter.GetBytes(MSB).CopyTo(bytes, 0);
BitConverter.GetBytes(LSB).CopyTo(bytes, 8);
return new UUID(bytes);
}
public override string ToString()
{
return ToUUID().ToString();
}
}
}

142
NBT/API/NbtUtils.cs Normal file
View file

@ -0,0 +1,142 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LibAC.NBT.API;
using LibAC.Utilities;
namespace LibAC.NBT.API
{
public static class NbtUtils
{
public static void WriteBoolean(CompoundTag tag, string name, bool value)
{
tag.Put(name, ByteTag.ValueOf(value ? (byte)1 : (byte)0));
}
public static bool ReadBoolean(CompoundTag tag, string name)
{
if (tag.ContainsKey(name))
{
return tag.Get(name).AsByte() == 1;
}
return false;
}
public static void WriteVector2d(CompoundTag tag, string name, Vector2d pos)
{
var posTag = new ListTag();
posTag.Add(DoubleTag.ValueOf(pos.X));
posTag.Add(DoubleTag.ValueOf(pos.Z));
tag.Put(name, posTag);
}
public static Vector2d ReadVector2d(CompoundTag tag, string name)
{
if (tag.ContainsKey(name))
{
var listTag = (ListTag)tag.Get(name);
return new Vector2d(listTag.Get(0).AsDouble(), listTag.Get(1).AsDouble());
}
return Vector2d.ZERO;
}
public static void WriteVector2i(CompoundTag tag, string name, Vector2i pos)
{
var posTag = new ListTag();
posTag.Add(IntTag.ValueOf(pos.X));
posTag.Add(IntTag.ValueOf(pos.Z));
tag.Put(name, posTag);
}
public static Vector2i ReadVector2i(CompoundTag tag, string name)
{
if (tag.ContainsKey(name))
{
var listTag = (ListTag)tag.Get(name);
return new Vector2i(listTag.Get(0).AsInt(), listTag.Get(1).AsInt());
}
return Vector2i.ZERO;
}
public static void WriteVector3d(CompoundTag tag, string name, Vector3d pos)
{
var posTag = new ListTag();
posTag.Add(DoubleTag.ValueOf(pos.X));
posTag.Add(DoubleTag.ValueOf(pos.Y));
posTag.Add(DoubleTag.ValueOf(pos.Z));
tag.Put(name, posTag);
}
public static Vector3d ReadVector3d(CompoundTag tag, string name)
{
if (tag.ContainsKey(name))
{
var listTag = (ListTag)tag.Get(name);
return new Vector3d(
listTag.Get(0).AsDouble(),
listTag.Get(1).AsDouble(),
listTag.Get(2).AsDouble());
}
return Vector3d.ZERO;
}
public static void WriteVector3i(CompoundTag tag, string name, Vector3i pos)
{
var posTag = new ListTag();
posTag.Add(IntTag.ValueOf(pos.X));
posTag.Add(IntTag.ValueOf(pos.Y));
posTag.Add(IntTag.ValueOf(pos.Z));
tag.Put(name, posTag);
}
public static Vector3i ReadVector3i(CompoundTag tag, string name)
{
if (tag.ContainsKey(name))
{
var listTag = (ListTag)tag.Get(name);
return new Vector3i(
listTag.Get(0).AsInt(),
listTag.Get(1).AsInt(),
listTag.Get(2).AsInt());
}
return Vector3i.ZERO;
}
private static int[] MsbLsbToIntArray(long msb, long lsb)
{
return new[]
{
(int)(msb >> 32),
(int)msb,
(int)(lsb >> 32),
(int)lsb
};
}
private static int[] UuidToIntArray(NbtUUID id)
{
return MsbLsbToIntArray(id.GetMostSignificantBits(), id.GetLeastSignificantBits());
}
private static NbtUUID UuidFromIntArray(int[] values)
{
return new NbtUUID(
((long)values[0] << 32) | (values[1] & 0xFFFFFFFFL),
((long)values[2] << 32) | (values[3] & 0xFFFFFFFFL));
}
public static void WriteUUID(CompoundTag tag, string name, NbtUUID id)
{
tag.Put(name, IntArrayTag.ValueOf(UuidToIntArray(id).ToList()));
}
public static NbtUUID ReadUUID(CompoundTag tag, string name)
{
if (!tag.ContainsKey(name))
{
return NbtUUID.ZERO;
}
return UuidFromIntArray(tag.Get(name).AsIntArray().ToArray());
}
}
}

92
NBT/API/SnbtIo.cs Normal file
View file

@ -0,0 +1,92 @@
using System;
using System.IO;
using System.Threading.Tasks;
using System.Text;
using LibAC.NBT.API;
namespace LibAC.NBT.API
{
public static class SnbtIo
{
/// <summary>
/// Writes a CompoundTag to a file in SNBT format.
/// </summary>
/// <param name="file">The file path to write to.</param>
/// <param name="tag">The CompoundTag to write.</param>
public static async Task WriteToFile(string file, CompoundTag tag)
{
var fileInfo = new FileInfo(file);
if (fileInfo.Exists)
{
fileInfo.Delete(); // Ensure the file is reset to 0 bytes.
}
var builder = new StringBuilder();
Tag.WriteStringifiedNamedTag(tag, builder, 0);
using (var writer = new StreamWriter(file))
{
await writer.WriteAsync(builder.ToString());
}
}
/// <summary>
/// Reads a tag from a file in SNBT format.
/// </summary>
/// <param name="file">The file path to read from.</param>
/// <returns>A Task resolving to the read Tag.</returns>
public static async Task<Tag> ReadFromFile(string file)
{
var fileInfo = new FileInfo(file);
if (!fileInfo.Exists)
{
throw new FileNotFoundException($"File not found: {file}");
}
string data;
using (var reader = new StreamReader(file))
{
data = await reader.ReadToEndAsync();
}
var stringReader = new StringReader(data);
Tag tag = new CompoundTag();
try
{
tag = Tag.ReadStringifiedNamedTag(stringReader);
}
catch (Exception ex)
{
Console.WriteLine($"FATAL ERROR OCCURRED AT LOCATION:\n{stringReader.GetSnapshot()}");
Console.WriteLine(ex);
}
return tag;
}
/// <summary>
/// Converts a CompoundTag to its SNBT string representation.
/// </summary>
/// <param name="tag">The CompoundTag to convert.</param>
/// <returns>A string representing the CompoundTag in SNBT format.</returns>
public static string WriteToString(CompoundTag tag)
{
var builder = new StringBuilder();
Tag.WriteStringifiedNamedTag(tag, builder, 0);
return builder.ToString();
}
/// <summary>
/// Reads a Tag from an SNBT string.
/// </summary>
/// <param name="data">The SNBT string to read.</param>
/// <returns>A Task resolving to the read Tag.</returns>
public static Task<Tag> ReadFromString(string data)
{
var stringReader = new StringReader(data);
return Task.FromResult(Tag.ReadStringifiedNamedTag(stringReader));
}
}
}

32
NBT/API/StringBuilder.cs Normal file
View file

@ -0,0 +1,32 @@
using System.Text;
namespace LibAC.NBT.API
{
public class StringBuilder
{
private string _buffer = string.Empty;
public StringBuilder()
{
}
public bool IsEmpty => string.IsNullOrEmpty(_buffer);
public int Length => _buffer.Length;
public void Append(string value)
{
_buffer += value;
}
public void Clear()
{
_buffer = string.Empty;
}
public override string ToString()
{
return _buffer;
}
}
}

196
NBT/API/StringReader.cs Normal file
View file

@ -0,0 +1,196 @@
using System;
using System.Text;
namespace LibAC.NBT.API
{
public class StringReader
{
private readonly string _buffer;
private int _position = 0;
private int _lastPosition = 0;
private bool _quotedString = false;
public StringReader(string buffer)
{
_buffer = buffer;
}
// Check if there's more to read
public bool CanRead => _CanRead();
private bool _CanRead()
{
SkipWhitespace();
return _position < _buffer.Length;
}
// Get the number of chars seeked
public int Seeked => _lastPosition - _position;
// Read the next character
public char Next()
{
if (CanRead)
{
SkipWhitespace();
return _buffer[_position++];
}
else
{
throw new Exception("End of buffer reached");
}
}
// Generates a snapshot of the text location if applicable
public string GetSnapshot()
{
if (CanRead)
{
if (_position + 64 < _buffer.Length)
{
return _buffer.Substring(_position, 64);
}
else
{
return _buffer.Substring(_position);
}
}
else
{
return string.Empty;
}
}
// Peek the next character without advancing the position
public char Peek()
{
SkipWhitespace();
if (CanRead)
{
return _buffer[_position];
}
else
{
throw new Exception("End of buffer reached");
}
}
// Skip any whitespace characters
public void SkipWhitespace()
{
if (_quotedString) return; // We need whitespace for strings
while (_position < _buffer.Length && IsWhitespace(_buffer[_position]))
{
_position++;
}
}
// Check if a character is a whitespace
private bool IsWhitespace(char character)
{
return char.IsWhiteSpace(character);
}
// Read until a specific character is found
public string ReadUntil(char stopChar, int maxChars)
{
var result = new StringBuilder();
int index = 0;
while (CanRead && Peek() != stopChar && index < maxChars)
{
result.Append(Next().ToString());
index++;
}
return result.ToString();
}
// Read a string enclosed in double quotes
public string ReadQuotedString()
{
_quotedString = true;
if (Next() != '"')
{
throw new Exception("Expected double quotes at the start of a string");
}
var result = new StringBuilder();
while (CanRead)
{
char character = Next();
if (character == '"')
{
break;
}
result.Append(character.ToString());
}
_quotedString = false;
return result.ToString();
}
// Read a number (int or double)
public string ReadNumber()
{
var result = new StringBuilder();
while (CanRead && (IsDigit(Peek()) || Peek() == '.' || Peek() == '-'))
{
result.Append(Next().ToString());
}
return result.ToString();
}
// Check if a character is a digit
private bool IsDigit(char character)
{
return char.IsDigit(character);
}
// Read an unquoted string (used for keys in SNBT)
public string ReadUnquotedString()
{
var result = new StringBuilder();
while (CanRead &&
!IsWhitespace(Peek()) &&
Peek() != ':' &&
Peek() != ',' &&
Peek() != '{' &&
Peek() != '}' &&
Peek() != '[' &&
Peek() != ']')
{
result.Append(Next().ToString());
}
return result.ToString();
}
public string ReadString()
{
if (Peek() == '"')
{
return ReadQuotedString();
}
else
{
return ReadUnquotedString();
}
}
// Read a specific character and throw an exception if it's not found
public void Expect(char expectedChar)
{
if (char.ToLower(Next()) != char.ToLower(expectedChar))
{
throw new Exception($"Expected {expectedChar}");
}
}
public void StartSeek()
{
_lastPosition = _position;
}
public void EndSeek()
{
_position = _lastPosition;
_lastPosition = 0;
}
}
}

280
NBT/API/Tag.cs Normal file
View file

@ -0,0 +1,280 @@
using System.Collections.Generic;
namespace LibAC.NBT.API;
using System;
using System.Text;
public abstract class Tag
{
private TagType _parentTagType = TagType.End;
private Tag _parentTag;
private string _key;
public byte GetType()
{
return (byte)GetTagType();
}
public abstract TagType GetTagType();
public bool HasParent => _parentTag != null;
public void UpdateParent(Tag tag)
{
if (tag == null)
{
_parentTag = null;
SetParentTagType(TagType.End);
}
else
{
_parentTag = tag;
SetParentTagType(tag.GetTagType());
}
}
public Tag GetParent => _parentTag;
public TagType ParentTagType => _parentTagType;
public void SetParentTagType(TagType type)
{
_parentTagType = type;
}
public abstract void WriteValue(ByteLayer data);
public abstract void ReadValue(ByteLayer data);
public string GetKey()
{
return _key ?? string.Empty;
}
public void SetKey(string key)
{
_key = key;
}
public abstract dynamic GetValue();
public abstract void SetValue(dynamic val);
public static Tag ReadNamedTag(ByteLayer data)
{
var type = data.ReadByte();
if (type == 0)
{
return new EndTag();
}
else
{
Tag tag = MakeTagOfType(TagTypeExtensions.GetTagTypeFromByte(type));
tag._key = data.ReadString();
tag.ReadValue(data);
return tag;
}
}
public static void WriteNamedTag(Tag tag, ByteLayer data)
{
data.WriteByte(tag.GetType());
if (tag.GetType() != 0)
{
data.WriteString(tag.GetKey());
tag.WriteValue(data);
}
}
public static void WriteStringifiedNamedTag(Tag tag, StringBuilder builder, int indents)
{
if (tag.GetType() != 0)
{
if (builder.Length > 0)
{
if (string.IsNullOrEmpty(tag.GetKey()))
{
builder.Append(new string('\t', indents));
}
else
{
if (tag.ShouldQuoteName())
{
builder.Append(new string('\t', indents) + $"\"{tag.GetKey()}\": ");
}
else
{
builder.Append(new string('\t', indents) + $"{tag.GetKey()}: ");
}
}
}
tag.WriteStringifiedValue(builder, indents + 1, false);
}
}
public static Tag ReadStringifiedNamedTag(StringReader stringReader)
{
string name = stringReader.Peek() == '{' || stringReader.Peek() == '[' ? "" : stringReader.ReadString();
if (!string.IsNullOrEmpty(name)) stringReader.Expect(':');
TagType type = TagTypeExtensions.GetStringifiedTagType(stringReader);
Tag tag = MakeTagOfType(type);
tag._key = name;
tag.ReadStringifiedValue(stringReader);
return tag;
}
public bool ShouldQuoteName()
{
if (string.IsNullOrEmpty(GetKey()))
{
return false;
}
else
{
string letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
foreach (char c in GetKey())
{
if (!letters.Contains(c.ToString())) return true;
}
return false;
}
}
public bool ShouldQuote(string value)
{
if (string.IsNullOrEmpty(value)) return true;
string letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
foreach (char c in value)
{
if (!letters.Contains(c.ToString())) return true;
}
return false;
}
public abstract void WriteStringifiedValue(StringBuilder builder, int indent, bool isList);
public abstract void ReadStringifiedValue(StringReader reader);
public bool Equals(object obj)
{
if (obj == null || !(obj is Tag)) return false;
var tag = (Tag)obj;
if (tag.GetType() != GetType()) return false;
if (GetKey() != tag.GetKey()) return false;
return true;
}
public static Tag MakeTagOfType(TagType type)
{
switch (type)
{
case TagType.Byte: return new ByteTag();
case TagType.ByteArray: return new ByteArrayTag();
case TagType.Compound: return new CompoundTag();
case TagType.Double: return new DoubleTag();
case TagType.End: return new EndTag();
case TagType.Short: return new ShortTag();
case TagType.Int: return new IntTag();
case TagType.Long: return new LongTag();
case TagType.Float: return new FloatTag();
case TagType.IntArray: return new IntArrayTag();
case TagType.LongArray: return new LongArrayTag();
case TagType.List: return new ListTag();
case TagType.String: return new StringTag();
default: throw new ArgumentOutOfRangeException();
}
}
public int AsByte()
{
if (this is ByteTag byteTag) return byteTag.Value;
return 0;
}
public List<byte> AsByteArray()
{
if (this is ByteArrayTag byteArrayTag) return byteArrayTag.Value;
return new List<byte>();
}
public double AsDouble()
{
if (this is DoubleTag doubleTag) return doubleTag.Value;
return 0.0;
}
public double AsFloat()
{
if (this is FloatTag floatTag) return floatTag.value;
return 0.0;
}
public List<int> AsIntArray()
{
if (this is IntArrayTag intArrayTag) return intArrayTag.Value;
return new List<int>();
}
public int AsInt()
{
if (this is IntTag intTag) return intTag.Value;
return 0;
}
public List<long> AsLongArray()
{
if (this is LongArrayTag longArrayTag) return longArrayTag.value;
return new List<long>();
}
public long AsLong()
{
if (this is LongTag longTag) return longTag.value;
return 0;
}
public short AsShort()
{
if (this is ShortTag shortTag) return shortTag.value;
return 0;
}
public string AsString()
{
if (this is StringTag stringTag) return stringTag.value;
return string.Empty;
}
public CompoundTag AsCompoundTag()
{
return this is CompoundTag compoundTag ? compoundTag : new CompoundTag();
}
public abstract void PrettyPrint(int indent, bool recurse);
public static string GetCanonicalName(TagType type)
{
return type switch
{
TagType.String => "TAG_String",
TagType.List => "TAG_List",
TagType.LongArray => "TAG_LongArray",
TagType.Long => "TAG_Long",
TagType.IntArray => "TAG_IntArray",
TagType.Int => "TAG_Int",
TagType.Compound => "TAG_Compound",
TagType.ByteArray => "TAG_ByteArray",
TagType.Byte => "TAG_Byte",
TagType.Double => "TAG_Double",
TagType.Float => "TAG_Float",
TagType.Short => "TAG_Short",
TagType.End => "TAG_End",
_ => throw new ArgumentOutOfRangeException()
};
}
}

18
NBT/API/TagType.cs Normal file
View file

@ -0,0 +1,18 @@
namespace LibAC.NBT.API;
public enum TagType
{
End = 0,
Byte = 1,
Short = 2,
Int = 3,
Long = 4,
Float = 5,
Double = 6,
ByteArray = 7,
String = 8,
List = 9,
Compound = 10,
IntArray = 11,
LongArray = 12
}

View file

@ -0,0 +1,145 @@
namespace LibAC.NBT.API;
using System;
using System.Text;
using System.Text.RegularExpressions;
public static class TagTypeExtensions
{
private static readonly string ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public static TagType GetTagTypeFromByte(int byteValue)
{
return Enum.IsDefined(typeof(TagType), byteValue) ? (TagType)byteValue : TagType.End;
}
public static TagType GetStringifiedTagType(StringReader reader)
{
reader.StartSeek(); // Enter fake read mode
TagType ret = TagType.End;
bool isQuoted = false;
bool isNumeric = true; // Assume true until proven otherwise
bool isAlpha = true; // Assume true until proven otherwise
bool hasDecimalPoint = false;
StringBuilder buffer = new StringBuilder();
while (reader.CanRead)
{
var val = reader.Peek(); // Peek at the next character without consuming it
if (val == ',' || val == '\n' || val == ']' || val == '}')
{
break; // Stop at comma or newline
}
if (val == '"')
{
reader.Next(); // Consume the quote character
isQuoted = true; // Toggle quoted state
break;
}
if (val == '{')
{
ret = TagType.Compound; // Detected a CompoundTag
reader.Next(); // Consume '{'
reader.EndSeek(); // Restore the original stream position
return ret;
}
if (val == '[')
{
reader.Next(); // Consume '['
// Peek ahead to differentiate between List and Array
var prefix = reader.ReadUntil(';', 2).ToUpper();
switch (prefix)
{
case "B":
ret = TagType.ByteArray;
break;
case "I":
ret = TagType.IntArray;
break;
case "L":
ret = TagType.LongArray;
break;
default:
ret = TagType.List; // No type prefix, it's a List
break;
}
reader.EndSeek(); // Restore the original stream position
return ret;
}
// Adjusting numeric and alphabetic checks
var current = reader.Next(); // Consume the character
buffer.Append(current.ToString());
// Updated check to allow digits, decimal points, and numeric suffixes
if (!Regex.IsMatch(current.ToString(), @"^[0-9]$"))
{
if (current == '.' && !hasDecimalPoint)
{
hasDecimalPoint = true; // Allow only one decimal point
}
else if (!Regex.IsMatch(current.ToString(), @"^[sSbBiIlLfFdD]$"))
{
isNumeric = false; // Not purely numeric or allowed decimal/suffix
}
}
// Check if current character is purely alphabetical
if (!Regex.IsMatch(current.ToString(), @"^[A-Za-z]$"))
{
isAlpha = false; // Not purely alphabetical
}
}
var input = buffer.ToString().Trim();
reader.EndSeek(); // Restore the original stream position
if (string.IsNullOrEmpty(input))
{
return TagType.String; // No input detected
}
if (isQuoted)
{
return TagType.String; // Quoted string
}
if (isNumeric)
{
// Check the last character for type indicator (only for numeric input)
var lastChar = input.Substring(input.Length - 1).ToUpper();
switch (lastChar)
{
case "S":
return TagType.Short;
case "B":
return TagType.Byte;
case "I":
return TagType.Int;
case "F":
return TagType.Float;
case "D":
return TagType.Double;
case "L":
return TagType.Long;
default:
return TagType.Int; // Default to Int if purely numeric with no specific suffix
}
}
else if (isAlpha && !input.Contains(' '))
{
return TagType.String; // Unquoted purely alphabetical string
}
else
{
return TagType.String; // Unquoted string with mixed content or spaces
}
}
}