using System; using System.Collections.Generic; using System.IO; using System.Text; namespace SabreTools.Text.Extensions { /// /// Extensions for BinaryReader /// public static class BinaryReaderExtensions { #region Read Null Terminated /// /// Read a null-terminated string from the underlying stream /// public static string? ReadNullTerminatedString(this BinaryReader reader, Encoding encoding) { // Short-circuit to explicit implementations if (encoding.CodePage == Encoding.ASCII.CodePage) return reader.ReadNullTerminatedAnsiString(); #if NET5_0_OR_GREATER else if (encoding.CodePage == Encoding.Latin1.CodePage) return reader.ReadNullTerminatedLatin1String(); #endif else if (encoding.CodePage == Encoding.UTF8.CodePage) return reader.ReadNullTerminatedUTF8String(); else if (encoding.CodePage == Encoding.Unicode.CodePage) return reader.ReadNullTerminatedUnicodeString(); else if (encoding.CodePage == Encoding.BigEndianUnicode.CodePage) return reader.ReadNullTerminatedBigEndianUnicodeString(); else if (encoding.CodePage == Encoding.UTF32.CodePage) return reader.ReadNullTerminatedUTF32String(); if (reader.BaseStream.Position >= reader.BaseStream.Length) return null; List buffer = []; while (reader.BaseStream.Position < reader.BaseStream.Length) { byte ch = reader.ReadByte(); if (ch == '\0') break; buffer.Add(ch); } return encoding.GetString([.. buffer]); } /// /// Read a null-terminated ASCII string from the underlying stream /// public static string? ReadNullTerminatedAnsiString(this BinaryReader reader) { if (reader.BaseStream.Position >= reader.BaseStream.Length) return null; byte[] buffer = ReadUntilNull1Byte(reader); return Encoding.ASCII.GetString(buffer); } #if NET5_0_OR_GREATER /// /// Read a null-terminated Latin1 string from the underlying stream /// public static string? ReadNullTerminatedLatin1String(this BinaryReader reader) { if (reader.BaseStream.Position >= reader.BaseStream.Length) return null; byte[] buffer = ReadUntilNull1Byte(reader); return Encoding.Latin1.GetString(buffer); } #endif /// /// Read a null-terminated UTF-8 string from the underlying stream /// public static string? ReadNullTerminatedUTF8String(this BinaryReader reader) { if (reader.BaseStream.Position >= reader.BaseStream.Length) return null; byte[] buffer = ReadUntilNull1Byte(reader); return Encoding.ASCII.GetString(buffer); } /// /// Read a null-terminated UTF-16 (Unicode) string from the underlying stream /// public static string? ReadNullTerminatedUnicodeString(this BinaryReader reader) { if (reader.BaseStream.Position >= reader.BaseStream.Length) return null; byte[] buffer = ReadUntilNull2Byte(reader); return Encoding.Unicode.GetString(buffer); } /// /// Read a null-terminated UTF-16 (Unicode) string from the underlying stream /// public static string? ReadNullTerminatedBigEndianUnicodeString(this BinaryReader reader) { if (reader.BaseStream.Position >= reader.BaseStream.Length) return null; byte[] buffer = ReadUntilNull2Byte(reader); return Encoding.BigEndianUnicode.GetString(buffer); } /// /// Read a null-terminated UTF-32 string from the underlying stream /// public static string? ReadNullTerminatedUTF32String(this BinaryReader reader) { if (reader.BaseStream.Position >= reader.BaseStream.Length) return null; byte[] buffer = ReadUntilNull4Byte(reader); return Encoding.UTF32.GetString(buffer); } #endregion #region Read Prefixed /// /// Read a byte-prefixed ASCII string from the underlying stream /// public static string? ReadPrefixedAnsiString(this BinaryReader reader) { if (reader.BaseStream.Position >= reader.BaseStream.Length) return null; byte size = reader.ReadByte(); if (reader.BaseStream.Position + size >= reader.BaseStream.Length) return null; byte[] buffer = reader.ReadBytes(size); return Encoding.ASCII.GetString(buffer); } /// /// Read a ushort-prefixed Unicode string from the underlying stream /// public static string? ReadPrefixedUnicodeString(this BinaryReader reader) { if (reader.BaseStream.Position >= reader.BaseStream.Length) return null; ushort size = reader.ReadUInt16(); if (reader.BaseStream.Position + (size * 2) >= reader.BaseStream.Length) return null; byte[] buffer = reader.ReadBytes(size * 2); return Encoding.Unicode.GetString(buffer); } /// /// Read a ushort-prefixed Unicode string from the underlying stream /// public static string? ReadPrefixedBigEndianUnicodeString(this BinaryReader reader) { if (reader.BaseStream.Position >= reader.BaseStream.Length) return null; ushort size = reader.ReadUInt16(); if (reader.BaseStream.Position + (size * 2) >= reader.BaseStream.Length) return null; byte[] buffer = reader.ReadBytes(size * 2); return Encoding.BigEndianUnicode.GetString(buffer); } #endregion #region Read Until Null Helpers /// /// Read bytes until a 1-byte null terminator is found /// private static byte[] ReadUntilNull1Byte(BinaryReader reader) { var bytes = new List(); while (reader.BaseStream.Position < reader.BaseStream.Length) { byte next = reader.ReadByte(); if (next == 0x00) break; bytes.Add(next); } return [.. bytes]; } /// /// Read bytes until a 2-byte null terminator is found /// private static byte[] ReadUntilNull2Byte(BinaryReader reader) { var bytes = new List(); while (reader.BaseStream.Position < reader.BaseStream.Length) { ushort next = reader.ReadUInt16(); if (next == 0x0000) break; bytes.AddRange(BitConverter.GetBytes(next)); } return [.. bytes]; } /// /// Read bytes until a 4-byte null terminator is found /// private static byte[] ReadUntilNull4Byte(BinaryReader reader) { var bytes = new List(); while (reader.BaseStream.Position < reader.BaseStream.Length) { uint next = reader.ReadUInt32(); if (next == 0x00000000) break; bytes.AddRange(BitConverter.GetBytes(next)); } return [.. bytes]; } #endregion } }