Port NBT impl from LibAC Dart
This commit is contained in:
parent
0a022634c1
commit
ad7b619706
55 changed files with 3226 additions and 2983 deletions
358
NBT/API/ByteLayer.cs
Normal file
358
NBT/API/ByteLayer.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
8
NBT/API/InvalidNbtDataException.cs
Normal file
8
NBT/API/InvalidNbtDataException.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
using System;
|
||||
|
||||
namespace LibAC.NBT.API
|
||||
{
|
||||
public class InvalidNbtDataException : Exception
|
||||
{
|
||||
}
|
||||
}
|
32
NBT/API/NBTAccountant.cs
Normal file
32
NBT/API/NBTAccountant.cs
Normal 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
132
NBT/API/NbtIo.cs
Normal 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
43
NBT/API/NbtUUID.cs
Normal 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
142
NBT/API/NbtUtils.cs
Normal 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
92
NBT/API/SnbtIo.cs
Normal 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
32
NBT/API/StringBuilder.cs
Normal 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
196
NBT/API/StringReader.cs
Normal 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
280
NBT/API/Tag.cs
Normal 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
18
NBT/API/TagType.cs
Normal 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
|
||||
}
|
145
NBT/API/TagTypeExtensions.cs
Normal file
145
NBT/API/TagTypeExtensions.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
82
NBT/ByteArrayTag.cs
Normal file
82
NBT/ByteArrayTag.cs
Normal file
|
@ -0,0 +1,82 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using LibAC.NBT.API;
|
||||
|
||||
namespace LibAC.NBT
|
||||
{
|
||||
public class ByteArrayTag : Tag
|
||||
{
|
||||
public List<byte> Value { get; private set; } = new List<byte>();
|
||||
|
||||
public ByteArrayTag() { }
|
||||
|
||||
private ByteArrayTag(List<byte> value)
|
||||
{
|
||||
Value.AddRange(value);
|
||||
}
|
||||
|
||||
public static ByteArrayTag ValueOf(List<byte> value)
|
||||
{
|
||||
return new ByteArrayTag(value);
|
||||
}
|
||||
|
||||
public override void ReadValue(ByteLayer data)
|
||||
{
|
||||
int len = data.ReadInt();
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
Value.Add(data.ReadByte());
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteValue(ByteLayer data)
|
||||
{
|
||||
data.WriteInt(Value.Count);
|
||||
foreach (byte i in Value)
|
||||
{
|
||||
data.WriteByte(i);
|
||||
}
|
||||
}
|
||||
|
||||
public override TagType GetTagType()
|
||||
{
|
||||
return TagType.ByteArray;
|
||||
}
|
||||
|
||||
public override dynamic GetValue()
|
||||
{
|
||||
return null; // Implementation-specific value retrieval
|
||||
}
|
||||
|
||||
public override void SetValue(dynamic val)
|
||||
{
|
||||
// Implementation-specific value setting
|
||||
}
|
||||
|
||||
public override void PrettyPrint(int indent, bool recurse)
|
||||
{
|
||||
string array = string.Join(", ", Value);
|
||||
Console.WriteLine($"{new string('\t', indent)}{Tag.GetCanonicalName(GetTagType())}: [{array}]");
|
||||
}
|
||||
|
||||
public override void WriteStringifiedValue(StringBuilder builder, int indent, bool isList)
|
||||
{
|
||||
builder.Append($"{(isList ? new string('\t', indent) : "")}[B; {string.Join("B, ", Value)}B]");
|
||||
}
|
||||
|
||||
public override void ReadStringifiedValue(StringReader reader)
|
||||
{
|
||||
reader.Expect('[');
|
||||
reader.Expect('B');
|
||||
reader.Expect(';');
|
||||
while (reader.Peek() != ']')
|
||||
{
|
||||
Value.Add(byte.Parse(reader.ReadNumber()));
|
||||
reader.Expect('b');
|
||||
|
||||
if (reader.Peek() == ',') reader.Next();
|
||||
}
|
||||
reader.Expect(']');
|
||||
}
|
||||
}
|
||||
}
|
67
NBT/ByteTag.cs
Normal file
67
NBT/ByteTag.cs
Normal file
|
@ -0,0 +1,67 @@
|
|||
using System;
|
||||
using LibAC.NBT.API;
|
||||
|
||||
namespace LibAC.NBT
|
||||
{
|
||||
public class ByteTag : Tag
|
||||
{
|
||||
public byte Value { get; private set; } = 0;
|
||||
|
||||
public ByteTag() { }
|
||||
|
||||
private ByteTag(byte value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public static ByteTag ValueOf(byte value)
|
||||
{
|
||||
return new ByteTag(value);
|
||||
}
|
||||
|
||||
public override void ReadValue(ByteLayer data)
|
||||
{
|
||||
Value = data.ReadByte();
|
||||
}
|
||||
|
||||
public override void WriteValue(ByteLayer data)
|
||||
{
|
||||
data.WriteByte(Value);
|
||||
}
|
||||
|
||||
public override TagType GetTagType()
|
||||
{
|
||||
return TagType.Byte;
|
||||
}
|
||||
|
||||
public override dynamic GetValue()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
|
||||
public override void SetValue(dynamic val)
|
||||
{
|
||||
if (val is byte)
|
||||
{
|
||||
Value = (byte)val;
|
||||
}
|
||||
}
|
||||
|
||||
public override void PrettyPrint(int indent, bool recurse)
|
||||
{
|
||||
Console.WriteLine($"{new string('\t', indent)}{Tag.GetCanonicalName(GetTagType())}: {Value}");
|
||||
}
|
||||
|
||||
public override void WriteStringifiedValue(StringBuilder builder, int indent, bool isList)
|
||||
{
|
||||
builder.Append($"{Value}b");
|
||||
}
|
||||
|
||||
public override void ReadStringifiedValue(StringReader reader)
|
||||
{
|
||||
string val = reader.ReadNumber();
|
||||
Value = byte.Parse(val);
|
||||
reader.Expect('b');
|
||||
}
|
||||
}
|
||||
}
|
283
NBT/CompoundTag.cs
Normal file
283
NBT/CompoundTag.cs
Normal file
|
@ -0,0 +1,283 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using LibAC.NBT.API;
|
||||
|
||||
namespace LibAC.NBT
|
||||
{
|
||||
public class CompoundTag : Tag, IDictionary<string, Tag>
|
||||
{
|
||||
private readonly Dictionary<string, Tag> _value = new Dictionary<string, Tag>();
|
||||
|
||||
public CompoundTag() { }
|
||||
|
||||
public override void ReadValue(ByteLayer data)
|
||||
{
|
||||
_value.Clear();
|
||||
|
||||
while (true)
|
||||
{
|
||||
Tag tag = Tag.ReadNamedTag(data);
|
||||
if (tag.GetType() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
tag.SetParentTagType(TagType.Compound);
|
||||
this[tag.GetKey()] = tag;
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteValue(ByteLayer data)
|
||||
{
|
||||
foreach (var tag in _value.Values)
|
||||
{
|
||||
Tag.WriteNamedTag(tag, data);
|
||||
}
|
||||
|
||||
data.WriteByte((byte)TagType.End);
|
||||
}
|
||||
|
||||
public void Put(string name, Tag tag)
|
||||
{
|
||||
_value[name] = tag;
|
||||
tag.SetKey(name);
|
||||
tag.UpdateParent(this);
|
||||
}
|
||||
|
||||
public Tag? Get(string name)
|
||||
{
|
||||
return ContainsKey(name) ? _value[name] : null;
|
||||
}
|
||||
|
||||
public override TagType GetTagType()
|
||||
{
|
||||
return TagType.Compound;
|
||||
}
|
||||
|
||||
public override object GetValue()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void SetValue(object val) { }
|
||||
|
||||
public override void PrettyPrint(int indent, bool recurse)
|
||||
{
|
||||
Console.WriteLine($"{new string('\t', indent)}{Tag.GetCanonicalName(GetTagType())}: [{_value.Count} entries]");
|
||||
Console.WriteLine($"{new string('\t', indent)}");
|
||||
if (recurse)
|
||||
{
|
||||
foreach (var tag in _value.Values)
|
||||
{
|
||||
tag.PrettyPrint(indent + 1, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void EndPrettyPrint(int indent)
|
||||
{
|
||||
Console.WriteLine($"{new string('\t', indent)}");
|
||||
}
|
||||
|
||||
public override void WriteStringifiedValue(StringBuilder builder, int indent, bool isList)
|
||||
{
|
||||
builder.Append($"{(isList ? new string('\t', indent - 1) : "")}{{\n");
|
||||
|
||||
bool firstEntry = true;
|
||||
foreach (var tag in _value.Values)
|
||||
{
|
||||
if (!firstEntry)
|
||||
{
|
||||
builder.Append(",\n");
|
||||
}
|
||||
firstEntry = false;
|
||||
Tag.WriteStringifiedNamedTag(tag, builder, indent);
|
||||
}
|
||||
|
||||
builder.Append($"\n{new string('\t', indent - 1)}}}");
|
||||
}
|
||||
|
||||
public bool TryGetValue(string key, out Tag value)
|
||||
{
|
||||
return _value.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
public Tag? this[string key]
|
||||
{
|
||||
get => _value.ContainsKey(key) ? _value[key] : null;
|
||||
set
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
_value[key] = value;
|
||||
value.UpdateParent(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(string key, Tag value)
|
||||
{
|
||||
_value.Add(key, value);
|
||||
value.UpdateParent(this);
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<string, Tag> item)
|
||||
{
|
||||
_value.Add(item.Key, item.Value);
|
||||
item.Value.UpdateParent(this);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
UnsetParent();
|
||||
_value.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(KeyValuePair<string, Tag> item)
|
||||
{
|
||||
return _value.ContainsKey(item.Key);
|
||||
}
|
||||
|
||||
public void CopyTo(KeyValuePair<string, Tag>[] array, int arrayIndex)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool Remove(KeyValuePair<string, Tag> item)
|
||||
{
|
||||
return _value.Remove(item.Key);
|
||||
}
|
||||
|
||||
public void UnsetParent()
|
||||
{
|
||||
foreach (var entry in _value)
|
||||
{
|
||||
entry.Value.SetParentTagType(TagType.End);
|
||||
entry.Value.UpdateParent(null);
|
||||
}
|
||||
}
|
||||
|
||||
public bool ContainsKey(string key)
|
||||
{
|
||||
return _value.ContainsKey(key);
|
||||
}
|
||||
|
||||
public bool ContainsValue(Tag value)
|
||||
{
|
||||
return _value.ContainsValue(value);
|
||||
}
|
||||
|
||||
public void ForEach(Action<string, Tag> action)
|
||||
{
|
||||
foreach (var entry in _value)
|
||||
{
|
||||
action(entry.Key, entry.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public int Count => _value.Count;
|
||||
public bool IsReadOnly { get; }
|
||||
|
||||
public bool IsEmpty => _value.Count == 0;
|
||||
|
||||
public bool IsNotEmpty => _value.Count > 0;
|
||||
|
||||
public ICollection<string> Keys => _value.Keys;
|
||||
|
||||
public ICollection<Tag> Values => _value.Values;
|
||||
|
||||
public override void ReadStringifiedValue(StringReader reader)
|
||||
{
|
||||
reader.Expect('{');
|
||||
|
||||
while (reader.Peek() != '}')
|
||||
{
|
||||
Tag tag = Tag.ReadStringifiedNamedTag(reader);
|
||||
Put(tag.GetKey(), tag);
|
||||
|
||||
if (reader.Peek() == ',') reader.Next();
|
||||
}
|
||||
|
||||
reader.Expect('}');
|
||||
}
|
||||
|
||||
public void AddAll(IDictionary<string, Tag> other)
|
||||
{
|
||||
foreach (var entry in other)
|
||||
{
|
||||
_value[entry.Key] = entry.Value;
|
||||
entry.Value.UpdateParent(this);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(string key)
|
||||
{
|
||||
if (_value.ContainsKey(key))
|
||||
{
|
||||
_value[key].UpdateParent(null);
|
||||
return _value.Remove(key);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void RemoveWhere(Predicate<KeyValuePair<string, Tag>> predicate)
|
||||
{
|
||||
var toRemove = new List<string>();
|
||||
foreach (var entry in _value)
|
||||
{
|
||||
if (predicate(entry))
|
||||
{
|
||||
toRemove.Add(entry.Key);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var key in toRemove)
|
||||
{
|
||||
_value[key].UpdateParent(null);
|
||||
_value.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateAll(Action<string, Tag> update)
|
||||
{
|
||||
foreach (var entry in _value)
|
||||
{
|
||||
update(entry.Key, entry.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public Tag PutIfAbsent(string key, Func<Tag> ifAbsent)
|
||||
{
|
||||
if (!_value.ContainsKey(key))
|
||||
{
|
||||
var tag = ifAbsent();
|
||||
tag.UpdateParent(this);
|
||||
_value[key] = tag;
|
||||
return tag;
|
||||
}
|
||||
|
||||
return _value[key];
|
||||
}
|
||||
|
||||
public void AddEntries(IEnumerable<KeyValuePair<string, Tag>> newEntries)
|
||||
{
|
||||
foreach (var entry in newEntries)
|
||||
{
|
||||
_value.Add(entry.Key, entry.Value);
|
||||
entry.Value.UpdateParent(this);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<string, Tag>> GetEnumerator()
|
||||
{
|
||||
return _value.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return _value.GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
67
NBT/DoubleTag.cs
Normal file
67
NBT/DoubleTag.cs
Normal file
|
@ -0,0 +1,67 @@
|
|||
using System;
|
||||
using LibAC.NBT.API;
|
||||
|
||||
namespace LibAC.NBT
|
||||
{
|
||||
public class DoubleTag : Tag
|
||||
{
|
||||
public double Value { get; private set; } = 0.0;
|
||||
|
||||
public DoubleTag() { }
|
||||
|
||||
private DoubleTag(double value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public static DoubleTag ValueOf(double value)
|
||||
{
|
||||
return new DoubleTag(value);
|
||||
}
|
||||
|
||||
public override void ReadValue(ByteLayer data)
|
||||
{
|
||||
Value = data.ReadDouble();
|
||||
}
|
||||
|
||||
public override void WriteValue(ByteLayer data)
|
||||
{
|
||||
data.WriteDouble(Value);
|
||||
}
|
||||
|
||||
public override TagType GetTagType()
|
||||
{
|
||||
return TagType.Double;
|
||||
}
|
||||
|
||||
public override dynamic GetValue()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
|
||||
public override void SetValue(dynamic val)
|
||||
{
|
||||
if (val is double)
|
||||
{
|
||||
Value = (double)val;
|
||||
}
|
||||
}
|
||||
|
||||
public override void PrettyPrint(int indent, bool recurse)
|
||||
{
|
||||
Console.WriteLine($"{new string('\t', indent)}{Tag.GetCanonicalName(GetTagType())}: {Value}");
|
||||
}
|
||||
|
||||
public override void WriteStringifiedValue(StringBuilder builder, int indent, bool isList)
|
||||
{
|
||||
builder.Append($"{(isList ? new string('\t', indent) : "")}{Value}d");
|
||||
}
|
||||
|
||||
public override void ReadStringifiedValue(StringReader reader)
|
||||
{
|
||||
double val = double.Parse(reader.ReadNumber());
|
||||
Value = val;
|
||||
reader.Expect('d');
|
||||
}
|
||||
}
|
||||
}
|
42
NBT/EndTag.cs
Normal file
42
NBT/EndTag.cs
Normal file
|
@ -0,0 +1,42 @@
|
|||
using System;
|
||||
using LibAC.NBT.API;
|
||||
|
||||
namespace LibAC.NBT;
|
||||
|
||||
public class EndTag : Tag
|
||||
{
|
||||
public override TagType GetTagType()
|
||||
{
|
||||
return TagType.End;
|
||||
}
|
||||
|
||||
public override void WriteValue(ByteLayer data)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ReadValue(ByteLayer data)
|
||||
{
|
||||
}
|
||||
|
||||
public override dynamic GetValue()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void SetValue(dynamic val)
|
||||
{
|
||||
}
|
||||
|
||||
public override void WriteStringifiedValue(StringBuilder builder, int indent, bool isList)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ReadStringifiedValue(StringReader reader)
|
||||
{
|
||||
}
|
||||
|
||||
public override void PrettyPrint(int indent, bool recurse)
|
||||
{
|
||||
Console.WriteLine($"{"".PadLeft(indent, '\t')}{Tag.GetCanonicalName(GetTagType())}");
|
||||
}
|
||||
}
|
66
NBT/FloatTag.cs
Normal file
66
NBT/FloatTag.cs
Normal file
|
@ -0,0 +1,66 @@
|
|||
using System;
|
||||
using LibAC.NBT.API;
|
||||
|
||||
namespace LibAC.NBT
|
||||
{
|
||||
public class FloatTag : Tag
|
||||
{
|
||||
public float value { get; private set; } = 0.0f;
|
||||
|
||||
public FloatTag() { }
|
||||
|
||||
private FloatTag(float value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static FloatTag ValueOf(float value)
|
||||
{
|
||||
return new FloatTag(value);
|
||||
}
|
||||
|
||||
public override void ReadValue(ByteLayer data)
|
||||
{
|
||||
value = data.ReadFloat();
|
||||
}
|
||||
|
||||
public override void WriteValue(ByteLayer data)
|
||||
{
|
||||
data.WriteFloat(value);
|
||||
}
|
||||
|
||||
public override TagType GetTagType()
|
||||
{
|
||||
return TagType.Float;
|
||||
}
|
||||
|
||||
public override object GetValue()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
public override void SetValue(object val)
|
||||
{
|
||||
if (val is float)
|
||||
{
|
||||
value = (float)val;
|
||||
}
|
||||
}
|
||||
|
||||
public override void PrettyPrint(int indent, bool recurse)
|
||||
{
|
||||
Console.WriteLine($"{new string('\t', indent)}{Tag.GetCanonicalName(GetTagType())}: {value}");
|
||||
}
|
||||
|
||||
public override void WriteStringifiedValue(StringBuilder builder, int indent, bool isList)
|
||||
{
|
||||
builder.Append($"{(isList ? new string('\t', indent) : "")}{value}f");
|
||||
}
|
||||
|
||||
public override void ReadStringifiedValue(StringReader reader)
|
||||
{
|
||||
value = float.Parse(reader.ReadNumber());
|
||||
reader.Expect('f');
|
||||
}
|
||||
}
|
||||
}
|
76
NBT/IntArrayTag.cs
Normal file
76
NBT/IntArrayTag.cs
Normal file
|
@ -0,0 +1,76 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using LibAC.NBT.API;
|
||||
|
||||
namespace LibAC.NBT
|
||||
{
|
||||
public class IntArrayTag : Tag
|
||||
{
|
||||
public List<int> Value { get; private set; } = new List<int>();
|
||||
|
||||
public IntArrayTag() { }
|
||||
|
||||
private IntArrayTag(List<int> value)
|
||||
{
|
||||
this.Value.AddRange(value);
|
||||
}
|
||||
|
||||
public static IntArrayTag ValueOf(List<int> value)
|
||||
{
|
||||
return new IntArrayTag(value);
|
||||
}
|
||||
|
||||
public override void ReadValue(ByteLayer data)
|
||||
{
|
||||
int count = data.ReadInt();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Value.Add(data.ReadInt());
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteValue(ByteLayer data)
|
||||
{
|
||||
data.WriteInt(Value.Count);
|
||||
foreach (int i in Value)
|
||||
{
|
||||
data.WriteInt(i);
|
||||
}
|
||||
}
|
||||
|
||||
public override TagType GetTagType()
|
||||
{
|
||||
return TagType.IntArray;
|
||||
}
|
||||
|
||||
public override void SetValue(dynamic val) { }
|
||||
|
||||
public override dynamic GetValue() { return null; }
|
||||
|
||||
public override void PrettyPrint(int indent, bool recurse)
|
||||
{
|
||||
string array = string.Join(", ", Value);
|
||||
Console.WriteLine($"{new string('\t', indent)}{Tag.GetCanonicalName(GetTagType())}: [{array}]");
|
||||
}
|
||||
|
||||
public override void WriteStringifiedValue(StringBuilder builder, int indent, bool isList)
|
||||
{
|
||||
builder.Append($"{(isList ? new string('\t', indent) : "")}[I; {string.Join("I, ", Value)}I]");
|
||||
}
|
||||
|
||||
public override void ReadStringifiedValue(StringReader reader)
|
||||
{
|
||||
reader.Expect('[');
|
||||
reader.Expect('I');
|
||||
reader.Expect(';');
|
||||
while (reader.Peek() != ']')
|
||||
{
|
||||
Value.Add(int.Parse(reader.ReadNumber()));
|
||||
|
||||
if (reader.Peek() == ',')
|
||||
reader.Next();
|
||||
}
|
||||
reader.Expect('I');
|
||||
}
|
||||
}
|
||||
}
|
70
NBT/IntTag.cs
Normal file
70
NBT/IntTag.cs
Normal file
|
@ -0,0 +1,70 @@
|
|||
using System;
|
||||
using LibAC.NBT.API;
|
||||
|
||||
namespace LibAC.NBT
|
||||
{
|
||||
public class IntTag : Tag
|
||||
{
|
||||
public int Value { get; private set; } = 0;
|
||||
|
||||
public IntTag() { }
|
||||
|
||||
private IntTag(int value)
|
||||
{
|
||||
this.Value = value;
|
||||
}
|
||||
|
||||
public static IntTag ValueOf(int value)
|
||||
{
|
||||
return new IntTag(value);
|
||||
}
|
||||
|
||||
public override void ReadValue(ByteLayer data)
|
||||
{
|
||||
Value = data.ReadInt();
|
||||
}
|
||||
|
||||
public override void WriteValue(ByteLayer data)
|
||||
{
|
||||
data.WriteInt(Value);
|
||||
}
|
||||
|
||||
public override TagType GetTagType()
|
||||
{
|
||||
return TagType.Int;
|
||||
}
|
||||
|
||||
public override void SetValue(dynamic val)
|
||||
{
|
||||
if (val is int)
|
||||
Value = val;
|
||||
}
|
||||
|
||||
public override dynamic GetValue()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
|
||||
public override void PrettyPrint(int indent, bool recurse)
|
||||
{
|
||||
Console.WriteLine($"{new string('\t', indent)}{Tag.GetCanonicalName(GetTagType())}: {Value}");
|
||||
}
|
||||
|
||||
public override void WriteStringifiedValue(StringBuilder builder, int indent, bool isList)
|
||||
{
|
||||
builder.Append($"{(isList ? new string('\t', indent) : "")}{Value}i");
|
||||
}
|
||||
|
||||
public override void ReadStringifiedValue(StringReader reader)
|
||||
{
|
||||
string val = reader.ReadNumber();
|
||||
Value = int.Parse(val);
|
||||
|
||||
// Since a type indicator is optional for an int, check for a comma
|
||||
if (reader.Peek() == ',')
|
||||
return;
|
||||
else
|
||||
reader.Expect('i');
|
||||
}
|
||||
}
|
||||
}
|
173
NBT/ListTag.cs
Normal file
173
NBT/ListTag.cs
Normal file
|
@ -0,0 +1,173 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using LibAC.NBT.API;
|
||||
|
||||
namespace LibAC.NBT
|
||||
{
|
||||
public class ListTag : Tag
|
||||
{
|
||||
private List<Tag> value = new List<Tag>();
|
||||
|
||||
public List<Tag> GetList => new List<Tag>(value);
|
||||
|
||||
public ListTag() { }
|
||||
|
||||
public TagType ListTagType
|
||||
{
|
||||
get
|
||||
{
|
||||
return value.Count == 0 ? TagType.End : value[0].GetTagType();
|
||||
}
|
||||
}
|
||||
|
||||
public override void ReadValue(ByteLayer data)
|
||||
{
|
||||
TagType type = TagTypeExtensions.GetTagTypeFromByte(data.ReadByte());
|
||||
int size = data.ReadInt();
|
||||
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
Tag tag = Tag.MakeTagOfType(type);
|
||||
tag.ReadValue(data);
|
||||
Add(tag);
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteValue(ByteLayer data)
|
||||
{
|
||||
TagType type = TagType.End;
|
||||
if (Size() > 0)
|
||||
{
|
||||
type = value[0].GetTagType();
|
||||
}
|
||||
|
||||
data.WriteByte((byte)type);
|
||||
data.WriteInt(Size());
|
||||
for (int i = 0; i < Size(); i++)
|
||||
{
|
||||
value[i].WriteValue(data);
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(Tag tag)
|
||||
{
|
||||
TagType type = TagType.End;
|
||||
if (Size() > 0)
|
||||
{
|
||||
type = value[0].GetTagType();
|
||||
}
|
||||
if (type == TagType.End || type == tag.GetTagType())
|
||||
{
|
||||
value.Add(tag);
|
||||
tag.UpdateParent(this);
|
||||
}
|
||||
}
|
||||
|
||||
public Tag Get(int index)
|
||||
{
|
||||
if (Size() > index)
|
||||
{
|
||||
return value[index];
|
||||
}
|
||||
else
|
||||
{
|
||||
return new EndTag();
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(Tag tag)
|
||||
{
|
||||
value.Remove(tag);
|
||||
tag.UpdateParent(null);
|
||||
}
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
value[index].UpdateParent(null);
|
||||
value.RemoveAt(index);
|
||||
}
|
||||
|
||||
public int IndexOf(Tag tag)
|
||||
{
|
||||
return value.IndexOf(tag);
|
||||
}
|
||||
|
||||
public override TagType GetTagType()
|
||||
{
|
||||
return TagType.List;
|
||||
}
|
||||
|
||||
public override void SetValue(dynamic val) { }
|
||||
|
||||
public override dynamic GetValue()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public int Size()
|
||||
{
|
||||
return value.Count;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
// Clear the list
|
||||
foreach (var entry in value)
|
||||
{
|
||||
entry.UpdateParent(null);
|
||||
entry.SetParentTagType(TagType.End);
|
||||
}
|
||||
|
||||
value.Clear();
|
||||
}
|
||||
|
||||
public override void PrettyPrint(int indent, bool recurse)
|
||||
{
|
||||
Console.WriteLine($"{new string('\t', indent)}{Tag.GetCanonicalName(GetTagType())}: [{value.Count} entries]");
|
||||
Console.WriteLine($"{new string('\t', indent)}[");
|
||||
foreach (Tag tag in value)
|
||||
{
|
||||
tag.PrettyPrint(indent + 1, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void EndPrettyPrint(int indent)
|
||||
{
|
||||
Console.WriteLine($"{new string('\t', indent)}]");
|
||||
}
|
||||
|
||||
public override void WriteStringifiedValue(StringBuilder builder, int indent, bool isList)
|
||||
{
|
||||
builder.Append($"{(isList ? new string('\t', indent - 1) : "")}[");
|
||||
|
||||
bool firstTag = true;
|
||||
foreach (Tag tag in value)
|
||||
{
|
||||
if (!firstTag)
|
||||
{
|
||||
builder.Append(",");
|
||||
}
|
||||
|
||||
firstTag = false;
|
||||
tag.WriteStringifiedValue(builder, indent + 1, true);
|
||||
}
|
||||
|
||||
builder.Append($"{new string('\t', indent - 1)}]");
|
||||
}
|
||||
|
||||
public override void ReadStringifiedValue(StringReader reader)
|
||||
{
|
||||
reader.Expect('[');
|
||||
while (reader.Peek() != ']')
|
||||
{
|
||||
TagType type = TagTypeExtensions.GetStringifiedTagType(reader);
|
||||
Tag newTag = Tag.MakeTagOfType(type);
|
||||
newTag.ReadStringifiedValue(reader);
|
||||
Add(newTag);
|
||||
|
||||
if (reader.Peek() == ',') reader.Next();
|
||||
}
|
||||
reader.Expect(']');
|
||||
}
|
||||
}
|
||||
}
|
84
NBT/LongArrayTag.cs
Normal file
84
NBT/LongArrayTag.cs
Normal file
|
@ -0,0 +1,84 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using LibAC.NBT.API;
|
||||
|
||||
namespace LibAC.NBT
|
||||
{
|
||||
public class LongArrayTag : Tag
|
||||
{
|
||||
public List<long> value { get; private set; } = new List<long>();
|
||||
|
||||
public LongArrayTag() { }
|
||||
|
||||
public LongArrayTag(List<long> lst)
|
||||
{
|
||||
value.AddRange(lst);
|
||||
}
|
||||
|
||||
public static LongArrayTag ValueOf(List<long> value)
|
||||
{
|
||||
return new LongArrayTag(value);
|
||||
}
|
||||
|
||||
public override void ReadValue(ByteLayer data)
|
||||
{
|
||||
int count = data.ReadInt();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
value.Add(data.ReadLong());
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteValue(ByteLayer data)
|
||||
{
|
||||
data.WriteInt(Size());
|
||||
foreach (long val in value)
|
||||
{
|
||||
data.WriteLong(val);
|
||||
}
|
||||
}
|
||||
|
||||
public int Size()
|
||||
{
|
||||
return value.Count;
|
||||
}
|
||||
|
||||
public override TagType GetTagType()
|
||||
{
|
||||
return TagType.LongArray;
|
||||
}
|
||||
|
||||
public override void SetValue(dynamic val) { }
|
||||
|
||||
public override dynamic GetValue()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void PrettyPrint(int indent, bool recurse)
|
||||
{
|
||||
string array = string.Join(", ", value);
|
||||
Console.WriteLine($"{new string('\t', indent)}{Tag.GetCanonicalName(GetTagType())}: [{array}]");
|
||||
}
|
||||
|
||||
public override void WriteStringifiedValue(StringBuilder builder, int indent, bool isList)
|
||||
{
|
||||
builder.Append($"{(isList ? new string('\t', indent) : "")}[L; {string.Join("L, ", value)}L]");
|
||||
}
|
||||
|
||||
public override void ReadStringifiedValue(StringReader reader)
|
||||
{
|
||||
reader.Expect('[');
|
||||
reader.Expect('L');
|
||||
reader.Expect(';');
|
||||
while (reader.Peek() != ']')
|
||||
{
|
||||
value.Add(long.Parse(reader.ReadNumber()));
|
||||
reader.Expect('l');
|
||||
|
||||
if (reader.Peek() == ',') reader.Next();
|
||||
}
|
||||
reader.Expect(']');
|
||||
}
|
||||
}
|
||||
}
|
65
NBT/LongTag.cs
Normal file
65
NBT/LongTag.cs
Normal file
|
@ -0,0 +1,65 @@
|
|||
using System;
|
||||
using LibAC.NBT.API;
|
||||
|
||||
namespace LibAC.NBT
|
||||
{
|
||||
public class LongTag : Tag
|
||||
{
|
||||
public long value { get; private set; } = 0;
|
||||
|
||||
public LongTag() { }
|
||||
|
||||
public LongTag(long value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static LongTag ValueOf(long value)
|
||||
{
|
||||
return new LongTag(value);
|
||||
}
|
||||
|
||||
public override void ReadValue(ByteLayer data)
|
||||
{
|
||||
value = data.ReadLong();
|
||||
}
|
||||
|
||||
public override void WriteValue(ByteLayer data)
|
||||
{
|
||||
data.WriteLong(value);
|
||||
}
|
||||
|
||||
public override TagType GetTagType()
|
||||
{
|
||||
return TagType.Long;
|
||||
}
|
||||
|
||||
public override void SetValue(dynamic val)
|
||||
{
|
||||
if (val is long) value = val;
|
||||
}
|
||||
|
||||
public override dynamic GetValue()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
public override void PrettyPrint(int indent, bool recurse)
|
||||
{
|
||||
Console.WriteLine($"{new string('\t', indent)}{Tag.GetCanonicalName(GetTagType())}: {value}");
|
||||
}
|
||||
|
||||
public override void WriteStringifiedValue(StringBuilder builder, int indent, bool isList)
|
||||
{
|
||||
builder.Append($"{(isList ? new string('\t', indent) : "")}{value}L");
|
||||
}
|
||||
|
||||
public override void ReadStringifiedValue(StringReader reader)
|
||||
{
|
||||
long val = long.Parse(reader.ReadNumber());
|
||||
value = val;
|
||||
|
||||
reader.Expect('l');
|
||||
}
|
||||
}
|
||||
}
|
65
NBT/ShortTag.cs
Normal file
65
NBT/ShortTag.cs
Normal file
|
@ -0,0 +1,65 @@
|
|||
using System;
|
||||
using LibAC.NBT.API;
|
||||
|
||||
namespace LibAC.NBT
|
||||
{
|
||||
public class ShortTag : Tag
|
||||
{
|
||||
public short value { get; private set; }
|
||||
= 0;
|
||||
public ShortTag() { }
|
||||
|
||||
public ShortTag(short value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static ShortTag ValueOf(short value)
|
||||
{
|
||||
return new ShortTag(value);
|
||||
}
|
||||
|
||||
public override void ReadValue(ByteLayer data)
|
||||
{
|
||||
value = data.ReadShort();
|
||||
}
|
||||
|
||||
public override void WriteValue(ByteLayer data)
|
||||
{
|
||||
data.WriteShort(value);
|
||||
}
|
||||
|
||||
public override TagType GetTagType()
|
||||
{
|
||||
return TagType.Short;
|
||||
}
|
||||
|
||||
public override void SetValue(dynamic val)
|
||||
{
|
||||
if (val is short) value = val;
|
||||
}
|
||||
|
||||
public override dynamic GetValue()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
public override void PrettyPrint(int indent, bool recurse)
|
||||
{
|
||||
Console.WriteLine($"{new string('\t', indent)}{Tag.GetCanonicalName(GetTagType())}: {value}");
|
||||
}
|
||||
|
||||
public override void WriteStringifiedValue(StringBuilder builder, int indent, bool isList)
|
||||
{
|
||||
builder.Append($"{(isList ? new string('\t', indent) : "")}{value}S");
|
||||
}
|
||||
|
||||
public override void ReadStringifiedValue(StringReader reader)
|
||||
{
|
||||
short val = short.Parse(reader.ReadNumber());
|
||||
value = val;
|
||||
|
||||
reader.Expect('s');
|
||||
}
|
||||
}
|
||||
}
|
73
NBT/StringTag.cs
Normal file
73
NBT/StringTag.cs
Normal file
|
@ -0,0 +1,73 @@
|
|||
using System;
|
||||
using LibAC.NBT.API;
|
||||
|
||||
namespace LibAC.NBT
|
||||
{
|
||||
public class StringTag : Tag
|
||||
{
|
||||
public string value { get; private set; } = "";
|
||||
|
||||
public StringTag() { }
|
||||
|
||||
public StringTag(string str)
|
||||
{
|
||||
value = str;
|
||||
}
|
||||
|
||||
public static StringTag ValueOf(string str)
|
||||
{
|
||||
return new StringTag(str);
|
||||
}
|
||||
|
||||
public override void ReadValue(ByteLayer data)
|
||||
{
|
||||
value = data.ReadString();
|
||||
}
|
||||
|
||||
public override void WriteValue(ByteLayer data)
|
||||
{
|
||||
data.WriteString(value);
|
||||
}
|
||||
|
||||
public override TagType GetTagType()
|
||||
{
|
||||
return TagType.String;
|
||||
}
|
||||
|
||||
public override void SetValue(dynamic val)
|
||||
{
|
||||
if (val is string) value = val;
|
||||
}
|
||||
|
||||
public override dynamic GetValue()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
public override void PrettyPrint(int indent, bool recurse)
|
||||
{
|
||||
Console.WriteLine($"{new string('\t', indent)}{Tag.GetCanonicalName(GetTagType())}: {value}");
|
||||
}
|
||||
|
||||
public override void WriteStringifiedValue(StringBuilder builder, int indent, bool isList)
|
||||
{
|
||||
if (ShouldQuote(value))
|
||||
builder.Append($"{(isList ? new string('\t', indent) : "")}\"{value}\"");
|
||||
else
|
||||
builder.Append($"{(isList ? new string('\t', indent) : "")}{value}");
|
||||
}
|
||||
|
||||
public override void ReadStringifiedValue(StringReader reader)
|
||||
{
|
||||
string str = reader.ReadString();
|
||||
value = str;
|
||||
}
|
||||
|
||||
// This method will need to be implemented for checking if a string should be quoted
|
||||
private bool ShouldQuote(string str)
|
||||
{
|
||||
// Implement logic for determining if the string needs to be quoted
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue