// 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 { /// BinaryWriter wrapper that writes NBT primitives to a stream, /// while taking care of endianness and string encoding, and counting bytes written. 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); } } }