mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-09-22 14:55:11 +00:00
Intermediary step for extractable shells
This commit is contained in:
@@ -41,6 +41,7 @@
|
||||
<PackageReference Include="SabreTools.IO" Version="1.7.0" />
|
||||
<PackageReference Include="SabreTools.Models" Version="1.6.1" />
|
||||
<PackageReference Include="SabreTools.Matching" Version="1.6.0" />
|
||||
<PackageReference Include="SharpCompress" Version="0.39.0" Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`)) AND !$(TargetFramework.StartsWith(`net452`))" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,4 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Compression.BZip2;
|
||||
using SabreTools.Serialization.Interfaces;
|
||||
|
||||
namespace SabreTools.Serialization.Wrappers
|
||||
{
|
||||
@@ -7,7 +10,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
/// any actual parsing. It is used as a placeholder for
|
||||
/// types that typically do not have models.
|
||||
/// </summary>
|
||||
public class BZip2 : WrapperBase
|
||||
public class BZip2 : WrapperBase, IExtractable
|
||||
{
|
||||
#region Descriptive Properties
|
||||
|
||||
@@ -16,8 +19,43 @@ 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)
|
||||
{
|
||||
_filename = filename;
|
||||
_stream = File.Open(_filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new instance of the wrapper from a Stream
|
||||
/// </summary>
|
||||
public BZip2(Stream stream)
|
||||
{
|
||||
_filename = null;
|
||||
_stream = stream;
|
||||
|
||||
if (stream is FileStream fs)
|
||||
_filename = fs.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a BZip2 archive from a byte array and offset
|
||||
/// </summary>
|
||||
@@ -50,7 +88,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
return new BZip2();
|
||||
return new BZip2(data);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -59,10 +97,52 @@ namespace SabreTools.Serialization.Wrappers
|
||||
|
||||
#if NETCOREAPP
|
||||
/// <inheritdoc/>
|
||||
public override string ExportJSON() => throw new System.NotImplementedException();
|
||||
public override string ExportJSON() => throw new NotImplementedException();
|
||||
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extraction
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Extract(string outputDirectory, bool includeDebug)
|
||||
{
|
||||
if (_stream == null || !_stream.CanRead)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
// Try opening the stream
|
||||
using var bz2File = new BZip2InputStream(_stream, true);
|
||||
|
||||
// Ensure directory separators are consistent
|
||||
string filename = Guid.NewGuid().ToString();
|
||||
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);
|
||||
|
||||
// Extract the file
|
||||
using FileStream fs = File.OpenWrite(filename);
|
||||
bz2File.CopyTo(fs);
|
||||
fs.Flush();
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine(ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SabreTools.IO.Compression.Deflate;
|
||||
using SabreTools.Serialization.Interfaces;
|
||||
|
||||
namespace SabreTools.Serialization.Wrappers
|
||||
{
|
||||
@@ -7,7 +10,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
/// any actual parsing. It is used as a placeholder for
|
||||
/// types that typically do not have models.
|
||||
/// </summary>
|
||||
public class GZip : WrapperBase
|
||||
public class GZip : WrapperBase, IExtractable
|
||||
{
|
||||
#region Descriptive Properties
|
||||
|
||||
@@ -16,8 +19,43 @@ 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)
|
||||
{
|
||||
_filename = filename;
|
||||
_stream = File.Open(_filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new instance of the wrapper from a Stream
|
||||
/// </summary>
|
||||
public GZip(Stream stream)
|
||||
{
|
||||
_filename = null;
|
||||
_stream = stream;
|
||||
|
||||
if (stream is FileStream fs)
|
||||
_filename = fs.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a GZip archive from a byte array and offset
|
||||
/// </summary>
|
||||
@@ -50,7 +88,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
return new GZip();
|
||||
return new GZip(data);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -59,9 +97,51 @@ namespace SabreTools.Serialization.Wrappers
|
||||
|
||||
#if NETCOREAPP
|
||||
/// <inheritdoc/>
|
||||
public override string ExportJSON() => throw new System.NotImplementedException();
|
||||
public override string ExportJSON() => throw new NotImplementedException();
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extraction
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Extract(string outputDirectory, bool includeDebug)
|
||||
{
|
||||
if (_stream == null || !_stream.CanRead)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
// Try opening the stream
|
||||
using var gzipFile = new GZipStream(_stream, CompressionMode.Decompress, true);
|
||||
|
||||
// Ensure directory separators are consistent
|
||||
string filename = Guid.NewGuid().ToString();
|
||||
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);
|
||||
|
||||
// Extract the file
|
||||
using FileStream fs = File.OpenWrite(filename);
|
||||
gzipFile.CopyTo(fs);
|
||||
fs.Flush();
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (includeDebug) Console.Error.WriteLine(ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
using System.IO;
|
||||
using SabreTools.Serialization.Interfaces;
|
||||
#if NET462_OR_GREATER || NETCOREAPP
|
||||
using SharpCompress.Archives;
|
||||
using SharpCompress.Archives.Rar;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Readers;
|
||||
#endif
|
||||
|
||||
namespace SabreTools.Serialization.Wrappers
|
||||
{
|
||||
@@ -7,7 +14,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
/// any actual parsing. It is used as a placeholder for
|
||||
/// types that typically do not have models.
|
||||
/// </summary>
|
||||
public class RAR : WrapperBase
|
||||
public class RAR : WrapperBase, IExtractable
|
||||
{
|
||||
#region Descriptive Properties
|
||||
|
||||
@@ -16,8 +23,43 @@ 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)
|
||||
{
|
||||
_filename = filename;
|
||||
_stream = File.Open(_filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new instance of the wrapper from a Stream
|
||||
/// </summary>
|
||||
public RAR(Stream stream)
|
||||
{
|
||||
_filename = null;
|
||||
_stream = stream;
|
||||
|
||||
if (stream is FileStream fs)
|
||||
_filename = fs.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a RAR archive (or derived format) from a byte array and offset
|
||||
/// </summary>
|
||||
@@ -50,7 +92,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
return new RAR();
|
||||
return new RAR(data);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -63,5 +105,122 @@ namespace SabreTools.Serialization.Wrappers
|
||||
#endif
|
||||
|
||||
#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 (_stream == null || !_stream.CanRead)
|
||||
return false;
|
||||
|
||||
#if NET462_OR_GREATER || NETCOREAPP
|
||||
try
|
||||
{
|
||||
var readerOptions = new ReaderOptions() { LookForHeader = lookForHeader };
|
||||
RarArchive rarFile = RarArchive.Open(_stream, 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.IsComplete)
|
||||
return false;
|
||||
|
||||
if (rarFile.IsSolid)
|
||||
return ExtractSolid(rarFile, outputDirectory, includeDebug);
|
||||
else
|
||||
return ExtractNonSolid(rarFile, outputDirectory, includeDebug);
|
||||
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
if (includeDebug) System.Console.Error.WriteLine(ex);
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if NET462_OR_GREATER || NETCOREAPP
|
||||
|
||||
/// <summary>
|
||||
/// Extraction method for non-solid archives. This iterates over each entry in the archive to extract every
|
||||
/// file individually, in order to extract all valid files from the archive.
|
||||
/// </summary>
|
||||
private static bool ExtractNonSolid(RarArchive rarFile, string outDir, bool includeDebug)
|
||||
{
|
||||
foreach (var entry in rarFile.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 we have a partial entry 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(outDir, 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extraction method for solid archives. Uses ExtractAllEntries because extraction for solid archives must be
|
||||
/// done sequentially, and files beyond a corrupted point in a solid archive will be unreadable anyways.
|
||||
/// </summary>
|
||||
private static bool ExtractSolid(RarArchive rarFile, string outDir, bool includeDebug)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(outDir))
|
||||
Directory.CreateDirectory(outDir);
|
||||
|
||||
rarFile.WriteToDirectory(outDir, new ExtractionOptions()
|
||||
{
|
||||
ExtractFullPath = true,
|
||||
Overwrite = true,
|
||||
});
|
||||
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
if (includeDebug) System.Console.Error.WriteLine(ex);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
using System.IO;
|
||||
using SabreTools.Serialization.Interfaces;
|
||||
#if NET462_OR_GREATER || NETCOREAPP
|
||||
using SharpCompress.Archives;
|
||||
using SharpCompress.Archives.SevenZip;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Readers;
|
||||
#endif
|
||||
|
||||
namespace SabreTools.Serialization.Wrappers
|
||||
{
|
||||
@@ -7,7 +14,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
/// any actual parsing. It is used as a placeholder for
|
||||
/// types that typically do not have models.
|
||||
/// </summary>
|
||||
public class SevenZip : WrapperBase
|
||||
public class SevenZip : WrapperBase, IExtractable
|
||||
{
|
||||
#region Descriptive Properties
|
||||
|
||||
@@ -16,8 +23,43 @@ 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)
|
||||
{
|
||||
_filename = filename;
|
||||
_stream = File.Open(_filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new instance of the wrapper from a Stream
|
||||
/// </summary>
|
||||
public SevenZip(Stream stream)
|
||||
{
|
||||
_filename = null;
|
||||
_stream = stream;
|
||||
|
||||
if (stream is FileStream fs)
|
||||
_filename = fs.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a SevenZip archive (or derived format) from a byte array and offset
|
||||
/// </summary>
|
||||
@@ -50,7 +92,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
return new SevenZip();
|
||||
return new SevenZip(data);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -63,5 +105,119 @@ namespace SabreTools.Serialization.Wrappers
|
||||
#endif
|
||||
|
||||
#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 (_stream == null || !_stream.CanRead)
|
||||
return false;
|
||||
|
||||
#if NET462_OR_GREATER || NETCOREAPP
|
||||
try
|
||||
{
|
||||
var readerOptions = new ReaderOptions() { LookForHeader = lookForHeader };
|
||||
var sevenZip = SevenZipArchive.Open(_stream, 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);
|
||||
|
||||
// Currently doesn't flag solid 7z archives with only 1 solid block as solid, but practically speaking
|
||||
// this is not much of a concern.
|
||||
if (sevenZip.IsSolid)
|
||||
return ExtractSolid(sevenZip, outputDirectory, includeDebug);
|
||||
else
|
||||
return ExtractNonSolid(sevenZip, outputDirectory, includeDebug);
|
||||
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
if (includeDebug) System.Console.Error.WriteLine(ex);
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if NET462_OR_GREATER || NETCOREAPP
|
||||
/// <summary>
|
||||
/// Extraction method for non-solid archives. This iterates over each entry in the archive to extract every
|
||||
/// file individually, in order to extract all valid files from the archive.
|
||||
/// </summary>
|
||||
private static bool ExtractNonSolid(SevenZipArchive sevenZip, string outputDirectory, bool includeDebug)
|
||||
{
|
||||
foreach (var entry in sevenZip.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 we have a partial entry 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extraction method for solid archives. Uses ExtractAllEntries because extraction for solid archives must be
|
||||
/// done sequentially, and files beyond a corrupted point in a solid archive will be unreadable anyways.
|
||||
/// </summary>
|
||||
private static bool ExtractSolid(SevenZipArchive sevenZip, string outputDirectory, bool includeDebug)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(outputDirectory))
|
||||
Directory.CreateDirectory(outputDirectory);
|
||||
|
||||
sevenZip.WriteToDirectory(outputDirectory, new ExtractionOptions()
|
||||
{
|
||||
ExtractFullPath = true,
|
||||
Overwrite = true,
|
||||
});
|
||||
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
if (includeDebug) System.Console.Error.WriteLine(ex);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
using System.IO;
|
||||
using SabreTools.Serialization.Interfaces;
|
||||
#if NET462_OR_GREATER || NETCOREAPP
|
||||
using SharpCompress.Archives;
|
||||
using SharpCompress.Archives.Tar;
|
||||
#endif
|
||||
|
||||
namespace SabreTools.Serialization.Wrappers
|
||||
{
|
||||
@@ -7,7 +12,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
/// any actual parsing. It is used as a placeholder for
|
||||
/// types that typically do not have models.
|
||||
/// </summary>
|
||||
public class TapeArchive : WrapperBase
|
||||
public class TapeArchive : WrapperBase, IExtractable
|
||||
{
|
||||
#region Descriptive Properties
|
||||
|
||||
@@ -16,8 +21,43 @@ 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)
|
||||
{
|
||||
_filename = filename;
|
||||
_stream = File.Open(_filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new instance of the wrapper from a Stream
|
||||
/// </summary>
|
||||
public TapeArchive(Stream stream)
|
||||
{
|
||||
_filename = null;
|
||||
_stream = stream;
|
||||
|
||||
if (stream is FileStream fs)
|
||||
_filename = fs.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a tape archive (or derived format) from a byte array and offset
|
||||
/// </summary>
|
||||
@@ -50,7 +90,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
return new TapeArchive();
|
||||
return new TapeArchive(data);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -63,5 +103,73 @@ namespace SabreTools.Serialization.Wrappers
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extraction
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Extract(string outputDirectory, bool includeDebug)
|
||||
{
|
||||
if (_stream == null || !_stream.CanRead)
|
||||
return false;
|
||||
|
||||
#if NET462_OR_GREATER || NETCOREAPP
|
||||
try
|
||||
{
|
||||
var tarFile = TarArchive.Open(_stream);
|
||||
|
||||
// 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);
|
||||
|
||||
foreach (var entry in tarFile.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 we have a partial entry 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
using System.IO;
|
||||
using SabreTools.Serialization.Interfaces;
|
||||
#if NET462_OR_GREATER || NETCOREAPP
|
||||
using SharpCompress.Compressors.Xz;
|
||||
#endif
|
||||
|
||||
namespace SabreTools.Serialization.Wrappers
|
||||
{
|
||||
@@ -7,7 +11,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
/// any actual parsing. It is used as a placeholder for
|
||||
/// types that typically do not have models.
|
||||
/// </summary>
|
||||
public class XZ : WrapperBase
|
||||
public class XZ : WrapperBase, IExtractable
|
||||
{
|
||||
#region Descriptive Properties
|
||||
|
||||
@@ -16,8 +20,43 @@ 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)
|
||||
{
|
||||
_filename = filename;
|
||||
_stream = File.Open(_filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new instance of the wrapper from a Stream
|
||||
/// </summary>
|
||||
public XZ(Stream stream)
|
||||
{
|
||||
_filename = null;
|
||||
_stream = stream;
|
||||
|
||||
if (stream is FileStream fs)
|
||||
_filename = fs.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a XZ archive from a byte array and offset
|
||||
/// </summary>
|
||||
@@ -50,7 +89,7 @@ namespace SabreTools.Serialization.Wrappers
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
return new XZ();
|
||||
return new XZ(data);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -63,5 +102,51 @@ namespace SabreTools.Serialization.Wrappers
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extraction
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Extract(string outDir, bool includeDebug)
|
||||
{
|
||||
#if NET462_OR_GREATER || NETCOREAPP
|
||||
if (_stream == null || !_stream.CanRead)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
// Try opening the stream
|
||||
using var xzFile = new XZStream(_stream);
|
||||
|
||||
// Ensure directory separators are consistent
|
||||
string filename = System.Guid.NewGuid().ToString();
|
||||
if (Path.DirectorySeparatorChar == '\\')
|
||||
filename = filename.Replace('/', '\\');
|
||||
else if (Path.DirectorySeparatorChar == '/')
|
||||
filename = filename.Replace('\\', '/');
|
||||
|
||||
// Ensure the full output directory exists
|
||||
filename = Path.Combine(outDir, filename);
|
||||
var directoryName = Path.GetDirectoryName(filename);
|
||||
if (directoryName != null && !Directory.Exists(directoryName))
|
||||
Directory.CreateDirectory(directoryName);
|
||||
|
||||
// Extract the file
|
||||
using FileStream fs = File.OpenWrite(filename);
|
||||
xzFile.CopyTo(fs);
|
||||
fs.Flush();
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
if (includeDebug) System.Console.Error.WriteLine(ex);
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user