diff --git a/SabreTools.IO.Extensions.Test/ByteArrayExtensionsTests.cs b/SabreTools.IO.Extensions.Test/ByteArrayExtensionsTests.cs
index e32b6b3..46d2ca8 100644
--- a/SabreTools.IO.Extensions.Test/ByteArrayExtensionsTests.cs
+++ b/SabreTools.IO.Extensions.Test/ByteArrayExtensionsTests.cs
@@ -1,5 +1,4 @@
using System;
-using System.Linq;
using System.Text;
using Xunit;
@@ -597,439 +596,5 @@ namespace SabreTools.IO.Extensions.Test
}
#endregion
-
- #region ToHexString
-
- [Fact]
- public void ToHexString_Null()
- {
- byte[]? arr = null;
- string? actual = arr.ToHexString();
- Assert.Null(actual);
- }
-
- [Fact]
- public void ToHexString_Valid()
- {
- byte[]? arr = [0x01, 0x02, 0x03, 0x04];
- string expected = "01020304";
-
- string? actual = arr.ToHexString();
- Assert.NotNull(actual);
- Assert.Equal(expected, actual);
- }
-
- #endregion
-
- #region FromHexString
-
- [Fact]
- public void FromHexString_Null()
- {
- string? str = null;
- byte[]? actual = str.FromHexString();
- Assert.Null(actual);
- }
-
- [Fact]
- public void FromHexString_Valid()
- {
- string str = "01020304";
- byte[]? expected = [0x01, 0x02, 0x03, 0x04];
-
- byte[]? actual = str.FromHexString();
- Assert.NotNull(actual);
- Assert.True(expected.SequenceEqual(actual));
- }
-
- [Fact]
- public void FromHexString_Invalid()
- {
- string str = "0102030G";
- byte[]? actual = str.FromHexString();
- Assert.Null(actual);
- }
-
- #endregion
-
- #region ReadStringsFrom
-
- [Fact]
- public void ReadStringsFrom_Null_Null()
- {
- byte[]? arr = null;
- var actual = arr.ReadStringsFrom(3);
- Assert.Null(actual);
- }
-
- [Fact]
- public void ReadStringsFrom_Empty_Null()
- {
- byte[]? arr = [];
- var actual = arr.ReadStringsFrom(3);
- Assert.Null(actual);
- }
-
- [Theory]
- [InlineData(-1)]
- [InlineData(0)]
- [InlineData(2048)]
- public void ReadStringsFrom_InvalidLimit_Empty(int charLimit)
- {
- byte[]? arr = new byte[1024];
- var actual = arr.ReadStringsFrom(charLimit);
- Assert.NotNull(actual);
- Assert.Empty(actual);
- }
-
- [Fact]
- public void ReadStringsFrom_NoValidStrings_Empty()
- {
- byte[]? arr = new byte[1024];
- var actual = arr.ReadStringsFrom(4);
- Assert.NotNull(actual);
- Assert.Empty(actual);
- }
-
- [Fact]
- public void ReadStringsFrom_AsciiStrings_Filled()
- {
- byte[]? arr =
- [
- .. Encoding.ASCII.GetBytes("TEST"),
- .. new byte[] { 0x00 },
- .. Encoding.ASCII.GetBytes("TWO"),
- .. new byte[] { 0x00 },
- .. Encoding.ASCII.GetBytes("DATA"),
- .. new byte[] { 0x00 },
- ];
- var actual = arr.ReadStringsFrom(4);
- Assert.NotNull(actual);
- Assert.Equal(2, actual.Count);
- }
-
- [Fact]
- public void ReadStringsFrom_Latin1Strings_Filled()
- {
- byte[]? arr =
- [
- .. Encoding.Latin1.GetBytes("TEST"),
- .. new byte[] { 0x00 },
- .. Encoding.Latin1.GetBytes("TWO"),
- .. new byte[] { 0x00 },
- .. Encoding.Latin1.GetBytes("DATA"),
- .. new byte[] { 0x00 },
- ];
- var actual = arr.ReadStringsFrom(4);
- Assert.NotNull(actual);
- Assert.Equal(2, actual.Count);
- }
-
- [Fact]
- public void ReadStringsFrom_UTF16_Filled()
- {
- byte[]? arr =
- [
- .. Encoding.Unicode.GetBytes("TEST"),
- .. new byte[] { 0x00 },
- .. Encoding.Unicode.GetBytes("TWO"),
- .. new byte[] { 0x00 },
- .. Encoding.Unicode.GetBytes("DATA"),
- .. new byte[] { 0x00 },
- ];
- var actual = arr.ReadStringsFrom(4);
- Assert.NotNull(actual);
- Assert.Equal(2, actual.Count);
- }
-
- [Fact]
- public void ReadStringsFrom_Mixed_Filled()
- {
- byte[]? arr =
- [
- .. Encoding.ASCII.GetBytes("TEST1"),
- .. new byte[] { 0x00 },
- .. Encoding.ASCII.GetBytes("TWO1"),
- .. new byte[] { 0x00 },
- .. Encoding.ASCII.GetBytes("DATA1"),
- .. new byte[] { 0x00 },
- .. Encoding.Latin1.GetBytes("TEST2"),
- .. new byte[] { 0x00 },
- .. Encoding.Latin1.GetBytes("TWO2"),
- .. new byte[] { 0x00 },
- .. Encoding.Latin1.GetBytes("DATA2"),
- .. new byte[] { 0x00 },
- .. Encoding.Unicode.GetBytes("TEST3"),
- .. new byte[] { 0x00 },
- .. Encoding.Unicode.GetBytes("TWO3"),
- .. new byte[] { 0x00 },
- .. Encoding.Unicode.GetBytes("DATA3"),
- .. new byte[] { 0x00 },
- ];
- var actual = arr.ReadStringsFrom(5);
- Assert.NotNull(actual);
- Assert.Equal(6, actual.Count);
- }
-
- ///
- /// This test is here mainly for performance testing
- /// and should not be enabled unless there are changes
- /// to the core reading methods that need comparison.
- ///
- // [Fact]
- // public void ReadStringsFrom_Mixed_MASSIVE()
- // {
- // byte[]? arr =
- // [
- // .. Encoding.ASCII.GetBytes("TEST1"),
- // .. new byte[] { 0x00 },
- // .. Encoding.ASCII.GetBytes("TWO1"),
- // .. new byte[] { 0x00 },
- // .. Encoding.ASCII.GetBytes("DATA1"),
- // .. new byte[] { 0x00 },
- // .. Encoding.UTF8.GetBytes("TEST2"),
- // .. new byte[] { 0x00 },
- // .. Encoding.UTF8.GetBytes("TWO2"),
- // .. new byte[] { 0x00 },
- // .. Encoding.UTF8.GetBytes("DATA2"),
- // .. new byte[] { 0x00 },
- // .. Encoding.Unicode.GetBytes("TEST3"),
- // .. new byte[] { 0x00 },
- // .. Encoding.Unicode.GetBytes("TWO3"),
- // .. new byte[] { 0x00 },
- // .. Encoding.Unicode.GetBytes("DATA3"),
- // .. new byte[] { 0x00 },
- // ];
- // arr = [.. arr, .. arr, .. arr, .. arr];
- // arr = [.. arr, .. arr, .. arr, .. arr];
- // arr = [.. arr, .. arr, .. arr, .. arr];
- // arr = [.. arr, .. arr, .. arr, .. arr];
- // arr = [.. arr, .. arr, .. arr, .. arr];
- // arr = [.. arr, .. arr, .. arr, .. arr];
- // arr = [.. arr, .. arr, .. arr, .. arr];
- // arr = [.. arr, .. arr, .. arr, .. arr];
- // arr = [.. arr, .. arr, .. arr, .. arr];
- // arr = [.. arr, .. arr, .. arr, .. arr];
- // // arr = [.. arr, .. arr, .. arr, .. arr];
- // // arr = [.. arr, .. arr, .. arr, .. arr];
-
- // var actual = arr.ReadStringsFrom(5);
- // Assert.NotNull(actual);
- // Assert.NotEmpty(actual);
- // }
-
- #endregion
-
- #region ReadStringsWithEncoding
-
- [Fact]
- public void ReadStringsWithEncoding_Null_Empty()
- {
- byte[]? bytes = null;
- var actual = bytes.ReadStringsWithEncoding(1, Encoding.ASCII);
- Assert.Empty(actual);
- }
-
- [Fact]
- public void ReadStringsWithEncoding_Empty_Empty()
- {
- byte[]? bytes = [];
- var actual = bytes.ReadStringsWithEncoding(1, Encoding.ASCII);
- Assert.Empty(actual);
- }
-
- [Theory]
- [InlineData(-1)]
- [InlineData(0)]
- [InlineData(2048)]
- public void ReadStringsWithEncoding_InvalidLimit_Empty(int charLimit)
- {
- byte[]? bytes = new byte[1024];
- var actual = bytes.ReadStringsWithEncoding(charLimit, Encoding.ASCII);
- Assert.Empty(actual);
- }
-
- [Fact]
- public void ReadStringsWithEncoding_NoValidStrings_Empty()
- {
- byte[]? bytes = new byte[1024];
- var actual = bytes.ReadStringsWithEncoding(5, Encoding.ASCII);
- Assert.Empty(actual);
- }
-
- [Fact]
- public void ReadStringsWithEncoding_AsciiStrings_Filled()
- {
- byte[]? bytes =
- [
- .. Encoding.ASCII.GetBytes("TEST"),
- .. new byte[] { 0x00 },
- .. Encoding.ASCII.GetBytes("ONE"),
- .. new byte[] { 0x00 },
- .. Encoding.ASCII.GetBytes("TWO"),
- .. new byte[] { 0x00 },
- .. Encoding.ASCII.GetBytes("DATA"),
- .. new byte[] { 0x00 },
- ];
- var actual = bytes.ReadStringsWithEncoding(4, Encoding.ASCII);
- Assert.Equal(2, actual.Count);
- }
-
- [Fact]
- public void ReadStringsWithEncoding_InvalidAsciiChars_Empty()
- {
- byte[]? arr =
- [
- 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
- 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
- 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
- 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
- .. Enumerable.Range(0x80, 0x80).Select(i => (byte)i),
- ];
- var actual = arr.ReadStringsWithEncoding(1, Encoding.ASCII);
- Assert.NotNull(actual);
- Assert.Empty(actual);
- }
-
- [Fact]
- public void ReadStringsWithEncoding_Latin1_Filled()
- {
- byte[]? bytes =
- [
- .. Encoding.Latin1.GetBytes("TEST"),
- .. new byte[] { 0x00 },
- .. Encoding.Latin1.GetBytes("ONE"),
- .. new byte[] { 0x00 },
- .. Encoding.Latin1.GetBytes("TWO"),
- .. new byte[] { 0x00 },
- .. Encoding.Latin1.GetBytes("DATA"),
- .. new byte[] { 0x00 },
- ];
- var actual = bytes.ReadStringsWithEncoding(4, Encoding.Latin1);
- Assert.Equal(2, actual.Count);
- }
-
- [Fact]
- public void ReadStringsWithEncoding_InvalidLatin1Chars_Empty()
- {
- byte[]? arr =
- [
- 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
- 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
- 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
- 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
- 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
- 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
- 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
- 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
- ];
- var actual = arr.ReadStringsWithEncoding(1, Encoding.Latin1);
- Assert.NotNull(actual);
- Assert.Empty(actual);
- }
-
- [Fact]
- public void ReadStringsWithEncoding_UTF8_Filled()
- {
- byte[]? bytes =
- [
- .. Encoding.UTF8.GetBytes("TEST"),
- .. new byte[] { 0x00 },
- .. Encoding.UTF8.GetBytes("ONE"),
- .. new byte[] { 0x00 },
- .. Encoding.UTF8.GetBytes("TWO"),
- .. new byte[] { 0x00 },
- .. Encoding.UTF8.GetBytes("DATA"),
- .. new byte[] { 0x00 },
- ];
- var actual = bytes.ReadStringsWithEncoding(4, Encoding.UTF8);
- Assert.Equal(2, actual.Count);
- }
-
- [Fact]
- public void ReadStringsWithEncoding_InvalidUTF8Chars_Empty()
- {
- byte[]? arr =
- [
- 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
- 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
- 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
- 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
- .. Enumerable.Range(0x80, 0x42).Select(i => (byte)i),
- 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC,
- 0xFD, 0xFE, 0xFF,
- ];
- var actual = arr.ReadStringsWithEncoding(1, Encoding.UTF8);
- Assert.NotNull(actual);
- Assert.Empty(actual);
- }
-
- [Fact]
- public void ReadStringsWithEncoding_UTF16_Filled()
- {
- byte[]? bytes =
- [
- .. Encoding.Unicode.GetBytes("TEST"),
- .. new byte[] { 0x00 },
- .. Encoding.Unicode.GetBytes("ONE"),
- .. new byte[] { 0x00 },
- .. Encoding.Unicode.GetBytes("TWO"),
- .. new byte[] { 0x00 },
- .. Encoding.Unicode.GetBytes("DATA"),
- .. new byte[] { 0x00 },
- ];
- var actual = bytes.ReadStringsWithEncoding(4, Encoding.Unicode);
- Assert.Equal(2, actual.Count);
- }
-
- [Fact]
- public void ReadStringsWithEncoding_InvalidUTF16Chars_Empty()
- {
- byte[]? arr =
- [
- 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
- 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
- 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
- 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
- ];
- var actual = arr.ReadStringsWithEncoding(1, Encoding.Unicode);
- Assert.NotNull(actual);
- Assert.Empty(actual);
- }
-
- [Fact]
- public void ReadStringsWithEncoding_UTF32_Filled()
- {
- byte[]? bytes =
- [
- .. Encoding.UTF32.GetBytes("TEST"),
- .. new byte[] { 0x00 },
- .. Encoding.UTF32.GetBytes("ONE"),
- .. new byte[] { 0x00 },
- .. Encoding.UTF32.GetBytes("TWO"),
- .. new byte[] { 0x00 },
- .. Encoding.UTF32.GetBytes("DATA"),
- .. new byte[] { 0x00 },
- ];
- var actual = bytes.ReadStringsWithEncoding(4, Encoding.UTF32);
- Assert.Equal(2, actual.Count);
- }
-
- [Fact]
- public void ReadStringsWithEncoding_InvalidUTF32Chars_Empty()
- {
- byte[]? arr =
- [
- 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
- 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
- 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
- 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
- ];
- var actual = arr.ReadStringsWithEncoding(1, Encoding.UTF32);
- Assert.NotNull(actual);
- Assert.Empty(actual);
- }
-
- #endregion
}
}
diff --git a/SabreTools.IO.Extensions/ByteArrayExtensions.cs b/SabreTools.IO.Extensions/ByteArrayExtensions.cs
index 8c3ce0e..99e885c 100644
--- a/SabreTools.IO.Extensions/ByteArrayExtensions.cs
+++ b/SabreTools.IO.Extensions/ByteArrayExtensions.cs
@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
-using System.IO;
-using System.Text;
using SabreTools.Matching;
using SabreTools.Numerics.Extensions;
@@ -447,311 +445,5 @@ namespace SabreTools.IO.Extensions
}
#endregion
-
- #region Strings
-
- ///
- /// Convert a byte array to a hex string
- ///
- public static string? ToHexString(this byte[]? bytes)
- {
- // If we get null in, we send null out
- if (bytes is null)
- return null;
-
- string hex = BitConverter.ToString(bytes);
- return hex.Replace("-", string.Empty).ToLowerInvariant();
- }
-
- ///
- /// Convert a hex string to a byte array
- ///
- public static byte[]? FromHexString(this string? hex)
- {
- // If we get null in, we send null out
- if (string.IsNullOrEmpty(hex))
- return null;
-
- try
- {
- int chars = hex!.Length;
- byte[] bytes = new byte[chars / 2];
- for (int i = 0; i < chars; i += 2)
- {
- bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
- }
-
- return bytes;
- }
- catch
- {
- return null;
- }
- }
-
- ///
- /// Read string data from a byte array
- ///
- /// Number of characters needed to be a valid string, default 5
- /// String list containing the requested data, null on error
-#if NET5_0_OR_GREATER
- /// This reads both Latin1 and UTF-16 strings from the input data
-#else
- /// This reads both ASCII and UTF-16 strings from the input data
-#endif
- public static List? ReadStringsFrom(this byte[]? input, int charLimit = 5)
- {
- // Validate the data
- if (input is null || input.Length == 0)
- return null;
-
-#if NET5_0_OR_GREATER
- // Check for Latin1 strings
- var asciiStrings = input.ReadStringsWithEncoding(charLimit, Encoding.Latin1);
-#else
- // Check for ASCII strings
- var asciiStrings = input.ReadStringsWithEncoding(charLimit, Encoding.ASCII);
-#endif
-
- // Check for Unicode strings
- // We are limiting the check for Unicode characters with a second byte of 0x00 for now
- var unicodeStrings = input.ReadStringsWithEncoding(charLimit, Encoding.Unicode);
-
- // Ignore duplicate strings across encodings
- List sourceStrings = [.. asciiStrings, .. unicodeStrings];
-
- // Sort the strings and return
- sourceStrings.Sort();
- return sourceStrings;
- }
-
- ///
- /// Read string data from a byte array with an encoding
- ///
- /// Byte array representing the source data
- /// Number of characters needed to be a valid string
- /// Character encoding to use for checking
- /// String list containing the requested data, empty on error
- /// Characters with the higher bytes set are unused
-#if NET20
- public static List ReadStringsWithEncoding(this byte[]? bytes, int charLimit, Encoding encoding)
-#else
- public static HashSet ReadStringsWithEncoding(this byte[]? bytes, int charLimit, Encoding encoding)
-#endif
- {
- if (bytes is null || bytes.Length == 0)
- return [];
- if (charLimit <= 0 || charLimit > bytes.Length)
- return [];
-
- // Short-circuit for some encoding types
- if (encoding.CodePage == Encoding.ASCII.CodePage)
- return bytes.ReadAsciiStrings(charLimit);
-#if NET5_0_OR_GREATER
- else if (encoding.CodePage == Encoding.Latin1.CodePage)
- return bytes.ReadFixedWidthEncodingStrings(charLimit, Encoding.Latin1, 1);
-#endif
- else if (encoding.IsSingleByte)
- return bytes.ReadFixedWidthEncodingStrings(charLimit, encoding, 1);
- else if (encoding.CodePage == Encoding.Unicode.CodePage)
- return bytes.ReadFixedWidthEncodingStrings(charLimit, Encoding.Unicode, 2);
- else if (encoding.CodePage == Encoding.BigEndianUnicode.CodePage)
- return bytes.ReadFixedWidthEncodingStrings(charLimit, Encoding.BigEndianUnicode, 2);
- else if (encoding.CodePage == Encoding.UTF32.CodePage)
- return bytes.ReadFixedWidthEncodingStrings(charLimit, Encoding.UTF32, 4);
-
- // Create the string set to return
-#if NET20
- var strings = new List();
-#else
- var strings = new HashSet();
-#endif
-
- // Open the text reader with the correct encoding
- using var ms = new MemoryStream(bytes);
- using var reader = new StreamReader(ms, encoding);
-
- // Create a string builder for the loop
- var sb = new StringBuilder();
-
- // Check for strings
- long lastOffset = 0;
- while (!reader.EndOfStream)
- {
- // Read the next character from the stream
- char c = (char)reader.Read();
-
- // If the character is invalid
- if (char.IsControl(c) || (c & 0xFFFFFF00) != 0)
- {
- // Seek to the end of the last found string
- string str = sb.ToString();
- lastOffset += encoding.GetByteCount(str) + 1;
- ms.Seek(lastOffset, SeekOrigin.Begin);
- reader.DiscardBufferedData();
-
- // If there is no cached string
- if (str.Length == 0)
- continue;
-
- // Add the string if long enough
- if (str.Length >= charLimit)
- strings.Add(str);
-
- // Clear the builder and continue
-#if NET20 || NET35
- sb = new();
-#else
- sb.Clear();
-#endif
- continue;
- }
-
- // Otherwise, add the character to the builder and continue
- sb.Append(c);
- }
-
- // Handle any remaining data
- if (sb.Length >= charLimit)
- strings.Add(sb.ToString());
-
- return strings;
- }
-
- ///
- /// Read string data from a byte array using an encoding with a fixed width
- ///
- /// Byte array representing the source data
- /// Number of characters needed to be a valid string
- /// Character encoding to use for checking
- /// Character width of the encoding
- /// String list containing the requested data, empty on error
- /// Characters with the higher bytes set are unused
-#if NET20
- private static List ReadFixedWidthEncodingStrings(this byte[] bytes, int charLimit, Encoding encoding, int width)
-#else
- private static HashSet ReadFixedWidthEncodingStrings(this byte[] bytes, int charLimit, Encoding encoding, int width)
-#endif
- {
- if (charLimit <= 0 || charLimit > bytes.Length)
- return [];
-
- // Create the string set to return
-#if NET20
- var strings = new List();
-#else
- var strings = new HashSet();
-#endif
-
- // Create a string builder for the loop
- var sb = new StringBuilder();
-
- // Check for strings
- int offset = 0;
- while (offset <= bytes.Length - width)
- {
- // Read the next character from the stream
- char c = encoding.GetChars(bytes, offset, width)[0];
- offset += width;
-
- // If the character is invalid
- if (char.IsControl(c) || (c & 0xFFFFFF00) != 0)
- {
- // Pretend only one byte was read
- offset -= width - 1;
-
- // If there is no cached string
- if (sb.Length == 0)
- continue;
-
- // Add the string if long enough
- if (sb.Length >= charLimit)
- strings.Add(sb.ToString());
-
- // Clear the builder and continue
-#if NET20 || NET35
- sb = new();
-#else
- sb.Clear();
-#endif
- continue;
- }
-
- // Otherwise, add the character to the builder and continue
- sb.Append(c);
- }
-
- // Handle any remaining data
- if (sb.Length >= charLimit)
- strings.Add(sb.ToString());
-
- return strings;
- }
-
- ///
- /// Read string data from a byte array using ASCII encoding
- ///
- /// Byte array representing the source data
- /// Number of characters needed to be a valid string
- /// String list containing the requested data, empty on error
- /// Handling for 7-bit ASCII needs to be done differently than other fixed-width encodings
-#if NET20
- private static List ReadAsciiStrings(this byte[] bytes, int charLimit)
-#else
- private static HashSet ReadAsciiStrings(this byte[] bytes, int charLimit)
-#endif
- {
- if (charLimit <= 0 || charLimit > bytes.Length)
- return [];
-
- // Create the string set to return
-#if NET20
- var strings = new List();
-#else
- var strings = new HashSet();
-#endif
-
- // Create a string builder for the loop
- var sb = new StringBuilder();
-
- // Check for strings
- int offset = 0;
- while (offset < bytes.Length)
- {
- // Read the next character from the stream
- char c = bytes.ReadChar(ref offset);
-
- // If the character is invalid
- if (char.IsControl(c) || c > 0x7F)
- {
- // If there is no cached string
- if (sb.Length == 0)
- continue;
-
- // Add the string if long enough
- if (sb.Length >= charLimit)
- strings.Add(sb.ToString());
-
- // Clear the builder and continue
-#if NET20 || NET35
- sb = new();
-#else
- sb.Clear();
-#endif
- continue;
- }
-
- // Otherwise, add the character to the builder and continue
- sb.Append(c);
- }
-
- // Handle any remaining data
- if (sb.Length >= charLimit)
- strings.Add(sb.ToString());
-
- return strings;
- }
-
- #endregion
}
}
diff --git a/SabreTools.Text.Extensions.Test/ByteArrayReaderExtensionsTests.cs b/SabreTools.Text.Extensions.Test/ByteArrayReaderExtensionsTests.cs
index 4d3545f..13ac526 100644
--- a/SabreTools.Text.Extensions.Test/ByteArrayReaderExtensionsTests.cs
+++ b/SabreTools.Text.Extensions.Test/ByteArrayReaderExtensionsTests.cs
@@ -1,3 +1,4 @@
+using System.Linq;
using System.Text;
using Xunit;
@@ -5,6 +6,8 @@ namespace SabreTools.Text.Extensions.Test
{
public class ByteArrayReaderExtensionsTests
{
+ #region ReadNullTerminatedString
+
[Fact]
public void ReadNullTerminatedAnsiStringTest()
{
@@ -58,5 +61,388 @@ namespace SabreTools.Text.Extensions.Test
string? actual = bytes.ReadNullTerminatedString(ref offset, Encoding.UTF32);
Assert.Equal("ABC", actual);
}
+
+ #endregion
+
+ #region ReadStringsFrom
+
+ [Fact]
+ public void ReadStringsFrom_Null_Null()
+ {
+ byte[]? arr = null;
+ var actual = arr.ReadStringsFrom(3);
+ Assert.Null(actual);
+ }
+
+ [Fact]
+ public void ReadStringsFrom_Empty_Null()
+ {
+ byte[]? arr = [];
+ var actual = arr.ReadStringsFrom(3);
+ Assert.Null(actual);
+ }
+
+ [Theory]
+ [InlineData(-1)]
+ [InlineData(0)]
+ [InlineData(2048)]
+ public void ReadStringsFrom_InvalidLimit_Empty(int charLimit)
+ {
+ byte[]? arr = new byte[1024];
+ var actual = arr.ReadStringsFrom(charLimit);
+ Assert.NotNull(actual);
+ Assert.Empty(actual);
+ }
+
+ [Fact]
+ public void ReadStringsFrom_NoValidStrings_Empty()
+ {
+ byte[]? arr = new byte[1024];
+ var actual = arr.ReadStringsFrom(4);
+ Assert.NotNull(actual);
+ Assert.Empty(actual);
+ }
+
+ [Fact]
+ public void ReadStringsFrom_AsciiStrings_Filled()
+ {
+ byte[]? arr =
+ [
+ .. Encoding.ASCII.GetBytes("TEST"),
+ .. new byte[] { 0x00 },
+ .. Encoding.ASCII.GetBytes("TWO"),
+ .. new byte[] { 0x00 },
+ .. Encoding.ASCII.GetBytes("DATA"),
+ .. new byte[] { 0x00 },
+ ];
+ var actual = arr.ReadStringsFrom(4);
+ Assert.NotNull(actual);
+ Assert.Equal(2, actual.Count);
+ }
+
+ [Fact]
+ public void ReadStringsFrom_Latin1Strings_Filled()
+ {
+ byte[]? arr =
+ [
+ .. Encoding.Latin1.GetBytes("TEST"),
+ .. new byte[] { 0x00 },
+ .. Encoding.Latin1.GetBytes("TWO"),
+ .. new byte[] { 0x00 },
+ .. Encoding.Latin1.GetBytes("DATA"),
+ .. new byte[] { 0x00 },
+ ];
+ var actual = arr.ReadStringsFrom(4);
+ Assert.NotNull(actual);
+ Assert.Equal(2, actual.Count);
+ }
+
+ [Fact]
+ public void ReadStringsFrom_UTF16_Filled()
+ {
+ byte[]? arr =
+ [
+ .. Encoding.Unicode.GetBytes("TEST"),
+ .. new byte[] { 0x00 },
+ .. Encoding.Unicode.GetBytes("TWO"),
+ .. new byte[] { 0x00 },
+ .. Encoding.Unicode.GetBytes("DATA"),
+ .. new byte[] { 0x00 },
+ ];
+ var actual = arr.ReadStringsFrom(4);
+ Assert.NotNull(actual);
+ Assert.Equal(2, actual.Count);
+ }
+
+ [Fact]
+ public void ReadStringsFrom_Mixed_Filled()
+ {
+ byte[]? arr =
+ [
+ .. Encoding.ASCII.GetBytes("TEST1"),
+ .. new byte[] { 0x00 },
+ .. Encoding.ASCII.GetBytes("TWO1"),
+ .. new byte[] { 0x00 },
+ .. Encoding.ASCII.GetBytes("DATA1"),
+ .. new byte[] { 0x00 },
+ .. Encoding.Latin1.GetBytes("TEST2"),
+ .. new byte[] { 0x00 },
+ .. Encoding.Latin1.GetBytes("TWO2"),
+ .. new byte[] { 0x00 },
+ .. Encoding.Latin1.GetBytes("DATA2"),
+ .. new byte[] { 0x00 },
+ .. Encoding.Unicode.GetBytes("TEST3"),
+ .. new byte[] { 0x00 },
+ .. Encoding.Unicode.GetBytes("TWO3"),
+ .. new byte[] { 0x00 },
+ .. Encoding.Unicode.GetBytes("DATA3"),
+ .. new byte[] { 0x00 },
+ ];
+ var actual = arr.ReadStringsFrom(5);
+ Assert.NotNull(actual);
+ Assert.Equal(6, actual.Count);
+ }
+
+ ///
+ /// This test is here mainly for performance testing
+ /// and should not be enabled unless there are changes
+ /// to the core reading methods that need comparison.
+ ///
+ // [Fact]
+ // public void ReadStringsFrom_Mixed_MASSIVE()
+ // {
+ // byte[]? arr =
+ // [
+ // .. Encoding.ASCII.GetBytes("TEST1"),
+ // .. new byte[] { 0x00 },
+ // .. Encoding.ASCII.GetBytes("TWO1"),
+ // .. new byte[] { 0x00 },
+ // .. Encoding.ASCII.GetBytes("DATA1"),
+ // .. new byte[] { 0x00 },
+ // .. Encoding.UTF8.GetBytes("TEST2"),
+ // .. new byte[] { 0x00 },
+ // .. Encoding.UTF8.GetBytes("TWO2"),
+ // .. new byte[] { 0x00 },
+ // .. Encoding.UTF8.GetBytes("DATA2"),
+ // .. new byte[] { 0x00 },
+ // .. Encoding.Unicode.GetBytes("TEST3"),
+ // .. new byte[] { 0x00 },
+ // .. Encoding.Unicode.GetBytes("TWO3"),
+ // .. new byte[] { 0x00 },
+ // .. Encoding.Unicode.GetBytes("DATA3"),
+ // .. new byte[] { 0x00 },
+ // ];
+ // arr = [.. arr, .. arr, .. arr, .. arr];
+ // arr = [.. arr, .. arr, .. arr, .. arr];
+ // arr = [.. arr, .. arr, .. arr, .. arr];
+ // arr = [.. arr, .. arr, .. arr, .. arr];
+ // arr = [.. arr, .. arr, .. arr, .. arr];
+ // arr = [.. arr, .. arr, .. arr, .. arr];
+ // arr = [.. arr, .. arr, .. arr, .. arr];
+ // arr = [.. arr, .. arr, .. arr, .. arr];
+ // arr = [.. arr, .. arr, .. arr, .. arr];
+ // arr = [.. arr, .. arr, .. arr, .. arr];
+ // // arr = [.. arr, .. arr, .. arr, .. arr];
+ // // arr = [.. arr, .. arr, .. arr, .. arr];
+
+ // var actual = arr.ReadStringsFrom(5);
+ // Assert.NotNull(actual);
+ // Assert.NotEmpty(actual);
+ // }
+
+ #endregion
+
+ #region ReadStringsWithEncoding
+
+ [Fact]
+ public void ReadStringsWithEncoding_Null_Empty()
+ {
+ byte[]? bytes = null;
+ var actual = bytes.ReadStringsWithEncoding(1, Encoding.ASCII);
+ Assert.Empty(actual);
+ }
+
+ [Fact]
+ public void ReadStringsWithEncoding_Empty_Empty()
+ {
+ byte[]? bytes = [];
+ var actual = bytes.ReadStringsWithEncoding(1, Encoding.ASCII);
+ Assert.Empty(actual);
+ }
+
+ [Theory]
+ [InlineData(-1)]
+ [InlineData(0)]
+ [InlineData(2048)]
+ public void ReadStringsWithEncoding_InvalidLimit_Empty(int charLimit)
+ {
+ byte[]? bytes = new byte[1024];
+ var actual = bytes.ReadStringsWithEncoding(charLimit, Encoding.ASCII);
+ Assert.Empty(actual);
+ }
+
+ [Fact]
+ public void ReadStringsWithEncoding_NoValidStrings_Empty()
+ {
+ byte[]? bytes = new byte[1024];
+ var actual = bytes.ReadStringsWithEncoding(5, Encoding.ASCII);
+ Assert.Empty(actual);
+ }
+
+ [Fact]
+ public void ReadStringsWithEncoding_AsciiStrings_Filled()
+ {
+ byte[]? bytes =
+ [
+ .. Encoding.ASCII.GetBytes("TEST"),
+ .. new byte[] { 0x00 },
+ .. Encoding.ASCII.GetBytes("ONE"),
+ .. new byte[] { 0x00 },
+ .. Encoding.ASCII.GetBytes("TWO"),
+ .. new byte[] { 0x00 },
+ .. Encoding.ASCII.GetBytes("DATA"),
+ .. new byte[] { 0x00 },
+ ];
+ var actual = bytes.ReadStringsWithEncoding(4, Encoding.ASCII);
+ Assert.Equal(2, actual.Count);
+ }
+
+ [Fact]
+ public void ReadStringsWithEncoding_InvalidAsciiChars_Empty()
+ {
+ byte[]? arr =
+ [
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
+ .. Enumerable.Range(0x80, 0x80).Select(i => (byte)i),
+ ];
+ var actual = arr.ReadStringsWithEncoding(1, Encoding.ASCII);
+ Assert.NotNull(actual);
+ Assert.Empty(actual);
+ }
+
+ [Fact]
+ public void ReadStringsWithEncoding_Latin1_Filled()
+ {
+ byte[]? bytes =
+ [
+ .. Encoding.Latin1.GetBytes("TEST"),
+ .. new byte[] { 0x00 },
+ .. Encoding.Latin1.GetBytes("ONE"),
+ .. new byte[] { 0x00 },
+ .. Encoding.Latin1.GetBytes("TWO"),
+ .. new byte[] { 0x00 },
+ .. Encoding.Latin1.GetBytes("DATA"),
+ .. new byte[] { 0x00 },
+ ];
+ var actual = bytes.ReadStringsWithEncoding(4, Encoding.Latin1);
+ Assert.Equal(2, actual.Count);
+ }
+
+ [Fact]
+ public void ReadStringsWithEncoding_InvalidLatin1Chars_Empty()
+ {
+ byte[]? arr =
+ [
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
+ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
+ 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
+ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
+ 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
+ ];
+ var actual = arr.ReadStringsWithEncoding(1, Encoding.Latin1);
+ Assert.NotNull(actual);
+ Assert.Empty(actual);
+ }
+
+ [Fact]
+ public void ReadStringsWithEncoding_UTF8_Filled()
+ {
+ byte[]? bytes =
+ [
+ .. Encoding.UTF8.GetBytes("TEST"),
+ .. new byte[] { 0x00 },
+ .. Encoding.UTF8.GetBytes("ONE"),
+ .. new byte[] { 0x00 },
+ .. Encoding.UTF8.GetBytes("TWO"),
+ .. new byte[] { 0x00 },
+ .. Encoding.UTF8.GetBytes("DATA"),
+ .. new byte[] { 0x00 },
+ ];
+ var actual = bytes.ReadStringsWithEncoding(4, Encoding.UTF8);
+ Assert.Equal(2, actual.Count);
+ }
+
+ [Fact]
+ public void ReadStringsWithEncoding_InvalidUTF8Chars_Empty()
+ {
+ byte[]? arr =
+ [
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
+ .. Enumerable.Range(0x80, 0x42).Select(i => (byte)i),
+ 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC,
+ 0xFD, 0xFE, 0xFF,
+ ];
+ var actual = arr.ReadStringsWithEncoding(1, Encoding.UTF8);
+ Assert.NotNull(actual);
+ Assert.Empty(actual);
+ }
+
+ [Fact]
+ public void ReadStringsWithEncoding_UTF16_Filled()
+ {
+ byte[]? bytes =
+ [
+ .. Encoding.Unicode.GetBytes("TEST"),
+ .. new byte[] { 0x00 },
+ .. Encoding.Unicode.GetBytes("ONE"),
+ .. new byte[] { 0x00 },
+ .. Encoding.Unicode.GetBytes("TWO"),
+ .. new byte[] { 0x00 },
+ .. Encoding.Unicode.GetBytes("DATA"),
+ .. new byte[] { 0x00 },
+ ];
+ var actual = bytes.ReadStringsWithEncoding(4, Encoding.Unicode);
+ Assert.Equal(2, actual.Count);
+ }
+
+ [Fact]
+ public void ReadStringsWithEncoding_InvalidUTF16Chars_Empty()
+ {
+ byte[]? arr =
+ [
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
+ ];
+ var actual = arr.ReadStringsWithEncoding(1, Encoding.Unicode);
+ Assert.NotNull(actual);
+ Assert.Empty(actual);
+ }
+
+ [Fact]
+ public void ReadStringsWithEncoding_UTF32_Filled()
+ {
+ byte[]? bytes =
+ [
+ .. Encoding.UTF32.GetBytes("TEST"),
+ .. new byte[] { 0x00 },
+ .. Encoding.UTF32.GetBytes("ONE"),
+ .. new byte[] { 0x00 },
+ .. Encoding.UTF32.GetBytes("TWO"),
+ .. new byte[] { 0x00 },
+ .. Encoding.UTF32.GetBytes("DATA"),
+ .. new byte[] { 0x00 },
+ ];
+ var actual = bytes.ReadStringsWithEncoding(4, Encoding.UTF32);
+ Assert.Equal(2, actual.Count);
+ }
+
+ [Fact]
+ public void ReadStringsWithEncoding_InvalidUTF32Chars_Empty()
+ {
+ byte[]? arr =
+ [
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
+ ];
+ var actual = arr.ReadStringsWithEncoding(1, Encoding.UTF32);
+ Assert.NotNull(actual);
+ Assert.Empty(actual);
+ }
+
+ #endregion
+
}
}
diff --git a/SabreTools.Text.Extensions.Test/TextHelperTests.cs b/SabreTools.Text.Extensions.Test/TextHelperTests.cs
index 3363063..2a80f32 100644
--- a/SabreTools.Text.Extensions.Test/TextHelperTests.cs
+++ b/SabreTools.Text.Extensions.Test/TextHelperTests.cs
@@ -1,9 +1,64 @@
+using System.Linq;
using Xunit;
namespace SabreTools.Text.Extensions.Test
{
public class TextHelperTests
{
+ #region ToHexString
+
+ [Fact]
+ public void ToHexString_Null()
+ {
+ byte[]? arr = null;
+ string? actual = arr.ToHexString();
+ Assert.Null(actual);
+ }
+
+ [Fact]
+ public void ToHexString_Valid()
+ {
+ byte[]? arr = [0x01, 0x02, 0x03, 0x04];
+ string expected = "01020304";
+
+ string? actual = arr.ToHexString();
+ Assert.NotNull(actual);
+ Assert.Equal(expected, actual);
+ }
+
+ #endregion
+
+ #region FromHexString
+
+ [Fact]
+ public void FromHexString_Null()
+ {
+ string? str = null;
+ byte[]? actual = str.FromHexString();
+ Assert.Null(actual);
+ }
+
+ [Fact]
+ public void FromHexString_Valid()
+ {
+ string str = "01020304";
+ byte[]? expected = [0x01, 0x02, 0x03, 0x04];
+
+ byte[]? actual = str.FromHexString();
+ Assert.NotNull(actual);
+ Assert.True(expected.SequenceEqual(actual));
+ }
+
+ [Fact]
+ public void FromHexString_Invalid()
+ {
+ string str = "0102030G";
+ byte[]? actual = str.FromHexString();
+ Assert.Null(actual);
+ }
+
+ #endregion
+
#region NormalizeCharacters
// TODO: Write tests for NormalizeCharacters
diff --git a/SabreTools.Text.Extensions/BinaryReaderExtensions.cs b/SabreTools.Text.Extensions/BinaryReaderExtensions.cs
index aaafca7..a537353 100644
--- a/SabreTools.Text.Extensions/BinaryReaderExtensions.cs
+++ b/SabreTools.Text.Extensions/BinaryReaderExtensions.cs
@@ -10,6 +10,8 @@ namespace SabreTools.Text.Extensions
///
public static class BinaryReaderExtensions
{
+ #region Read Null Terminated
+
///
/// Read a null-terminated string from the underlying stream
///
@@ -121,6 +123,10 @@ namespace SabreTools.Text.Extensions
return Encoding.UTF32.GetString(buffer);
}
+ #endregion
+
+ #region Read Prefixed
+
///
/// Read a byte-prefixed ASCII string from the underlying stream
///
@@ -169,6 +175,10 @@ namespace SabreTools.Text.Extensions
return Encoding.BigEndianUnicode.GetString(buffer);
}
+ #endregion
+
+ #region Read Until Null Helpers
+
///
/// Read bytes until a 1-byte null terminator is found
///
@@ -222,5 +232,7 @@ namespace SabreTools.Text.Extensions
return [.. bytes];
}
+
+ #endregion
}
}
diff --git a/SabreTools.Text.Extensions/BinaryWriterExtensions.cs b/SabreTools.Text.Extensions/BinaryWriterExtensions.cs
index e5e9c5f..0c1c94f 100644
--- a/SabreTools.Text.Extensions/BinaryWriterExtensions.cs
+++ b/SabreTools.Text.Extensions/BinaryWriterExtensions.cs
@@ -8,6 +8,8 @@ namespace SabreTools.Text.Extensions
///
public static class BinaryWriterExtensions
{
+ #region Write Null Terminated
+
///
/// Write a null-terminated string to the underlying stream
///
@@ -61,6 +63,10 @@ namespace SabreTools.Text.Extensions
public static bool WriteNullTerminatedUTF32String(this BinaryWriter writer, string? value)
=> writer.WriteNullTerminatedString(value, Encoding.UTF32);
+ #endregion
+
+ #region Write Prefixed
+
///
/// Write a byte-prefixed ASCII string to the underlying stream
///
@@ -139,6 +145,8 @@ namespace SabreTools.Text.Extensions
return WriteFromBuffer(writer, buffer);
}
+ #endregion
+
///
/// Write an array of bytes to the underlying stream
///
diff --git a/SabreTools.Text.Extensions/ByteArrayReaderExtensions.cs b/SabreTools.Text.Extensions/ByteArrayReaderExtensions.cs
index 6ac0d4a..bb91b0c 100644
--- a/SabreTools.Text.Extensions/ByteArrayReaderExtensions.cs
+++ b/SabreTools.Text.Extensions/ByteArrayReaderExtensions.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Text;
using SabreTools.Numerics.Extensions;
@@ -10,6 +11,8 @@ namespace SabreTools.Text.Extensions
///
public static class ByteArrayReaderExtensions
{
+ #region Read Null Terminated
+
///
/// Read a null-terminated string from the array
///
@@ -121,6 +124,10 @@ namespace SabreTools.Text.Extensions
return Encoding.UTF32.GetString(buffer);
}
+ #endregion
+
+ #region Read Prefixed
+
///
/// Read a byte-prefixed ASCII string from the byte array
///
@@ -187,6 +194,281 @@ namespace SabreTools.Text.Extensions
return Encoding.BigEndianUnicode.GetString(buffer);
}
+ #endregion
+
+ #region Read All Strings
+
+ ///
+ /// Read string data from a byte array
+ ///
+ /// Number of characters needed to be a valid string, default 5
+ /// String list containing the requested data, null on error
+#if NET5_0_OR_GREATER
+ /// This reads both Latin1 and UTF-16 strings from the input data
+#else
+ /// This reads both ASCII and UTF-16 strings from the input data
+#endif
+ public static List? ReadStringsFrom(this byte[]? input, int charLimit = 5)
+ {
+ // Validate the data
+ if (input is null || input.Length == 0)
+ return null;
+
+#if NET5_0_OR_GREATER
+ // Check for Latin1 strings
+ var asciiStrings = input.ReadStringsWithEncoding(charLimit, Encoding.Latin1);
+#else
+ // Check for ASCII strings
+ var asciiStrings = input.ReadStringsWithEncoding(charLimit, Encoding.ASCII);
+#endif
+
+ // Check for Unicode strings
+ // We are limiting the check for Unicode characters with a second byte of 0x00 for now
+ var unicodeStrings = input.ReadStringsWithEncoding(charLimit, Encoding.Unicode);
+
+ // Ignore duplicate strings across encodings
+ List sourceStrings = [.. asciiStrings, .. unicodeStrings];
+
+ // Sort the strings and return
+ sourceStrings.Sort();
+ return sourceStrings;
+ }
+
+ ///
+ /// Read string data from a byte array with an encoding
+ ///
+ /// Byte array representing the source data
+ /// Number of characters needed to be a valid string
+ /// Character encoding to use for checking
+ /// String list containing the requested data, empty on error
+ /// Characters with the higher bytes set are unused
+#if NET20
+ public static List ReadStringsWithEncoding(this byte[]? bytes, int charLimit, Encoding encoding)
+#else
+ public static HashSet ReadStringsWithEncoding(this byte[]? bytes, int charLimit, Encoding encoding)
+#endif
+ {
+ if (bytes is null || bytes.Length == 0)
+ return [];
+ if (charLimit <= 0 || charLimit > bytes.Length)
+ return [];
+
+ // Short-circuit for some encoding types
+ if (encoding.CodePage == Encoding.ASCII.CodePage)
+ return bytes.ReadAsciiStrings(charLimit);
+#if NET5_0_OR_GREATER
+ else if (encoding.CodePage == Encoding.Latin1.CodePage)
+ return bytes.ReadFixedWidthEncodingStrings(charLimit, Encoding.Latin1, 1);
+#endif
+ else if (encoding.IsSingleByte)
+ return bytes.ReadFixedWidthEncodingStrings(charLimit, encoding, 1);
+ else if (encoding.CodePage == Encoding.Unicode.CodePage)
+ return bytes.ReadFixedWidthEncodingStrings(charLimit, Encoding.Unicode, 2);
+ else if (encoding.CodePage == Encoding.BigEndianUnicode.CodePage)
+ return bytes.ReadFixedWidthEncodingStrings(charLimit, Encoding.BigEndianUnicode, 2);
+ else if (encoding.CodePage == Encoding.UTF32.CodePage)
+ return bytes.ReadFixedWidthEncodingStrings(charLimit, Encoding.UTF32, 4);
+
+ // Create the string set to return
+#if NET20
+ var strings = new List();
+#else
+ var strings = new HashSet();
+#endif
+
+ // Open the text reader with the correct encoding
+ using var ms = new MemoryStream(bytes);
+ using var reader = new StreamReader(ms, encoding);
+
+ // Create a string builder for the loop
+ var sb = new StringBuilder();
+
+ // Check for strings
+ long lastOffset = 0;
+ while (!reader.EndOfStream)
+ {
+ // Read the next character from the stream
+ char c = (char)reader.Read();
+
+ // If the character is invalid
+ if (char.IsControl(c) || (c & 0xFFFFFF00) != 0)
+ {
+ // Seek to the end of the last found string
+ string str = sb.ToString();
+ lastOffset += encoding.GetByteCount(str) + 1;
+ ms.Seek(lastOffset, SeekOrigin.Begin);
+ reader.DiscardBufferedData();
+
+ // If there is no cached string
+ if (str.Length == 0)
+ continue;
+
+ // Add the string if long enough
+ if (str.Length >= charLimit)
+ strings.Add(str);
+
+ // Clear the builder and continue
+#if NET20 || NET35
+ sb = new();
+#else
+ sb.Clear();
+#endif
+ continue;
+ }
+
+ // Otherwise, add the character to the builder and continue
+ sb.Append(c);
+ }
+
+ // Handle any remaining data
+ if (sb.Length >= charLimit)
+ strings.Add(sb.ToString());
+
+ return strings;
+ }
+
+ #endregion
+
+ #region Read With Encoding Helpers
+
+ ///
+ /// Read string data from a byte array using an encoding with a fixed width
+ ///
+ /// Byte array representing the source data
+ /// Number of characters needed to be a valid string
+ /// Character encoding to use for checking
+ /// Character width of the encoding
+ /// String list containing the requested data, empty on error
+ /// Characters with the higher bytes set are unused
+#if NET20
+ private static List ReadFixedWidthEncodingStrings(this byte[] bytes, int charLimit, Encoding encoding, int width)
+#else
+ private static HashSet ReadFixedWidthEncodingStrings(this byte[] bytes, int charLimit, Encoding encoding, int width)
+#endif
+ {
+ if (charLimit <= 0 || charLimit > bytes.Length)
+ return [];
+
+ // Create the string set to return
+#if NET20
+ var strings = new List();
+#else
+ var strings = new HashSet();
+#endif
+
+ // Create a string builder for the loop
+ var sb = new StringBuilder();
+
+ // Check for strings
+ int offset = 0;
+ while (offset <= bytes.Length - width)
+ {
+ // Read the next character from the stream
+ char c = encoding.GetChars(bytes, offset, width)[0];
+ offset += width;
+
+ // If the character is invalid
+ if (char.IsControl(c) || (c & 0xFFFFFF00) != 0)
+ {
+ // Pretend only one byte was read
+ offset -= width - 1;
+
+ // If there is no cached string
+ if (sb.Length == 0)
+ continue;
+
+ // Add the string if long enough
+ if (sb.Length >= charLimit)
+ strings.Add(sb.ToString());
+
+ // Clear the builder and continue
+#if NET20 || NET35
+ sb = new();
+#else
+ sb.Clear();
+#endif
+ continue;
+ }
+
+ // Otherwise, add the character to the builder and continue
+ sb.Append(c);
+ }
+
+ // Handle any remaining data
+ if (sb.Length >= charLimit)
+ strings.Add(sb.ToString());
+
+ return strings;
+ }
+
+ ///
+ /// Read string data from a byte array using ASCII encoding
+ ///
+ /// Byte array representing the source data
+ /// Number of characters needed to be a valid string
+ /// String list containing the requested data, empty on error
+ /// Handling for 7-bit ASCII needs to be done differently than other fixed-width encodings
+#if NET20
+ private static List ReadAsciiStrings(this byte[] bytes, int charLimit)
+#else
+ private static HashSet ReadAsciiStrings(this byte[] bytes, int charLimit)
+#endif
+ {
+ if (charLimit <= 0 || charLimit > bytes.Length)
+ return [];
+
+ // Create the string set to return
+#if NET20
+ var strings = new List();
+#else
+ var strings = new HashSet();
+#endif
+
+ // Create a string builder for the loop
+ var sb = new StringBuilder();
+
+ // Check for strings
+ int offset = 0;
+ while (offset < bytes.Length)
+ {
+ // Read the next character from the stream
+ char c = bytes.ReadChar(ref offset);
+
+ // If the character is invalid
+ if (char.IsControl(c) || c > 0x7F)
+ {
+ // If there is no cached string
+ if (sb.Length == 0)
+ continue;
+
+ // Add the string if long enough
+ if (sb.Length >= charLimit)
+ strings.Add(sb.ToString());
+
+ // Clear the builder and continue
+#if NET20 || NET35
+ sb = new();
+#else
+ sb.Clear();
+#endif
+ continue;
+ }
+
+ // Otherwise, add the character to the builder and continue
+ sb.Append(c);
+ }
+
+ // Handle any remaining data
+ if (sb.Length >= charLimit)
+ strings.Add(sb.ToString());
+
+ return strings;
+ }
+
+ #endregion
+
+ #region Read Until Null Helpers
+
///
/// Read bytes until a 1-byte null terminator is found
///
@@ -240,5 +522,7 @@ namespace SabreTools.Text.Extensions
return [.. bytes];
}
+
+ #endregion
}
}
diff --git a/SabreTools.Text.Extensions/ByteArrayWriterExtensions.cs b/SabreTools.Text.Extensions/ByteArrayWriterExtensions.cs
index 2e88d88..e25083b 100644
--- a/SabreTools.Text.Extensions/ByteArrayWriterExtensions.cs
+++ b/SabreTools.Text.Extensions/ByteArrayWriterExtensions.cs
@@ -9,6 +9,8 @@ namespace SabreTools.Text.Extensions
///
public static class ByteArrayWriterExtensions
{
+ #region Write Null Terminated
+
///
/// Write a null-terminated string to the array
///
@@ -62,6 +64,10 @@ namespace SabreTools.Text.Extensions
public static bool WriteNullTerminatedUTF32String(this byte[] content, ref int offset, string? value)
=> content.WriteNullTerminatedString(ref offset, value, Encoding.UTF32);
+ #endregion
+
+ #region Write Prefixed
+
///
/// Write a byte-prefixed ASCII string to the byte array
///
@@ -144,6 +150,8 @@ namespace SabreTools.Text.Extensions
return WriteFromBuffer(content, ref offset, buffer);
}
+ #endregion
+
///
/// Write an array of bytes to the byte array
///
diff --git a/SabreTools.Text.Extensions/StreamReaderExtensions.cs b/SabreTools.Text.Extensions/StreamReaderExtensions.cs
index 0eac8cc..13d9de8 100644
--- a/SabreTools.Text.Extensions/StreamReaderExtensions.cs
+++ b/SabreTools.Text.Extensions/StreamReaderExtensions.cs
@@ -11,6 +11,8 @@ namespace SabreTools.Text.Extensions
///
public static class StreamReaderExtensions
{
+ #region Read Null Terminated
+
///
/// Read a null-terminated string from the stream
///
@@ -122,6 +124,10 @@ namespace SabreTools.Text.Extensions
return Encoding.UTF32.GetString(buffer);
}
+ #endregion
+
+ #region Read Prefixed
+
///
/// Read a byte-prefixed ASCII string from the stream
///
@@ -188,6 +194,10 @@ namespace SabreTools.Text.Extensions
return Encoding.BigEndianUnicode.GetString(buffer);
}
+ #endregion
+
+ #region Read Until Null Helpers
+
///
/// Read bytes until a 1-byte null terminator is found
///
@@ -241,5 +251,7 @@ namespace SabreTools.Text.Extensions
return [.. bytes];
}
+
+ #endregion
}
}
diff --git a/SabreTools.Text.Extensions/StreamWriterExtensions.cs b/SabreTools.Text.Extensions/StreamWriterExtensions.cs
index abb73fc..2e482eb 100644
--- a/SabreTools.Text.Extensions/StreamWriterExtensions.cs
+++ b/SabreTools.Text.Extensions/StreamWriterExtensions.cs
@@ -9,6 +9,8 @@ namespace SabreTools.Text.Extensions
///
public static class StreamWriterExtensions
{
+ #region Write Null Terminated
+
///
/// Write a null-terminated string to the stream
///
@@ -62,6 +64,10 @@ namespace SabreTools.Text.Extensions
public static bool WriteNullTerminatedUTF32String(this Stream stream, string? value)
=> stream.WriteNullTerminatedString(value, Encoding.UTF32);
+ #endregion
+
+ #region Write Prefixed
+
////
/// Write a byte-prefixed ASCII string to the stream
///
@@ -144,6 +150,8 @@ namespace SabreTools.Text.Extensions
return WriteFromBuffer(stream, buffer);
}
+ #endregion
+
///
/// Write an array of bytes to the stream
///
diff --git a/SabreTools.Text.Extensions/TextHelper.cs b/SabreTools.Text.Extensions/TextHelper.cs
index 767eeb5..22c0376 100644
--- a/SabreTools.Text.Extensions/TextHelper.cs
+++ b/SabreTools.Text.Extensions/TextHelper.cs
@@ -6,6 +6,49 @@ namespace SabreTools.Text.Extensions
{
public static class TextHelper
{
+ #region Hex Strings
+
+ ///
+ /// Convert a byte array to a hex string
+ ///
+ public static string? ToHexString(this byte[]? bytes)
+ {
+ // If we get null in, we send null out
+ if (bytes is null)
+ return null;
+
+ string hex = BitConverter.ToString(bytes);
+ return hex.Replace("-", string.Empty).ToLowerInvariant();
+ }
+
+ ///
+ /// Convert a hex string to a byte array
+ ///
+ public static byte[]? FromHexString(this string? hex)
+ {
+ // If we get null in, we send null out
+ if (string.IsNullOrEmpty(hex))
+ return null;
+
+ try
+ {
+ int chars = hex!.Length;
+ byte[] bytes = new byte[chars / 2];
+ for (int i = 0; i < chars; i += 2)
+ {
+ bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
+ }
+
+ return bytes;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ #endregion
+
#region Normalization
///