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();