mirror of
https://github.com/wabbajack-tools/wabbajack.git
synced 2024-08-30 18:42:17 +00:00
Make ImageConverter polymorphic and revert back to texcov on Windows (#2281)
* Make ImageConverter polymorphic and revert back to texcov on Windows * Add files I forgot to add, make CHANGELOG.md additions * Don't run texconv tests on Linux/OSX
This commit is contained in:
parent
31ec53aa29
commit
5b77574b5e
@ -4,7 +4,7 @@
|
|||||||
* Add support for Cubemaps in BA2 files, if you have problems with BA2 recompression, be sure to delete your `GlobalVFSCache3.sqlite` from your AppData before the next compile
|
* Add support for Cubemaps in BA2 files, if you have problems with BA2 recompression, be sure to delete your `GlobalVFSCache3.sqlite` from your AppData before the next compile
|
||||||
* Fixed slides not being shown during installation for lists compile with the 3.0 compiler
|
* Fixed slides not being shown during installation for lists compile with the 3.0 compiler
|
||||||
* Set the "While loading slide" debug message to be `Trace` level, set the default minimum log level to `Information`
|
* Set the "While loading slide" debug message to be `Trace` level, set the default minimum log level to `Information`
|
||||||
|
* Switched back to using TexConv for texture converting on Windows, should greatly improve compatability of texture conversion (on windows systems)
|
||||||
|
|
||||||
#### Version - 3.0.5.0 - 12/22/2022
|
#### Version - 3.0.5.0 - 12/22/2022
|
||||||
* Add support for https://www.nexusmods.com/site hosted mods.
|
* Add support for https://www.nexusmods.com/site hosted mods.
|
||||||
|
@ -25,4 +25,5 @@ public static class Ext
|
|||||||
public static Extension ModlistMetadataExtension = new(".modlist_metadata");
|
public static Extension ModlistMetadataExtension = new(".modlist_metadata");
|
||||||
public static Extension Txt = new(".txt");
|
public static Extension Txt = new(".txt");
|
||||||
public static Extension Webp = new(".webp");
|
public static Extension Webp = new(".webp");
|
||||||
|
public static Extension Png = new(".png");
|
||||||
}
|
}
|
@ -28,10 +28,13 @@ public class CompilerSanityTests : IAsyncLifetime
|
|||||||
private readonly IServiceProvider _serviceProvider;
|
private readonly IServiceProvider _serviceProvider;
|
||||||
private Mod _mod;
|
private Mod _mod;
|
||||||
private ModList? _modlist;
|
private ModList? _modlist;
|
||||||
|
private readonly IImageLoader _imageLoader;
|
||||||
|
|
||||||
public CompilerSanityTests(ILogger<CompilerSanityTests> logger, IServiceProvider serviceProvider,
|
public CompilerSanityTests(ILogger<CompilerSanityTests> logger, IServiceProvider serviceProvider,
|
||||||
FileExtractor.FileExtractor fileExtractor,
|
FileExtractor.FileExtractor fileExtractor,
|
||||||
TemporaryFileManager manager, ParallelOptions parallelOptions)
|
TemporaryFileManager manager,
|
||||||
|
ParallelOptions parallelOptions,
|
||||||
|
IImageLoader imageLoader)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_serviceProvider = serviceProvider;
|
_serviceProvider = serviceProvider;
|
||||||
@ -40,6 +43,7 @@ public class CompilerSanityTests : IAsyncLifetime
|
|||||||
_fileExtractor = fileExtractor;
|
_fileExtractor = fileExtractor;
|
||||||
_manager = manager;
|
_manager = manager;
|
||||||
_parallelOptions = parallelOptions;
|
_parallelOptions = parallelOptions;
|
||||||
|
_imageLoader = imageLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -195,12 +199,12 @@ public class CompilerSanityTests : IAsyncLifetime
|
|||||||
|
|
||||||
foreach (var file in _mod.FullPath.EnumerateFiles())
|
foreach (var file in _mod.FullPath.EnumerateFiles())
|
||||||
{
|
{
|
||||||
var oldState = await ImageLoader.Load(file);
|
var oldState = await _imageLoader.Load(file);
|
||||||
Assert.NotEqual(DXGI_FORMAT.UNKNOWN, oldState.Format);
|
Assert.NotEqual(DXGI_FORMAT.UNKNOWN, oldState.Format);
|
||||||
_logger.LogInformation("Recompressing {file}", file.FileName);
|
_logger.LogInformation("Recompressing {file}", file.FileName);
|
||||||
await ImageLoader.Recompress(file, 512, 512, DXGI_FORMAT.BC7_UNORM, file, CancellationToken.None);
|
await _imageLoader.Recompress(file, 512, 512, DXGI_FORMAT.BC7_UNORM, file, CancellationToken.None);
|
||||||
|
|
||||||
var state = await ImageLoader.Load(file);
|
var state = await _imageLoader.Load(file);
|
||||||
Assert.Equal(DXGI_FORMAT.BC7_UNORM, state.Format);
|
Assert.Equal(DXGI_FORMAT.BC7_UNORM, state.Format);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ using Wabbajack.DTOs.Directives;
|
|||||||
using Wabbajack.DTOs.DownloadStates;
|
using Wabbajack.DTOs.DownloadStates;
|
||||||
using Wabbajack.DTOs.JsonConverters;
|
using Wabbajack.DTOs.JsonConverters;
|
||||||
using Wabbajack.FileExtractor.ExtractedFiles;
|
using Wabbajack.FileExtractor.ExtractedFiles;
|
||||||
|
using Wabbajack.Hashing.PHash;
|
||||||
using Wabbajack.Hashing.xxHash64;
|
using Wabbajack.Hashing.xxHash64;
|
||||||
using Wabbajack.Installer;
|
using Wabbajack.Installer;
|
||||||
using Wabbajack.Networking.WabbajackClientApi;
|
using Wabbajack.Networking.WabbajackClientApi;
|
||||||
@ -64,7 +65,8 @@ public abstract class ACompiler
|
|||||||
TemporaryFileManager manager, CompilerSettings settings,
|
TemporaryFileManager manager, CompilerSettings settings,
|
||||||
ParallelOptions parallelOptions, DownloadDispatcher dispatcher, Client wjClient, IGameLocator locator,
|
ParallelOptions parallelOptions, DownloadDispatcher dispatcher, Client wjClient, IGameLocator locator,
|
||||||
DTOSerializer dtos, IResource<ACompiler> compilerLimiter,
|
DTOSerializer dtos, IResource<ACompiler> compilerLimiter,
|
||||||
IBinaryPatchCache patchCache)
|
IBinaryPatchCache patchCache,
|
||||||
|
IImageLoader imageLoader)
|
||||||
{
|
{
|
||||||
CompilerLimiter = compilerLimiter;
|
CompilerLimiter = compilerLimiter;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
@ -83,9 +85,12 @@ public abstract class ACompiler
|
|||||||
_patchOptions = new ConcurrentDictionary<PatchedFromArchive, VirtualFile[]>();
|
_patchOptions = new ConcurrentDictionary<PatchedFromArchive, VirtualFile[]>();
|
||||||
_sourceFileLinks = new ConcurrentDictionary<Directive, RawSourceFile>();
|
_sourceFileLinks = new ConcurrentDictionary<Directive, RawSourceFile>();
|
||||||
_patchCache = patchCache;
|
_patchCache = patchCache;
|
||||||
|
ImageLoader = imageLoader;
|
||||||
_updateStopWatch = new Stopwatch();
|
_updateStopWatch = new Stopwatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IImageLoader ImageLoader { get; }
|
||||||
|
|
||||||
protected long MaxSteps { get; set; }
|
protected long MaxSteps { get; set; }
|
||||||
|
|
||||||
public CompilerSettings Settings
|
public CompilerSettings Settings
|
||||||
|
@ -34,7 +34,7 @@ public class MatchSimilarTextures : ACompilationStep
|
|||||||
_compiler._logger.LogInformation("Looking for texture match for {source}", source.File.FullPath);
|
_compiler._logger.LogInformation("Looking for texture match for {source}", source.File.FullPath);
|
||||||
(float Similarity, VirtualFile File) found = _byName[source.Path.FileNameWithoutExtension]
|
(float Similarity, VirtualFile File) found = _byName[source.Path.FileNameWithoutExtension]
|
||||||
.Select(f => (
|
.Select(f => (
|
||||||
ImageLoader.ComputeDifference(f.ImageState!.PerceptualHash, source.File.ImageState.PerceptualHash),
|
IImageLoader.ComputeDifference(f.ImageState!.PerceptualHash, source.File.ImageState.PerceptualHash),
|
||||||
f))
|
f))
|
||||||
.Select(f => { return f; })
|
.Select(f => { return f; })
|
||||||
.OrderByDescending(f => f.Item1)
|
.OrderByDescending(f => f.Item1)
|
||||||
@ -58,7 +58,7 @@ public class MatchSimilarTextures : ACompilationStep
|
|||||||
from mainMatch in _byName[mainFile.FullPath.FileName.FileNameWithoutExtension]
|
from mainMatch in _byName[mainFile.FullPath.FileName.FileNameWithoutExtension]
|
||||||
where mainMatch.ImageState != null
|
where mainMatch.ImageState != null
|
||||||
where mainFile.ImageState != null
|
where mainFile.ImageState != null
|
||||||
let similarity = ImageLoader.ComputeDifference(mainFile.ImageState!.PerceptualHash,
|
let similarity = IImageLoader.ComputeDifference(mainFile.ImageState!.PerceptualHash,
|
||||||
mainMatch.ImageState!.PerceptualHash)
|
mainMatch.ImageState!.PerceptualHash)
|
||||||
where similarity >= PerceptualTolerance
|
where similarity >= PerceptualTolerance
|
||||||
orderby similarity descending
|
orderby similarity descending
|
||||||
|
@ -14,6 +14,7 @@ using Wabbajack.Downloaders.GameFile;
|
|||||||
using Wabbajack.DTOs;
|
using Wabbajack.DTOs;
|
||||||
using Wabbajack.DTOs.Directives;
|
using Wabbajack.DTOs.Directives;
|
||||||
using Wabbajack.DTOs.JsonConverters;
|
using Wabbajack.DTOs.JsonConverters;
|
||||||
|
using Wabbajack.Hashing.PHash;
|
||||||
using Wabbajack.Installer;
|
using Wabbajack.Installer;
|
||||||
using Wabbajack.Networking.WabbajackClientApi;
|
using Wabbajack.Networking.WabbajackClientApi;
|
||||||
using Wabbajack.Paths;
|
using Wabbajack.Paths;
|
||||||
@ -30,9 +31,10 @@ public class MO2Compiler : ACompiler
|
|||||||
TemporaryFileManager manager, CompilerSettings settings, ParallelOptions parallelOptions,
|
TemporaryFileManager manager, CompilerSettings settings, ParallelOptions parallelOptions,
|
||||||
DownloadDispatcher dispatcher,
|
DownloadDispatcher dispatcher,
|
||||||
Client wjClient, IGameLocator locator, DTOSerializer dtos, IResource<ACompiler> compilerLimiter,
|
Client wjClient, IGameLocator locator, DTOSerializer dtos, IResource<ACompiler> compilerLimiter,
|
||||||
IBinaryPatchCache patchCache) :
|
IBinaryPatchCache patchCache,
|
||||||
|
IImageLoader imageLoader) :
|
||||||
base(logger, extractor, hashCache, vfs, manager, settings, parallelOptions, dispatcher, wjClient, locator, dtos,
|
base(logger, extractor, hashCache, vfs, manager, settings, parallelOptions, dispatcher, wjClient, locator, dtos,
|
||||||
compilerLimiter, patchCache)
|
compilerLimiter, patchCache, imageLoader)
|
||||||
{
|
{
|
||||||
MaxSteps = 14;
|
MaxSteps = 14;
|
||||||
}
|
}
|
||||||
@ -51,7 +53,8 @@ public class MO2Compiler : ACompiler
|
|||||||
provider.GetRequiredService<IGameLocator>(),
|
provider.GetRequiredService<IGameLocator>(),
|
||||||
provider.GetRequiredService<DTOSerializer>(),
|
provider.GetRequiredService<DTOSerializer>(),
|
||||||
provider.GetRequiredService<IResource<ACompiler>>(),
|
provider.GetRequiredService<IResource<ACompiler>>(),
|
||||||
provider.GetRequiredService<IBinaryPatchCache>());
|
provider.GetRequiredService<IBinaryPatchCache>(),
|
||||||
|
provider.GetRequiredService<IImageLoader>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompilerSettings Mo2Settings => (CompilerSettings) Settings;
|
public CompilerSettings Mo2Settings => (CompilerSettings) Settings;
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
@ -10,8 +12,31 @@ using Xunit;
|
|||||||
|
|
||||||
namespace Wabbajack.Hashing.PHash.Test;
|
namespace Wabbajack.Hashing.PHash.Test;
|
||||||
|
|
||||||
public class FileLoadingTests
|
public class FileLoadingTests : IAsyncDisposable
|
||||||
{
|
{
|
||||||
|
private readonly IImageLoader[] _imageLoaders;
|
||||||
|
private readonly TemporaryFileManager _tmp;
|
||||||
|
|
||||||
|
public FileLoadingTests()
|
||||||
|
{
|
||||||
|
_tmp = new TemporaryFileManager();
|
||||||
|
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
|
||||||
|
_imageLoaders = new IImageLoader[]
|
||||||
|
{
|
||||||
|
new CrossPlatformImageLoader(),
|
||||||
|
new TexConvImageLoader(_tmp)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_imageLoaders = new IImageLoader[]
|
||||||
|
{
|
||||||
|
new CrossPlatformImageLoader(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("test-dxt5.dds", 1.0f)]
|
[InlineData("test-dxt5.dds", 1.0f)]
|
||||||
[InlineData("test-dxt5-recompressed.dds", 1f)]
|
[InlineData("test-dxt5-recompressed.dds", 1f)]
|
||||||
@ -19,40 +44,47 @@ public class FileLoadingTests
|
|||||||
[InlineData("test-dxt5-small-bc7-vflip.dds", 0.189f)]
|
[InlineData("test-dxt5-small-bc7-vflip.dds", 0.189f)]
|
||||||
public async Task LoadAllFiles(string file, float difference)
|
public async Task LoadAllFiles(string file, float difference)
|
||||||
{
|
{
|
||||||
var baseState =
|
foreach (var imageLoader in _imageLoaders)
|
||||||
await ImageLoader.Load("TestData/test-dxt5.dds".ToRelativePath().RelativeTo(KnownFolders.EntryPoint));
|
{
|
||||||
var state = await ImageLoader.Load("TestData".ToRelativePath().Combine(file)
|
var baseState =
|
||||||
.RelativeTo(KnownFolders.EntryPoint));
|
await imageLoader.Load("TestData/test-dxt5.dds".ToRelativePath().RelativeTo(KnownFolders.EntryPoint));
|
||||||
|
var state = await imageLoader.Load("TestData".ToRelativePath().Combine(file)
|
||||||
|
.RelativeTo(KnownFolders.EntryPoint));
|
||||||
|
|
||||||
Assert.NotEqual(DXGI_FORMAT.UNKNOWN, baseState.Format);
|
Assert.NotEqual(DXGI_FORMAT.UNKNOWN, baseState.Format);
|
||||||
|
|
||||||
Assert.Equal(difference,
|
Assert.Equal(difference,
|
||||||
ImagePhash.GetCrossCorrelation(
|
ImagePhash.GetCrossCorrelation(
|
||||||
new Digest {Coefficients = baseState.PerceptualHash.Data},
|
new Digest { Coefficients = baseState.PerceptualHash.Data },
|
||||||
new Digest {Coefficients = state.PerceptualHash.Data}),
|
new Digest { Coefficients = state.PerceptualHash.Data }),
|
||||||
1.0);
|
1.0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CanConvertCubeMaps()
|
public async Task CanConvertCubeMaps()
|
||||||
{
|
{
|
||||||
// File used here via re-upload permissions found on the mod's Nexus page:
|
foreach (var imageLoader in _imageLoaders)
|
||||||
// https://www.nexusmods.com/fallout4/mods/43458?tab=description
|
{
|
||||||
// Used for testing purposes only
|
// File used here via re-upload permissions found on the mod's Nexus page:
|
||||||
var path = "TestData/WindowDisabled_CGPlayerHouseCube.dds".ToRelativePath().RelativeTo(KnownFolders.EntryPoint);
|
// https://www.nexusmods.com/fallout4/mods/43458?tab=description
|
||||||
|
// Used for testing purposes only
|
||||||
|
var path = "TestData/WindowDisabled_CGPlayerHouseCube.dds".ToRelativePath().RelativeTo(KnownFolders.EntryPoint);
|
||||||
|
|
||||||
var baseState = await ImageLoader.Load(path);
|
var baseState = await imageLoader.Load(path);
|
||||||
baseState.Height.Should().Be(128);
|
baseState.Height.Should().Be(128);
|
||||||
baseState.Width.Should().Be(128);
|
baseState.Width.Should().Be(128);
|
||||||
//baseState.Frames.Should().Be(6);
|
//baseState.Frames.Should().Be(6);
|
||||||
|
|
||||||
using var ms = new MemoryStream();
|
|
||||||
await using var ins = path.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
|
|
||||||
await ImageLoader.Recompress(ins, 128, 128, DXGI_FORMAT.BC1_UNORM, ms, CancellationToken.None, leaveOpen:true);
|
|
||||||
ms.Length.Should().Be(ins.Length);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
using var ms = new MemoryStream();
|
||||||
|
await using var ins = path.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||||
|
await imageLoader.Recompress(ins, 128, 128, DXGI_FORMAT.BC1_UNORM, ms, CancellationToken.None, leaveOpen:true);
|
||||||
|
ms.Length.Should().Be(ins.Length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
await _tmp.DisposeAsync();
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BCnEncoder.Decoder;
|
using BCnEncoder.Decoder;
|
||||||
@ -13,21 +14,22 @@ using Shipwreck.Phash.Imaging;
|
|||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
using SixLabors.ImageSharp.Processing;
|
using SixLabors.ImageSharp.Processing;
|
||||||
|
using Wabbajack.Common;
|
||||||
using Wabbajack.DTOs.Texture;
|
using Wabbajack.DTOs.Texture;
|
||||||
using Wabbajack.Paths;
|
using Wabbajack.Paths;
|
||||||
using Wabbajack.Paths.IO;
|
using Wabbajack.Paths.IO;
|
||||||
|
|
||||||
namespace Wabbajack.Hashing.PHash;
|
namespace Wabbajack.Hashing.PHash;
|
||||||
|
|
||||||
public class ImageLoader
|
public class CrossPlatformImageLoader : IImageLoader
|
||||||
{
|
{
|
||||||
public static async ValueTask<ImageState> Load(AbsolutePath path)
|
public async ValueTask<ImageState> Load(AbsolutePath path)
|
||||||
{
|
{
|
||||||
await using var fs = path.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
|
await using var fs = path.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||||
return await Load(fs);
|
return await Load(fs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async ValueTask<ImageState> Load(Stream stream)
|
public async ValueTask<ImageState> Load(Stream stream)
|
||||||
{
|
{
|
||||||
var decoder = new BcDecoder();
|
var decoder = new BcDecoder();
|
||||||
var ddsFile = DdsFile.Load(stream);
|
var ddsFile = DdsFile.Load(stream);
|
||||||
@ -57,8 +59,8 @@ public class ImageLoader
|
|||||||
new Digest {Coefficients = a.Data},
|
new Digest {Coefficients = a.Data},
|
||||||
new Digest {Coefficients = b.Data});
|
new Digest {Coefficients = b.Data});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task Recompress(AbsolutePath input, int width, int height, DXGI_FORMAT format,
|
public async Task Recompress(AbsolutePath input, int width, int height, DXGI_FORMAT format,
|
||||||
AbsolutePath output,
|
AbsolutePath output,
|
||||||
CancellationToken token)
|
CancellationToken token)
|
||||||
{
|
{
|
||||||
@ -67,24 +69,23 @@ public class ImageLoader
|
|||||||
await Recompress(new MemoryStream(inData), width, height, format, outStream, token);
|
await Recompress(new MemoryStream(inData), width, height, format, outStream, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task Recompress(Stream input, int width, int height, DXGI_FORMAT format, Stream output,
|
public async Task Recompress(Stream input, int width, int height, DXGI_FORMAT format, Stream output,
|
||||||
CancellationToken token, bool leaveOpen = false)
|
CancellationToken token, bool leaveOpen = false)
|
||||||
{
|
{
|
||||||
var decoder = new BcDecoder();
|
var decoder = new BcDecoder();
|
||||||
var ddsFile = DdsFile.Load(input);
|
var ddsFile = DdsFile.Load(input);
|
||||||
|
|
||||||
if (!leaveOpen) await input.DisposeAsync();
|
if (!leaveOpen) await input.DisposeAsync();
|
||||||
|
|
||||||
var faces = new List<Image<Rgba32>>();
|
var faces = new List<Image<Rgba32>>();
|
||||||
|
|
||||||
var origFormat = ddsFile.dx10Header.dxgiFormat == DxgiFormat.DxgiFormatUnknown
|
var origFormat = ddsFile.dx10Header.dxgiFormat == DxgiFormat.DxgiFormatUnknown
|
||||||
? ddsFile.header.ddsPixelFormat.DxgiFormat
|
? ddsFile.header.ddsPixelFormat.DxgiFormat
|
||||||
: ddsFile.dx10Header.dxgiFormat;
|
: ddsFile.dx10Header.dxgiFormat;
|
||||||
|
|
||||||
foreach (var face in ddsFile.Faces)
|
foreach (var face in ddsFile.Faces)
|
||||||
{
|
{
|
||||||
|
var data = await decoder.DecodeRawToImageRgba32Async(face.MipMaps[0].Data,
|
||||||
var data = await decoder.DecodeRawToImageRgba32Async(face.MipMaps[0].Data,
|
|
||||||
(int)face.Width, (int)face.Height, ToCompressionFormat((DXGI_FORMAT)origFormat), token);
|
(int)face.Width, (int)face.Height, ToCompressionFormat((DXGI_FORMAT)origFormat), token);
|
||||||
|
|
||||||
data.Mutate(x => x.Resize(width, height, KnownResamplers.Welch));
|
data.Mutate(x => x.Resize(width, height, KnownResamplers.Welch));
|
||||||
@ -101,7 +102,7 @@ public class ImageLoader
|
|||||||
FileFormat = OutputFileFormat.Dds
|
FileFormat = OutputFileFormat.Dds
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (faces.Count)
|
switch (faces.Count)
|
||||||
{
|
{
|
||||||
case 1:
|
case 1:
|
||||||
@ -114,11 +115,11 @@ public class ImageLoader
|
|||||||
default:
|
default:
|
||||||
throw new NotImplementedException($"Can't encode dds with {faces.Count} faces");
|
throw new NotImplementedException($"Can't encode dds with {faces.Count} faces");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!leaveOpen)
|
if (!leaveOpen)
|
||||||
await output.DisposeAsync();
|
await output.DisposeAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CompressionFormat ToCompressionFormat(DXGI_FORMAT dx)
|
public static CompressionFormat ToCompressionFormat(DXGI_FORMAT dx)
|
||||||
{
|
{
|
||||||
return dx switch
|
return dx switch
|
27
Wabbajack.Hashing.PHash/IImageLoader.cs
Normal file
27
Wabbajack.Hashing.PHash/IImageLoader.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Shipwreck.Phash;
|
||||||
|
using Wabbajack.DTOs.Texture;
|
||||||
|
using Wabbajack.Paths;
|
||||||
|
|
||||||
|
namespace Wabbajack.Hashing.PHash;
|
||||||
|
|
||||||
|
public interface IImageLoader
|
||||||
|
{
|
||||||
|
public ValueTask<ImageState> Load(AbsolutePath path);
|
||||||
|
public ValueTask<ImageState> Load(Stream stream);
|
||||||
|
public static float ComputeDifference(DTOs.Texture.PHash a, DTOs.Texture.PHash b)
|
||||||
|
{
|
||||||
|
return ImagePhash.GetCrossCorrelation(
|
||||||
|
new Digest {Coefficients = a.Data},
|
||||||
|
new Digest {Coefficients = b.Data});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Recompress(AbsolutePath input, int width, int height, DXGI_FORMAT format,
|
||||||
|
AbsolutePath output,
|
||||||
|
CancellationToken token);
|
||||||
|
|
||||||
|
public Task Recompress(Stream input, int width, int height, DXGI_FORMAT format, Stream output,
|
||||||
|
CancellationToken token, bool leaveOpen = false);
|
||||||
|
}
|
160
Wabbajack.Hashing.PHash/TexConvImageLoader.cs
Normal file
160
Wabbajack.Hashing.PHash/TexConvImageLoader.cs
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reactive.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Shipwreck.Phash;
|
||||||
|
using SixLabors.ImageSharp;
|
||||||
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
using SixLabors.ImageSharp.Processing;
|
||||||
|
using Wabbajack.Common;
|
||||||
|
using Wabbajack.Common.FileSignatures;
|
||||||
|
using Wabbajack.DTOs.Texture;
|
||||||
|
using Wabbajack.Paths;
|
||||||
|
using Wabbajack.Paths.IO;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Wabbajack.Hashing.PHash;
|
||||||
|
|
||||||
|
public class TexConvImageLoader : IImageLoader
|
||||||
|
{
|
||||||
|
private readonly SignatureChecker _sigs;
|
||||||
|
private readonly TemporaryFileManager _tempManager;
|
||||||
|
|
||||||
|
public TexConvImageLoader(TemporaryFileManager manager)
|
||||||
|
{
|
||||||
|
_tempManager = manager;
|
||||||
|
_sigs = new SignatureChecker(FileType.DDS, FileType.PNG, FileType.JPG, FileType.BMP);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<ImageState> Load(AbsolutePath path)
|
||||||
|
{
|
||||||
|
return await GetState(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<ImageState> Load(Stream stream)
|
||||||
|
{
|
||||||
|
|
||||||
|
var ext = await DetermineType(stream);
|
||||||
|
var temp = _tempManager.CreateFile(ext);
|
||||||
|
await using var fs = temp.Path.Open(FileMode.Create, FileAccess.Write, FileShare.None);
|
||||||
|
await stream.CopyToAsync(fs);
|
||||||
|
fs.Close();
|
||||||
|
return await GetState(temp.Path);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Extension> DetermineType(Stream stream)
|
||||||
|
{
|
||||||
|
var sig = await _sigs.MatchesAsync(stream);
|
||||||
|
|
||||||
|
var ext = new Extension(".tga");
|
||||||
|
if (sig != null)
|
||||||
|
ext = new Extension("." + Enum.GetName(sig.Value));
|
||||||
|
|
||||||
|
stream.Position = 0;
|
||||||
|
return ext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Recompress(AbsolutePath input, int width, int height, DXGI_FORMAT format, AbsolutePath output,
|
||||||
|
CancellationToken token)
|
||||||
|
{
|
||||||
|
var outFolder = _tempManager.CreateFolder();
|
||||||
|
var outFile = input.FileName.RelativeTo(outFolder.Path);
|
||||||
|
await ConvertImage(input, outFolder.Path, width, height, format, input.Extension);
|
||||||
|
await outFile.MoveToAsync(output, token: token, overwrite:true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Recompress(Stream input, int width, int height, DXGI_FORMAT format, Stream output, CancellationToken token,
|
||||||
|
bool leaveOpen = false)
|
||||||
|
{
|
||||||
|
var type = await DetermineType(input);
|
||||||
|
await using var toFolder = _tempManager.CreateFolder();
|
||||||
|
await using var fromFile = _tempManager.CreateFile(type);
|
||||||
|
await input.CopyToAsync(fromFile.Path, token);
|
||||||
|
var toFile = fromFile.Path.FileName.RelativeTo(toFolder);
|
||||||
|
|
||||||
|
await ConvertImage(fromFile.Path, toFolder.Path, width, height, format, type);
|
||||||
|
await using var fs = toFile.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||||
|
await fs.CopyToAsync(output, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task ConvertImage(AbsolutePath from, AbsolutePath toFolder, int w, int h, DXGI_FORMAT format, Extension fileFormat)
|
||||||
|
{
|
||||||
|
// User isn't renaming the file, so we don't have to create a temporary folder
|
||||||
|
var ph = new ProcessHelper
|
||||||
|
{
|
||||||
|
Path = @"Tools\texconv.exe".ToRelativePath().RelativeTo(KnownFolders.EntryPoint),
|
||||||
|
Arguments = new object[] {from, "-ft", fileFormat.ToString()[1..], "-f", format, "-o", toFolder, "-w", w, "-h", h, "-if", "CUBIC", "-singleproc"},
|
||||||
|
ThrowOnNonZeroExitCode = true,
|
||||||
|
LogError = true
|
||||||
|
};
|
||||||
|
await ph.Start();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ConvertImage(Stream from, ImageState state, Extension ext, AbsolutePath to)
|
||||||
|
{
|
||||||
|
await using var tmpFile = _tempManager.CreateFolder();
|
||||||
|
var inFile = to.FileName.RelativeTo(tmpFile.Path);
|
||||||
|
await inFile.WriteAllAsync(from, CancellationToken.None);
|
||||||
|
await ConvertImage(inFile, to.Parent, state.Width, state.Height, state.Format, ext);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internals
|
||||||
|
public async Task<ImageState> GetState(AbsolutePath path)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var ph = new ProcessHelper
|
||||||
|
{
|
||||||
|
Path = @"Tools\texdiag.exe".ToRelativePath().RelativeTo(KnownFolders.EntryPoint),
|
||||||
|
Arguments = new object[] {"info", path, "-nologo"},
|
||||||
|
ThrowOnNonZeroExitCode = true,
|
||||||
|
LogError = true
|
||||||
|
};
|
||||||
|
var lines = new ConcurrentStack<string>();
|
||||||
|
using var _ = ph.Output.Where(p => p.Type == ProcessHelper.StreamType.Output)
|
||||||
|
.Select(p => p.Line)
|
||||||
|
.Where(p => p.Contains(" = "))
|
||||||
|
.Subscribe(l => lines.Push(l));
|
||||||
|
await ph.Start();
|
||||||
|
|
||||||
|
var data = lines.Select(l =>
|
||||||
|
{
|
||||||
|
var split = l.Split(" = ");
|
||||||
|
return (split[0].Trim(), split[1].Trim());
|
||||||
|
}).ToDictionary(p => p.Item1, p => p.Item2);
|
||||||
|
|
||||||
|
return new ImageState
|
||||||
|
{
|
||||||
|
Width = int.Parse(data["width"]),
|
||||||
|
Height = int.Parse(data["height"]),
|
||||||
|
Format = Enum.Parse<DXGI_FORMAT>(data["format"]),
|
||||||
|
PerceptualHash = await GetPHash(path)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<DTOs.Texture.PHash> GetPHash(AbsolutePath path)
|
||||||
|
{
|
||||||
|
if (!path.FileExists())
|
||||||
|
throw new FileNotFoundException($"Can't hash non-existent file {path}");
|
||||||
|
|
||||||
|
await using var tmp = _tempManager.CreateFolder();
|
||||||
|
await ConvertImage(path, tmp.Path, 512, 512, DXGI_FORMAT.R8G8B8A8_UNORM, Ext.Png);
|
||||||
|
|
||||||
|
using var img = await Image.LoadAsync(path.FileName.RelativeTo(tmp.Path).ReplaceExtension(Ext.Png).ToString());
|
||||||
|
img.Mutate(x => x.Resize(512, 512, KnownResamplers.Welch).Grayscale(GrayscaleMode.Bt601));
|
||||||
|
|
||||||
|
return new DTOs.Texture.PHash(ImagePhash.ComputeDigest(new CrossPlatformImageLoader.ImageBitmap((Image<Rgba32>)img)).Coefficients);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
BIN
Wabbajack.Hashing.PHash/Tools/texconv.exe
Normal file
BIN
Wabbajack.Hashing.PHash/Tools/texconv.exe
Normal file
Binary file not shown.
BIN
Wabbajack.Hashing.PHash/Tools/texdiag.exe
Normal file
BIN
Wabbajack.Hashing.PHash/Tools/texdiag.exe
Normal file
Binary file not shown.
@ -13,9 +13,23 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Wabbajack.Common\Wabbajack.Common.csproj" />
|
||||||
<ProjectReference Include="..\Wabbajack.DTOs\Wabbajack.DTOs.csproj" />
|
<ProjectReference Include="..\Wabbajack.DTOs\Wabbajack.DTOs.csproj" />
|
||||||
<ProjectReference Include="..\Wabbajack.Paths.IO\Wabbajack.Paths.IO.csproj" />
|
<ProjectReference Include="..\Wabbajack.Paths.IO\Wabbajack.Paths.IO.csproj" />
|
||||||
<ProjectReference Include="..\Wabbajack.Paths\Wabbajack.Paths.csproj" />
|
<ProjectReference Include="..\Wabbajack.Paths\Wabbajack.Paths.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Tools" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="Tools\texconv.exe">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Tools\texdiag.exe">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -75,7 +75,8 @@ public abstract class AInstaller<T>
|
|||||||
DownloadDispatcher downloadDispatcher,
|
DownloadDispatcher downloadDispatcher,
|
||||||
ParallelOptions parallelOptions,
|
ParallelOptions parallelOptions,
|
||||||
IResource<IInstaller> limiter,
|
IResource<IInstaller> limiter,
|
||||||
Client wjClient)
|
Client wjClient,
|
||||||
|
IImageLoader imageLoader)
|
||||||
{
|
{
|
||||||
_limiter = limiter;
|
_limiter = limiter;
|
||||||
_manager = new TemporaryFileManager(config.Install.Combine("__temp__"));
|
_manager = new TemporaryFileManager(config.Install.Combine("__temp__"));
|
||||||
@ -90,8 +91,11 @@ public abstract class AInstaller<T>
|
|||||||
_parallelOptions = parallelOptions;
|
_parallelOptions = parallelOptions;
|
||||||
_gameLocator = gameLocator;
|
_gameLocator = gameLocator;
|
||||||
_wjClient = wjClient;
|
_wjClient = wjClient;
|
||||||
|
ImageLoader = imageLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IImageLoader ImageLoader { get; }
|
||||||
|
|
||||||
protected long MaxSteps { get; set; }
|
protected long MaxSteps { get; set; }
|
||||||
|
|
||||||
public Dictionary<Hash, AbsolutePath> HashedArchives { get; set; } = new();
|
public Dictionary<Hash, AbsolutePath> HashedArchives { get; set; } = new();
|
||||||
|
@ -23,6 +23,7 @@ using Wabbajack.DTOs.BSA.FileStates;
|
|||||||
using Wabbajack.DTOs.Directives;
|
using Wabbajack.DTOs.Directives;
|
||||||
using Wabbajack.DTOs.DownloadStates;
|
using Wabbajack.DTOs.DownloadStates;
|
||||||
using Wabbajack.DTOs.JsonConverters;
|
using Wabbajack.DTOs.JsonConverters;
|
||||||
|
using Wabbajack.Hashing.PHash;
|
||||||
using Wabbajack.Hashing.xxHash64;
|
using Wabbajack.Hashing.xxHash64;
|
||||||
using Wabbajack.Installer.Utilities;
|
using Wabbajack.Installer.Utilities;
|
||||||
using Wabbajack.Networking.WabbajackClientApi;
|
using Wabbajack.Networking.WabbajackClientApi;
|
||||||
@ -40,9 +41,9 @@ public class StandardInstaller : AInstaller<StandardInstaller>
|
|||||||
InstallerConfiguration config,
|
InstallerConfiguration config,
|
||||||
IGameLocator gameLocator, FileExtractor.FileExtractor extractor,
|
IGameLocator gameLocator, FileExtractor.FileExtractor extractor,
|
||||||
DTOSerializer jsonSerializer, Context vfs, FileHashCache fileHashCache,
|
DTOSerializer jsonSerializer, Context vfs, FileHashCache fileHashCache,
|
||||||
DownloadDispatcher downloadDispatcher, ParallelOptions parallelOptions, IResource<IInstaller> limiter, Client wjClient) :
|
DownloadDispatcher downloadDispatcher, ParallelOptions parallelOptions, IResource<IInstaller> limiter, Client wjClient, IImageLoader imageLoader) :
|
||||||
base(logger, config, gameLocator, extractor, jsonSerializer, vfs, fileHashCache, downloadDispatcher,
|
base(logger, config, gameLocator, extractor, jsonSerializer, vfs, fileHashCache, downloadDispatcher,
|
||||||
parallelOptions, limiter, wjClient)
|
parallelOptions, limiter, wjClient, imageLoader)
|
||||||
{
|
{
|
||||||
MaxSteps = 14;
|
MaxSteps = 14;
|
||||||
}
|
}
|
||||||
@ -59,7 +60,8 @@ public class StandardInstaller : AInstaller<StandardInstaller>
|
|||||||
provider.GetRequiredService<DownloadDispatcher>(),
|
provider.GetRequiredService<DownloadDispatcher>(),
|
||||||
provider.GetRequiredService<ParallelOptions>(),
|
provider.GetRequiredService<ParallelOptions>(),
|
||||||
provider.GetRequiredService<IResource<IInstaller>>(),
|
provider.GetRequiredService<IResource<IInstaller>>(),
|
||||||
provider.GetRequiredService<Client>());
|
provider.GetRequiredService<Client>(),
|
||||||
|
provider.GetRequiredService<IImageLoader>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<bool> Begin(CancellationToken token)
|
public override async Task<bool> Begin(CancellationToken token)
|
||||||
|
@ -252,6 +252,18 @@ public static class AbsolutePathExtensions
|
|||||||
return file.ToString();
|
return file.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task CopyToAsync(this Stream from, AbsolutePath path, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
await using var to = path.Open(FileMode.Create, FileAccess.Write, FileShare.None);
|
||||||
|
await from.CopyToAsync(to, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task CopyToAsync(this AbsolutePath from, Stream to, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
await using var fromStream = from.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||||
|
await fromStream.CopyToAsync(to, token);
|
||||||
|
}
|
||||||
|
|
||||||
#region Directories
|
#region Directories
|
||||||
|
|
||||||
public static void CreateDirectory(this AbsolutePath path)
|
public static void CreateDirectory(this AbsolutePath path)
|
||||||
|
@ -85,7 +85,12 @@ public struct AbsolutePath : IPath, IComparable<AbsolutePath>, IEquatable<Absolu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public AbsolutePath ReplaceExtension(Extension newExtension)
|
/// <summary>
|
||||||
|
/// Returns a new path that is this path with the extension changed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newExtension"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public readonly AbsolutePath ReplaceExtension(Extension newExtension)
|
||||||
{
|
{
|
||||||
var paths = new string[Parts.Length];
|
var paths = new string[Parts.Length];
|
||||||
Array.Copy(Parts, paths, paths.Length);
|
Array.Copy(Parts, paths, paths.Length);
|
||||||
@ -94,7 +99,7 @@ public struct AbsolutePath : IPath, IComparable<AbsolutePath>, IEquatable<Absolu
|
|||||||
paths[^1] = newName;
|
paths[^1] = newName;
|
||||||
return new AbsolutePath(paths, PathFormat);
|
return new AbsolutePath(paths, PathFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static explicit operator AbsolutePath(string input)
|
public static explicit operator AbsolutePath(string input)
|
||||||
{
|
{
|
||||||
return Parse(input);
|
return Parse(input);
|
||||||
|
@ -48,12 +48,10 @@ public struct RelativePath : IPath, IEquatable<RelativePath>, IComparable<Relati
|
|||||||
internal static string ReplaceExtension(string oldName, Extension newExtension)
|
internal static string ReplaceExtension(string oldName, Extension newExtension)
|
||||||
{
|
{
|
||||||
var oldExtLength = oldName.LastIndexOf(".", StringComparison.CurrentCultureIgnoreCase);
|
var oldExtLength = oldName.LastIndexOf(".", StringComparison.CurrentCultureIgnoreCase);
|
||||||
if (oldExtLength < 0)
|
if (oldExtLength <= 0)
|
||||||
oldExtLength = 0;
|
return oldName + newExtension;
|
||||||
else
|
|
||||||
oldExtLength++;
|
var newName = oldName[..oldExtLength] + newExtension;
|
||||||
|
|
||||||
var newName = oldName[..^oldExtLength] + newExtension;
|
|
||||||
return newName;
|
return newName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ using Wabbajack.DTOs;
|
|||||||
using Wabbajack.DTOs.Interventions;
|
using Wabbajack.DTOs.Interventions;
|
||||||
using Wabbajack.DTOs.JsonConverters;
|
using Wabbajack.DTOs.JsonConverters;
|
||||||
using Wabbajack.DTOs.Logins;
|
using Wabbajack.DTOs.Logins;
|
||||||
|
using Wabbajack.Hashing.PHash;
|
||||||
using Wabbajack.Installer;
|
using Wabbajack.Installer;
|
||||||
using Wabbajack.Networking.BethesdaNet;
|
using Wabbajack.Networking.BethesdaNet;
|
||||||
using Wabbajack.Networking.Discord;
|
using Wabbajack.Networking.Discord;
|
||||||
@ -180,6 +181,12 @@ public static class ServiceExtensions
|
|||||||
service.AddAllSingleton<IGameLocator, StubbedGameLocator>();
|
service.AddAllSingleton<IGameLocator, StubbedGameLocator>();
|
||||||
else
|
else
|
||||||
service.AddAllSingleton<IGameLocator, GameLocator>();
|
service.AddAllSingleton<IGameLocator, GameLocator>();
|
||||||
|
|
||||||
|
// ImageLoader
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
service.AddSingleton<IImageLoader, TexConvImageLoader>();
|
||||||
|
else
|
||||||
|
service.AddSingleton<IImageLoader, CrossPlatformImageLoader>();
|
||||||
|
|
||||||
// Installer/Compiler Configuration
|
// Installer/Compiler Configuration
|
||||||
service.AddScoped<InstallerConfiguration>();
|
service.AddScoped<InstallerConfiguration>();
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Wabbajack.DTOs;
|
using Wabbajack.DTOs;
|
||||||
|
using Wabbajack.Hashing.PHash;
|
||||||
using Wabbajack.Paths.IO;
|
using Wabbajack.Paths.IO;
|
||||||
using Wabbajack.RateLimiter;
|
using Wabbajack.RateLimiter;
|
||||||
using Wabbajack.VFS.Interfaces;
|
using Wabbajack.VFS.Interfaces;
|
||||||
@ -28,6 +30,13 @@ public class Startup
|
|||||||
.AddAllSingleton<IResource, IResource<FileHashCache>, Resource<FileHashCache>>(
|
.AddAllSingleton<IResource, IResource<FileHashCache>, Resource<FileHashCache>>(
|
||||||
s =>
|
s =>
|
||||||
new ("File Hash Cache", 2));
|
new ("File Hash Cache", 2));
|
||||||
|
|
||||||
|
// ImageLoader
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
service.AddSingleton<IImageLoader, TexConvImageLoader>();
|
||||||
|
else
|
||||||
|
service.AddSingleton<IImageLoader, CrossPlatformImageLoader>();
|
||||||
|
|
||||||
|
|
||||||
// Keep this fixed at 2 so that we can detect deadlocks in the VFS parallelOptions
|
// Keep this fixed at 2 so that we can detect deadlocks in the VFS parallelOptions
|
||||||
service.AddSingleton(new ParallelOptions {MaxDegreeOfParallelism = 2});
|
service.AddSingleton(new ParallelOptions {MaxDegreeOfParallelism = 2});
|
||||||
|
@ -7,6 +7,7 @@ using System.Threading.Tasks;
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Wabbajack.Common;
|
using Wabbajack.Common;
|
||||||
using Wabbajack.FileExtractor.ExtractedFiles;
|
using Wabbajack.FileExtractor.ExtractedFiles;
|
||||||
|
using Wabbajack.Hashing.PHash;
|
||||||
using Wabbajack.Hashing.xxHash64;
|
using Wabbajack.Hashing.xxHash64;
|
||||||
using Wabbajack.Paths;
|
using Wabbajack.Paths;
|
||||||
using Wabbajack.Paths.IO;
|
using Wabbajack.Paths.IO;
|
||||||
@ -32,7 +33,8 @@ public class Context
|
|||||||
|
|
||||||
public Context(ILogger<Context> logger, ParallelOptions parallelOptions, TemporaryFileManager manager,
|
public Context(ILogger<Context> logger, ParallelOptions parallelOptions, TemporaryFileManager manager,
|
||||||
IVfsCache vfsCache,
|
IVfsCache vfsCache,
|
||||||
FileHashCache hashCache, IResource<Context> limiter, IResource<FileHashCache> hashLimiter, FileExtractor.FileExtractor extractor)
|
FileHashCache hashCache, IResource<Context> limiter, IResource<FileHashCache> hashLimiter,
|
||||||
|
FileExtractor.FileExtractor extractor, IImageLoader imageLoader)
|
||||||
{
|
{
|
||||||
Limiter = limiter;
|
Limiter = limiter;
|
||||||
HashLimiter = hashLimiter;
|
HashLimiter = hashLimiter;
|
||||||
@ -42,16 +44,18 @@ public class Context
|
|||||||
VfsCache = vfsCache;
|
VfsCache = vfsCache;
|
||||||
HashCache = hashCache;
|
HashCache = hashCache;
|
||||||
_parallelOptions = parallelOptions;
|
_parallelOptions = parallelOptions;
|
||||||
|
ImageLoader = imageLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Context WithTemporaryFileManager(TemporaryFileManager manager)
|
public Context WithTemporaryFileManager(TemporaryFileManager manager)
|
||||||
{
|
{
|
||||||
return new Context(Logger, _parallelOptions, manager, VfsCache, HashCache, Limiter, HashLimiter,
|
return new Context(Logger, _parallelOptions, manager, VfsCache, HashCache, Limiter, HashLimiter,
|
||||||
Extractor.WithTemporaryFileManager(manager));
|
Extractor.WithTemporaryFileManager(manager), ImageLoader);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public IndexRoot Index { get; private set; } = IndexRoot.Empty;
|
public IndexRoot Index { get; private set; } = IndexRoot.Empty;
|
||||||
|
public IImageLoader ImageLoader { get; }
|
||||||
|
|
||||||
public async Task<IndexRoot> AddRoot(AbsolutePath root, CancellationToken token)
|
public async Task<IndexRoot> AddRoot(AbsolutePath root, CancellationToken token)
|
||||||
{
|
{
|
||||||
|
@ -204,7 +204,7 @@ public class VirtualFile
|
|||||||
if (TextureExtensions.Contains(relPath.FileName.Extension) && await DDSSig.MatchesAsync(stream) != null)
|
if (TextureExtensions.Contains(relPath.FileName.Extension) && await DDSSig.MatchesAsync(stream) != null)
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
self.ImageState = await ImageLoader.Load(stream);
|
self.ImageState = await context.ImageLoader.Load(stream);
|
||||||
if (job != null)
|
if (job != null)
|
||||||
{
|
{
|
||||||
job.Size += self.Size;
|
job.Size += self.Size;
|
||||||
|
Loading…
Reference in New Issue
Block a user