2020-09-04 21:00:29 +00:00
|
|
|
|
using System;
|
2020-09-29 12:35:31 +00:00
|
|
|
|
using System.IO;
|
2020-09-04 21:00:29 +00:00
|
|
|
|
using System.IO.Compression;
|
2020-09-08 22:15:33 +00:00
|
|
|
|
using System.Linq;
|
2020-09-04 21:00:29 +00:00
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using Wabbajack.Common;
|
2020-09-08 22:15:33 +00:00
|
|
|
|
using Wabbajack.Lib.Downloaders;
|
|
|
|
|
using Wabbajack.Lib.NexusApi;
|
2020-09-04 21:00:29 +00:00
|
|
|
|
using Xunit;
|
2020-09-09 02:34:53 +00:00
|
|
|
|
using Xunit.Abstractions;
|
2020-09-04 21:00:29 +00:00
|
|
|
|
|
|
|
|
|
namespace Wabbajack.VirtualFileSystem.Test
|
|
|
|
|
{
|
2020-09-09 02:34:53 +00:00
|
|
|
|
public class FileExtractorTests : IAsyncLifetime
|
2020-09-04 21:00:29 +00:00
|
|
|
|
{
|
2020-09-09 02:34:53 +00:00
|
|
|
|
private ITestOutputHelper _helper;
|
|
|
|
|
private IDisposable _unsub;
|
|
|
|
|
|
|
|
|
|
public FileExtractorTests(ITestOutputHelper helper)
|
|
|
|
|
{
|
|
|
|
|
_helper = helper;
|
2020-09-09 04:06:15 +00:00
|
|
|
|
_unsub = Utils.LogMessages.Subscribe(f =>
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_helper.WriteLine(f.ShortDescription);
|
|
|
|
|
}
|
2020-10-01 03:50:09 +00:00
|
|
|
|
catch (Exception)
|
2020-09-09 04:06:15 +00:00
|
|
|
|
{
|
|
|
|
|
// ignored
|
|
|
|
|
}
|
|
|
|
|
});
|
2020-09-09 02:34:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task InitializeAsync()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task DisposeAsync()
|
|
|
|
|
{
|
|
|
|
|
_unsub.Dispose();
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-04 21:00:29 +00:00
|
|
|
|
[Fact]
|
|
|
|
|
public async Task CanGatherDataFromZipFiles()
|
|
|
|
|
{
|
|
|
|
|
await using var temp = await TempFolder.Create();
|
|
|
|
|
await using var archive = new TempFile();
|
|
|
|
|
for (int i = 0; i < 10; i ++)
|
|
|
|
|
{
|
|
|
|
|
await WriteRandomData(temp.Dir.Combine($"{i}.bin"), _rng.Next(10, 1024));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await ZipUpFolder(temp.Dir, archive.Path, false);
|
|
|
|
|
|
|
|
|
|
var results = await FileExtractor2.GatheringExtract(new NativeFileStreamFactory(archive.Path),
|
|
|
|
|
_ => true,
|
|
|
|
|
async (path, sfn) =>
|
2020-09-11 01:22:07 +00:00
|
|
|
|
{
|
|
|
|
|
await using var s = await sfn.GetStream();
|
|
|
|
|
return await s.xxHashAsync();
|
|
|
|
|
});
|
2020-09-04 21:00:29 +00:00
|
|
|
|
|
|
|
|
|
Assert.Equal(10, results.Count);
|
|
|
|
|
foreach (var (path, hash) in results)
|
|
|
|
|
{
|
|
|
|
|
Assert.Equal(await temp.Dir.Combine(path).FileHashAsync(), hash);
|
|
|
|
|
}
|
2020-09-08 22:15:33 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-09-29 12:35:31 +00:00
|
|
|
|
[Fact]
|
|
|
|
|
public async Task SmallFilesShouldntCrash()
|
|
|
|
|
{
|
|
|
|
|
await using var temp = await TempFolder.Create();
|
|
|
|
|
await using var archive = new TempFile();
|
|
|
|
|
for (int i = 0; i < 1; i ++)
|
|
|
|
|
{
|
|
|
|
|
await WriteRandomData(temp.Dir.Combine($"{i}.bin"), _rng.Next(10, 10));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await ZipUpFolder(temp.Dir, archive.Path, false);
|
|
|
|
|
|
|
|
|
|
var results = await FileExtractor2.GatheringExtract(new NativeFileStreamFactory(archive.Path),
|
|
|
|
|
_ => true,
|
|
|
|
|
async (path, sfn) =>
|
|
|
|
|
{
|
|
|
|
|
await using var s = await sfn.GetStream();
|
|
|
|
|
return await s.xxHashAsync();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Assert.Equal(1, results.Count);
|
|
|
|
|
foreach (var (path, hash) in results)
|
|
|
|
|
{
|
|
|
|
|
Assert.Equal(await temp.Dir.Combine(path).FileHashAsync(), hash);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-10-06 02:52:33 +00:00
|
|
|
|
|
|
|
|
|
/* Takes to long to run in CI, enable when needed for verification
|
2020-09-29 12:35:31 +00:00
|
|
|
|
|
2020-10-05 22:12:21 +00:00
|
|
|
|
[Fact]
|
|
|
|
|
public async Task MissingFileFromArchiveShouldBeFound()
|
|
|
|
|
{
|
|
|
|
|
FileExtractor2.FavorPerfOverRAM = true;
|
|
|
|
|
|
|
|
|
|
// From a bug in 2.3.0.1
|
|
|
|
|
var src = await DownloadMod(Game.SkyrimSpecialEdition, 21166, 136259);
|
|
|
|
|
|
|
|
|
|
var results = await FileExtractor2.GatheringExtract(new NativeFileStreamFactory(src),
|
|
|
|
|
f => true,
|
|
|
|
|
async (p, s) =>
|
|
|
|
|
{
|
|
|
|
|
await using var stream = await s.GetStream();
|
|
|
|
|
return stream.Length;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Assert.NotEmpty(results);
|
|
|
|
|
}
|
2020-10-06 02:52:33 +00:00
|
|
|
|
*/
|
2020-09-29 12:35:31 +00:00
|
|
|
|
|
2020-09-11 01:22:07 +00:00
|
|
|
|
[Fact]
|
|
|
|
|
public async Task CanExtractEmptyFiles()
|
|
|
|
|
{
|
|
|
|
|
await using var temp = await TempFolder.Create();
|
|
|
|
|
await using var archive = new TempFile();
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < 1; i ++)
|
|
|
|
|
{
|
|
|
|
|
await WriteRandomData(temp.Dir.Combine($"{i}.bin"), _rng.Next(10, 1024));
|
|
|
|
|
}
|
|
|
|
|
await (await temp.Dir.Combine("empty.txt").Create()).DisposeAsync();
|
|
|
|
|
|
|
|
|
|
await ZipUpFolder(temp.Dir, archive.Path, false);
|
|
|
|
|
|
|
|
|
|
var results = await FileExtractor2.GatheringExtract(new NativeFileStreamFactory(archive.Path),
|
|
|
|
|
_ => true,
|
|
|
|
|
async (path, sfn) =>
|
|
|
|
|
{
|
|
|
|
|
await using var s = await sfn.GetStream();
|
|
|
|
|
return await s.xxHashAsync();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Assert.Equal(2, results.Count);
|
|
|
|
|
foreach (var (path, hash) in results)
|
|
|
|
|
{
|
|
|
|
|
Assert.Equal(await temp.Dir.Combine(path).FileHashAsync(), hash);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-08 22:15:33 +00:00
|
|
|
|
private static Extension OMODExtension = new Extension(".omod");
|
|
|
|
|
private static Extension CRCExtension = new Extension(".crc");
|
2020-09-04 21:00:29 +00:00
|
|
|
|
|
2020-09-08 22:15:33 +00:00
|
|
|
|
[Fact]
|
|
|
|
|
public async Task CanGatherDataFromOMODFiles()
|
|
|
|
|
{
|
|
|
|
|
var src = await DownloadMod(Game.Oblivion, 18498);
|
2020-09-04 21:00:29 +00:00
|
|
|
|
|
2020-09-08 22:15:33 +00:00
|
|
|
|
await FileExtractor2.GatheringExtract(new NativeFileStreamFactory(src),
|
|
|
|
|
p => p.Extension == OMODExtension, async (path, sfn) =>
|
|
|
|
|
{
|
|
|
|
|
await FileExtractor2.GatheringExtract(sfn, _ => true, async (ipath, isfn) => {
|
2020-09-29 12:35:31 +00:00
|
|
|
|
// We shouldn't have any .crc files because this file should be recognized as a OMOD and extracted correctly
|
|
|
|
|
Assert.NotEqual(CRCExtension, ipath.Extension);
|
|
|
|
|
return 0;
|
2020-09-08 22:15:33 +00:00
|
|
|
|
});
|
|
|
|
|
return 0;
|
|
|
|
|
});
|
2020-09-04 21:00:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-09-29 12:35:31 +00:00
|
|
|
|
[Fact]
|
|
|
|
|
public async Task SmallZipNoLongerCrashes()
|
|
|
|
|
{
|
|
|
|
|
var src = await DownloadMod(Game.Fallout4, 29596, 120918);
|
|
|
|
|
await using var tmpFolder = await TempFolder.Create();
|
|
|
|
|
await FileExtractor2.ExtractAll(src, tmpFolder.Dir);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-09-04 21:00:29 +00:00
|
|
|
|
|
|
|
|
|
private static readonly Random _rng = new Random();
|
|
|
|
|
private static async Task WriteRandomData(AbsolutePath path, int size)
|
|
|
|
|
{
|
|
|
|
|
var buff = new byte[size];
|
|
|
|
|
_rng.NextBytes(buff);
|
|
|
|
|
await path.WriteAllBytesAsync(buff);
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-29 12:35:31 +00:00
|
|
|
|
|
|
|
|
|
private async Task WriteRandomData(Stream path, long size)
|
|
|
|
|
{
|
|
|
|
|
var buff = new byte[size];
|
|
|
|
|
_rng.NextBytes(buff);
|
|
|
|
|
await path.WriteAsync(buff);
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-04 21:00:29 +00:00
|
|
|
|
private static async Task AddFile(AbsolutePath filename, string text)
|
|
|
|
|
{
|
|
|
|
|
filename.Parent.CreateDirectory();
|
|
|
|
|
await filename.WriteAllTextAsync(text);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static async Task ZipUpFolder(AbsolutePath folder, AbsolutePath output, bool deleteSource = true)
|
|
|
|
|
{
|
|
|
|
|
ZipFile.CreateFromDirectory((string)folder, (string)output);
|
|
|
|
|
if (deleteSource)
|
|
|
|
|
await folder.DeleteDirectory();
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-08 22:15:33 +00:00
|
|
|
|
|
|
|
|
|
private static AbsolutePath _stagingFolder = ((RelativePath)"NexusDownloads").RelativeToEntryPoint();
|
2020-09-09 02:34:53 +00:00
|
|
|
|
|
2020-09-08 22:15:33 +00:00
|
|
|
|
private static async Task<AbsolutePath> DownloadMod(Game game, int mod)
|
|
|
|
|
{
|
|
|
|
|
using var client = await NexusApiClient.Get();
|
|
|
|
|
var results = await client.GetModFiles(game, mod);
|
|
|
|
|
var file = results.files.FirstOrDefault(f => f.is_primary) ??
|
|
|
|
|
results.files.OrderByDescending(f => f.uploaded_timestamp).First();
|
2020-09-29 12:35:31 +00:00
|
|
|
|
return await DownloadNexusFile(game, mod, file);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static async Task<AbsolutePath> DownloadNexusFile(Game game, int mod, NexusFileInfo file)
|
|
|
|
|
{
|
2020-09-08 22:15:33 +00:00
|
|
|
|
var src = _stagingFolder.Combine(file.file_name);
|
|
|
|
|
|
|
|
|
|
if (src.Exists) return src;
|
|
|
|
|
|
|
|
|
|
var state = new NexusDownloader.State
|
|
|
|
|
{
|
|
|
|
|
ModID = mod,
|
|
|
|
|
Game = game,
|
|
|
|
|
FileID = file.file_id
|
|
|
|
|
};
|
|
|
|
|
await state.Download(src);
|
|
|
|
|
return src;
|
|
|
|
|
}
|
2020-09-29 12:35:31 +00:00
|
|
|
|
|
|
|
|
|
private async Task<AbsolutePath> DownloadMod(Game game, int mod, int fileId)
|
|
|
|
|
{
|
|
|
|
|
using var client = await NexusApiClient.Get();
|
|
|
|
|
var results = await client.GetModFiles(game, mod);
|
|
|
|
|
var file = results.files.FirstOrDefault(f => f.file_id == fileId);
|
|
|
|
|
return await DownloadNexusFile(game, mod, file);
|
|
|
|
|
|
|
|
|
|
}
|
2020-09-08 22:15:33 +00:00
|
|
|
|
|
2020-09-04 21:00:29 +00:00
|
|
|
|
}
|
|
|
|
|
}
|