diff --git a/Directory.Packages.props b/Directory.Packages.props
index a6e9f40d89c..c1b4f1a19b0 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -6,6 +6,7 @@
+
diff --git a/NuGet.config b/NuGet.config
new file mode 100644
index 00000000000..282ecf659e8
--- /dev/null
+++ b/NuGet.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/References/LibArchive.Net.0.2.0-alpha.0.82.nupkg b/References/LibArchive.Net.0.2.0-alpha.0.82.nupkg
new file mode 100644
index 00000000000..1f16759114d
Binary files /dev/null and b/References/LibArchive.Net.0.2.0-alpha.0.82.nupkg differ
diff --git a/src/BizHawk.Common/BizHawk.Common.csproj b/src/BizHawk.Common/BizHawk.Common.csproj
index a110093bce4..967ca162778 100644
--- a/src/BizHawk.Common/BizHawk.Common.csproj
+++ b/src/BizHawk.Common/BizHawk.Common.csproj
@@ -15,6 +15,7 @@
+
diff --git a/src/BizHawk.Common/LibarchiveArchiveFile.cs b/src/BizHawk.Common/LibarchiveArchiveFile.cs
new file mode 100644
index 00000000000..b5c68b90e0e
--- /dev/null
+++ b/src/BizHawk.Common/LibarchiveArchiveFile.cs
@@ -0,0 +1,64 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+using BizHawk.Common;
+
+using LibArchive.Net;
+
+namespace BizHawk.Client.Common
+{
+ ///
+ public sealed class LibarchiveArchiveFile : IHawkArchiveFile
+ {
+ private LibArchiveReader? _handle;
+
+ private FileInfo? _tempOnDisk;
+
+ private (int ArchiveIndex, LibArchiveReader.Entry Entry)[]? field = null;
+
+ private (int ArchiveIndex, LibArchiveReader.Entry Entry)[] AllEntries
+ => field ??= _handle?.Entries().Index().OrderBy(static tuple => tuple.Item.Name).ToArray()
+ ?? throw new ObjectDisposedException(nameof(LibarchiveArchiveFile));
+
+ public LibarchiveArchiveFile(string path)
+ => _handle = new(path);
+
+ public LibarchiveArchiveFile(Stream fileStream)
+ {
+ _tempOnDisk = new(TempFileManager.GetTempFilename("dearchive"));
+ using (FileStream fsCopy = new(_tempOnDisk.FullName, FileMode.Create)) fileStream.CopyTo(fsCopy);
+ try
+ {
+ _handle = new(_tempOnDisk.FullName);
+ }
+ catch (Exception e)
+ {
+ _tempOnDisk.Delete();
+ Console.WriteLine(e);
+ throw;
+ }
+ }
+
+ public void Dispose()
+ {
+ _handle?.Dispose();
+ _handle = null;
+ _tempOnDisk?.Delete();
+ _tempOnDisk = null;
+ }
+
+ public void ExtractFile(int index, Stream stream)
+ {
+ using var entryStream = AllEntries[index].Entry.Stream;
+ entryStream.CopyTo(stream);
+ }
+
+ public List? Scan()
+ => AllEntries.Select(static (tuple, i) => new HawkArchiveFileItem(
+ tuple.Entry.Name,
+ size: tuple.Entry.LengthBytes ?? 0,
+ index: i,
+ archiveIndex: tuple.ArchiveIndex)).ToList();
+ }
+}
diff --git a/src/BizHawk.Common/LibarchiveDearchivalMethod.cs b/src/BizHawk.Common/LibarchiveDearchivalMethod.cs
new file mode 100644
index 00000000000..9e8ba53c784
--- /dev/null
+++ b/src/BizHawk.Common/LibarchiveDearchivalMethod.cs
@@ -0,0 +1,71 @@
+using System.Collections.Generic;
+using System.IO;
+
+using BizHawk.Common;
+
+namespace BizHawk.Client.Common
+{
+ /// A dearchival method for implemented using libarchive (via LibArchive.Net bindings).
+ public class LibarchiveDearchivalMethod : IFileDearchivalMethod
+ {
+ public static readonly LibarchiveDearchivalMethod Instance = new();
+
+ public IReadOnlyCollection AllowedArchiveExtensions { get; } = [
+ ".7z",
+ ".gz",
+ ".rar",
+ ".tar",
+ /*.tar*/".bz2", ".tb2", ".tbz", ".tbz2", ".tz2",
+ /*.tar.gz,*/ ".taz", ".tgz",
+ /*.tar*/".lz",
+ ".zip",
+ ];
+
+ private LibarchiveDearchivalMethod() {}
+
+ public bool CheckSignature(string fileName)
+ {
+ LibarchiveArchiveFile? file = null;
+ try
+ {
+ file = new(fileName);
+ return true;
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ finally
+ {
+ file?.Dispose();
+ }
+ }
+
+ public bool CheckSignature(Stream fileStream, string? filenameHint)
+ {
+ if (!fileStream.CanRead || !fileStream.CanSeek) return false;
+ var initialPosition = fileStream.Position;
+ LibarchiveArchiveFile? file = null;
+ try
+ {
+ file = new(fileStream);
+ return true;
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ finally
+ {
+ file?.Dispose();
+ fileStream.Seek(initialPosition, SeekOrigin.Begin);
+ }
+ }
+
+ public LibarchiveArchiveFile Construct(string path)
+ => new(path);
+
+ public LibarchiveArchiveFile Construct(Stream fileStream)
+ => new(fileStream);
+ }
+}
diff --git a/src/BizHawk.Tests.Client.Common/dearchive/DearchivalTests.cs b/src/BizHawk.Tests.Client.Common/dearchive/DearchivalTests.cs
index 7526d88fef9..1d6aae04ca8 100644
--- a/src/BizHawk.Tests.Client.Common/dearchive/DearchivalTests.cs
+++ b/src/BizHawk.Tests.Client.Common/dearchive/DearchivalTests.cs
@@ -1,5 +1,5 @@
-using System.IO;
-using System.Linq;
+using System.Collections.Generic;
+using System.IO;
using BizHawk.Client.Common;
using BizHawk.Common;
@@ -12,13 +12,14 @@ public class DearchivalTests
{
private const string EMBED_GROUP = "data.dearchive.";
- private static readonly (string Filename, bool HasSharpCompressSupport)[] TestCases = {
- ("m3_scy_change.7z", true),
- ("m3_scy_change.gb.gz", true),
- ("m3_scy_change.rar", true),
- ("m3_scy_change.bsdtar.tar", true),
- ("m3_scy_change.gnutar.tar", true),
- ("m3_scy_change.zip", true),
+ private static IEnumerable