LibZNI/Serialization/NBTReader.cs
2023-09-20 08:29:44 -07:00

236 lines
6.7 KiB
C#

// DISCLAIMER: Taken from fNBT to be altered to fit the needs of ZNI NBT
// All credit for the implementation of this file should go to the fNBT Authors, unless stated otherwise by comment!
using LibAC.Serialization.ACFile;
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Text;
namespace LibAC.Serialization
{
/// <summary> BinaryReader wrapper that takes care of reading primitives from an NBT stream,
/// while taking care of endianness, string encoding, and skipping. </summary>
public sealed class NBTReader : BinaryReader
{
readonly byte[] buffer = new byte[sizeof(double)];
byte[] seekBuffer;
const int SeekBufferSize = 8 * 1024;
readonly bool swapNeeded;
readonly byte[] stringConversionBuffer = new byte[64];
public NBTReader(Stream input, bool bigEndian)
: base(input)
{
swapNeeded = (BitConverter.IsLittleEndian == bigEndian);
}
public TagType ReadTagType()
{
int type = ReadByte();
if (type < 0)
{
throw new EndOfStreamException();
}
else if (type > (int)TagType.UUID) // ZNI: Other data types are supported
{
throw new Exception("NBT tag type out of range: " + type);
}
return (TagType)type;
}
public override short ReadInt16()
{
if (swapNeeded)
{
return Swap(base.ReadInt16());
}
else
{
return base.ReadInt16();
}
}
public override int ReadInt32()
{
if (swapNeeded)
{
return Swap(base.ReadInt32());
}
else
{
return base.ReadInt32();
}
}
public override long ReadInt64()
{
if (swapNeeded)
{
return Swap(base.ReadInt64());
}
else
{
return base.ReadInt64();
}
}
public override float ReadSingle()
{
if (swapNeeded)
{
FillBuffer(sizeof(float));
Array.Reverse(buffer, 0, sizeof(float));
return BitConverter.ToSingle(buffer, 0);
}
else
{
return base.ReadSingle();
}
}
public override double ReadDouble()
{
if (swapNeeded)
{
FillBuffer(sizeof(double));
Array.Reverse(buffer);
return BitConverter.ToDouble(buffer, 0);
}
return base.ReadDouble();
}
public override string ReadString()
{
short length = ReadInt16();
if (length < 0)
{
throw new Exception("Negative string length given!");
}
if (length < stringConversionBuffer.Length)
{
int stringBytesRead = 0;
while (stringBytesRead < length)
{
int bytesToRead = length - stringBytesRead;
int bytesReadThisTime = BaseStream.Read(stringConversionBuffer, stringBytesRead, bytesToRead);
if (bytesReadThisTime == 0)
{
throw new EndOfStreamException();
}
stringBytesRead += bytesReadThisTime;
}
return Encoding.UTF8.GetString(stringConversionBuffer, 0, length);
}
else
{
byte[] stringData = ReadBytes(length);
if (stringData.Length < length)
{
throw new EndOfStreamException();
}
return Encoding.UTF8.GetString(stringData);
}
}
public void Skip(int bytesToSkip)
{
if (bytesToSkip < 0)
{
throw new ArgumentOutOfRangeException("bytesToSkip");
}
else if (BaseStream.CanSeek)
{
BaseStream.Position += bytesToSkip;
}
else if (bytesToSkip != 0)
{
if (seekBuffer == null) seekBuffer = new byte[SeekBufferSize];
int bytesSkipped = 0;
while (bytesSkipped < bytesToSkip)
{
int bytesToRead = Math.Min(SeekBufferSize, bytesToSkip - bytesSkipped);
int bytesReadThisTime = BaseStream.Read(seekBuffer, 0, bytesToRead);
if (bytesReadThisTime == 0)
{
throw new EndOfStreamException();
}
bytesSkipped += bytesReadThisTime;
}
}
}
new void FillBuffer(int numBytes)
{
int offset = 0;
do
{
int num = BaseStream.Read(buffer, offset, numBytes - offset);
if (num == 0) throw new EndOfStreamException();
offset += num;
} while (offset < numBytes);
}
public void SkipString()
{
short length = ReadInt16();
if (length < 0)
{
throw new Exception("Negative string length given!");
}
Skip(length);
}
[DebuggerStepThrough]
static short Swap(short v)
{
unchecked
{
return (short)((v >> 8) & 0x00FF |
(v << 8) & 0xFF00);
}
}
[DebuggerStepThrough]
static int Swap(int v)
{
unchecked
{
var v2 = (uint)v;
return (int)((v2 >> 24) & 0x000000FF |
(v2 >> 8) & 0x0000FF00 |
(v2 << 8) & 0x00FF0000 |
(v2 << 24) & 0xFF000000);
}
}
[DebuggerStepThrough]
static long Swap(long v)
{
unchecked
{
return (Swap((int)v) & uint.MaxValue) << 32 |
Swap((int)(v >> 32)) & uint.MaxValue;
}
}
// ZNI Removed selector. Not needed for the ZNI NBT implementation
}
}