From 4b13dfe8d409ca20eb3359d367375cdfd20025ec Mon Sep 17 00:00:00 2001 From: azyges Date: Sat, 3 Jan 2026 03:19:10 +0900 Subject: [PATCH] skip decimation for direct pairs and make it a toggle in settings --- .../Configurations/PlayerPerformanceConfig.cs | 2 + .../PlayerData/Factories/PlayerDataFactory.cs | 47 +++++++++++++----- .../PlayerData/Pairs/PairHandlerAdapter.cs | 48 ++++++++++++++----- .../Pairs/PairHandlerAdapterFactory.cs | 5 ++ .../Services/PlayerPerformanceService.cs | 13 ++++- LightlessSync/UI/SettingsUi.cs | 16 +++++++ .../WebAPI/Files/FileDownloadManager.cs | 38 ++++++++------- 7 files changed, 126 insertions(+), 43 deletions(-) diff --git a/LightlessSync/LightlessConfiguration/Configurations/PlayerPerformanceConfig.cs b/LightlessSync/LightlessConfiguration/Configurations/PlayerPerformanceConfig.cs index b905c05..5cdfd4e 100644 --- a/LightlessSync/LightlessConfiguration/Configurations/PlayerPerformanceConfig.cs +++ b/LightlessSync/LightlessConfiguration/Configurations/PlayerPerformanceConfig.cs @@ -22,10 +22,12 @@ public class PlayerPerformanceConfig : ILightlessConfiguration public int TextureDownscaleMaxDimension { get; set; } = 2048; public bool OnlyDownscaleUncompressedTextures { get; set; } = true; public bool KeepOriginalTextureFiles { get; set; } = false; + public bool SkipTextureDownscaleForPreferredPairs { get; set; } = true; public bool EnableModelDecimation { get; set; } = false; public int ModelDecimationTriangleThreshold { get; set; } = 50_000; public double ModelDecimationTargetRatio { get; set; } = 0.8; public bool KeepOriginalModelFiles { get; set; } = true; + public bool SkipModelDecimationForPreferredPairs { get; set; } = true; public bool ModelDecimationAllowBody { get; set; } = false; public bool ModelDecimationAllowFaceHead { get; set; } = false; public bool ModelDecimationAllowTail { get; set; } = false; diff --git a/LightlessSync/PlayerData/Factories/PlayerDataFactory.cs b/LightlessSync/PlayerData/Factories/PlayerDataFactory.cs index 9ecfcc3..7b7434e 100644 --- a/LightlessSync/PlayerData/Factories/PlayerDataFactory.cs +++ b/LightlessSync/PlayerData/Factories/PlayerDataFactory.cs @@ -146,7 +146,8 @@ public class PlayerDataFactory fragment.FileReplacements = new HashSet(resolvedPaths.Select(c => new FileReplacement([.. c.Value], c.Key)), FileReplacementComparer.Instance) .Where(p => p.HasFileReplacement).ToHashSet(); - fragment.FileReplacements.RemoveWhere(c => c.GamePaths.Any(g => !CacheMonitor.AllowedFileExtensions.Any(e => g.EndsWith(e, StringComparison.OrdinalIgnoreCase)))); + var allowedExtensions = CacheMonitor.AllowedFileExtensions; + fragment.FileReplacements.RemoveWhere(c => c.GamePaths.Any(g => !allowedExtensions.Any(e => g.EndsWith(e, StringComparison.OrdinalIgnoreCase)))); ct.ThrowIfCancellationRequested(); @@ -194,7 +195,9 @@ public class PlayerDataFactory // get all remaining paths and resolve them var transientPaths = ManageSemiTransientData(objectKind); - var resolvedTransientPaths = await GetFileReplacementsFromPaths(playerRelatedObject, transientPaths, new HashSet(StringComparer.Ordinal)).ConfigureAwait(false); + var resolvedTransientPaths = transientPaths.Count == 0 + ? new Dictionary(StringComparer.OrdinalIgnoreCase).AsReadOnly() + : await GetFileReplacementsFromPaths(playerRelatedObject, transientPaths, new HashSet(StringComparer.Ordinal)).ConfigureAwait(false); if (logDebug) { @@ -377,7 +380,15 @@ public class PlayerDataFactory { var forwardPaths = forwardResolve.ToArray(); var reversePaths = reverseResolve.ToArray(); - Dictionary> resolvedPaths = new(StringComparer.Ordinal); + if (forwardPaths.Length == 0 && reversePaths.Length == 0) + { + return new Dictionary(StringComparer.OrdinalIgnoreCase).AsReadOnly(); + } + + var forwardPathsLower = forwardPaths.Length == 0 ? Array.Empty() : forwardPaths.Select(p => p.ToLowerInvariant()).ToArray(); + var reversePathsLower = reversePaths.Length == 0 ? Array.Empty() : reversePaths.Select(p => p.ToLowerInvariant()).ToArray(); + + Dictionary> resolvedPaths = new(forwardPaths.Length + reversePaths.Length, StringComparer.Ordinal); if (handler.ObjectKind != ObjectKind.Player) { var (objectIndex, forwardResolved, reverseResolved) = await _dalamudUtil.RunOnFrameworkThread(() => @@ -415,24 +426,29 @@ public class PlayerDataFactory if (resolvedPaths.TryGetValue(filePath, out var list)) { - list.Add(forwardPaths[i].ToLowerInvariant()); + list.Add(forwardPathsLower[i]); } else { - resolvedPaths[filePath] = [forwardPaths[i].ToLowerInvariant()]; + resolvedPaths[filePath] = [forwardPathsLower[i]]; } } for (int i = 0; i < reversePaths.Length; i++) { - var filePath = reversePaths[i].ToLowerInvariant(); + var filePath = reversePathsLower[i]; + var reverseResolvedLower = new string[reverseResolved[i].Length]; + for (var j = 0; j < reverseResolvedLower.Length; j++) + { + reverseResolvedLower[j] = reverseResolved[i][j].ToLowerInvariant(); + } if (resolvedPaths.TryGetValue(filePath, out var list)) { - list.AddRange(reverseResolved[i].Select(c => c.ToLowerInvariant())); + list.AddRange(reverseResolvedLower); } else { - resolvedPaths[filePath] = new List(reverseResolved[i].Select(c => c.ToLowerInvariant()).ToList()); + resolvedPaths[filePath] = new List(reverseResolvedLower); } } @@ -446,24 +462,29 @@ public class PlayerDataFactory var filePath = forward[i].ToLowerInvariant(); if (resolvedPaths.TryGetValue(filePath, out var list)) { - list.Add(forwardPaths[i].ToLowerInvariant()); + list.Add(forwardPathsLower[i]); } else { - resolvedPaths[filePath] = [forwardPaths[i].ToLowerInvariant()]; + resolvedPaths[filePath] = [forwardPathsLower[i]]; } } for (int i = 0; i < reversePaths.Length; i++) { - var filePath = reversePaths[i].ToLowerInvariant(); + var filePath = reversePathsLower[i]; + var reverseResolvedLower = new string[reverse[i].Length]; + for (var j = 0; j < reverseResolvedLower.Length; j++) + { + reverseResolvedLower[j] = reverse[i][j].ToLowerInvariant(); + } if (resolvedPaths.TryGetValue(filePath, out var list)) { - list.AddRange(reverse[i].Select(c => c.ToLowerInvariant())); + list.AddRange(reverseResolvedLower); } else { - resolvedPaths[filePath] = new List(reverse[i].Select(c => c.ToLowerInvariant()).ToList()); + resolvedPaths[filePath] = new List(reverseResolvedLower); } } diff --git a/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs b/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs index 03c4f94..6d859ac 100644 --- a/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs +++ b/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs @@ -6,6 +6,7 @@ using LightlessSync.API.Data.Enum; using LightlessSync.API.Data.Extensions; using LightlessSync.FileCache; using LightlessSync.Interop.Ipc; +using LightlessSync.LightlessConfiguration; using LightlessSync.PlayerData.Factories; using LightlessSync.PlayerData.Handlers; using LightlessSync.Services; @@ -38,6 +39,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa private readonly ActorObjectService _actorObjectService; private readonly FileDownloadManager _downloadManager; private readonly FileCacheManager _fileDbManager; + private readonly PlayerPerformanceConfigService _playerPerformanceConfigService; private readonly GameObjectHandlerFactory _gameObjectHandlerFactory; private readonly IpcManager _ipcManager; private readonly IHostApplicationLifetime _lifetime; @@ -197,6 +199,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa ActorObjectService actorObjectService, IHostApplicationLifetime lifetime, FileCacheManager fileDbManager, + PlayerPerformanceConfigService playerPerformanceConfigService, PlayerPerformanceService playerPerformanceService, PairProcessingLimiter pairProcessingLimiter, ServerConfigurationManager serverConfigManager, @@ -217,6 +220,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa _actorObjectService = actorObjectService; _lifetime = lifetime; _fileDbManager = fileDbManager; + _playerPerformanceConfigService = playerPerformanceConfigService; _playerPerformanceService = playerPerformanceService; _pairProcessingLimiter = pairProcessingLimiter; _serverConfigManager = serverConfigManager; @@ -522,11 +526,31 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa return GetCurrentPairs().Any(predicate); } - private bool ShouldSkipDownscale() + private bool IsPreferredDirectPair() { return GetCurrentPairs().Any(p => p.IsDirectlyPaired && p.SelfToOtherPermissions.IsSticky()); } + private bool ShouldSkipDownscale() + { + if (!_playerPerformanceConfigService.Current.SkipTextureDownscaleForPreferredPairs) + { + return false; + } + + return IsPreferredDirectPair(); + } + + private bool ShouldSkipDecimation() + { + if (!_playerPerformanceConfigService.Current.SkipModelDecimationForPreferredPairs) + { + return false; + } + + return IsPreferredDirectPair(); + } + private bool IsPaused() { var pairs = GetCurrentPairs(); @@ -1843,6 +1867,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa try { bool skipDownscaleForPair = ShouldSkipDownscale(); + bool skipDecimationForPair = ShouldSkipDecimation(); var user = GetPrimaryUserData(); Dictionary<(string GamePath, string? Hash), string> moddedPaths; List missingReplacements = []; @@ -1881,7 +1906,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa } var handlerForDownload = _charaHandler; - _pairDownloadTask = Task.Run(async () => await _downloadManager.DownloadFiles(handlerForDownload, toDownloadReplacements, downloadToken, skipDownscaleForPair).ConfigureAwait(false)); + _pairDownloadTask = Task.Run(async () => await _downloadManager.DownloadFiles(handlerForDownload, toDownloadReplacements, downloadToken, skipDownscaleForPair, skipDecimationForPair).ConfigureAwait(false)); await _pairDownloadTask.ConfigureAwait(false); @@ -1904,7 +1929,10 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa { await _textureDownscaleService.WaitForPendingJobsAsync(downloadedTextureHashes, downloadToken).ConfigureAwait(false); } + } + if (!skipDecimationForPair) + { var downloadedModelHashes = toDownloadReplacements .Where(static replacement => replacement.GamePaths.Any(static path => path.EndsWith(".mdl", StringComparison.OrdinalIgnoreCase))) .Select(static replacement => replacement.Hash) @@ -2388,6 +2416,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa ConcurrentDictionary<(string GamePath, string? Hash), string> outputDict = new(); bool hasMigrationChanges = false; bool skipDownscaleForPair = ShouldSkipDownscale(); + bool skipDecimationForPair = ShouldSkipDecimation(); try { @@ -2419,16 +2448,13 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa foreach (var gamePath in item.GamePaths) { var preferredPath = fileCache.ResolvedFilepath; - if (!skipDownscaleForPair) + if (!skipDownscaleForPair && gamePath.EndsWith(".tex", StringComparison.OrdinalIgnoreCase)) { - if (gamePath.EndsWith(".tex", StringComparison.OrdinalIgnoreCase)) - { - preferredPath = _textureDownscaleService.GetPreferredPath(item.Hash, fileCache.ResolvedFilepath); - } - else if (gamePath.EndsWith(".mdl", StringComparison.OrdinalIgnoreCase)) - { - preferredPath = _modelDecimationService.GetPreferredPath(item.Hash, fileCache.ResolvedFilepath); - } + preferredPath = _textureDownscaleService.GetPreferredPath(item.Hash, fileCache.ResolvedFilepath); + } + else if (!skipDecimationForPair && gamePath.EndsWith(".mdl", StringComparison.OrdinalIgnoreCase)) + { + preferredPath = _modelDecimationService.GetPreferredPath(item.Hash, fileCache.ResolvedFilepath); } outputDict[(gamePath, item.Hash)] = preferredPath; } diff --git a/LightlessSync/PlayerData/Pairs/PairHandlerAdapterFactory.cs b/LightlessSync/PlayerData/Pairs/PairHandlerAdapterFactory.cs index 088b115..b4c2c71 100644 --- a/LightlessSync/PlayerData/Pairs/PairHandlerAdapterFactory.cs +++ b/LightlessSync/PlayerData/Pairs/PairHandlerAdapterFactory.cs @@ -1,5 +1,6 @@ using LightlessSync.FileCache; using LightlessSync.Interop.Ipc; +using LightlessSync.LightlessConfiguration; using LightlessSync.PlayerData.Factories; using LightlessSync.Services; using LightlessSync.Services.ActorTracking; @@ -27,6 +28,7 @@ internal sealed class PairHandlerAdapterFactory : IPairHandlerAdapterFactory private readonly IServiceProvider _serviceProvider; private readonly IHostApplicationLifetime _lifetime; private readonly FileCacheManager _fileCacheManager; + private readonly PlayerPerformanceConfigService _playerPerformanceConfigService; private readonly PlayerPerformanceService _playerPerformanceService; private readonly PairProcessingLimiter _pairProcessingLimiter; private readonly ServerConfigurationManager _serverConfigManager; @@ -49,6 +51,7 @@ internal sealed class PairHandlerAdapterFactory : IPairHandlerAdapterFactory IFramework framework, IHostApplicationLifetime lifetime, FileCacheManager fileCacheManager, + PlayerPerformanceConfigService playerPerformanceConfigService, PlayerPerformanceService playerPerformanceService, PairProcessingLimiter pairProcessingLimiter, ServerConfigurationManager serverConfigManager, @@ -69,6 +72,7 @@ internal sealed class PairHandlerAdapterFactory : IPairHandlerAdapterFactory _framework = framework; _lifetime = lifetime; _fileCacheManager = fileCacheManager; + _playerPerformanceConfigService = playerPerformanceConfigService; _playerPerformanceService = playerPerformanceService; _pairProcessingLimiter = pairProcessingLimiter; _serverConfigManager = serverConfigManager; @@ -98,6 +102,7 @@ internal sealed class PairHandlerAdapterFactory : IPairHandlerAdapterFactory actorObjectService, _lifetime, _fileCacheManager, + _playerPerformanceConfigService, _playerPerformanceService, _pairProcessingLimiter, _serverConfigManager, diff --git a/LightlessSync/Services/PlayerPerformanceService.cs b/LightlessSync/Services/PlayerPerformanceService.cs index 553e87b..5fa0049 100644 --- a/LightlessSync/Services/PlayerPerformanceService.cs +++ b/LightlessSync/Services/PlayerPerformanceService.cs @@ -129,6 +129,8 @@ public class PlayerPerformanceService .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); + var skipDecimation = config.SkipModelDecimationForPreferredPairs && pairHandler.IsDirectlyPaired && pairHandler.HasStickyPermissions; + foreach (var hash in moddedModelHashes) { var tris = await _xivDataAnalyzer.GetTrianglesByHash(hash).ConfigureAwait(false); @@ -138,7 +140,12 @@ public class PlayerPerformanceService var fileEntry = _fileCacheManager.GetFileCacheByHash(hash); if (fileEntry != null) { - var preferredPath = _modelDecimationService.GetPreferredPath(hash, fileEntry.ResolvedFilepath); + var preferredPath = fileEntry.ResolvedFilepath; + if (!skipDecimation) + { + preferredPath = _modelDecimationService.GetPreferredPath(hash, fileEntry.ResolvedFilepath); + } + if (!string.Equals(preferredPath, fileEntry.ResolvedFilepath, StringComparison.OrdinalIgnoreCase)) { var decimatedTris = await _xivDataAnalyzer.GetEffectiveTrianglesByHash(hash, preferredPath).ConfigureAwait(false); @@ -192,7 +199,9 @@ public class PlayerPerformanceService public bool ComputeAndAutoPauseOnVRAMUsageThresholds(IPairPerformanceSubject pairHandler, CharacterData charaData, List toDownloadFiles) { var config = _playerPerformanceConfigService.Current; - bool skipDownscale = pairHandler.IsDirectlyPaired && pairHandler.HasStickyPermissions; + bool skipDownscale = config.SkipTextureDownscaleForPreferredPairs + && pairHandler.IsDirectlyPaired + && pairHandler.HasStickyPermissions; long vramUsage = 0; long effectiveVramUsage = 0; diff --git a/LightlessSync/UI/SettingsUi.cs b/LightlessSync/UI/SettingsUi.cs index cf64df1..ea0d0e1 100644 --- a/LightlessSync/UI/SettingsUi.cs +++ b/LightlessSync/UI/SettingsUi.cs @@ -3590,6 +3590,14 @@ public class SettingsUi : WindowMediatorSubscriberBase ImGui.SameLine(); _uiShared.DrawNoteLine("! ", UIColors.Get("LightlessYellow"), new SeStringUtils.RichTextEntry("If disabled, saved + effective VRAM usage information will not work.", UIColors.Get("LightlessYellow"))); + var skipPreferredDownscale = textureConfig.SkipTextureDownscaleForPreferredPairs; + if (ImGui.Checkbox("Skip downscale for preferred/direct pairs", ref skipPreferredDownscale)) + { + textureConfig.SkipTextureDownscaleForPreferredPairs = skipPreferredDownscale; + _playerPerformanceConfigService.Save(); + } + _uiShared.DrawHelpText("When enabled, textures for direct pairs with preferred permissions are left untouched."); + if (!textureConfig.EnableNonIndexTextureMipTrim && !textureConfig.EnableIndexTextureDownscale) { UiSharedService.ColorTextWrapped("Both trimming and downscale are disabled. Lightless will keep original textures regardless of size.", UIColors.Get("DimRed")); @@ -3649,6 +3657,14 @@ public class SettingsUi : WindowMediatorSubscriberBase ImGui.SameLine(); _uiShared.DrawNoteLine("! ", UIColors.Get("LightlessYellow"), new SeStringUtils.RichTextEntry("If disabled, saved + effective triangle usage information will not work.", UIColors.Get("LightlessYellow"))); + var skipPreferredDecimation = performanceConfig.SkipModelDecimationForPreferredPairs; + if (ImGui.Checkbox("Skip decimation for preferred/direct pairs", ref skipPreferredDecimation)) + { + performanceConfig.SkipModelDecimationForPreferredPairs = skipPreferredDecimation; + _playerPerformanceConfigService.Save(); + } + _uiShared.DrawHelpText("When enabled, models for direct pairs with preferred permissions are left untouched."); + var triangleThreshold = performanceConfig.ModelDecimationTriangleThreshold; ImGui.SetNextItemWidth(300 * ImGuiHelpers.GlobalScale); if (ImGui.SliderInt("Decimate models above", ref triangleThreshold, 10_000, 100_000)) diff --git a/LightlessSync/WebAPI/Files/FileDownloadManager.cs b/LightlessSync/WebAPI/Files/FileDownloadManager.cs index e8b0af5..e558e74 100644 --- a/LightlessSync/WebAPI/Files/FileDownloadManager.cs +++ b/LightlessSync/WebAPI/Files/FileDownloadManager.cs @@ -89,12 +89,12 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase CurrentOwnerToken = null; } - public async Task DownloadFiles(GameObjectHandler? gameObject, List fileReplacementDto, CancellationToken ct, bool skipDownscale = false) + public async Task DownloadFiles(GameObjectHandler? gameObject, List fileReplacementDto, CancellationToken ct, bool skipDownscale = false, bool skipDecimation = false) { Mediator.Publish(new HaltScanMessage(nameof(DownloadFiles))); try { - await DownloadFilesInternal(gameObject, fileReplacementDto, ct, skipDownscale).ConfigureAwait(false); + await DownloadFilesInternal(gameObject, fileReplacementDto, ct, skipDownscale, skipDecimation).ConfigureAwait(false); } catch { @@ -498,7 +498,8 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase IReadOnlyDictionary rawSizeLookup, string downloadLabel, CancellationToken ct, - bool skipDownscale) + bool skipDownscale, + bool skipDecimation) { SetStatus(downloadStatusKey, DownloadStatus.Decompressing); MarkTransferredFiles(downloadStatusKey, 1); @@ -552,7 +553,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase } await _fileCompactor.WriteAllBytesAsync(filePath, decompressed, ct).ConfigureAwait(false); - PersistFileToStorage(fileHash, filePath, repl.GamePath, skipDownscale); + PersistFileToStorage(fileHash, filePath, repl.GamePath, skipDownscale, skipDecimation); } catch (EndOfStreamException) { @@ -638,7 +639,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase yield return items.GetRange(i, Math.Min(chunkSize, items.Count - i)); } - private async Task DownloadFilesInternal(GameObjectHandler? gameObjectHandler, List fileReplacement, CancellationToken ct, bool skipDownscale) + private async Task DownloadFilesInternal(GameObjectHandler? gameObjectHandler, List fileReplacement, CancellationToken ct, bool skipDownscale, bool skipDecimation) { var objectName = gameObjectHandler?.Name ?? "Unknown"; @@ -742,13 +743,13 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase Task batchTask = batchChunks.Length == 0 ? Task.CompletedTask : Parallel.ForEachAsync(batchChunks, new ParallelOptions { MaxDegreeOfParallelism = workerDop, CancellationToken = ct }, - async (chunk, token) => await ProcessBatchChunkAsync(chunk, replacementLookup, rawSizeLookup, token, skipDownscale).ConfigureAwait(false)); + async (chunk, token) => await ProcessBatchChunkAsync(chunk, replacementLookup, rawSizeLookup, token, skipDownscale, skipDecimation).ConfigureAwait(false)); // direct downloads Task directTask = directDownloads.Count == 0 ? Task.CompletedTask : Parallel.ForEachAsync(directDownloads, new ParallelOptions { MaxDegreeOfParallelism = workerDop, CancellationToken = ct }, - async (d, token) => await ProcessDirectAsync(d, replacementLookup, rawSizeLookup, token, skipDownscale).ConfigureAwait(false)); + async (d, token) => await ProcessDirectAsync(d, replacementLookup, rawSizeLookup, token, skipDownscale, skipDecimation).ConfigureAwait(false)); await Task.WhenAll(batchTask, directTask).ConfigureAwait(false); @@ -761,7 +762,8 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase Dictionary replacementLookup, IReadOnlyDictionary rawSizeLookup, CancellationToken ct, - bool skipDownscale) + bool skipDownscale, + bool skipDecimation) { var statusKey = chunk.StatusKey; @@ -795,7 +797,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase return; } - await DecompressBlockFileAsync(statusKey, blockFile, replacementLookup, rawSizeLookup, fi.Name, ct, skipDownscale).ConfigureAwait(false); + await DecompressBlockFileAsync(statusKey, blockFile, replacementLookup, rawSizeLookup, fi.Name, ct, skipDownscale, skipDecimation).ConfigureAwait(false); } catch (OperationCanceledException) { @@ -817,7 +819,8 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase Dictionary replacementLookup, IReadOnlyDictionary rawSizeLookup, CancellationToken ct, - bool skipDownscale) + bool skipDownscale, + bool skipDecimation) { var progress = CreateInlineProgress(bytes => { @@ -827,7 +830,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase if (!ShouldUseDirectDownloads() || string.IsNullOrEmpty(directDownload.DirectDownloadUrl)) { - await ProcessDirectAsQueuedFallbackAsync(directDownload, replacementLookup, rawSizeLookup, progress, ct, skipDownscale).ConfigureAwait(false); + await ProcessDirectAsQueuedFallbackAsync(directDownload, replacementLookup, rawSizeLookup, progress, ct, skipDownscale, skipDecimation).ConfigureAwait(false); return; } @@ -875,7 +878,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase } await _fileCompactor.WriteAllBytesAsync(finalFilename, decompressedBytes, ct).ConfigureAwait(false); - PersistFileToStorage(directDownload.Hash, finalFilename, repl.GamePath, skipDownscale); + PersistFileToStorage(directDownload.Hash, finalFilename, repl.GamePath, skipDownscale, skipDecimation); MarkTransferredFiles(directDownload.DirectDownloadUrl!, 1); SetStatus(directDownload.DirectDownloadUrl!, DownloadStatus.Completed); @@ -902,7 +905,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase try { - await ProcessDirectAsQueuedFallbackAsync(directDownload, replacementLookup, rawSizeLookup, progress, ct, skipDownscale).ConfigureAwait(false); + await ProcessDirectAsQueuedFallbackAsync(directDownload, replacementLookup, rawSizeLookup, progress, ct, skipDownscale, skipDecimation).ConfigureAwait(false); if (!expectedDirectDownloadFailure && failureCount >= 3 && !_disableDirectDownloads) { @@ -932,7 +935,8 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase IReadOnlyDictionary rawSizeLookup, IProgress progress, CancellationToken ct, - bool skipDownscale) + bool skipDownscale, + bool skipDecimation) { if (string.IsNullOrEmpty(directDownload.DirectDownloadUrl)) throw new InvalidOperationException("Direct download fallback requested without a direct download URL."); @@ -957,7 +961,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase if (!File.Exists(blockFile)) throw new FileNotFoundException("Block file missing after direct download fallback.", blockFile); - await DecompressBlockFileAsync(statusKey, blockFile, replacementLookup, rawSizeLookup, $"fallback-{directDownload.Hash}", ct, skipDownscale) + await DecompressBlockFileAsync(statusKey, blockFile, replacementLookup, rawSizeLookup, $"fallback-{directDownload.Hash}", ct, skipDownscale, skipDecimation) .ConfigureAwait(false); } finally @@ -986,7 +990,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase return await response.Content.ReadFromJsonAsync>(cancellationToken: ct).ConfigureAwait(false) ?? []; } - private void PersistFileToStorage(string fileHash, string filePath, string gamePath, bool skipDownscale) + private void PersistFileToStorage(string fileHash, string filePath, string gamePath, bool skipDownscale, bool skipDecimation) { var fi = new FileInfo(filePath); @@ -1014,7 +1018,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase () => _textureMetadataHelper.DetermineMapKind(gamePath, filePath)); } - if (!skipDownscale && _modelDecimationService.ShouldScheduleDecimation(fileHash, filePath, gamePath)) + if (!skipDecimation && _modelDecimationService.ShouldScheduleDecimation(fileHash, filePath, gamePath)) { _modelDecimationService.ScheduleDecimation(fileHash, filePath, gamePath); }