/// <summary> Saves this NBT file to a stream. Nothing is written to stream if RootTag is <c>null</c>. </summary> /// <param name="stream"> Stream to write data to. May not be <c>null</c>. </param> /// <param name="compression"> Compression mode to use for saving. May not be AutoDetect. </param> /// <returns> Number of bytes written to the stream. </returns> /// <exception cref="ArgumentNullException"> <paramref name="stream"/> is <c>null</c>. </exception> /// <exception cref="ArgumentException"> If AutoDetect was given as the <paramref name="compression"/> mode. </exception> /// <exception cref="ArgumentOutOfRangeException"> If an unrecognized/unsupported value was given for <paramref name="compression"/>. </exception> /// <exception cref="InvalidDataException"> If given stream does not support writing. </exception> /// <exception cref="NbtFormatException"> If RootTag is null; /// or if RootTag is unnamed; /// or if one of the NbtCompound tags contained unnamed tags; /// or if an NbtList tag had Unknown list type and no elements. </exception> public long SaveToStream([NotNull] Stream stream, NbtCompression compression) { if (stream == null) { throw new ArgumentNullException("stream"); } switch (compression) { case NbtCompression.AutoDetect: throw new ArgumentException("AutoDetect is not a valid NbtCompression value for saving."); case NbtCompression.ZLib: case NbtCompression.GZip: case NbtCompression.None: break; default: throw new ArgumentOutOfRangeException("compression"); } if (rootTag.Name == null) { // This may trigger if root tag has been renamed throw new NbtFormatException( "Cannot save NbtFile: Root tag is not named. Its name may be an empty string, but not null."); } long startOffset = 0; if (stream.CanSeek) { startOffset = stream.Position; } else { stream = new ByteCountingStream(stream); } switch (compression) { case NbtCompression.ZLib: stream.WriteByte(0x78); stream.WriteByte(0x01); int checksum; using (var compressStream = new ZLibStream(stream, CompressionMode.Compress, true)) { var bufferedStream = new BufferedStream(compressStream, WriteBufferSize); RootTag.WriteTag(new NbtBinaryWriter(bufferedStream, BigEndian) { UseVarInt = UseVarInt }); bufferedStream.Flush(); checksum = compressStream.Checksum; } byte[] checksumBytes = BitConverter.GetBytes(checksum); if (BitConverter.IsLittleEndian) { // Adler32 checksum is big-endian Array.Reverse(checksumBytes); } stream.Write(checksumBytes, 0, checksumBytes.Length); break; case NbtCompression.GZip: using (var compressStream = new GZipStream(stream, CompressionMode.Compress, true)) { // use a buffered stream to avoid GZipping in small increments (which has a lot of overhead) var bufferedStream = new BufferedStream(compressStream, WriteBufferSize); RootTag.WriteTag(new NbtBinaryWriter(bufferedStream, BigEndian) { UseVarInt = UseVarInt }); bufferedStream.Flush(); } break; case NbtCompression.None: var writer = new NbtBinaryWriter(stream, BigEndian) { UseVarInt = UseVarInt }; RootTag.WriteTag(writer); break; default: throw new ArgumentOutOfRangeException("compression"); } if (stream.CanSeek) { return(stream.Position - startOffset); } else { return(((ByteCountingStream)stream).BytesWritten); } }
/// <summary> Saves this NBT file to a stream. Nothing is written to stream if RootTag is <c>null</c>. </summary> /// <param name="stream"> Stream to write data to. May not be <c>null</c>. </param> /// <param name="compression"> Compression mode to use for saving. May not be AutoDetect. </param> /// <returns> Number of bytes written to the stream. </returns> /// <exception cref="ArgumentNullException"> <paramref name="stream"/> is <c>null</c>. </exception> /// <exception cref="ArgumentException"> If AutoDetect was given as the <paramref name="compression"/> mode. </exception> /// <exception cref="ArgumentOutOfRangeException"> If an unrecognized/unsupported value was given for <paramref name="compression"/>. </exception> /// <exception cref="InvalidDataException"> If given stream does not support writing. </exception> /// <exception cref="NbtFormatException"> If RootTag is null; /// or if RootTag is unnamed; /// or if one of the NbtCompound tags contained unnamed tags; /// or if an NbtList tag had Unknown list type and no elements. </exception> public long SaveToStream([NotNull] Stream stream, NbtCompression compression) { if (stream == null) throw new ArgumentNullException("stream"); switch (compression) { case NbtCompression.AutoDetect: throw new ArgumentException("AutoDetect is not a valid NbtCompression value for saving."); case NbtCompression.ZLib: case NbtCompression.GZip: case NbtCompression.None: break; default: throw new ArgumentOutOfRangeException("compression"); } if (rootTag.Name == null) { throw new NbtFormatException( "Cannot save NbtFile: Root tag is not named. Its name may be an empty string, but not null."); } long startOffset = 0; if (stream.CanSeek) { startOffset = stream.Position; } else { stream = new ByteCountingStream(stream); } switch (compression) { case NbtCompression.ZLib: stream.WriteByte(0x78); stream.WriteByte(0x01); int checksum; using (var compressStream = new ZLibStream(stream, CompressionMode.Compress, true)) { var bufferedStream = new BufferedStream(compressStream, WriteBufferSize); RootTag.WriteTag(new NbtBinaryWriter(bufferedStream, BigEndian)); bufferedStream.Flush(); checksum = compressStream.Checksum; } byte[] checksumBytes = BitConverter.GetBytes(checksum); if (BitConverter.IsLittleEndian) { // Adler32 checksum is big-endian Array.Reverse(checksumBytes); } stream.Write(checksumBytes, 0, checksumBytes.Length); break; case NbtCompression.GZip: using (var compressStream = new GZipStream(stream, CompressionMode.Compress, true)) { // use a buffered stream to avoid GZipping in small increments (which has a lot of overhead) var bufferedStream = new BufferedStream(compressStream, WriteBufferSize); RootTag.WriteTag(new NbtBinaryWriter(bufferedStream, BigEndian)); bufferedStream.Flush(); } break; case NbtCompression.None: { var writer = new NbtBinaryWriter(stream, BigEndian); RootTag.WriteTag(writer); } break; default: throw new ArgumentOutOfRangeException("compression"); } if (stream.CanSeek) { return stream.Position - startOffset; } else { return ((ByteCountingStream)stream).BytesWritten; } }
/// <summary> Loads NBT data from a stream. Existing <c>RootTag</c> will be replaced </summary> /// <param name="stream"> Stream from which data will be loaded. If compression is set to AutoDetect, this stream must support seeking. </param> /// <param name="compression"> Compression method to use for loading/saving this file. </param> /// <param name="selector"> Optional callback to select which tags to load into memory. Root may not be skipped. /// No reference is stored to this callback after loading (don't worry about implicitly captured closures). May be <c>null</c>. </param> /// <returns> Number of bytes read from the stream. </returns> /// <exception cref="ArgumentNullException"> <paramref name="stream"/> is <c>null</c>. </exception> /// <exception cref="ArgumentOutOfRangeException"> If an unrecognized/unsupported value was given for <paramref name="compression"/>. </exception> /// <exception cref="NotSupportedException"> If <paramref name="compression"/> is set to AutoDetect, but the stream is not seekable. </exception> /// <exception cref="EndOfStreamException"> If file ended earlier than expected. </exception> /// <exception cref="InvalidDataException"> If file compression could not be detected, decompressing failed, or given stream does not support reading. </exception> /// <exception cref="NbtFormatException"> If an error occurred while parsing data in NBT format. </exception> public long LoadFromStream([NotNull] Stream stream, NbtCompression compression, [CanBeNull] TagSelector selector) { if (stream == null) { throw new ArgumentNullException("stream"); } FileName = null; // detect compression, based on the first byte if (compression == NbtCompression.AutoDetect) { FileCompression = DetectCompression(stream); } else { FileCompression = compression; } // prepare to count bytes read long startOffset = 0; if (stream.CanSeek) { startOffset = stream.Position; } else { stream = new ByteCountingStream(stream); } switch (FileCompression) { case NbtCompression.GZip: using (var decStream = new GZipStream(stream, CompressionMode.Decompress, true)) { if (bufferSize > 0) { LoadFromStreamInternal(new BufferedStream(decStream, bufferSize), selector); } else { LoadFromStreamInternal(decStream, selector); } } break; case NbtCompression.None: LoadFromStreamInternal(stream, selector); break; case NbtCompression.ZLib: if (stream.ReadByte() != 0x78) { throw new InvalidDataException(WrongZLibHeaderMessage); } stream.ReadByte(); using (var decStream = new DeflateStream(stream, CompressionMode.Decompress, true)) { if (bufferSize > 0) { LoadFromStreamInternal(new BufferedStream(decStream, bufferSize), selector); } else { LoadFromStreamInternal(decStream, selector); } } break; default: throw new ArgumentOutOfRangeException("compression"); } // report bytes read if (stream.CanSeek) { return(stream.Position - startOffset); } else { return(((ByteCountingStream)stream).BytesRead); } }
/// <summary> Loads NBT data from a stream. Existing <c>RootTag</c> will be replaced </summary> /// <param name="stream"> Stream from which data will be loaded. If compression is set to AutoDetect, this stream must support seeking. </param> /// <param name="compression"> Compression method to use for loading/saving this file. </param> /// <param name="selector"> Optional callback to select which tags to load into memory. Root may not be skipped. /// No reference is stored to this callback after loading (don't worry about implicitly captured closures). May be <c>null</c>. </param> /// <returns> Number of bytes read from the stream. </returns> /// <exception cref="ArgumentNullException"> <paramref name="stream"/> is <c>null</c>. </exception> /// <exception cref="ArgumentOutOfRangeException"> If an unrecognized/unsupported value was given for <paramref name="compression"/>. </exception> /// <exception cref="NotSupportedException"> If <paramref name="compression"/> is set to AutoDetect, but the stream is not seekable. </exception> /// <exception cref="EndOfStreamException"> If file ended earlier than expected. </exception> /// <exception cref="InvalidDataException"> If file compression could not be detected, decompressing failed, or given stream does not support reading. </exception> /// <exception cref="NbtFormatException"> If an error occurred while parsing data in NBT format. </exception> public long LoadFromStream([NotNull] Stream stream, NbtCompression compression, [CanBeNull] TagSelector selector) { if (stream == null) throw new ArgumentNullException("stream"); FileName = null; // detect compression, based on the first byte if (compression == NbtCompression.AutoDetect) { FileCompression = DetectCompression(stream); } else { FileCompression = compression; } // prepare to count bytes read long startOffset = 0; if (stream.CanSeek) { startOffset = stream.Position; } else { stream = new ByteCountingStream(stream); } switch (FileCompression) { case NbtCompression.GZip: using (var decStream = new GZipStream(stream, CompressionMode.Decompress, true)) { if (bufferSize > 0) { LoadFromStreamInternal(new BufferedStream(decStream, bufferSize), selector); } else { LoadFromStreamInternal(decStream, selector); } } break; case NbtCompression.None: LoadFromStreamInternal(stream, selector); break; case NbtCompression.ZLib: if (stream.ReadByte() != 0x78) { throw new InvalidDataException("Incorrect ZLib header. Expected 0x78 0x9C"); } stream.ReadByte(); using (var decStream = new DeflateStream(stream, CompressionMode.Decompress, true)) { if (bufferSize > 0) { LoadFromStreamInternal(new BufferedStream(decStream, bufferSize), selector); } else { LoadFromStreamInternal(decStream, selector); } } break; default: throw new ArgumentOutOfRangeException("compression"); } // report bytes read if (stream.CanSeek) { return stream.Position - startOffset; } else { return ((ByteCountingStream)stream).BytesRead; } }