using System.Text; using Nbt.Tag; namespace Nbt.Serialization; public interface INbtWriter : IDisposable { void WriteNamedTag(NamedTag namedTag); void WriteTag(INbtTag tag); void WriteTagType(NbtTagType tagType); void WriteSByte(sbyte b); void WriteShort(short s); void WriteInt(int i); void WriteLong(long l); void WriteFloat(float f); void WriteDouble(double d); void WriteString(string s); } public sealed class NbtWriter(Stream stream, bool leaveOpen = false) : INbtWriter { private readonly Stream stream = stream; private readonly bool leaveOpen = leaveOpen; public Stream BaseStream => stream; public static NbtWriter Create(Stream stream, Compression compression = Compression.None, bool leaveOpen = false) => compression switch { Compression.GZip => new(Utils.CreateGZipDeflater(stream, leaveOpen), false), Compression.ZLib => new(Utils.CreateZLibDeflater(stream, leaveOpen), false), Compression.LZ4 => new(Utils.CreateLZ4Deflater(stream, leaveOpen), false), Compression.Auto => throw new ArgumentException($"{nameof(Compression.Auto)} is invalid for a writer", nameof(compression)), _ => new(stream, leaveOpen) }; public void Flush() => stream.Flush(); public void WriteBytes(byte[] buffer, int offset, int count) => stream.Write(buffer, offset, count); public void WriteBytes(byte[] buffer) => stream.Write(buffer, 0, buffer.Length); public void WriteBytes(ReadOnlySpan buffer) => stream.Write(buffer); public void WriteByte(byte b) => stream.WriteByte(b); private void WriteEndian(Span buffer) { if (BitConverter.IsLittleEndian) { for (var i = buffer.Length - 1; i >= 0; i--) { WriteByte(buffer[i]); } } else { WriteBytes(buffer); } } public void WriteSByte(sbyte b) { WriteByte((byte)b); } public void WriteUShort(ushort s) { Span buffer = stackalloc byte[sizeof(ushort)]; BitConverter.TryWriteBytes(buffer, s); WriteEndian(buffer); } public void WriteShort(short s) { Span buffer = stackalloc byte[sizeof(short)]; BitConverter.TryWriteBytes(buffer, s); WriteEndian(buffer); } public void WriteUInt(uint i) { Span buffer = stackalloc byte[sizeof(uint)]; BitConverter.TryWriteBytes(buffer, i); WriteEndian(buffer); } public void WriteInt(int i) { Span buffer = stackalloc byte[sizeof(int)]; BitConverter.TryWriteBytes(buffer, i); WriteEndian(buffer); } public void WriteULong(ulong l) { Span buffer = stackalloc byte[sizeof(ulong)]; BitConverter.TryWriteBytes(buffer, l); WriteEndian(buffer); } public void WriteLong(long l) { Span buffer = stackalloc byte[sizeof(long)]; BitConverter.TryWriteBytes(buffer, l); WriteEndian(buffer); } public void WriteFloat(float f) { Span buffer = stackalloc byte[sizeof(float)]; BitConverter.TryWriteBytes(buffer, f); WriteEndian(buffer); } public void WriteDouble(double d) { Span buffer = stackalloc byte[sizeof(double)]; BitConverter.TryWriteBytes(buffer, d); WriteEndian(buffer); } public void WriteString(string s) { WriteUShort((ushort)s.Length); var buffer = Encoding.UTF8.GetBytes(s); WriteBytes(buffer); } public void WriteNamedTag(NamedTag namedTag) => WriteNamedTag(namedTag.Name, namedTag.Tag); public void WriteNamedTag(string tagName, INbtTag tag) { var tagType = NbtUtils.GetTagType(tag.Type); WriteTagType(tag.Type); WriteString(tagName); tagType.Write(this, tag); } public void WriteTag(INbtTag tag) { var tagType = NbtUtils.GetTagType(tag.Type); WriteTagType(tag.Type); tagType.Write(this, tag); } public void WriteTagType(NbtTagType tagType) => WriteByte((byte)tagType); #region IDisposable private bool _disposedValue; private void Dispose(bool disposing) { if (!_disposedValue) { if (disposing) { if (!leaveOpen) { stream.Dispose(); } } _disposedValue = true; } } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } #endregion }