Transitioned from a single NbtStream class to a separate TagReader and TagWriter classes
Added a static NbtFile class for convenience methods when working with files
This commit is contained in:
parent
e3a1ad7cfe
commit
8a35ed1d9f
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -13,8 +14,8 @@ namespace SharpNBT.Tests
|
|||
var compound = new CompoundTag("Top-Level Compound");
|
||||
compound.Add(new ByteTag("Child Byte", 255));
|
||||
compound.Add(new StringTag("Child String", "Hello World!"));
|
||||
|
||||
using var stream = NbtStream.OpenWrite("./Data/write-test-uncompressed.nbt", CompressionLevel.NoCompression);
|
||||
|
||||
using var stream = NbtFile.OpenWrite("./Data/write-test-uncompressed.nbt", CompressionLevel.NoCompression);
|
||||
stream.WriteTag(compound);
|
||||
}
|
||||
|
||||
|
|
@ -25,7 +26,7 @@ namespace SharpNBT.Tests
|
|||
compound.Add(new ByteTag("Child Byte", 255));
|
||||
compound.Add(new StringTag("Child String", "Hello World!"));
|
||||
|
||||
using var stream = NbtStream.OpenWrite("./Data/write-test-compressed.nbt", CompressionLevel.Optimal);
|
||||
using var stream = NbtFile.OpenWrite("./Data/write-test-compressed.nbt", CompressionLevel.Optimal);
|
||||
stream.WriteTag(compound);
|
||||
}
|
||||
|
||||
|
|
@ -33,7 +34,7 @@ namespace SharpNBT.Tests
|
|||
[Fact]
|
||||
public void ReadUncompressed()
|
||||
{
|
||||
using var stream = NbtStream.OpenRead("./Data/hello_world.nbt");
|
||||
using var stream = NbtFile.OpenRead("./Data/hello_world.nbt");
|
||||
var compound = stream.ReadTag<CompoundTag>();
|
||||
Assert.Equal("hello world", compound.Name);
|
||||
}
|
||||
|
|
@ -41,7 +42,7 @@ namespace SharpNBT.Tests
|
|||
[Fact]
|
||||
public void ReadCompressed()
|
||||
{
|
||||
using var stream = NbtStream.OpenRead("./Data/bigtest.nbt");
|
||||
using var stream = NbtFile.OpenRead("./Data/bigtest.nbt");
|
||||
var compound = stream.ReadTag<CompoundTag>();
|
||||
Assert.Equal("Level", compound.Name);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ namespace SharpNBT.Tests
|
|||
public FormatConverters(ITestOutputHelper output)
|
||||
{
|
||||
this.output = output;
|
||||
using var stream = NbtStream.OpenRead("./Data/bigtest.nbt");
|
||||
using var stream = NbtFile.OpenRead("./Data/bigtest.nbt");
|
||||
compoundTag = stream.ReadTag<CompoundTag>();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ namespace SharpNBT.Tests
|
|||
[Fact]
|
||||
public void PrettyPrintToStdout()
|
||||
{
|
||||
using var stream = NbtStream.OpenRead("./Data/bigtest.nbt");
|
||||
using var stream = NbtFile.OpenRead("./Data/bigtest.nbt");
|
||||
var topLevel = stream.ReadTag<CompoundTag>();
|
||||
|
||||
output.WriteLine(topLevel.PrettyPrinted());
|
||||
|
|
|
|||
|
|
@ -48,11 +48,11 @@ namespace SharpNBT.Tests
|
|||
var temp = builder.Create();
|
||||
output.WriteLine(temp.PrettyPrinted());
|
||||
|
||||
using (var stream = NbtStream.OpenWrite(FILE_PATH))
|
||||
using (var stream = NbtFile.OpenWrite(FILE_PATH))
|
||||
stream.WriteTag(temp);
|
||||
|
||||
output.WriteLine("\n**** POST SERIALIZATION/DESERIALIZATION ****\n");
|
||||
using (var stream = NbtStream.OpenRead(FILE_PATH))
|
||||
using (var stream = NbtFile.OpenRead(FILE_PATH))
|
||||
{
|
||||
var compound = stream.ReadTag<CompoundTag>();
|
||||
output.WriteLine(compound.PrettyPrinted());
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ namespace SharpNBT
|
|||
/// Initializes a new instance of the <see cref="ByteArrayTag"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
|
||||
public ByteArrayTag([CanBeNull] string name) : base(TagType.ByteArray, name)
|
||||
/// <param name="values">A collection of values to include in this tag.</param>
|
||||
public ByteArrayTag([CanBeNull] string name, byte[] values) : base(TagType.ByteArray, name, values)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,17 @@ namespace SharpNBT
|
|||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EnumerableTag{T}"/> with the specified <paramref name="values"/>.
|
||||
/// </summary>
|
||||
/// <param name="type">A constant describing the NBT type for this tag.</param>
|
||||
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
|
||||
/// <param name="values">A collection of values to include in this tag.</param>
|
||||
protected EnumerableTag(TagType type, [CanBeNull] string name, [NotNull] T[] values) : base(type, name)
|
||||
{
|
||||
internalList.AddRange(values);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EnumerableTag{T}"/> with the specified <paramref name="values"/>.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,15 @@ namespace SharpNBT
|
|||
public IntArrayTag([CanBeNull] string name) : base(TagType.IntArray, name)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="IntArrayTag"/> with the specified <paramref name="values"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
|
||||
/// <param name="values">A collection of values to include in this tag.</param>
|
||||
public IntArrayTag([CanBeNull] string name, [NotNull] int[] values) : base(TagType.IntArray, name, values)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="IntArrayTag"/> with the specified <paramref name="values"/>.
|
||||
|
|
|
|||
|
|
@ -18,6 +18,15 @@ namespace SharpNBT
|
|||
public LongArrayTag([CanBeNull] string name) : base(TagType.LongArray, name)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LongArrayTag"/> with the specified <paramref name="values"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
|
||||
/// <param name="values">A collection of values to include in this tag.</param>
|
||||
public LongArrayTag([CanBeNull] string name, [NotNull] long[] values) : base(TagType.LongArray, name, values)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LongArrayTag"/> with the specified <paramref name="values"/>.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,98 @@
|
|||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace SharpNBT
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides static convenience methods for working with NBT-formatted files, including both reading and writing.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public static class NbtFile
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads a file at the given <paramref name="path"/> and deserializes the top-level <see cref="CompoundTag"/> contained in the file.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the file to be read.</param>
|
||||
/// <returns>The deserialized <see cref="CompoundTag"/> instance.</returns>
|
||||
public static CompoundTag Read([NotNull] string path)
|
||||
{
|
||||
using var stream = File.OpenRead(path);
|
||||
using var reader = new TagReader(stream, IsCompressed(path), false);
|
||||
return reader.ReadTag<CompoundTag>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously reads a file at the given <paramref name="path"/> and deserializes the top-level <see cref="CompoundTag"/> contained in the file.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the file to be read.</param>
|
||||
/// <returns>The deserialized <see cref="CompoundTag"/> instance.</returns>
|
||||
public static async Task<CompoundTag> ReadAsync([NotNull] string path)
|
||||
{
|
||||
await using var stream = File.OpenRead(path);
|
||||
await using var reader = new TagReader(stream, IsCompressed(path), false);
|
||||
return await reader.ReadTagAsync<CompoundTag>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the given <paramref name="tag"/> to a file at the specified <paramref name="path"/>.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the file to be written to.</param>
|
||||
/// <param name="tag">The top-level <see cref="CompoundTag"/> instance to be serialized.</param>
|
||||
/// <param name="compression">Indicates a compression strategy to be used, if any.</param>
|
||||
public static void Write([NotNull] string path, [NotNull] CompoundTag tag, CompressionLevel compression = CompressionLevel.NoCompression)
|
||||
{
|
||||
using var stream = File.OpenWrite(path);
|
||||
using var writer = new TagWriter(stream, compression);
|
||||
writer.WriteTag(tag);
|
||||
}
|
||||
|
||||
public static async Task WriteAsync([NotNull] string path, [NotNull] CompoundTag tag, CompressionLevel compression = CompressionLevel.NoCompression)
|
||||
{
|
||||
await using var stream = File.OpenWrite(path);
|
||||
await using var writer = new TagWriter(stream, compression);
|
||||
await writer.WriteTagAsync(tag);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detects the presence of a GZip compressed file at the given <paramref name="path"/> by searching for the "magic number" in the header.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the file to query.</param>
|
||||
/// <returns><see langword="true"/> if GZip compression was detected, otherwise <see langword="false"/>.</returns>
|
||||
public static bool IsCompressed(string path)
|
||||
{
|
||||
using var str = File.OpenRead(path);
|
||||
return str.ReadByte() == 0x1F && str.ReadByte() == 0x8B;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens an existing NBT file for reading, and returns a <see cref="TagReader"/> instance for it.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the file to query write.</param>
|
||||
/// <returns>A <see cref="TagReader"/> instance for the file stream.</returns>
|
||||
/// <remarks>File compression will be automatically detected and used handled when necessary.</remarks>
|
||||
public static TagReader OpenRead(string path)
|
||||
{
|
||||
var compressed = IsCompressed(path);
|
||||
var stream = File.OpenRead(path);
|
||||
return new TagReader(stream, compressed, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens an existing NBT file or creates a new one if one if it does not exist, and returns a <see cref="TagWriter"/> instance for it.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the file to query write.</param>
|
||||
/// <param name="compression">A flag indicating the compression strategy that will be used, if any.</param>
|
||||
/// <returns>A <see cref="TagWriter"/> instance for the file stream.</returns>
|
||||
public static TagWriter OpenWrite(string path, CompressionLevel compression = CompressionLevel.NoCompression)
|
||||
{
|
||||
var stream = File.OpenWrite(path);
|
||||
if (compression == CompressionLevel.NoCompression)
|
||||
return new TagWriter(stream, compression);
|
||||
|
||||
var gzip = new GZipStream(stream, compression, false);
|
||||
return new TagWriter(gzip, compression);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,185 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace SharpNBT
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods and properties to read and write as a stream using the NBT format specification.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public partial class NbtStream : Stream
|
||||
{
|
||||
protected readonly Stream BaseStream;
|
||||
private readonly Stack<bool> includeName;
|
||||
private readonly bool leaveOpen;
|
||||
|
||||
/// <summary>
|
||||
/// Opens a <see cref="NbtStream"/> on the specified <paramref name="path"/> with read access.
|
||||
/// <para/>
|
||||
/// GZip compressed files will be detected and handled automatically.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to a file to open.</param>
|
||||
///<returns>A <see cref="NbtStream"/> opened on the specified path with read access.</returns>
|
||||
public static NbtStream OpenRead(string path)
|
||||
{
|
||||
var compressed = false;
|
||||
|
||||
// Look for GZIP magic number
|
||||
using (var str = File.OpenRead(path))
|
||||
{
|
||||
if (str.ReadByte() == 0x1F && str.ReadByte() == 0x8B)
|
||||
compressed = true;
|
||||
}
|
||||
|
||||
return compressed ? new NbtStream(File.OpenRead(path), CompressionMode.Decompress) : new NbtStream(File.OpenRead(path));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a <see cref="NbtStream"/> on the specified <paramref name="path"/> with write access.
|
||||
/// <para/>
|
||||
/// GZip compressed files will be detected and handled automatically.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to a file to open.</param>
|
||||
/// <param name="level">Specify a compression strategy to emphasise either size or speed, or no compression at all.</param>
|
||||
/// <returns>A <see cref="NbtStream"/> opened on the specified path with write access.</returns>
|
||||
/// <remarks>Note that if the data is very small, compressing it will actually result in making it larger than when uncompressed due to additional
|
||||
/// information required by the GZip spec also being written.</remarks>
|
||||
public static NbtStream OpenWrite(string path, CompressionLevel level = CompressionLevel.NoCompression)
|
||||
{
|
||||
if (level != CompressionLevel.NoCompression)
|
||||
{
|
||||
var stream = new GZipStream(File.OpenWrite(path), level, false);
|
||||
return new NbtStream(stream);
|
||||
}
|
||||
return new NbtStream(File.OpenWrite(path));
|
||||
}
|
||||
|
||||
public NbtStream(Stream stream, bool leaveOpen = false)
|
||||
{
|
||||
this.leaveOpen = leaveOpen;
|
||||
BaseStream = stream ?? throw new ArgumentNullException(nameof(stream));
|
||||
includeName = new Stack<bool>();
|
||||
includeName.Push(true);
|
||||
}
|
||||
|
||||
public NbtStream(Stream stream, CompressionMode compression, bool leaveOpen = false) : this(new GZipStream(stream, compression, leaveOpen), leaveOpen)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Closes the current stream and releases any resources (such as sockets and file handles) associated with the current stream. Instead of calling this method, ensure that the stream is properly disposed.</summary>
|
||||
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.IO.Stream.Close?view=netstandard-2.1">`Stream.Close` on docs.microsoft.com</a></footer>
|
||||
public override void Close()
|
||||
{
|
||||
if (!leaveOpen)
|
||||
BaseStream.Close();
|
||||
base.Close();
|
||||
}
|
||||
|
||||
/// <summary>Releases the unmanaged resources used by the <see cref="T:System.IO.Stream" /> and optionally releases the managed resources.</summary>
|
||||
/// <param name="disposing">
|
||||
/// <see langword="true" /> to release both managed and unmanaged resources; <see langword="false" /> to release only unmanaged resources.</param>
|
||||
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.IO.Stream.Dispose?view=netstandard-2.1">`Stream.Dispose` on docs.microsoft.com</a></footer>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!leaveOpen)
|
||||
BaseStream.Dispose();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
/// <summary>Clears all buffers for this stream and causes any buffered data to be written to the underlying device.</summary>
|
||||
/// <exception cref="T:System.IO.IOException">An I/O error occurs.</exception>
|
||||
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.IO.Stream.Flush?view=netcore-5.0">`Stream.Flush` on docs.microsoft.com</a></footer>
|
||||
public override void Flush() => BaseStream.Flush();
|
||||
|
||||
/// <summary>Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.</summary>
|
||||
/// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between <paramref name="offset" /> and (<paramref name="offset" /> + <paramref name="count" /> - 1) replaced by the bytes read from the current source.</param>
|
||||
/// <param name="offset">The zero-based byte offset in <paramref name="buffer" /> at which to begin storing the data read from the current stream.</param>
|
||||
/// <param name="count">The maximum number of bytes to be read from the current stream.</param>
|
||||
/// <exception cref="T:System.ArgumentException">The sum of <paramref name="offset" /> and <paramref name="count" /> is larger than the buffer length.</exception>
|
||||
/// <exception cref="T:System.ArgumentNullException">
|
||||
/// <paramref name="buffer" /> is <see langword="null" />.</exception>
|
||||
/// <exception cref="T:System.ArgumentOutOfRangeException">
|
||||
/// <paramref name="offset" /> or <paramref name="count" /> is negative.</exception>
|
||||
/// <exception cref="T:System.IO.IOException">An I/O error occurs.</exception>
|
||||
/// <exception cref="T:System.NotSupportedException">The stream does not support reading.</exception>
|
||||
/// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed.</exception>
|
||||
/// <returns>The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.</returns>
|
||||
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.IO.Stream.Read?view=netcore-5.0">`Stream.Read` on docs.microsoft.com</a></footer>
|
||||
public override int Read(byte[] buffer, int offset, int count) => BaseStream.Read(buffer, offset, count);
|
||||
|
||||
/// <summary>Sets the position within the current stream.</summary>
|
||||
/// <param name="offset">A byte offset relative to the <paramref name="origin" /> parameter.</param>
|
||||
/// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin" /> indicating the reference point used to obtain the new position.</param>
|
||||
/// <exception cref="T:System.IO.IOException">An I/O error occurs.</exception>
|
||||
/// <exception cref="T:System.NotSupportedException">The stream does not support seeking, such as if the stream is constructed from a pipe or console output.</exception>
|
||||
/// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed.</exception>
|
||||
/// <returns>The new position within the current stream.</returns>
|
||||
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.IO.Stream.Seek?view=netcore-5.0">`Stream.Seek` on docs.microsoft.com</a></footer>
|
||||
public override long Seek(long offset, SeekOrigin origin) => BaseStream.Seek(offset, origin);
|
||||
|
||||
/// <summary>Sets the length of the current stream.</summary>
|
||||
/// <param name="value">The desired length of the current stream in bytes.</param>
|
||||
/// <exception cref="T:System.IO.IOException">An I/O error occurs.</exception>
|
||||
/// <exception cref="T:System.NotSupportedException">The stream does not support both writing and seeking, such as if the stream is constructed from a pipe or console output.</exception>
|
||||
/// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed.</exception>
|
||||
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.IO.Stream.SetLength?view=netcore-5.0">`Stream.SetLength` on docs.microsoft.com</a></footer>
|
||||
public override void SetLength(long value) => BaseStream.SetLength(value);
|
||||
|
||||
/// <summary>Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.</summary>
|
||||
/// <param name="buffer">An array of bytes. This method copies <paramref name="count" /> bytes from <paramref name="buffer" /> to the current stream.</param>
|
||||
/// <param name="offset">The zero-based byte offset in <paramref name="buffer" /> at which to begin copying bytes to the current stream.</param>
|
||||
/// <param name="count">The number of bytes to be written to the current stream.</param>
|
||||
/// <exception cref="T:System.ArgumentException">The sum of <paramref name="offset" /> and <paramref name="count" /> is greater than the buffer length.</exception>
|
||||
/// <exception cref="T:System.ArgumentNullException">
|
||||
/// <paramref name="buffer" /> is <see langword="null" />.</exception>
|
||||
/// <exception cref="T:System.ArgumentOutOfRangeException">
|
||||
/// <paramref name="offset" /> or <paramref name="count" /> is negative.</exception>
|
||||
/// <exception cref="T:System.IO.IOException">An I/O error occurred, such as the specified file cannot be found.</exception>
|
||||
/// <exception cref="T:System.NotSupportedException">The stream does not support writing.</exception>
|
||||
/// <exception cref="T:System.ObjectDisposedException">
|
||||
/// <see cref="M:System.IO.Stream.Write(System.Byte[],System.Int32,System.Int32)" /> was called after the stream was closed.</exception>
|
||||
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.IO.Stream.Write?view=netcore-5.0">`Stream.Write` on docs.microsoft.com</a></footer>
|
||||
public override void Write(byte[] buffer, int offset, int count) => BaseStream.Write(buffer, offset, count);
|
||||
|
||||
/// <summary>Gets a value indicating whether the current stream supports reading.</summary>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if the stream supports reading; otherwise, <see langword="false" />.</returns>
|
||||
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.IO.Stream.CanRead?view=netcore-5.0">`Stream.CanRead` on docs.microsoft.com</a></footer>
|
||||
public override bool CanRead => BaseStream.CanRead;
|
||||
|
||||
/// <summary>Gets a value indicating whether the current stream supports seeking.</summary>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if the stream supports seeking; otherwise, <see langword="false" />.</returns>
|
||||
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.IO.Stream.CanSeek?view=netcore-5.0">`Stream.CanSeek` on docs.microsoft.com</a></footer>
|
||||
public override bool CanSeek => BaseStream.CanSeek;
|
||||
|
||||
/// <summary>Gets a value indicating whether the current stream supports writing.</summary>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if the stream supports writing; otherwise, <see langword="false" />.</returns>
|
||||
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.IO.Stream.CanWrite?view=netcore-5.0">`Stream.CanWrite` on docs.microsoft.com</a></footer>
|
||||
public override bool CanWrite => BaseStream.CanWrite;
|
||||
|
||||
/// <summary>Gets the length in bytes of the stream.</summary>
|
||||
/// <exception cref="T:System.NotSupportedException">A class derived from <see langword="Stream" /> does not support seeking.</exception>
|
||||
/// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed.</exception>
|
||||
/// <returns>A long value representing the length of the stream in bytes.</returns>
|
||||
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.IO.Stream.Length?view=netcore-5.0">`Stream.Length` on docs.microsoft.com</a></footer>
|
||||
public override long Length => BaseStream.Length;
|
||||
|
||||
/// <summary>Gets or sets the position within the current stream.</summary>
|
||||
/// <exception cref="T:System.IO.IOException">An I/O error occurs.</exception>
|
||||
/// <exception cref="T:System.NotSupportedException">The stream does not support seeking.</exception>
|
||||
/// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed.</exception>
|
||||
/// <returns>The current position within the stream.</returns>
|
||||
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.IO.Stream.Position?view=netcore-5.0">`Stream.Position` on docs.microsoft.com</a></footer>
|
||||
public override long Position
|
||||
{
|
||||
get => BaseStream.Position;
|
||||
set => BaseStream.Position = value;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,46 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.IO.Compression;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace SharpNBT
|
||||
{
|
||||
public partial class NbtStream
|
||||
|
||||
public delegate void TagReadCallback(TagReader reader, TagType type, Tag tag);
|
||||
|
||||
|
||||
[PublicAPI]
|
||||
public class TagReader : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when a tag has been fully deserialized from the stream.
|
||||
/// </summary>
|
||||
public event TagReadCallback TagRead;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying stream this <see cref="TagReader"/> is operating on.
|
||||
/// </summary>
|
||||
[NotNull]
|
||||
protected Stream BaseStream { get; }
|
||||
|
||||
private readonly bool leaveOpen;
|
||||
|
||||
public TagReader(Stream stream, bool leaveOpen) : this(stream, stream is GZipStream, leaveOpen)
|
||||
{
|
||||
}
|
||||
|
||||
public TagReader(Stream stream, bool compressed, bool leaveOpen)
|
||||
{
|
||||
this.leaveOpen = leaveOpen;
|
||||
if (compressed && !(stream is GZipStream))
|
||||
BaseStream = new GZipStream(stream, CompressionMode.Decompress, leaveOpen);
|
||||
else
|
||||
BaseStream = stream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a <see cref="ByteTag"/> from the stream.
|
||||
/// </summary>
|
||||
|
|
@ -17,8 +49,8 @@ namespace SharpNBT
|
|||
/// <returns>The deserialized <see cref="ByteTag"/> instance.</returns>
|
||||
public ByteTag ReadByte(bool named = true)
|
||||
{
|
||||
var name = named ? ReadPrefixedString() : null;
|
||||
return new ByteTag(name, (byte)BaseStream.ReadByte());
|
||||
var name = named ? ReadUTF8String() : null;
|
||||
return new ByteTag(name, (byte) BaseStream.ReadByte());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -29,15 +61,14 @@ namespace SharpNBT
|
|||
/// <returns>The deserialized <see cref="ShortTag"/> instance.</returns>
|
||||
public ShortTag ReadShort(bool named = true)
|
||||
{
|
||||
var name = named ? ReadPrefixedString() : null;
|
||||
|
||||
Span<byte> buffer = stackalloc byte[2];
|
||||
var name = named ? ReadUTF8String() : null;
|
||||
Span<byte> buffer = stackalloc byte[sizeof(short)];
|
||||
BaseStream.Read(buffer);
|
||||
var value = BitConverter.ToInt16(buffer);
|
||||
|
||||
|
||||
return new ShortTag(name, BitConverter.IsLittleEndian ? value.SwapEndian() : value);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Reads a <see cref="IntTag"/> from the stream.
|
||||
/// </summary>
|
||||
|
|
@ -46,7 +77,7 @@ namespace SharpNBT
|
|||
/// <returns>The deserialized <see cref="IntTag"/> instance.</returns>
|
||||
public IntTag ReadInt(bool named = true)
|
||||
{
|
||||
var name = named ? ReadPrefixedString() : null;
|
||||
var name = named ? ReadUTF8String() : null;
|
||||
return new IntTag(name, ReadInt32());
|
||||
}
|
||||
|
||||
|
|
@ -58,15 +89,14 @@ namespace SharpNBT
|
|||
/// <returns>The deserialized <see cref="LongTag"/> instance.</returns>
|
||||
public LongTag ReadLong(bool named = true)
|
||||
{
|
||||
var name = named ? ReadPrefixedString() : null;
|
||||
|
||||
Span<byte> buffer = stackalloc byte[8];
|
||||
var name = named ? ReadUTF8String() : null;
|
||||
Span<byte> buffer = stackalloc byte[sizeof(long)];
|
||||
BaseStream.Read(buffer);
|
||||
var value = BitConverter.ToInt64(buffer);
|
||||
|
||||
|
||||
return new LongTag(name, BitConverter.IsLittleEndian ? value.SwapEndian() : value);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Reads a <see cref="FloatTag"/> from the stream.
|
||||
/// </summary>
|
||||
|
|
@ -75,7 +105,7 @@ namespace SharpNBT
|
|||
/// <returns>The deserialized <see cref="FloatTag"/> instance.</returns>
|
||||
public FloatTag ReadFloat(bool named = true)
|
||||
{
|
||||
var name = named ? ReadPrefixedString() : null;
|
||||
var name = named ? ReadUTF8String() : null;
|
||||
|
||||
var buffer = new byte[sizeof(float)];
|
||||
BaseStream.Read(buffer, 0, sizeof(float));
|
||||
|
|
@ -93,15 +123,15 @@ namespace SharpNBT
|
|||
/// <returns>The deserialized <see cref="DoubleTag"/> instance.</returns>
|
||||
public DoubleTag ReadDouble(bool named = true)
|
||||
{
|
||||
var name = named ? ReadPrefixedString() : null;
|
||||
var name = named ? ReadUTF8String() : null;
|
||||
var buffer = new byte[sizeof(double)];
|
||||
BaseStream.Read(buffer, 0, sizeof(double));
|
||||
BaseStream.Read(buffer, 0, buffer.Length);
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(buffer);
|
||||
|
||||
return new DoubleTag( name, BitConverter.ToDouble(buffer));
|
||||
}
|
||||
|
||||
return new DoubleTag( name, BitConverter.ToDouble(buffer, 0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a <see cref="StringTag"/> from the stream.
|
||||
/// </summary>
|
||||
|
|
@ -110,8 +140,8 @@ namespace SharpNBT
|
|||
/// <returns>The deserialized <see cref="StringTag"/> instance.</returns>
|
||||
public StringTag ReadString(bool named = true)
|
||||
{
|
||||
var name = named ? ReadPrefixedString() : null;
|
||||
var value = ReadPrefixedString();
|
||||
var name = named ? ReadUTF8String() : null;
|
||||
var value = ReadUTF8String();
|
||||
return new StringTag(name, value);
|
||||
}
|
||||
|
||||
|
|
@ -123,11 +153,11 @@ namespace SharpNBT
|
|||
/// <returns>The deserialized <see cref="ByteArrayTag"/> instance.</returns>
|
||||
public ByteArrayTag ReadByteArray(bool named = true)
|
||||
{
|
||||
var name = named ? ReadPrefixedString() : null;
|
||||
var name = named ? ReadUTF8String() : null;
|
||||
var count = ReadInt32();
|
||||
var buffer = new byte[count];
|
||||
BaseStream.Read(buffer, 0, count);
|
||||
return new ByteArrayTag(name, new ReadOnlySpan<byte>(buffer));
|
||||
return new ByteArrayTag(name, buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -140,7 +170,7 @@ namespace SharpNBT
|
|||
{
|
||||
const int INT_SIZE = sizeof(int);
|
||||
|
||||
var name = named ? ReadPrefixedString() : null;
|
||||
var name = named ? ReadUTF8String() : null;
|
||||
var count = ReadInt32();
|
||||
var buffer = new byte[count * INT_SIZE];
|
||||
BaseStream.Read(buffer, 0, count * INT_SIZE);
|
||||
|
|
@ -164,7 +194,7 @@ namespace SharpNBT
|
|||
{
|
||||
const int LONG_SIZE = sizeof(long);
|
||||
|
||||
var name = named ? ReadPrefixedString() : null;
|
||||
var name = named ? ReadUTF8String() : null;
|
||||
var count = ReadInt32();
|
||||
var buffer = new byte[count * LONG_SIZE];
|
||||
BaseStream.Read(buffer, 0, count * LONG_SIZE);
|
||||
|
|
@ -186,22 +216,22 @@ namespace SharpNBT
|
|||
/// <returns>The deserialized <see cref="ListTag"/> instance.</returns>
|
||||
public ListTag ReadList(bool named = true)
|
||||
{
|
||||
var name = named ? ReadPrefixedString() : null;
|
||||
var name = named ? ReadUTF8String() : null;
|
||||
var childType = ReadType();
|
||||
var count = ReadInt32();
|
||||
|
||||
if (childType == TagType.End && count > 0)
|
||||
throw new FormatException("An EndTag is not a valid child type for a ListTag.");
|
||||
throw new FormatException("An EndTag is not a valid child type for a non-empty ListTag.");
|
||||
|
||||
var list = new ListTag(name, childType);
|
||||
while (count-- > 0)
|
||||
{
|
||||
list.Add(ReadTag(childType, false));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Reads a <see cref="CompoundTag"/> from the stream.
|
||||
/// </summary>
|
||||
|
|
@ -210,9 +240,9 @@ namespace SharpNBT
|
|||
/// <returns>The deserialized <see cref="CompoundTag"/> instance.</returns>
|
||||
public CompoundTag ReadCompound(bool named = true)
|
||||
{
|
||||
var name = named ? ReadPrefixedString() : null;
|
||||
var name = named ? ReadUTF8String() : null;
|
||||
var compound = new CompoundTag(name);
|
||||
|
||||
|
||||
while (true)
|
||||
{
|
||||
var type = ReadType();
|
||||
|
|
@ -221,7 +251,7 @@ namespace SharpNBT
|
|||
|
||||
compound.Add(ReadTag(type, true));
|
||||
}
|
||||
|
||||
|
||||
return compound;
|
||||
}
|
||||
|
||||
|
|
@ -237,7 +267,17 @@ namespace SharpNBT
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience method to read a tag and cast it with automatically.
|
||||
/// Asynchronously reads a <see cref="Tag"/> from the current position in the stream.
|
||||
/// </summary>
|
||||
/// <param name="named">Flag indicating if this tag is named, only <see langowrd="false"/> when a tag is a direct child of a <see cref="ListTag"/>.</param>
|
||||
/// <returns>The tag instance that was read from the stream.</returns>
|
||||
public async Task<Tag> ReadTagAsync(bool named = true)
|
||||
{
|
||||
return await Task.Run(() => ReadTag(named));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience method to read a tag and cast it automatically.
|
||||
/// </summary>
|
||||
/// <param name="named">Flag indicating if this tag is named, only <see langowrd="false"/> when a tag is a direct child of a <see cref="ListTag"/>.</param>
|
||||
/// <typeparam name="T">The tag type that is being read from the stream.</typeparam>
|
||||
|
|
@ -247,11 +287,24 @@ namespace SharpNBT
|
|||
{
|
||||
return (T)ReadTag(named);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience method to asynchronously read a tag and cast it automatically.
|
||||
/// </summary>
|
||||
/// <param name="named">Flag indicating if this tag is named, only <see langowrd="false"/> when a tag is a direct child of a <see cref="ListTag"/>.</param>
|
||||
/// <typeparam name="T">The tag type that is being read from the stream.</typeparam>
|
||||
/// <returns>The tag instance that was read from the stream.</returns>
|
||||
/// <remarks>This is typically only used when reading the top-level <see cref="CompoundTag"/> of a document where the type is already known.</remarks>
|
||||
public async Task<T> ReadTagAsync<T>(bool named = true) where T : Tag
|
||||
{
|
||||
var tag = await ReadTagAsync(named);
|
||||
return (T)tag;
|
||||
}
|
||||
|
||||
[NotNull]
|
||||
protected Tag ReadTag(TagType type, bool named)
|
||||
private Tag ReadTag(TagType type, bool named)
|
||||
{
|
||||
return type switch
|
||||
Tag tag = type switch
|
||||
{
|
||||
TagType.End => new EndTag(),
|
||||
TagType.Byte => ReadByte(named),
|
||||
|
|
@ -268,8 +321,11 @@ namespace SharpNBT
|
|||
TagType.LongArray => ReadLongArray(named),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
|
||||
};
|
||||
OnTagRead(tag);
|
||||
return tag;
|
||||
}
|
||||
|
||||
|
||||
private TagType ReadType()
|
||||
{
|
||||
try
|
||||
|
|
@ -282,35 +338,73 @@ namespace SharpNBT
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Reads a length-prefixed (unsigned short) UTF-8 string from the <see cref="BaseStream"/>.
|
||||
/// Reads a length-prefixed UTF-8 string from the stream.
|
||||
/// </summary>
|
||||
/// <returns>The string instance, or <see langword="null"/> if a length of <c>0</c> was specified.</returns>
|
||||
/// <returns>The deserialized string instance.</returns>
|
||||
[CanBeNull]
|
||||
private string ReadPrefixedString()
|
||||
protected string ReadUTF8String()
|
||||
{
|
||||
Span<byte> lenBuffer = stackalloc byte[2];
|
||||
BaseStream.Read(lenBuffer);
|
||||
var len = BitConverter.ToUInt16(lenBuffer);
|
||||
Span<byte> buffer = stackalloc byte[sizeof(ushort)];
|
||||
BaseStream.Read(buffer);
|
||||
var length = BitConverter.ToUInt16(buffer);
|
||||
if (BitConverter.IsLittleEndian)
|
||||
len = len.SwapEndian();
|
||||
|
||||
if (len == 0)
|
||||
length = length.SwapEndian();
|
||||
|
||||
if (length == 0)
|
||||
return null;
|
||||
|
||||
Span<byte> buffer = stackalloc byte[len];
|
||||
BaseStream.Read(buffer);
|
||||
return Encoding.UTF8.GetString(buffer);
|
||||
var utf8 = new byte[length];
|
||||
BaseStream.Read(utf8, 0, length);
|
||||
return Encoding.UTF8.GetString(utf8);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
/// <summary>
|
||||
/// Reads a 64-bit signed (big-endian) integer from the stream, converting to native endian when necessary.
|
||||
/// </summary>
|
||||
/// <returns>The deserialized value.</returns>
|
||||
private int ReadInt32()
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[4];
|
||||
Span<byte> buffer = stackalloc byte[sizeof(int)];
|
||||
BaseStream.Read(buffer);
|
||||
var value = BitConverter.ToInt32(buffer);
|
||||
return BitConverter.IsLittleEndian ? value.SwapEndian() : value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (!leaveOpen)
|
||||
BaseStream.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously releases the unmanaged resources used by the <see cref="TagReader"/>.
|
||||
/// </summary>
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (!leaveOpen)
|
||||
await BaseStream.DisposeAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="TagRead"/> event when a tag has been fully deserialized from the <see cref="BaseStream"/>.
|
||||
/// </summary>
|
||||
/// <param name="tag">The deserialized <see cref="Tag"/> instance.</param>
|
||||
protected virtual void OnTagRead(Tag tag) => TagRead?.Invoke(this, tag.Type, tag);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,38 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace SharpNBT
|
||||
{
|
||||
public partial class NbtStream
|
||||
public class TagWriter : IDisposable
|
||||
{
|
||||
private readonly bool leaveOpen;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying stream this <see cref="TagReader"/> is operating on.
|
||||
/// </summary>
|
||||
[NotNull]
|
||||
protected Stream BaseStream { get; }
|
||||
|
||||
public TagWriter([NotNull] Stream stream, bool leaveOpen = false) : this(stream, CompressionLevel.NoCompression, leaveOpen)
|
||||
{
|
||||
}
|
||||
|
||||
public TagWriter([NotNull] Stream stream, CompressionLevel compression, bool leaveOpen = false)
|
||||
{
|
||||
this.leaveOpen = leaveOpen;
|
||||
if (compression != CompressionLevel.NoCompression && !(stream is GZipStream))
|
||||
BaseStream = new GZipStream(stream, compression, leaveOpen);
|
||||
else
|
||||
BaseStream = stream ?? throw new ArgumentNullException(nameof(stream), "Stream cannot be null");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a <see cref="ByteTag"/> to the stream.
|
||||
/// </summary>
|
||||
|
|
@ -28,7 +52,7 @@ namespace SharpNBT
|
|||
WriteTypeAndName(tag);
|
||||
BaseStream.Write(tag.Value.GetBytes(), 0, sizeof(short));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Writes a <see cref="IntTag"/> to the stream.
|
||||
/// </summary>
|
||||
|
|
@ -48,7 +72,7 @@ namespace SharpNBT
|
|||
WriteTypeAndName(tag);
|
||||
BaseStream.Write(tag.Value.GetBytes(), 0, sizeof(long));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Writes a <see cref="FloatTag"/> to the stream.
|
||||
/// </summary>
|
||||
|
|
@ -68,7 +92,7 @@ namespace SharpNBT
|
|||
WriteTypeAndName(tag);
|
||||
BaseStream.Write(tag.Value.GetBytes(), 0, sizeof(double));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Writes a <see cref="StringTag"/> to the stream.
|
||||
/// </summary>
|
||||
|
|
@ -76,7 +100,7 @@ namespace SharpNBT
|
|||
public virtual void WriteString(StringTag tag)
|
||||
{
|
||||
WriteTypeAndName(tag);
|
||||
WriteString(tag.Value);
|
||||
WriteUTF8String(tag.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -89,7 +113,7 @@ namespace SharpNBT
|
|||
BaseStream.Write(tag.Count.GetBytes(), 0, sizeof(int));
|
||||
BaseStream.Write(tag.ToArray(), 0, tag.Count);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Writes a <see cref="IntArrayTag"/> to the stream.
|
||||
/// </summary>
|
||||
|
|
@ -128,7 +152,7 @@ namespace SharpNBT
|
|||
|
||||
BaseStream.Write(MemoryMarshal.AsBytes(values));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Writes a <see cref="ListTag"/> to the stream.
|
||||
/// </summary>
|
||||
|
|
@ -138,20 +162,12 @@ namespace SharpNBT
|
|||
WriteTypeAndName(tag);
|
||||
BaseStream.WriteByte((byte) tag.ChildType);
|
||||
BaseStream.Write(tag.Count.GetBytes(), 0, sizeof(int));
|
||||
|
||||
includeName.Push(false);
|
||||
|
||||
foreach (var child in tag)
|
||||
{
|
||||
if (child.Name != null)
|
||||
throw new FormatException("Tags that are children of a ListTag may not have named.");
|
||||
if (child.Type != tag.ChildType)
|
||||
throw new FormatException("A ListTag may only contain a single type.");
|
||||
|
||||
WriteTag(child);
|
||||
}
|
||||
includeName.Pop();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Writes a <see cref="CompoundTag"/> to the stream.
|
||||
/// </summary>
|
||||
|
|
@ -159,8 +175,6 @@ namespace SharpNBT
|
|||
public virtual void WriteCompound(CompoundTag tag)
|
||||
{
|
||||
WriteTypeAndName(tag);
|
||||
|
||||
includeName.Push(true);
|
||||
foreach (var child in tag)
|
||||
{
|
||||
if (tag.Type == TagType.End)
|
||||
|
|
@ -171,7 +185,6 @@ namespace SharpNBT
|
|||
}
|
||||
|
||||
BaseStream.WriteByte((byte) TagType.End);
|
||||
includeName.Pop();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -179,7 +192,7 @@ namespace SharpNBT
|
|||
/// </summary>
|
||||
/// <param name="tag"></param>
|
||||
public virtual void WriteEndTag([CanBeNull] EndTag tag = null) => BaseStream.WriteByte(0);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Writes the given <see cref="Tag"/> to the stream.
|
||||
/// </summary>
|
||||
|
|
@ -233,6 +246,53 @@ namespace SharpNBT
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously writes the given <see cref="Tag"/> to the stream.
|
||||
/// </summary>
|
||||
/// <param name="tag">The <see cref="Tag"/> instance to be written.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the tag type is unrecognized.</exception>
|
||||
public async Task WriteTagAsync(Tag tag)
|
||||
{
|
||||
await Task.Run(() => WriteTag(tag));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
BaseStream.Flush();
|
||||
if (!leaveOpen)
|
||||
BaseStream.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously releases the unmanaged resources used by the <see cref="TagReader"/>.
|
||||
/// </summary>
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await BaseStream.FlushAsync();
|
||||
if (!leaveOpen)
|
||||
await BaseStream.DisposeAsync();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void WriteTypeAndName(Tag tag)
|
||||
{
|
||||
|
|
@ -240,11 +300,11 @@ namespace SharpNBT
|
|||
return;
|
||||
|
||||
BaseStream.WriteByte((byte) tag.Type);
|
||||
WriteString(tag.Name);
|
||||
WriteUTF8String(tag.Name);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void WriteString(string value)
|
||||
private void WriteUTF8String(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
Loading…
Reference in New Issue