Editor/HumanoidRetargeter/Embedded/ValveResourceFormat/Utils/StreamHelpers.cs
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using HumanoidRetargeterVrf.Utils;
using System.Buffers;
using System.IO;
using System.Text;

namespace HumanoidRetargeterVrf
{
    internal static class StreamHelpers
    {
        /// <summary>
        /// Reads a null terminated string.
        /// </summary>
        /// <param name="stream">Stream.</param>
        /// <param name="encoding">Encoding.</param>
        /// <param name="bufferLengthHint">Initial buffer length used when reading the string.</param>
        /// <returns>String.</returns>
        public static string ReadNullTermString(this BinaryReader stream, Encoding encoding, int bufferLengthHint = 32)
        {
            if (encoding == Encoding.UTF8)
            {
                return ReadNullTermUtf8String(stream, bufferLengthHint);
            }

            var characterSize = encoding.GetByteCount("e");
            Span<byte> data = stackalloc byte[characterSize];

            using var ms = new MemoryStream(capacity: bufferLengthHint);

            while (true)
            {
                data.Clear();
                stream.Read(data);

                if (encoding.GetString(data) == "\0")
                {
                    break;
                }

                ms.Write(data);
            }

            ms.TryGetBuffer(out var buffer);

            return encoding.GetString(buffer);
        }

        /// <summary>
        /// Reads a string at a given uint offset.
        /// </summary>
        /// <returns>String.</returns>
        /// <param name="stream">Stream.</param>
        /// <param name="encoding">Encoding.</param>
        public static string ReadOffsetString(this BinaryReader stream, Encoding encoding)
        {
            var currentOffset = stream.BaseStream.Position;
            var offset = stream.ReadInt32();

            if (offset == 0)
            {
                return string.Empty;
            }

            stream.BaseStream.Position = currentOffset + offset;

            var str = ReadNullTermString(stream, encoding);

            stream.BaseStream.Position = currentOffset + 4;

            return str;
        }

        private static string ReadNullTermUtf8String(BinaryReader stream, int bufferLengthHint)
        {
            var buffer = ArrayPool<byte>.Shared.Rent(bufferLengthHint);

            try
            {
                var position = 0;

                do
                {
                    var b = stream.ReadByte();

                    if (b == 0x00)
                    {
                        break;
                    }

                    if (position >= buffer.Length)
                    {
                        var newBuffer = ArrayPool<byte>.Shared.Rent(buffer.Length * 2);
                        Buffer.BlockCopy(buffer, 0, newBuffer, 0, buffer.Length);
                        ArrayPool<byte>.Shared.Return(buffer);
                        buffer = newBuffer;
                    }

                    buffer[position++] = b;
                }
                while (true);

                return Encoding.UTF8.GetString(buffer[..position]);
            }
            finally
            {
                ArrayPool<byte>.Shared.Return(buffer);
            }
        }
    }
}