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

263 lines
8.9 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.CodeAnalysis;
using System.IO;
using System.Text;
namespace LibAC.Serialization
{
/// <summary> BinaryWriter wrapper that writes NBT primitives to a stream,
/// while taking care of endianness and string encoding, and counting bytes written. </summary>
public sealed unsafe class NBTWriter
{
// Write at most 4 MiB at a time.
public const int MaxWriteChunk = 4 * 1024 * 1024;
// Encoding can be shared among all instances of NbtBinaryWriter, because it is stateless.
static readonly UTF8Encoding Encoding = new UTF8Encoding(false, true);
// Each instance has to have its own encoder, because it does maintain state.
readonly Encoder encoder = Encoding.GetEncoder();
public Stream BaseStream
{
get
{
stream.Flush();
return stream;
}
}
readonly Stream stream;
// Buffer used for temporary conversion
const int BufferSize = 256;
// UTF8 characters use at most 4 bytes each.
const int MaxBufferedStringLength = BufferSize / 4;
// Each NbtBinaryWriter needs to have its own instance of the buffer.
readonly byte[] buffer = new byte[BufferSize];
// Swap is only needed if endianness of the runtime differs from desired NBT stream
readonly bool swapNeeded;
public NBTWriter( Stream input, bool bigEndian)
{
if (input == null) throw new ArgumentNullException("input");
if (!input.CanWrite) throw new ArgumentException("Given stream must be writable", "input");
stream = input;
swapNeeded = (BitConverter.IsLittleEndian == bigEndian);
}
public void Write(byte value)
{
stream.WriteByte(value);
}
public void Write(TagType value)
{
stream.WriteByte((byte)value);
}
public void Write(short value)
{
unchecked
{
if (swapNeeded)
{
buffer[0] = (byte)(value >> 8);
buffer[1] = (byte)value;
}
else
{
buffer[0] = (byte)value;
buffer[1] = (byte)(value >> 8);
}
}
stream.Write(buffer, 0, 2);
}
public void Write(int value)
{
unchecked
{
if (swapNeeded)
{
buffer[0] = (byte)(value >> 24);
buffer[1] = (byte)(value >> 16);
buffer[2] = (byte)(value >> 8);
buffer[3] = (byte)value;
}
else
{
buffer[0] = (byte)value;
buffer[1] = (byte)(value >> 8);
buffer[2] = (byte)(value >> 16);
buffer[3] = (byte)(value >> 24);
}
}
stream.Write(buffer, 0, 4);
}
public void Write(long value)
{
unchecked
{
if (swapNeeded)
{
buffer[0] = (byte)(value >> 56);
buffer[1] = (byte)(value >> 48);
buffer[2] = (byte)(value >> 40);
buffer[3] = (byte)(value >> 32);
buffer[4] = (byte)(value >> 24);
buffer[5] = (byte)(value >> 16);
buffer[6] = (byte)(value >> 8);
buffer[7] = (byte)value;
}
else
{
buffer[0] = (byte)value;
buffer[1] = (byte)(value >> 8);
buffer[2] = (byte)(value >> 16);
buffer[3] = (byte)(value >> 24);
buffer[4] = (byte)(value >> 32);
buffer[5] = (byte)(value >> 40);
buffer[6] = (byte)(value >> 48);
buffer[7] = (byte)(value >> 56);
}
}
stream.Write(buffer, 0, 8);
}
public void Write(float value)
{
ulong tmpValue = *(uint*)&value;
unchecked
{
if (swapNeeded)
{
buffer[0] = (byte)(tmpValue >> 24);
buffer[1] = (byte)(tmpValue >> 16);
buffer[2] = (byte)(tmpValue >> 8);
buffer[3] = (byte)tmpValue;
}
else
{
buffer[0] = (byte)tmpValue;
buffer[1] = (byte)(tmpValue >> 8);
buffer[2] = (byte)(tmpValue >> 16);
buffer[3] = (byte)(tmpValue >> 24);
}
}
stream.Write(buffer, 0, 4);
}
public void Write(double value)
{
ulong tmpValue = *(ulong*)&value;
unchecked
{
if (swapNeeded)
{
buffer[0] = (byte)(tmpValue >> 56);
buffer[1] = (byte)(tmpValue >> 48);
buffer[2] = (byte)(tmpValue >> 40);
buffer[3] = (byte)(tmpValue >> 32);
buffer[4] = (byte)(tmpValue >> 24);
buffer[5] = (byte)(tmpValue >> 16);
buffer[6] = (byte)(tmpValue >> 8);
buffer[7] = (byte)tmpValue;
}
else
{
buffer[0] = (byte)tmpValue;
buffer[1] = (byte)(tmpValue >> 8);
buffer[2] = (byte)(tmpValue >> 16);
buffer[3] = (byte)(tmpValue >> 24);
buffer[4] = (byte)(tmpValue >> 32);
buffer[5] = (byte)(tmpValue >> 40);
buffer[6] = (byte)(tmpValue >> 48);
buffer[7] = (byte)(tmpValue >> 56);
}
}
stream.Write(buffer, 0, 8);
}
// Based on BinaryWriter.Write(String)
public void Write(string value)
{
if (value == null)
{
throw new ArgumentNullException("value");
}
// Write out string length (as number of bytes)
int numBytes = Encoding.GetByteCount(value);
Write((short)numBytes);
if (numBytes <= BufferSize)
{
// If the string fits entirely in the buffer, encode and write it as one
Encoding.GetBytes(value, 0, value.Length, buffer, 0);
stream.Write(buffer, 0, numBytes);
}
else
{
// Aggressively try to not allocate memory in this loop for runtime performance reasons.
// Use an Encoder to write out the string correctly (handling surrogates crossing buffer
// boundaries properly).
int charStart = 0;
int numLeft = value.Length;
while (numLeft > 0)
{
// Figure out how many chars to process this round.
int charCount = (numLeft > MaxBufferedStringLength) ? MaxBufferedStringLength : numLeft;
int byteLen;
fixed (char* pChars = value)
{
fixed (byte* pBytes = buffer)
{
byteLen = encoder.GetBytes(pChars + charStart, charCount, pBytes, BufferSize,
charCount == numLeft);
}
}
stream.Write(buffer, 0, byteLen);
charStart += charCount;
numLeft -= charCount;
}
}
}
public void Write(byte[] data, int offset, int count)
{
int written = 0;
while (written < count)
{
int toWrite = Math.Min(MaxWriteChunk, count - written);
stream.Write(data, offset + written, toWrite);
written += toWrite;
}
}
// Aria : Added a NBT compatible shortcut method for writing a byte array with the length prefix
public void Write(byte[] bytes)
{
this.Write(bytes.Length);
this.Write(bytes, 0, bytes.Length);
}
}
}