diff --git a/Wabbajack.Common/GameMetaData.cs b/Wabbajack.Common/GameMetaData.cs index 0b16bef7..1e305f27 100644 --- a/Wabbajack.Common/GameMetaData.cs +++ b/Wabbajack.Common/GameMetaData.cs @@ -575,6 +575,7 @@ namespace Wabbajack.Common MO2Name = "Dragon Age: Origins", // Probably wrong SteamIDs = new List{47810}, OriginIDs = new List{"DR:208591800"}, + GOGIDs = new List{1949616134}, RequiredFiles = new List { @"bin_ship\daorigins.exe" diff --git a/Wabbajack.Common/Util/PhysicalDisk.cs b/Wabbajack.Common/Util/PhysicalDisk.cs new file mode 100644 index 00000000..89546e0d --- /dev/null +++ b/Wabbajack.Common/Util/PhysicalDisk.cs @@ -0,0 +1,64 @@ +using System; +using System.Linq; +using System.Management; + +namespace Wabbajack.Common +{ + public class PhysicalDisk + { + public PhysicalDisk(string driveLetter) + { + if (driveLetter.Length == 2 && driveLetter[1] == ':') driveLetter = driveLetter.Remove(driveLetter.Length - 1); + if (driveLetter.Length == 3 && (driveLetter[1] == ':' && driveLetter[2] == '\\')) driveLetter = driveLetter.Remove(driveLetter.Length - 2); + if (driveLetter.Length > 3) Utils.Error("Incorrect drive name! Must be X, X: or X:\\"); + + Utils.Log($"Phsyical Disk: {driveLetter}"); + + // Connect to storage scope + var scope = new ManagementScope(@"\\.\root\microsoft\windows\storage"); + scope.Connect(); + + // Search partitions that use requested the drive letter + var partitionSearcher = new ManagementObjectSearcher($"SELECT DiskNumber FROM MSFT_Partition WHERE DriveLetter='{driveLetter}'"); + partitionSearcher.Scope = scope; + // Get first partition that matches + var partition = partitionSearcher.Get().OfType().First(); + + // Search for a disk where the device ID matches the one we got from the partition + var physicalSearcher = new ManagementObjectSearcher($"SELECT * FROM MSFT_PhysicalDisk WHERE DeviceId='{partition["DiskNumber"]}'"); + physicalSearcher.Scope = scope; + // Get disk information + var physical = physicalSearcher.Get().Cast().Single(); + + DriveLetter = driveLetter; + DeviceId = (string)physical["DeviceId"]; + MediaType = (MediaTypes)Convert.ToInt32(physical["MediaType"]); + BusType = (BusTypes)Convert.ToInt32(physical["BusType"]); + } + + public string DriveLetter { get; } + + public string DeviceId { get; } + + public MediaTypes MediaType { get; } + + public BusTypes BusType { get; } + + // https://docs.microsoft.com/en-us/previous-versions/windows/desktop/stormgmt/msft-physicaldisk#properties + public enum MediaTypes + { + Unspecified = 0, + HDD = 3, + SSD = 4, + SCM = 5 + } + + public enum BusTypes + { + Unknown = 0, + USB = 7, + SATA = 11, + NVMe = 17 + } + } +} diff --git a/Wabbajack.Common/Wabbajack.Common.csproj b/Wabbajack.Common/Wabbajack.Common.csproj index a3e40f84..9cad5c57 100644 --- a/Wabbajack.Common/Wabbajack.Common.csproj +++ b/Wabbajack.Common/Wabbajack.Common.csproj @@ -57,6 +57,7 @@ + diff --git a/Wabbajack.Lib/ABatchProcessor.cs b/Wabbajack.Lib/ABatchProcessor.cs index 0131c827..adde2f64 100644 --- a/Wabbajack.Lib/ABatchProcessor.cs +++ b/Wabbajack.Lib/ABatchProcessor.cs @@ -15,8 +15,9 @@ namespace Wabbajack.Lib { public WorkQueue Queue { get; } = new WorkQueue(); - public int DownloadThreads { get; set; } = Environment.ProcessorCount; + public int DownloadThreads { get; set; } = Environment.ProcessorCount <= 8 ? Environment.ProcessorCount : 8; public int DiskThreads { get; set; } = Environment.ProcessorCount; + public bool ReduceHDDThreads { get; set; } = true; public bool FavorPerfOverRam { get; set; } = false; public Context VFS { get; } diff --git a/Wabbajack.Lib/AInstaller.cs b/Wabbajack.Lib/AInstaller.cs index 7fa918b5..8930f44b 100644 --- a/Wabbajack.Lib/AInstaller.cs +++ b/Wabbajack.Lib/AInstaller.cs @@ -114,6 +114,7 @@ namespace Wabbajack.Lib public async Task InstallArchives() { + var grouped = ModList.Directives .OfType() .Select(a => new {VF = VFS.Index.FileForArchiveHashPath(a.ArchiveHashPath), Directive = a}) @@ -218,8 +219,6 @@ namespace Wabbajack.Lib } } - DesiredThreads.OnNext(DownloadThreads); - await missing.Where(a => a.State.GetType() != typeof(ManualDownloader.State)) .PMap(Queue, UpdateTracker, async archive => { @@ -240,9 +239,6 @@ namespace Wabbajack.Lib return await DownloadArchive(archive, download, outputPath); }); - - DesiredThreads.OnNext(DiskThreads); - } private async Task SendDownloadMetrics(List missing) @@ -289,7 +285,7 @@ namespace Wabbajack.Lib var toHash = ModList.Archives.Where(a => hashDict.ContainsKey(a.Size)).SelectMany(a => hashDict[a.Size]).ToList(); Utils.Log($"Found {allFiles.Count} total files, {toHash.Count} matching filesize"); - + var hashResults = await toHash .PMap(Queue, UpdateTracker,async e => (await e.FileHashCachedAsync(), e)); diff --git a/Wabbajack.Lib/MO2Installer.cs b/Wabbajack.Lib/MO2Installer.cs index 7f1a9aa4..127b6d03 100644 --- a/Wabbajack.Lib/MO2Installer.cs +++ b/Wabbajack.Lib/MO2Installer.cs @@ -56,7 +56,8 @@ namespace Wabbajack.Lib await Metrics.Send(Metrics.BeginInstall, ModList.Name); Utils.Log("Configuring Processor"); - DesiredThreads.OnNext(DiskThreads); + if (new PhysicalDisk(OutputFolder.DriveInfo().Name).MediaType == PhysicalDisk.MediaTypes.HDD && ReduceHDDThreads) DesiredThreads.OnNext(1); else DesiredThreads.OnNext(DiskThreads); + FileExtractor2.FavorPerfOverRAM = FavorPerfOverRam; if (GameFolder == null) @@ -123,14 +124,23 @@ namespace Wabbajack.Lib UpdateTracker.NextStep("Optimizing ModList"); await OptimizeModlist(); + // Reduce to one thread if downloads on HDD, else use specified. Hashing on HDD has no benefit with more threads. + if (new PhysicalDisk(DownloadFolder.DriveInfo().Name).MediaType == PhysicalDisk.MediaTypes.HDD && ReduceHDDThreads) DesiredThreads.OnNext(1); else DesiredThreads.OnNext(DiskThreads); + if (cancel.IsCancellationRequested) return false; UpdateTracker.NextStep("Hashing Archives"); await HashArchives(); + // Set to download thread count. + DesiredThreads.OnNext(DownloadThreads); + if (cancel.IsCancellationRequested) return false; UpdateTracker.NextStep("Downloading Missing Archives"); await DownloadArchives(); + // Reduce to one thread if downloads on HDD, else use specified. Hashing on HDD has no benefit with more threads. + if (new PhysicalDisk(DownloadFolder.DriveInfo().Name).MediaType == PhysicalDisk.MediaTypes.HDD && ReduceHDDThreads) DesiredThreads.OnNext(1); else DesiredThreads.OnNext(DiskThreads); + if (cancel.IsCancellationRequested) return false; UpdateTracker.NextStep("Hashing Remaining Archives"); await HashArchives(); @@ -146,6 +156,9 @@ namespace Wabbajack.Lib Error("Cannot continue, was unable to download one or more archives"); } + // Reduce to two threads if output on HDD, else use specified. Installing files seems to have a slight benefit with two threads. + if (new PhysicalDisk(OutputFolder.DriveInfo().Name).MediaType == PhysicalDisk.MediaTypes.HDD && ReduceHDDThreads) DesiredThreads.OnNext(2); else DesiredThreads.OnNext(DiskThreads); + if (cancel.IsCancellationRequested) return false; UpdateTracker.NextStep("Extracting Modlist contents"); await ExtractModlist(); diff --git a/Wabbajack/Settings.cs b/Wabbajack/Settings.cs index d4990ac8..963ce5ac 100644 --- a/Wabbajack/Settings.cs +++ b/Wabbajack/Settings.cs @@ -116,9 +116,10 @@ namespace Wabbajack { public PerformanceSettings() { + _reduceHDDThreads = true; _favorPerfOverRam = false; _diskThreads = Environment.ProcessorCount; - _downloadThreads = Environment.ProcessorCount; + _downloadThreads = Environment.ProcessorCount <= 8 ? Environment.ProcessorCount : 8; } private int _downloadThreads; @@ -126,7 +127,10 @@ namespace Wabbajack private int _diskThreads; public int DiskThreads { get => _diskThreads; set => RaiseAndSetIfChanged(ref _diskThreads, value); } - + + private bool _reduceHDDThreads; + public bool ReduceHDDThreads { get => _reduceHDDThreads; set => RaiseAndSetIfChanged(ref _reduceHDDThreads, value); } + private bool _favorPerfOverRam; public bool FavorPerfOverRam { get => _favorPerfOverRam; set => RaiseAndSetIfChanged(ref _favorPerfOverRam, value); } @@ -135,6 +139,7 @@ namespace Wabbajack { processor.DownloadThreads = DownloadThreads; processor.DiskThreads = DiskThreads; + processor.ReduceHDDThreads = ReduceHDDThreads; processor.FavorPerfOverRam = FavorPerfOverRam; } } diff --git a/Wabbajack/View Models/Installers/MO2InstallerVM.cs b/Wabbajack/View Models/Installers/MO2InstallerVM.cs index a49bb832..2499f462 100644 --- a/Wabbajack/View Models/Installers/MO2InstallerVM.cs +++ b/Wabbajack/View Models/Installers/MO2InstallerVM.cs @@ -76,18 +76,30 @@ namespace Wabbajack }) .ToProperty(this, nameof(CanInstall)); - // Have Installation location updates modify the downloads location if empty + // Have Installation location updates modify the downloads location if empty or the same path this.WhenAny(x => x.Location.TargetPath) .Skip(1) // Don't do it initially .Subscribe(installPath => { - if (DownloadLocation.TargetPath == default) + if (DownloadLocation.TargetPath == default || DownloadLocation.TargetPath == installPath) { DownloadLocation.TargetPath = installPath.Combine("downloads"); } }) .DisposeWith(CompositeDisposable); + // Have Download location updates change if the same as the install path + this.WhenAny(x => x.DownloadLocation.TargetPath) + .Skip(1) // Don't do it initially + .Subscribe(downloadPath => + { + if (downloadPath == Location.TargetPath) + { + DownloadLocation.TargetPath = Location.TargetPath.Combine("downloads"); + } + }) + .DisposeWith(CompositeDisposable); + // Load settings _CurrentSettings = installerVM.WhenAny(x => x.ModListLocation.TargetPath) .Select(path => path == default ? null : installerVM.MWVM.Settings.Installer.Mo2ModlistSettings.TryCreate(path)) diff --git a/Wabbajack/Views/Settings/PerformanceSettingsView.xaml b/Wabbajack/Views/Settings/PerformanceSettingsView.xaml index 45c2f730..9e539732 100644 --- a/Wabbajack/Views/Settings/PerformanceSettingsView.xaml +++ b/Wabbajack/Views/Settings/PerformanceSettingsView.xaml @@ -26,6 +26,7 @@ + @@ -67,10 +68,20 @@ Maximum="128" Minimum="2" /> + + - x, viewToVmConverter: x => (int)(x ?? 0)) .DisposeWith(disposable); + this.BindStrict(this.ViewModel, x => x.ReduceHDDThreads, x => x.ReduceHDDThreads.IsChecked) + .DisposeWith(disposable); this.BindStrict(this.ViewModel, x => x.FavorPerfOverRam, x => x.FavorPerfOverRam.IsChecked) .DisposeWith(disposable); });