diff --git a/SabreTools.Serialization/SabreTools.Serialization.csproj b/SabreTools.Serialization/SabreTools.Serialization.csproj
index edc50bc0..0a0c3f89 100644
--- a/SabreTools.Serialization/SabreTools.Serialization.csproj
+++ b/SabreTools.Serialization/SabreTools.Serialization.csproj
@@ -41,6 +41,7 @@
+
\ No newline at end of file
diff --git a/SabreTools.Serialization/Wrappers/BZip2.cs b/SabreTools.Serialization/Wrappers/BZip2.cs
index aae6d6d6..0664dec1 100644
--- a/SabreTools.Serialization/Wrappers/BZip2.cs
+++ b/SabreTools.Serialization/Wrappers/BZip2.cs
@@ -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.
///
- public class BZip2 : WrapperBase
+ public class BZip2 : WrapperBase, IExtractable
{
#region Descriptive Properties
@@ -16,8 +19,43 @@ 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)
+ {
+ _filename = filename;
+ _stream = File.Open(_filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+ }
+
+ ///
+ /// Construct a new instance of the wrapper from a Stream
+ ///
+ public BZip2(Stream stream)
+ {
+ _filename = null;
+ _stream = stream;
+
+ if (stream is FileStream fs)
+ _filename = fs.Name;
+ }
+
///
/// Create a BZip2 archive from a byte array and offset
///
@@ -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
///
- public override string ExportJSON() => throw new System.NotImplementedException();
+ public override string ExportJSON() => throw new NotImplementedException();
#endif
#endregion
+
+ #region Extraction
+
+ ///
+ 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
}
}
diff --git a/SabreTools.Serialization/Wrappers/GZip.cs b/SabreTools.Serialization/Wrappers/GZip.cs
index b3cb95d2..9c910deb 100644
--- a/SabreTools.Serialization/Wrappers/GZip.cs
+++ b/SabreTools.Serialization/Wrappers/GZip.cs
@@ -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.
///
- public class GZip : WrapperBase
+ public class GZip : WrapperBase, IExtractable
{
#region Descriptive Properties
@@ -16,8 +19,43 @@ 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)
+ {
+ _filename = filename;
+ _stream = File.Open(_filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+ }
+
+ ///
+ /// Construct a new instance of the wrapper from a Stream
+ ///
+ public GZip(Stream stream)
+ {
+ _filename = null;
+ _stream = stream;
+
+ if (stream is FileStream fs)
+ _filename = fs.Name;
+ }
+
///
/// Create a GZip archive from a byte array and offset
///
@@ -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
///
- public override string ExportJSON() => throw new System.NotImplementedException();
+ public override string ExportJSON() => throw new NotImplementedException();
#endif
#endregion
+
+ #region Extraction
+
+ ///
+ 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
}
}
diff --git a/SabreTools.Serialization/Wrappers/RAR.cs b/SabreTools.Serialization/Wrappers/RAR.cs
index 6d914c4d..7951d8d0 100644
--- a/SabreTools.Serialization/Wrappers/RAR.cs
+++ b/SabreTools.Serialization/Wrappers/RAR.cs
@@ -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.
///
- public class RAR : WrapperBase
+ public class RAR : WrapperBase, IExtractable
{
#region Descriptive Properties
@@ -16,8 +23,43 @@ 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)
+ {
+ _filename = filename;
+ _stream = File.Open(_filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+ }
+
+ ///
+ /// Construct a new instance of the wrapper from a Stream
+ ///
+ public RAR(Stream stream)
+ {
+ _filename = null;
+ _stream = stream;
+
+ if (stream is FileStream fs)
+ _filename = fs.Name;
+ }
+
///
/// Create a RAR archive (or derived format) from a byte array and offset
///
@@ -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
+
+ ///
+ public bool Extract(string outputDirectory, bool includeDebug)
+ => Extract(outputDirectory, lookForHeader: false, includeDebug);
+
+ ///
+ 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
+
+ ///
+ /// 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.
+ ///
+ 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;
+ }
+
+ ///
+ /// 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.
+ ///
+ 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
}
}
diff --git a/SabreTools.Serialization/Wrappers/SevenZip.cs b/SabreTools.Serialization/Wrappers/SevenZip.cs
index dbacb9b1..21421d35 100644
--- a/SabreTools.Serialization/Wrappers/SevenZip.cs
+++ b/SabreTools.Serialization/Wrappers/SevenZip.cs
@@ -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.
///
- public class SevenZip : WrapperBase
+ public class SevenZip : WrapperBase, IExtractable
{
#region Descriptive Properties
@@ -16,8 +23,43 @@ 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)
+ {
+ _filename = filename;
+ _stream = File.Open(_filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+ }
+
+ ///
+ /// Construct a new instance of the wrapper from a Stream
+ ///
+ public SevenZip(Stream stream)
+ {
+ _filename = null;
+ _stream = stream;
+
+ if (stream is FileStream fs)
+ _filename = fs.Name;
+ }
+
///
/// Create a SevenZip archive (or derived format) from a byte array and offset
///
@@ -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
+
+ ///
+ public bool Extract(string outputDirectory, bool includeDebug)
+ => Extract(outputDirectory, lookForHeader: false, includeDebug);
+
+ ///
+ 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
+ ///
+ /// 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.
+ ///
+ 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;
+ }
+
+ ///
+ /// 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.
+ ///
+ 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
}
}
diff --git a/SabreTools.Serialization/Wrappers/TapeArchive.cs b/SabreTools.Serialization/Wrappers/TapeArchive.cs
index fd39c125..c19448d4 100644
--- a/SabreTools.Serialization/Wrappers/TapeArchive.cs
+++ b/SabreTools.Serialization/Wrappers/TapeArchive.cs
@@ -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.
///
- public class TapeArchive : WrapperBase
+ public class TapeArchive : WrapperBase, IExtractable
{
#region Descriptive Properties
@@ -16,8 +21,43 @@ 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)
+ {
+ _filename = filename;
+ _stream = File.Open(_filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+ }
+
+ ///
+ /// Construct a new instance of the wrapper from a Stream
+ ///
+ public TapeArchive(Stream stream)
+ {
+ _filename = null;
+ _stream = stream;
+
+ if (stream is FileStream fs)
+ _filename = fs.Name;
+ }
+
///
/// Create a tape archive (or derived format) from a byte array and offset
///
@@ -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
+
+ ///
+ 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
}
}
diff --git a/SabreTools.Serialization/Wrappers/XZ.cs b/SabreTools.Serialization/Wrappers/XZ.cs
index 0cd7a7da..5e4944db 100644
--- a/SabreTools.Serialization/Wrappers/XZ.cs
+++ b/SabreTools.Serialization/Wrappers/XZ.cs
@@ -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.
///
- public class XZ : WrapperBase
+ public class XZ : WrapperBase, IExtractable
{
#region Descriptive Properties
@@ -16,8 +20,43 @@ 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)
+ {
+ _filename = filename;
+ _stream = File.Open(_filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+ }
+
+ ///
+ /// Construct a new instance of the wrapper from a Stream
+ ///
+ public XZ(Stream stream)
+ {
+ _filename = null;
+ _stream = stream;
+
+ if (stream is FileStream fs)
+ _filename = fs.Name;
+ }
+
///
/// Create a XZ archive from a byte array and offset
///
@@ -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
+
+ ///
+ 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
}
}