Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<PackageVersion Include="Google.FlatBuffers" Version="23.5.26" /> <!-- last version with .NET Standard 2.0 support -->
<PackageVersion Include="ImGui.NET" Version="1.90.6.1" />
<PackageVersion Include="JunitXml.TestLogger" Version="3.1.12" />
<PackageVersion Include="LibArchive.Net" Version="0.2.0-alpha.0.82" />
<PackageVersion Include="Magick.NET-Q8-AnyCPU" Version="14.8.2" />
<PackageVersion Include="Menees.Analyzers" Version="3.2.2" />
<PackageVersion Include="Meziantou.Analyzer" Version="2.0.206" />
Expand Down
6 changes: 6 additions & 0 deletions NuGet.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="LocalPackages" value="./References" />
</packageSources>
</configuration>
Binary file added References/LibArchive.Net.0.2.0-alpha.0.82.nupkg
Binary file not shown.
1 change: 1 addition & 0 deletions src/BizHawk.Common/BizHawk.Common.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<PackageReference Include="System.ComponentModel.Annotations" />
<PackageReference Include="System.Memory" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" />
<PackageReference Include="LibArchive.Net" />
</ItemGroup>
<ItemGroup>
<Analyzer Include="$(MSBuildProjectDirectory)/../../References/BizHawk.SrcGen.VersionInfo.dll" />
Expand Down
64 changes: 64 additions & 0 deletions src/BizHawk.Common/LibarchiveArchiveFile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;

using BizHawk.Common;

using LibArchive.Net;

namespace BizHawk.Client.Common
{
/// <see cref="LibarchiveDearchivalMethod"/>
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<HawkArchiveFileItem>? Scan()
=> AllEntries.Select(static (tuple, i) => new HawkArchiveFileItem(
tuple.Entry.Name,
size: tuple.Entry.LengthBytes ?? 0,
index: i,
archiveIndex: tuple.ArchiveIndex)).ToList();
}
}
71 changes: 71 additions & 0 deletions src/BizHawk.Common/LibarchiveDearchivalMethod.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System.Collections.Generic;
using System.IO;

using BizHawk.Common;

namespace BizHawk.Client.Common
{
/// <summary>A <see cref="IFileDearchivalMethod{T}">dearchival method</see> for <see cref="HawkFile"/> implemented using <c>libarchive</c> (via <c>LibArchive.Net</c> bindings).</summary>
public class LibarchiveDearchivalMethod : IFileDearchivalMethod<LibarchiveArchiveFile>
{
public static readonly LibarchiveDearchivalMethod Instance = new();

public IReadOnlyCollection<string> 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);
}
}
46 changes: 32 additions & 14 deletions src/BizHawk.Tests.Client.Common/dearchive/DearchivalTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.IO;

using BizHawk.Client.Common;
using BizHawk.Common;
Expand All @@ -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<object?[]> TestCases { get; } = new[]
{
new object?[] { "m3_scy_change.7z", true },
new object?[] { "m3_scy_change.gb.gz", true },
new object?[] { "m3_scy_change.rar", true },
new object?[] { "m3_scy_change.bsdtar.tar", true },
new object?[] { "m3_scy_change.gnutar.tar", true },
new object?[] { "m3_scy_change.zip", true },
};

private readonly Lazy<byte[]> _rom = new(static () => ReflectionCache.EmbeddedResourceStream(EMBED_GROUP + "m3_scy_change.gb").ReadAllBytes());
Expand All @@ -30,16 +31,33 @@ private static readonly (string Filename, bool HasSharpCompressSupport)[] TestCa
public void SanityCheck() => Assert.AreEqual("SHA1:70DCA8E791878BDD32426391E4233EA52B47CDD1", SHA1Checksum.ComputePrefixedHex(Rom));
#pragma warning restore BHI1600

[DynamicData(nameof(TestCases))]
[TestMethod]
public void TestLibarchive(string filename, bool hasSharpCompressSupport)
{
var sc = LibarchiveDearchivalMethod.Instance;
var archive = ReflectionCache.EmbeddedResourceStream(EMBED_GROUP + filename);
Assert.IsTrue(sc.CheckSignature(archive, filename), $"{filename} is an archive, but wasn't detected as such"); // puts the seek pos of the Stream param back where it was (in this case at the start)
using var af = sc.Construct(archive);
var items = af.Scan();
Assert.IsNotNull(items, $"{filename} contains 1 file, but it couldn't be enumerated correctly");
Assert.AreEqual(1, items!.Count, $"{filename} contains 1 file, but was detected as containing {items.Count} files");
using MemoryStream ms = new((int) items[0].Size);
af.ExtractFile(items[0].ArchiveIndex, ms);
ms.Seek(0L, SeekOrigin.Begin);
CollectionAssert.AreEqual(Rom, ms.ReadAllBytes(), $"the file extracted from {filename} doesn't match the uncompressed file");
}

[DynamicData(nameof(TestCases))]
[TestMethod]
public void TestSharpCompress()
public void TestSharpCompress(string filename, bool hasSharpCompressSupport)
{
if (!hasSharpCompressSupport) return;
var sc = SharpCompressDearchivalMethod.Instance;
foreach (var filename in TestCases.Where(testCase => testCase.HasSharpCompressSupport)
.Select(testCase => testCase.Filename))
{
/*scope*/{
var archive = ReflectionCache.EmbeddedResourceStream(EMBED_GROUP + filename);
Assert.IsTrue(sc.CheckSignature(archive, filename), $"{filename} is an archive, but wasn't detected as such"); // puts the seek pos of the Stream param back where it was (in this case at the start)
var af = sc.Construct(archive);
using var af = sc.Construct(archive);
var items = af.Scan();
Assert.IsNotNull(items, $"{filename} contains 1 file, but it couldn't be enumerated correctly");
Assert.AreEqual(1, items!.Count, $"{filename} contains 1 file, but was detected as containing {items.Count} files");
Expand Down
Loading