mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-09-22 14:55:11 +00:00
Move things to base classes
This commit is contained in:
@@ -19,41 +19,20 @@ namespace SabreTools.Serialization.Wrappers
|
||||
|
||||
#endregion
|
||||
|
||||
#region Instance Variables
|
||||
|
||||
/// <summary>
|
||||
/// Source filename for the wrapper
|
||||
/// </summary>
|
||||
private readonly string? _filename;
|
||||
|
||||
/// <summary>
|
||||
/// Source stream for the wrapper
|
||||
/// </summary>
|
||||
private readonly Stream _stream;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new instance of the wrapper from a file path
|
||||
/// </summary>
|
||||
public BZip2(string filename)
|
||||
/// <inheritdoc/>
|
||||
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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new instance of the wrapper from a Stream
|
||||
/// </summary>
|
||||
public BZip2(Stream stream)
|
||||
/// <inheritdoc/>
|
||||
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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -108,13 +87,13 @@ namespace SabreTools.Serialization.Wrappers
|
||||
/// <inheritdoc/>
|
||||
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();
|
||||
|
||||
@@ -115,6 +115,20 @@ namespace SabreTools.Serialization.Wrappers
|
||||
|
||||
#region Data
|
||||
|
||||
/// <summary>
|
||||
/// Return the underlying data as a stream
|
||||
/// </summary>
|
||||
/// <returns>Stream representing the data source on success, null on error</returns>
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read data from the source
|
||||
/// </summary>
|
||||
|
||||
@@ -19,41 +19,20 @@ namespace SabreTools.Serialization.Wrappers
|
||||
|
||||
#endregion
|
||||
|
||||
#region Instance Variables
|
||||
|
||||
/// <summary>
|
||||
/// Source filename for the wrapper
|
||||
/// </summary>
|
||||
private readonly string? _filename;
|
||||
|
||||
/// <summary>
|
||||
/// Source stream for the wrapper
|
||||
/// </summary>
|
||||
private readonly Stream _stream;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new instance of the wrapper from a file path
|
||||
/// </summary>
|
||||
public GZip(string filename)
|
||||
/// <inheritdoc/>
|
||||
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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new instance of the wrapper from a Stream
|
||||
/// </summary>
|
||||
public GZip(Stream stream)
|
||||
/// <inheritdoc/>
|
||||
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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -107,13 +86,13 @@ namespace SabreTools.Serialization.Wrappers
|
||||
/// <inheritdoc/>
|
||||
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();
|
||||
|
||||
@@ -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<Archive>
|
||||
public class PKZIP : WrapperBase<Archive>, IExtractable
|
||||
{
|
||||
#region Descriptive Properties
|
||||
|
||||
@@ -75,5 +81,78 @@ namespace SabreTools.Serialization.Wrappers
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extraction
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Extract(string outputDirectory, bool includeDebug)
|
||||
=> Extract(outputDirectory, lookForHeader: false, includeDebug);
|
||||
|
||||
/// <inheritdoc cref="Extract(string, bool)"/>
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,41 +23,20 @@ namespace SabreTools.Serialization.Wrappers
|
||||
|
||||
#endregion
|
||||
|
||||
#region Instance Variables
|
||||
|
||||
/// <summary>
|
||||
/// Source filename for the wrapper
|
||||
/// </summary>
|
||||
private readonly string? _filename;
|
||||
|
||||
/// <summary>
|
||||
/// Source stream for the wrapper
|
||||
/// </summary>
|
||||
private readonly Stream _stream;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new instance of the wrapper from a file path
|
||||
/// </summary>
|
||||
public RAR(string filename)
|
||||
/// <inheritdoc/>
|
||||
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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new instance of the wrapper from a Stream
|
||||
/// </summary>
|
||||
public RAR(Stream stream)
|
||||
/// <inheritdoc/>
|
||||
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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -115,18 +94,18 @@ namespace SabreTools.Serialization.Wrappers
|
||||
/// <inheritdoc cref="Extract(string, bool)"/>
|
||||
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;
|
||||
|
||||
@@ -23,41 +23,20 @@ namespace SabreTools.Serialization.Wrappers
|
||||
|
||||
#endregion
|
||||
|
||||
#region Instance Variables
|
||||
|
||||
/// <summary>
|
||||
/// Source filename for the wrapper
|
||||
/// </summary>
|
||||
private readonly string? _filename;
|
||||
|
||||
/// <summary>
|
||||
/// Source stream for the wrapper
|
||||
/// </summary>
|
||||
private readonly Stream _stream;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new instance of the wrapper from a file path
|
||||
/// </summary>
|
||||
public SevenZip(string filename)
|
||||
/// <inheritdoc/>
|
||||
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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new instance of the wrapper from a Stream
|
||||
/// </summary>
|
||||
public SevenZip(Stream stream)
|
||||
/// <inheritdoc/>
|
||||
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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -115,17 +94,17 @@ namespace SabreTools.Serialization.Wrappers
|
||||
/// <inheritdoc cref="Extract(string, bool)"/>
|
||||
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.
|
||||
|
||||
@@ -21,41 +21,20 @@ namespace SabreTools.Serialization.Wrappers
|
||||
|
||||
#endregion
|
||||
|
||||
#region Instance Variables
|
||||
|
||||
/// <summary>
|
||||
/// Source filename for the wrapper
|
||||
/// </summary>
|
||||
private readonly string? _filename;
|
||||
|
||||
/// <summary>
|
||||
/// Source stream for the wrapper
|
||||
/// </summary>
|
||||
private readonly Stream _stream;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new instance of the wrapper from a file path
|
||||
/// </summary>
|
||||
public TapeArchive(string filename)
|
||||
//// <inheritdoc/>
|
||||
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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new instance of the wrapper from a Stream
|
||||
/// </summary>
|
||||
public TapeArchive(Stream stream)
|
||||
/// <inheritdoc/>
|
||||
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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -109,17 +88,17 @@ namespace SabreTools.Serialization.Wrappers
|
||||
/// <inheritdoc/>
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
/// <inheritdoc cref="DataSource.AsStream"/>
|
||||
public Stream? DataSourceStream => _dataSource.AsStream();
|
||||
|
||||
/// <inheritdoc cref="DataSource.Filename"/>
|
||||
public string? Filename => _dataSource.Filename;
|
||||
|
||||
/// <inheritdoc cref="DataSource.Length"/>
|
||||
public long Length => _dataSource.Length;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Instance Variables
|
||||
|
||||
/// <summary>
|
||||
/// Source of the original data
|
||||
/// </summary>
|
||||
private readonly DataSource _dataSource;
|
||||
|
||||
#if NETCOREAPP
|
||||
/// <summary>
|
||||
/// JSON serializer options for output printing
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new instance of the wrapper from a byte array
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new instance of the wrapper from a Stream
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Read data from the source
|
||||
/// </summary>
|
||||
/// <param name="position">Position in the source to read from</param>
|
||||
/// <param name="length">Length of the requested data</param>
|
||||
/// <returns>Byte array containing the requested data, null on error</returns>
|
||||
public byte[]? ReadFromDataSource(int position, int length)
|
||||
=> _dataSource.Read(position, length);
|
||||
|
||||
/// <summary>
|
||||
/// Read string data from the source
|
||||
/// </summary>
|
||||
/// <param name="position">Position in the source to read from</param>
|
||||
/// <param name="length">Length of the requested data</param>
|
||||
/// <param name="charLimit">Number of characters needed to be a valid string</param>
|
||||
/// <returns>String list containing the requested data, null on error</returns>
|
||||
public List<string>? 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<string> sourceStrings = [.. asciiStrings, .. utf8Strings, .. unicodeStrings];
|
||||
|
||||
// Sort the strings and return
|
||||
sourceStrings.Sort();
|
||||
return sourceStrings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read string data from the source with an encoding
|
||||
/// </summary>
|
||||
/// <param name="sourceData">Byte array representing the source data</param>
|
||||
/// <param name="charLimit">Number of characters needed to be a valid string</param>
|
||||
/// <param name="encoding">Character encoding to use for checking</param>
|
||||
/// <returns>String list containing the requested data, empty on error</returns>
|
||||
/// <remarks>TODO: Move to IO?</remarks>
|
||||
#if NET20
|
||||
private static List<string> ReadStringsWithEncoding(byte[] sourceData, int charLimit, Encoding encoding)
|
||||
#else
|
||||
private static HashSet<string> 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<string>();
|
||||
#else
|
||||
var sourceStrings = new HashSet<string>();
|
||||
#endif
|
||||
|
||||
// Setup cached data
|
||||
int sourceDataIndex = 0;
|
||||
List<char> 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
|
||||
|
||||
@@ -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
|
||||
/// </summary>
|
||||
public T Model { get; }
|
||||
|
||||
/// <inheritdoc cref="DataSource.Filename"/>
|
||||
public string? Filename => _dataSource.Filename;
|
||||
|
||||
/// <inheritdoc cref="DataSource.Length"/>
|
||||
public long Length => _dataSource.Length;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Instance Variables
|
||||
|
||||
/// <summary>
|
||||
/// Source of the original data
|
||||
/// </summary>
|
||||
private readonly DataSource _dataSource;
|
||||
|
||||
#if NETCOREAPP
|
||||
/// <summary>
|
||||
/// JSON serializer options for output printing
|
||||
/// </summary>
|
||||
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
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new instance of the wrapper from a Stream
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Read data from the source
|
||||
/// </summary>
|
||||
/// <param name="position">Position in the source to read from</param>
|
||||
/// <param name="length">Length of the requested data</param>
|
||||
/// <returns>Byte array containing the requested data, null on error</returns>
|
||||
public byte[]? ReadFromDataSource(int position, int length)
|
||||
=> _dataSource.Read(position, length);
|
||||
|
||||
/// <summary>
|
||||
/// Read string data from the source
|
||||
/// </summary>
|
||||
/// <param name="position">Position in the source to read from</param>
|
||||
/// <param name="length">Length of the requested data</param>
|
||||
/// <param name="charLimit">Number of characters needed to be a valid string</param>
|
||||
/// <returns>String list containing the requested data, null on error</returns>
|
||||
public List<string>? 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<string> sourceStrings = [.. asciiStrings, .. utf8Strings, .. unicodeStrings];
|
||||
|
||||
// Sort the strings and return
|
||||
sourceStrings.Sort();
|
||||
return sourceStrings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read string data from the source with an encoding
|
||||
/// </summary>
|
||||
/// <param name="sourceData">Byte array representing the source data</param>
|
||||
/// <param name="charLimit">Number of characters needed to be a valid string</param>
|
||||
/// <param name="encoding">Character encoding to use for checking</param>
|
||||
/// <returns>String list containing the requested data, empty on error</returns>
|
||||
/// <remarks>TODO: Move to IO?</remarks>
|
||||
#if NET20
|
||||
private static List<string> ReadStringsWithEncoding(byte[] sourceData, int charLimit, Encoding encoding)
|
||||
#else
|
||||
private static HashSet<string> 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<string>();
|
||||
#else
|
||||
var sourceStrings = new HashSet<string>();
|
||||
#endif
|
||||
|
||||
// Setup cached data
|
||||
int sourceDataIndex = 0;
|
||||
List<char> 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
|
||||
|
||||
@@ -20,41 +20,20 @@ namespace SabreTools.Serialization.Wrappers
|
||||
|
||||
#endregion
|
||||
|
||||
#region Instance Variables
|
||||
|
||||
/// <summary>
|
||||
/// Source filename for the wrapper
|
||||
/// </summary>
|
||||
private readonly string? _filename;
|
||||
|
||||
/// <summary>
|
||||
/// Source stream for the wrapper
|
||||
/// </summary>
|
||||
private readonly Stream _stream;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new instance of the wrapper from a file path
|
||||
/// </summary>
|
||||
public XZ(string filename)
|
||||
/// <inheritdoc/>
|
||||
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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new instance of the wrapper from a Stream
|
||||
/// </summary>
|
||||
public XZ(Stream stream)
|
||||
/// <inheritdoc/>
|
||||
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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user