From c87a456a46c2d578f332ffba578195a90ea2e1c3 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Sat, 23 Aug 2025 12:58:09 -0400 Subject: [PATCH] Move things to base classes --- SabreTools.Serialization/Wrappers/BZip2.cs | 41 +--- .../Wrappers/DataSource.cs | 14 ++ SabreTools.Serialization/Wrappers/GZip.cs | 41 +--- SabreTools.Serialization/Wrappers/PKZIP.cs | 81 ++++++- SabreTools.Serialization/Wrappers/RAR.cs | 45 +--- SabreTools.Serialization/Wrappers/SevenZip.cs | 45 +--- .../Wrappers/TapeArchive.cs | 45 +--- .../Wrappers/WrapperBase.cs | 206 ++++++++++++++++++ .../Wrappers/WrapperBaseT.cs | 171 +-------------- SabreTools.Serialization/Wrappers/XZ.cs | 41 +--- 10 files changed, 368 insertions(+), 362 deletions(-) diff --git a/SabreTools.Serialization/Wrappers/BZip2.cs b/SabreTools.Serialization/Wrappers/BZip2.cs index 0664dec1..0ed2bef9 100644 --- a/SabreTools.Serialization/Wrappers/BZip2.cs +++ b/SabreTools.Serialization/Wrappers/BZip2.cs @@ -19,41 +19,20 @@ namespace SabreTools.Serialization.Wrappers #endregion - #region Instance Variables - - /// - /// Source filename for the wrapper - /// - private readonly string? _filename; - - /// - /// Source stream for the wrapper - /// - private readonly Stream _stream; - - #endregion - #region Constructors - /// - /// Construct a new instance of the wrapper from a file path - /// - public BZip2(string filename) + /// + public BZip2(byte[]? data, int offset) + : base(data, offset) { - _filename = filename; - _stream = File.Open(_filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + // All logic is handled by the base class } - /// - /// Construct a new instance of the wrapper from a Stream - /// - public BZip2(Stream stream) + /// + public BZip2(Stream? data) + : base(data) { - _filename = null; - _stream = stream; - - if (stream is FileStream fs) - _filename = fs.Name; + // All logic is handled by the base class } /// @@ -108,13 +87,13 @@ namespace SabreTools.Serialization.Wrappers /// public bool Extract(string outputDirectory, bool includeDebug) { - if (_stream == null || !_stream.CanRead) + if (DataSourceStream == null || !DataSourceStream.CanRead) return false; try { // Try opening the stream - using var bz2File = new BZip2InputStream(_stream, true); + using var bz2File = new BZip2InputStream(DataSourceStream, true); // Ensure directory separators are consistent string filename = Guid.NewGuid().ToString(); diff --git a/SabreTools.Serialization/Wrappers/DataSource.cs b/SabreTools.Serialization/Wrappers/DataSource.cs index e95021de..17745e0a 100644 --- a/SabreTools.Serialization/Wrappers/DataSource.cs +++ b/SabreTools.Serialization/Wrappers/DataSource.cs @@ -115,6 +115,20 @@ namespace SabreTools.Serialization.Wrappers #region Data + /// + /// Return the underlying data as a stream + /// + /// Stream representing the data source on success, null on error + public Stream? AsStream() + { + return _dataSourceType switch + { + DataSourceType.ByteArray => new MemoryStream(_byteArrayData!, (int)_initialPosition, (int)Length), + DataSourceType.Stream => _streamData, // TODO: This should be wrapped better + _ => null, + }; + } + /// /// Read data from the source /// diff --git a/SabreTools.Serialization/Wrappers/GZip.cs b/SabreTools.Serialization/Wrappers/GZip.cs index 9c910deb..0c959d24 100644 --- a/SabreTools.Serialization/Wrappers/GZip.cs +++ b/SabreTools.Serialization/Wrappers/GZip.cs @@ -19,41 +19,20 @@ namespace SabreTools.Serialization.Wrappers #endregion - #region Instance Variables - - /// - /// Source filename for the wrapper - /// - private readonly string? _filename; - - /// - /// Source stream for the wrapper - /// - private readonly Stream _stream; - - #endregion - #region Constructors - /// - /// Construct a new instance of the wrapper from a file path - /// - public GZip(string filename) + /// + public GZip(byte[]? data, int offset) + : base(data, offset) { - _filename = filename; - _stream = File.Open(_filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + // All logic is handled by the base class } - /// - /// Construct a new instance of the wrapper from a Stream - /// - public GZip(Stream stream) + /// + public GZip(Stream? data) + : base(data) { - _filename = null; - _stream = stream; - - if (stream is FileStream fs) - _filename = fs.Name; + // All logic is handled by the base class } /// @@ -107,13 +86,13 @@ namespace SabreTools.Serialization.Wrappers /// public bool Extract(string outputDirectory, bool includeDebug) { - if (_stream == null || !_stream.CanRead) + if (DataSourceStream == null || !DataSourceStream.CanRead) return false; try { // Try opening the stream - using var gzipFile = new GZipStream(_stream, CompressionMode.Decompress, true); + using var gzipFile = new GZipStream(DataSourceStream, CompressionMode.Decompress, true); // Ensure directory separators are consistent string filename = Guid.NewGuid().ToString(); diff --git a/SabreTools.Serialization/Wrappers/PKZIP.cs b/SabreTools.Serialization/Wrappers/PKZIP.cs index 64156755..8932c6bd 100644 --- a/SabreTools.Serialization/Wrappers/PKZIP.cs +++ b/SabreTools.Serialization/Wrappers/PKZIP.cs @@ -1,9 +1,15 @@ using System.IO; using SabreTools.Models.PKZIP; +using SabreTools.Serialization.Interfaces; +#if NET462_OR_GREATER || NETCOREAPP +using SharpCompress.Archives; +using SharpCompress.Archives.Zip; +using SharpCompress.Readers; +#endif namespace SabreTools.Serialization.Wrappers { - public class PKZIP : WrapperBase + public class PKZIP : WrapperBase, IExtractable { #region Descriptive Properties @@ -75,5 +81,78 @@ namespace SabreTools.Serialization.Wrappers } #endregion + + #region Extraction + + /// + public bool Extract(string outputDirectory, bool includeDebug) + => Extract(outputDirectory, lookForHeader: false, includeDebug); + + /// + public bool Extract(string outputDirectory, bool lookForHeader, bool includeDebug) + { + if (DataSourceStream == null || !DataSourceStream.CanRead) + return false; + +#if NET462_OR_GREATER || NETCOREAPP + try + { + var readerOptions = new ReaderOptions() { LookForHeader = lookForHeader }; + var zipFile = ZipArchive.Open(DataSourceStream, readerOptions); + + // Try to read the file path if no entries are found + if (zipFile.Entries.Count == 0 && !string.IsNullOrEmpty(Filename) && File.Exists(Filename!)) + zipFile = ZipArchive.Open(Filename!, readerOptions); + + foreach (var entry in zipFile.Entries) + { + try + { + // If the entry is a directory + if (entry.IsDirectory) + continue; + + // If the entry has an invalid key + if (entry.Key == null) + continue; + + // If the entry is partial due to an incomplete multi-part archive, skip it + if (!entry.IsComplete) + continue; + + // Ensure directory separators are consistent + string filename = entry.Key; + if (Path.DirectorySeparatorChar == '\\') + filename = filename.Replace('/', '\\'); + else if (Path.DirectorySeparatorChar == '/') + filename = filename.Replace('\\', '/'); + + // Ensure the full output directory exists + filename = Path.Combine(outputDirectory, filename); + var directoryName = Path.GetDirectoryName(filename); + if (directoryName != null && !Directory.Exists(directoryName)) + Directory.CreateDirectory(directoryName); + + entry.WriteToFile(filename); + } + catch (System.Exception ex) + { + if (includeDebug) System.Console.Error.WriteLine(ex); + } + } + + return true; + } + catch (System.Exception ex) + { + if (includeDebug) System.Console.Error.WriteLine(ex); + return false; + } +#else + return false; +#endif + } + + #endregion } } diff --git a/SabreTools.Serialization/Wrappers/RAR.cs b/SabreTools.Serialization/Wrappers/RAR.cs index 7951d8d0..1104979b 100644 --- a/SabreTools.Serialization/Wrappers/RAR.cs +++ b/SabreTools.Serialization/Wrappers/RAR.cs @@ -23,41 +23,20 @@ namespace SabreTools.Serialization.Wrappers #endregion - #region Instance Variables - - /// - /// Source filename for the wrapper - /// - private readonly string? _filename; - - /// - /// Source stream for the wrapper - /// - private readonly Stream _stream; - - #endregion - #region Constructors - /// - /// Construct a new instance of the wrapper from a file path - /// - public RAR(string filename) + /// + public RAR(byte[]? data, int offset) + : base(data, offset) { - _filename = filename; - _stream = File.Open(_filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + // All logic is handled by the base class } - /// - /// Construct a new instance of the wrapper from a Stream - /// - public RAR(Stream stream) + /// + public RAR(Stream? data) + : base(data) { - _filename = null; - _stream = stream; - - if (stream is FileStream fs) - _filename = fs.Name; + // All logic is handled by the base class } /// @@ -115,18 +94,18 @@ namespace SabreTools.Serialization.Wrappers /// public bool Extract(string outputDirectory, bool lookForHeader, bool includeDebug) { - if (_stream == null || !_stream.CanRead) + if (DataSourceStream == null || !DataSourceStream.CanRead) return false; #if NET462_OR_GREATER || NETCOREAPP try { var readerOptions = new ReaderOptions() { LookForHeader = lookForHeader }; - RarArchive rarFile = RarArchive.Open(_stream, readerOptions); + RarArchive rarFile = RarArchive.Open(DataSourceStream, readerOptions); // Try to read the file path if no entries are found - if (rarFile.Entries.Count == 0 && !string.IsNullOrEmpty(_filename) && File.Exists(_filename)) - rarFile = RarArchive.Open(_filename, readerOptions); + if (rarFile.Entries.Count == 0 && !string.IsNullOrEmpty(Filename) && File.Exists(Filename!)) + rarFile = RarArchive.Open(Filename!, readerOptions); if (!rarFile.IsComplete) return false; diff --git a/SabreTools.Serialization/Wrappers/SevenZip.cs b/SabreTools.Serialization/Wrappers/SevenZip.cs index 21421d35..150fe4a5 100644 --- a/SabreTools.Serialization/Wrappers/SevenZip.cs +++ b/SabreTools.Serialization/Wrappers/SevenZip.cs @@ -23,41 +23,20 @@ namespace SabreTools.Serialization.Wrappers #endregion - #region Instance Variables - - /// - /// Source filename for the wrapper - /// - private readonly string? _filename; - - /// - /// Source stream for the wrapper - /// - private readonly Stream _stream; - - #endregion - #region Constructors - /// - /// Construct a new instance of the wrapper from a file path - /// - public SevenZip(string filename) + /// + public SevenZip(byte[]? data, int offset) + : base(data, offset) { - _filename = filename; - _stream = File.Open(_filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + // All logic is handled by the base class } - /// - /// Construct a new instance of the wrapper from a Stream - /// - public SevenZip(Stream stream) + /// + public SevenZip(Stream? data) + : base(data) { - _filename = null; - _stream = stream; - - if (stream is FileStream fs) - _filename = fs.Name; + // All logic is handled by the base class } /// @@ -115,17 +94,17 @@ namespace SabreTools.Serialization.Wrappers /// public bool Extract(string outputDirectory, bool lookForHeader, bool includeDebug) { - if (_stream == null || !_stream.CanRead) + if (DataSourceStream == null || !DataSourceStream.CanRead) return false; #if NET462_OR_GREATER || NETCOREAPP try { var readerOptions = new ReaderOptions() { LookForHeader = lookForHeader }; - var sevenZip = SevenZipArchive.Open(_stream, readerOptions); + var sevenZip = SevenZipArchive.Open(DataSourceStream, readerOptions); // Try to read the file path if no entries are found - if (sevenZip.Entries.Count == 0 && !string.IsNullOrEmpty(_filename) && File.Exists(_filename)) - sevenZip = SevenZipArchive.Open(_filename, readerOptions); + if (sevenZip.Entries.Count == 0 && !string.IsNullOrEmpty(Filename) && File.Exists(Filename!)) + sevenZip = SevenZipArchive.Open(Filename!, readerOptions); // Currently doesn't flag solid 7z archives with only 1 solid block as solid, but practically speaking // this is not much of a concern. diff --git a/SabreTools.Serialization/Wrappers/TapeArchive.cs b/SabreTools.Serialization/Wrappers/TapeArchive.cs index c19448d4..b966a815 100644 --- a/SabreTools.Serialization/Wrappers/TapeArchive.cs +++ b/SabreTools.Serialization/Wrappers/TapeArchive.cs @@ -21,41 +21,20 @@ namespace SabreTools.Serialization.Wrappers #endregion - #region Instance Variables - - /// - /// Source filename for the wrapper - /// - private readonly string? _filename; - - /// - /// Source stream for the wrapper - /// - private readonly Stream _stream; - - #endregion - #region Constructors - /// - /// Construct a new instance of the wrapper from a file path - /// - public TapeArchive(string filename) + //// + public TapeArchive(byte[]? data, int offset) + : base(data, offset) { - _filename = filename; - _stream = File.Open(_filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + // All logic is handled by the base class } - /// - /// Construct a new instance of the wrapper from a Stream - /// - public TapeArchive(Stream stream) + /// + public TapeArchive(Stream? data) + : base(data) { - _filename = null; - _stream = stream; - - if (stream is FileStream fs) - _filename = fs.Name; + // All logic is handled by the base class } /// @@ -109,17 +88,17 @@ namespace SabreTools.Serialization.Wrappers /// public bool Extract(string outputDirectory, bool includeDebug) { - if (_stream == null || !_stream.CanRead) + if (DataSourceStream == null || !DataSourceStream.CanRead) return false; #if NET462_OR_GREATER || NETCOREAPP try { - var tarFile = TarArchive.Open(_stream); + var tarFile = TarArchive.Open(DataSourceStream); // Try to read the file path if no entries are found - if (tarFile.Entries.Count == 0 && !string.IsNullOrEmpty(_filename) && File.Exists(_filename)) - tarFile = TarArchive.Open(_filename); + if (tarFile.Entries.Count == 0 && !string.IsNullOrEmpty(Filename) && File.Exists(Filename!)) + tarFile = TarArchive.Open(Filename!); foreach (var entry in tarFile.Entries) { diff --git a/SabreTools.Serialization/Wrappers/WrapperBase.cs b/SabreTools.Serialization/Wrappers/WrapperBase.cs index 1dc94119..ca93c334 100644 --- a/SabreTools.Serialization/Wrappers/WrapperBase.cs +++ b/SabreTools.Serialization/Wrappers/WrapperBase.cs @@ -1,3 +1,7 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; using SabreTools.Serialization.Interfaces; namespace SabreTools.Serialization.Wrappers @@ -16,6 +20,208 @@ namespace SabreTools.Serialization.Wrappers #endregion + #region Properties + + /// + public Stream? DataSourceStream => _dataSource.AsStream(); + + /// + public string? Filename => _dataSource.Filename; + + /// + public long Length => _dataSource.Length; + + #endregion + + #region Instance Variables + + /// + /// Source of the original data + /// + private readonly DataSource _dataSource; + +#if NETCOREAPP + /// + /// JSON serializer options for output printing + /// + protected System.Text.Json.JsonSerializerOptions _jsonSerializerOptions + { + get + { +#if NETCOREAPP3_1 + var serializer = new System.Text.Json.JsonSerializerOptions { WriteIndented = true }; +#else + var serializer = new System.Text.Json.JsonSerializerOptions { IncludeFields = true, WriteIndented = true }; +#endif + serializer.Converters.Add(new ConcreteAbstractSerializer()); + serializer.Converters.Add(new ConcreteInterfaceSerializer()); + serializer.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter()); + return serializer; + } + } +#endif + + #endregion + + #region Constructors + + /// + /// Construct a new instance of the wrapper from a byte array + /// + protected WrapperBase(byte[]? data, int offset) + { + if (data == null) + throw new ArgumentNullException(nameof(data)); + if (offset < 0 || offset >= data.Length) + throw new ArgumentOutOfRangeException(nameof(offset)); + + _dataSource = new DataSource(data, offset); + } + + /// + /// Construct a new instance of the wrapper from a Stream + /// + protected WrapperBase(Stream? data) + { + if (data == null) + throw new ArgumentNullException(nameof(data)); + if (!data.CanSeek || !data.CanRead) + throw new ArgumentOutOfRangeException(nameof(data)); + + _dataSource = new DataSource(data); + } + + #endregion + + #region Data + + /// + /// Read data from the source + /// + /// Position in the source to read from + /// Length of the requested data + /// Byte array containing the requested data, null on error + public byte[]? ReadFromDataSource(int position, int length) + => _dataSource.Read(position, length); + + /// + /// Read string data from the source + /// + /// Position in the source to read from + /// Length of the requested data + /// Number of characters needed to be a valid string + /// String list containing the requested data, null on error + public List? ReadStringsFromDataSource(int position, int length, int charLimit = 5) + { + // Read the data as a byte array first + byte[]? sourceData = ReadFromDataSource(position, length); + if (sourceData == null) + return null; + + // Check for ASCII strings + var asciiStrings = ReadStringsWithEncoding(sourceData, charLimit, Encoding.ASCII); + + // Check for UTF-8 strings + // We are limiting the check for Unicode characters with a second byte of 0x00 for now + var utf8Strings = ReadStringsWithEncoding(sourceData, charLimit, Encoding.UTF8); + + // Check for Unicode strings + // We are limiting the check for Unicode characters with a second byte of 0x00 for now + var unicodeStrings = ReadStringsWithEncoding(sourceData, charLimit, Encoding.Unicode); + + // Ignore duplicate strings across encodings + List sourceStrings = [.. asciiStrings, .. utf8Strings, .. unicodeStrings]; + + // Sort the strings and return + sourceStrings.Sort(); + return sourceStrings; + } + + /// + /// Read string data from the source 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 + /// TODO: Move to IO? +#if NET20 + private static List ReadStringsWithEncoding(byte[] sourceData, int charLimit, Encoding encoding) +#else + private static HashSet ReadStringsWithEncoding(byte[] sourceData, int charLimit, Encoding encoding) +#endif + { + // If we have an invalid character limit, default to 5 + if (charLimit <= 0) + charLimit = 5; + + // Create the string hash set to return +#if NET20 + var sourceStrings = new List(); +#else + var sourceStrings = new HashSet(); +#endif + + // Setup cached data + int sourceDataIndex = 0; + List cachedChars = []; + + // Check for strings + while (sourceDataIndex < sourceData.Length) + { + // Read the next character + char ch = encoding.GetChars(sourceData, sourceDataIndex, 1)[0]; + + // If we have a control character or an invalid byte + bool isValid = !char.IsControl(ch) && (ch & 0xFF00) == 0; + if (!isValid) + { + // If we have no cached string + if (cachedChars.Count == 0) + { + sourceDataIndex++; + continue; + } + + // If we have a cached string greater than the limit + if (cachedChars.Count >= charLimit) + sourceStrings.Add(new string([.. cachedChars])); + + cachedChars.Clear(); + sourceDataIndex++; + continue; + } + + // If a long repeating string is found, discard it + if (cachedChars.Count >= 64 && cachedChars.TrueForAll(c => c == cachedChars[0])) + { + cachedChars.Clear(); + sourceDataIndex++; + continue; + } + + // Append the character to the cached string + cachedChars.Add(ch); + sourceDataIndex++; + } + + // If we have a cached string greater than the limit + if (cachedChars.Count >= charLimit) + { + // Get the string from the cached characters + string cachedString = new([.. cachedChars]); + cachedString = cachedString.Trim(); + + // Only include trimmed strings over the limit + if (cachedString.Length >= charLimit) + sourceStrings.Add(cachedString); + } + + return sourceStrings; + } + + #endregion + #region JSON Export #if NETCOREAPP diff --git a/SabreTools.Serialization/Wrappers/WrapperBaseT.cs b/SabreTools.Serialization/Wrappers/WrapperBaseT.cs index c345e855..8ba68457 100644 --- a/SabreTools.Serialization/Wrappers/WrapperBaseT.cs +++ b/SabreTools.Serialization/Wrappers/WrapperBaseT.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Text; using SabreTools.Serialization.Interfaces; namespace SabreTools.Serialization.Wrappers @@ -18,42 +16,6 @@ namespace SabreTools.Serialization.Wrappers /// public T Model { get; } - /// - public string? Filename => _dataSource.Filename; - - /// - public long Length => _dataSource.Length; - - #endregion - - #region Instance Variables - - /// - /// Source of the original data - /// - private readonly DataSource _dataSource; - -#if NETCOREAPP - /// - /// JSON serializer options for output printing - /// - private System.Text.Json.JsonSerializerOptions _jsonSerializerOptions - { - get - { -#if NETCOREAPP3_1 - var serializer = new System.Text.Json.JsonSerializerOptions { WriteIndented = true }; -#else - var serializer = new System.Text.Json.JsonSerializerOptions { IncludeFields = true, WriteIndented = true }; -#endif - serializer.Converters.Add(new ConcreteAbstractSerializer()); - serializer.Converters.Add(new ConcreteInterfaceSerializer()); - serializer.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter()); - return serializer; - } - } -#endif - #endregion #region Constructors @@ -62,6 +24,7 @@ namespace SabreTools.Serialization.Wrappers /// Construct a new instance of the wrapper from a byte array /// protected WrapperBase(T? model, byte[]? data, int offset) + : base(data, offset) { if (model == null) throw new ArgumentNullException(nameof(model)); @@ -71,13 +34,13 @@ namespace SabreTools.Serialization.Wrappers throw new ArgumentOutOfRangeException(nameof(offset)); Model = model; - _dataSource = new DataSource(data, offset); } /// /// Construct a new instance of the wrapper from a Stream /// protected WrapperBase(T? model, Stream? data) + : base(data) { if (model == null) throw new ArgumentNullException(nameof(model)); @@ -87,136 +50,6 @@ namespace SabreTools.Serialization.Wrappers throw new ArgumentOutOfRangeException(nameof(data)); Model = model; - _dataSource = new DataSource(data); - } - - #endregion - - #region Data - - /// - /// Read data from the source - /// - /// Position in the source to read from - /// Length of the requested data - /// Byte array containing the requested data, null on error - public byte[]? ReadFromDataSource(int position, int length) - => _dataSource.Read(position, length); - - /// - /// Read string data from the source - /// - /// Position in the source to read from - /// Length of the requested data - /// Number of characters needed to be a valid string - /// String list containing the requested data, null on error - public List? ReadStringsFromDataSource(int position, int length, int charLimit = 5) - { - // Read the data as a byte array first - byte[]? sourceData = ReadFromDataSource(position, length); - if (sourceData == null) - return null; - - // Check for ASCII strings - var asciiStrings = ReadStringsWithEncoding(sourceData, charLimit, Encoding.ASCII); - - // Check for UTF-8 strings - // We are limiting the check for Unicode characters with a second byte of 0x00 for now - var utf8Strings = ReadStringsWithEncoding(sourceData, charLimit, Encoding.UTF8); - - // Check for Unicode strings - // We are limiting the check for Unicode characters with a second byte of 0x00 for now - var unicodeStrings = ReadStringsWithEncoding(sourceData, charLimit, Encoding.Unicode); - - // Ignore duplicate strings across encodings - List sourceStrings = [.. asciiStrings, .. utf8Strings, .. unicodeStrings]; - - // Sort the strings and return - sourceStrings.Sort(); - return sourceStrings; - } - - /// - /// Read string data from the source 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 - /// TODO: Move to IO? -#if NET20 - private static List ReadStringsWithEncoding(byte[] sourceData, int charLimit, Encoding encoding) -#else - private static HashSet ReadStringsWithEncoding(byte[] sourceData, int charLimit, Encoding encoding) -#endif - { - // If we have an invalid character limit, default to 5 - if (charLimit <= 0) - charLimit = 5; - - // Create the string hash set to return -#if NET20 - var sourceStrings = new List(); -#else - var sourceStrings = new HashSet(); -#endif - - // Setup cached data - int sourceDataIndex = 0; - List cachedChars = []; - - // Check for strings - while (sourceDataIndex < sourceData.Length) - { - // Read the next character - char ch = encoding.GetChars(sourceData, sourceDataIndex, 1)[0]; - - // If we have a control character or an invalid byte - bool isValid = !char.IsControl(ch) && (ch & 0xFF00) == 0; - if (!isValid) - { - // If we have no cached string - if (cachedChars.Count == 0) - { - sourceDataIndex++; - continue; - } - - // If we have a cached string greater than the limit - if (cachedChars.Count >= charLimit) - sourceStrings.Add(new string([.. cachedChars])); - - cachedChars.Clear(); - sourceDataIndex++; - continue; - } - - // If a long repeating string is found, discard it - if (cachedChars.Count >= 64 && cachedChars.TrueForAll(c => c == cachedChars[0])) - { - cachedChars.Clear(); - sourceDataIndex++; - continue; - } - - // Append the character to the cached string - cachedChars.Add(ch); - sourceDataIndex++; - } - - // If we have a cached string greater than the limit - if (cachedChars.Count >= charLimit) - { - // Get the string from the cached characters - string cachedString = new([.. cachedChars]); - cachedString = cachedString.Trim(); - - // Only include trimmed strings over the limit - if (cachedString.Length >= charLimit) - sourceStrings.Add(cachedString); - } - - return sourceStrings; } #endregion diff --git a/SabreTools.Serialization/Wrappers/XZ.cs b/SabreTools.Serialization/Wrappers/XZ.cs index 5e4944db..b289d25d 100644 --- a/SabreTools.Serialization/Wrappers/XZ.cs +++ b/SabreTools.Serialization/Wrappers/XZ.cs @@ -20,41 +20,20 @@ namespace SabreTools.Serialization.Wrappers #endregion - #region Instance Variables - - /// - /// Source filename for the wrapper - /// - private readonly string? _filename; - - /// - /// Source stream for the wrapper - /// - private readonly Stream _stream; - - #endregion - #region Constructors - /// - /// Construct a new instance of the wrapper from a file path - /// - public XZ(string filename) + /// + public XZ(byte[]? data, int offset) + : base(data, offset) { - _filename = filename; - _stream = File.Open(_filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + // All logic is handled by the base class } - /// - /// Construct a new instance of the wrapper from a Stream - /// - public XZ(Stream stream) + /// + public XZ(Stream? data) + : base(data) { - _filename = null; - _stream = stream; - - if (stream is FileStream fs) - _filename = fs.Name; + // All logic is handled by the base class } /// @@ -109,13 +88,13 @@ namespace SabreTools.Serialization.Wrappers public bool Extract(string outDir, bool includeDebug) { #if NET462_OR_GREATER || NETCOREAPP - if (_stream == null || !_stream.CanRead) + if (DataSourceStream == null || !DataSourceStream.CanRead) return false; try { // Try opening the stream - using var xzFile = new XZStream(_stream); + using var xzFile = new XZStream(DataSourceStream); // Ensure directory separators are consistent string filename = System.Guid.NewGuid().ToString();