Skip to content

Commit 5717e4a

Browse files
committed
Add LibarchiveDearchivalMethod
1 parent 6df25f9 commit 5717e4a

File tree

5 files changed

+154
-0
lines changed

5 files changed

+154
-0
lines changed

Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
<PackageVersion Include="Google.FlatBuffers" Version="23.5.26" /> <!-- last version with .NET Standard 2.0 support -->
77
<PackageVersion Include="ImGui.NET" Version="1.90.6.1" />
88
<PackageVersion Include="JunitXml.TestLogger" Version="3.1.12" />
9+
<PackageVersion Include="LibArchive.Net" Version="0.2.0-alpha.0.82" />
910
<PackageVersion Include="Magick.NET-Q8-AnyCPU" Version="14.8.2" />
1011
<PackageVersion Include="Menees.Analyzers" Version="3.2.2" />
1112
<PackageVersion Include="Meziantou.Analyzer" Version="2.0.206" />

src/BizHawk.Common/BizHawk.Common.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<PackageReference Include="System.ComponentModel.Annotations" />
1616
<PackageReference Include="System.Memory" />
1717
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" />
18+
<PackageReference Include="LibArchive.Net" />
1819
</ItemGroup>
1920
<ItemGroup>
2021
<Analyzer Include="$(MSBuildProjectDirectory)/../../References/BizHawk.SrcGen.VersionInfo.dll" />
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System.Collections.Generic;
2+
using System.IO;
3+
using System.Linq;
4+
5+
using BizHawk.Common;
6+
7+
using LibArchive.Net;
8+
9+
namespace BizHawk.Client.Common
10+
{
11+
/// <see cref="LibarchiveDearchivalMethod"/>
12+
public sealed class LibarchiveArchiveFile : IHawkArchiveFile
13+
{
14+
private LibArchiveReader? _handle;
15+
16+
private FileInfo? _tempOnDisk;
17+
18+
private (int ArchiveIndex, LibArchiveReader.Entry Entry)[]? field = null;
19+
20+
private (int ArchiveIndex, LibArchiveReader.Entry Entry)[] AllEntries
21+
=> field ??= _handle?.Entries().Index().OrderBy(static tuple => tuple.Item.Name).ToArray()
22+
?? throw new ObjectDisposedException(nameof(LibarchiveArchiveFile));
23+
24+
public LibarchiveArchiveFile(string path)
25+
=> _handle = new(path);
26+
27+
public LibarchiveArchiveFile(Stream fileStream)
28+
{
29+
_tempOnDisk = new(TempFileManager.GetTempFilename("dearchive"));
30+
using (FileStream fsCopy = new(_tempOnDisk.FullName, FileMode.Create)) fileStream.CopyTo(fsCopy);
31+
try
32+
{
33+
_handle = new(_tempOnDisk.FullName);
34+
}
35+
catch (Exception e)
36+
{
37+
_tempOnDisk.Delete();
38+
Console.WriteLine(e);
39+
throw;
40+
}
41+
}
42+
43+
public void Dispose()
44+
{
45+
_handle?.Dispose();
46+
_handle = null;
47+
_tempOnDisk?.Delete();
48+
_tempOnDisk = null;
49+
}
50+
51+
public void ExtractFile(int index, Stream stream)
52+
{
53+
using var entryStream = AllEntries[index].Entry.Stream;
54+
entryStream.CopyTo(stream);
55+
}
56+
57+
public List<HawkArchiveFileItem>? Scan()
58+
=> AllEntries.Select(static (tuple, i) => new HawkArchiveFileItem(
59+
tuple.Entry.Name,
60+
size: tuple.Entry.LengthBytes ?? 0,
61+
index: i,
62+
archiveIndex: tuple.ArchiveIndex)).ToList();
63+
}
64+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
using System.Collections.Generic;
2+
using System.IO;
3+
4+
using BizHawk.Common;
5+
6+
namespace BizHawk.Client.Common
7+
{
8+
/// <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>
9+
public class LibarchiveDearchivalMethod : IFileDearchivalMethod<LibarchiveArchiveFile>
10+
{
11+
public static readonly LibarchiveDearchivalMethod Instance = new();
12+
13+
public IReadOnlyCollection<string> AllowedArchiveExtensions { get; } = [
14+
".7z",
15+
".gz",
16+
".rar",
17+
".tar",
18+
/*.tar*/".bz2", ".tb2", ".tbz", ".tbz2", ".tz2",
19+
/*.tar.gz,*/ ".taz", ".tgz",
20+
/*.tar*/".lz",
21+
".zip",
22+
];
23+
24+
private LibarchiveDearchivalMethod() {}
25+
26+
public bool CheckSignature(string fileName)
27+
{
28+
LibarchiveArchiveFile? file = null;
29+
try
30+
{
31+
file = new(fileName);
32+
return true;
33+
}
34+
catch (Exception)
35+
{
36+
return false;
37+
}
38+
finally
39+
{
40+
file?.Dispose();
41+
}
42+
}
43+
44+
public bool CheckSignature(Stream fileStream, string? filenameHint)
45+
{
46+
if (!fileStream.CanRead || !fileStream.CanSeek) return false;
47+
var initialPosition = fileStream.Position;
48+
LibarchiveArchiveFile? file = null;
49+
try
50+
{
51+
file = new(fileStream);
52+
return true;
53+
}
54+
catch (Exception)
55+
{
56+
return false;
57+
}
58+
finally
59+
{
60+
file?.Dispose();
61+
fileStream.Seek(initialPosition, SeekOrigin.Begin);
62+
}
63+
}
64+
65+
public LibarchiveArchiveFile Construct(string path)
66+
=> new(path);
67+
68+
public LibarchiveArchiveFile Construct(Stream fileStream)
69+
=> new(fileStream);
70+
}
71+
}

src/BizHawk.Tests.Client.Common/dearchive/DearchivalTests.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,23 @@ public class DearchivalTests
3131
public void SanityCheck() => Assert.AreEqual("SHA1:70DCA8E791878BDD32426391E4233EA52B47CDD1", SHA1Checksum.ComputePrefixedHex(Rom));
3232
#pragma warning restore BHI1600
3333

34+
[DynamicData(nameof(TestCases))]
35+
[TestMethod]
36+
public void TestLibarchive(string filename, bool hasSharpCompressSupport)
37+
{
38+
var sc = LibarchiveDearchivalMethod.Instance;
39+
var archive = ReflectionCache.EmbeddedResourceStream(EMBED_GROUP + filename);
40+
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)
41+
using var af = sc.Construct(archive);
42+
var items = af.Scan();
43+
Assert.IsNotNull(items, $"{filename} contains 1 file, but it couldn't be enumerated correctly");
44+
Assert.AreEqual(1, items!.Count, $"{filename} contains 1 file, but was detected as containing {items.Count} files");
45+
using MemoryStream ms = new((int) items[0].Size);
46+
af.ExtractFile(items[0].ArchiveIndex, ms);
47+
ms.Seek(0L, SeekOrigin.Begin);
48+
CollectionAssert.AreEqual(Rom, ms.ReadAllBytes(), $"the file extracted from {filename} doesn't match the uncompressed file");
49+
}
50+
3451
[DynamicData(nameof(TestCases))]
3552
[TestMethod]
3653
public void TestSharpCompress(string filename, bool hasSharpCompressSupport)

0 commit comments

Comments
 (0)