From 1669f8c88700a78291a663ef726b473d4e94191d Mon Sep 17 00:00:00 2001 From: pointfeev Date: Sun, 28 May 2023 21:20:35 -0400 Subject: [PATCH] v4.9.1 - Hash & concurrency fixes to stop duplicate games & DLC - Threading fixes for significantly faster operations - More significant refactoring to prevent possible issues --- CreamInstaller/Components/CustomTreeView.cs | 26 ++- .../Components/PlatformIdComparer.cs | 20 +- CreamInstaller/CreamInstaller.csproj | 2 +- CreamInstaller/Forms/InstallForm.cs | 13 +- CreamInstaller/Forms/SelectForm.cs | 189 +++++++----------- CreamInstaller/Forms/UpdateForm.cs | 28 +-- CreamInstaller/Platforms/Epic/EpicLibrary.cs | 10 +- .../Platforms/Epic/Heroic/HeroicAppData.cs | 2 - .../Platforms/Epic/Heroic/HeroicLibrary.cs | 6 +- .../Platforms/Paradox/ParadoxLauncher.cs | 7 +- CreamInstaller/Platforms/Steam/SteamCMD.cs | 19 +- .../Platforms/Steam/SteamLibrary.cs | 10 +- CreamInstaller/Platforms/Steam/SteamStore.cs | 7 +- .../Platforms/Ubisoft/UbisoftLibrary.cs | 10 +- CreamInstaller/Resources/Koaloader.cs | 2 +- CreamInstaller/Resources/Resources.cs | 43 ++-- CreamInstaller/Resources/ScreamAPI.cs | 10 +- CreamInstaller/Resources/SmokeAPI.cs | 11 +- CreamInstaller/Resources/UplayR1.cs | 15 +- CreamInstaller/Resources/UplayR2.cs | 6 +- CreamInstaller/Selection.cs | 61 ++++-- CreamInstaller/SelectionDLC.cs | 38 +++- 22 files changed, 263 insertions(+), 272 deletions(-) diff --git a/CreamInstaller/Components/CustomTreeView.cs b/CreamInstaller/Components/CustomTreeView.cs index 154bf1a..d21b564 100644 --- a/CreamInstaller/Components/CustomTreeView.cs +++ b/CreamInstaller/Components/CustomTreeView.cs @@ -76,16 +76,24 @@ internal sealed class CustomTreeView : TreeView Form form = FindForm(); if (form is not SelectForm and not SelectDialogForm) return; - string platformId = node.Name; + string id = node.Name; Platform platform = (node.Tag as Platform?).GetValueOrDefault(Platform.None); - if (string.IsNullOrWhiteSpace(platformId) || platform is Platform.None) + DLCType dlcType = (node.Tag as DLCType?).GetValueOrDefault(DLCType.None); + if (string.IsNullOrWhiteSpace(id) || platform is Platform.None && dlcType is DLCType.None) return; Color color = highlighted ? C1 : Enabled ? C2 : C3; - string text = platform.ToString(); + string text; + if (dlcType is not DLCType.None) + { + SelectionDLC dlc = SelectionDLC.FromTypeId(dlcType, id); + text = dlc is not null ? dlc.Selection.Platform.ToString() : dlcType.ToString(); + } + else + text = platform.ToString(); Size size = TextRenderer.MeasureText(graphics, text, font); bounds = bounds with { X = bounds.X + bounds.Width, Width = size.Width }; selectionBounds = new(selectionBounds.Location, selectionBounds.Size + bounds.Size with { Height = 0 }); @@ -99,7 +107,7 @@ internal sealed class CustomTreeView : TreeView : Enabled ? C5 : C6; - text = platformId; + text = id; size = TextRenderer.MeasureText(graphics, text, font); const int left = -4; bounds = bounds with { X = bounds.X + bounds.Width + left, Width = size.Width }; @@ -110,7 +118,7 @@ internal sealed class CustomTreeView : TreeView } if (form is SelectForm) { - Selection selection = Selection.FromPlatformId(platform, platformId); + Selection selection = Selection.FromPlatformId(platform, id); if (selection is not null) { if (bounds == node.Bounds) @@ -188,15 +196,15 @@ internal sealed class CustomTreeView : TreeView return; if (comboBoxBounds.Count > 0 && selectForm is not null) foreach (KeyValuePair pair in comboBoxBounds) - if (!Selection.All.Contains(pair.Key)) + if (!Selection.All.ContainsKey(pair.Key)) _ = comboBoxBounds.Remove(pair.Key); else if (pair.Value.Contains(clickPoint)) { - List proxies = EmbeddedResources.FindAll(r => r.StartsWith("Koaloader", StringComparison.Ordinal)).Select(p => + HashSet proxies = EmbeddedResources.Where(r => r.StartsWith("Koaloader", StringComparison.Ordinal)).Select(p => { p.GetProxyInfoFromIdentifier(out string proxyName, out _); return proxyName; - }).Distinct().ToList(); + }).ToHashSet(); comboBoxDropDown ??= new(); comboBoxDropDown.ShowItemToolTips = false; comboBoxDropDown.Items.Clear(); @@ -222,7 +230,7 @@ internal sealed class CustomTreeView : TreeView break; } foreach (KeyValuePair pair in checkBoxBounds) - if (!Selection.All.Contains(pair.Key)) + if (!Selection.All.ContainsKey(pair.Key)) _ = checkBoxBounds.Remove(pair.Key); else if (pair.Value.Contains(clickPoint)) { diff --git a/CreamInstaller/Components/PlatformIdComparer.cs b/CreamInstaller/Components/PlatformIdComparer.cs index d6c43dd..2c87d13 100644 --- a/CreamInstaller/Components/PlatformIdComparer.cs +++ b/CreamInstaller/Components/PlatformIdComparer.cs @@ -8,12 +8,10 @@ namespace CreamInstaller.Components; internal static class PlatformIdComparer { private static StringComparer stringComparer; - private static NodeComparer nodeComparer; - private static NodeNameComparer nodeNameComparer; - private static NodeTextComparer nodeTextComparer; + internal static StringComparer String => stringComparer ??= new(); internal static NodeComparer Node => nodeComparer ??= new(); internal static NodeNameComparer NodeName => nodeNameComparer ??= new(); @@ -39,15 +37,17 @@ internal sealed class StringComparer : IComparer internal sealed class NodeComparer : IComparer { public int Compare(TreeNode a, TreeNode b) - => a?.Tag is not Platform A + => a is null ? 1 - : b?.Tag is not Platform B + : b is null ? -1 - : A > B - ? 1 - : A < B - ? -1 - : 0; + : a.Tag is not Platform pA || b.Tag is not Platform pB + ? 0 + : pA > pB + ? 1 + : pA < pB + ? -1 + : 0; } internal sealed class NodeNameComparer : IComparer diff --git a/CreamInstaller/CreamInstaller.csproj b/CreamInstaller/CreamInstaller.csproj index bc8425b..4defe3c 100644 --- a/CreamInstaller/CreamInstaller.csproj +++ b/CreamInstaller/CreamInstaller.csproj @@ -4,7 +4,7 @@ net7.0-windows True Resources\ini.ico - 4.9.0 + 4.9.1 2021, pointfeev (https://github.com/pointfeev) CreamInstaller Automatic DLC Unlocker Installer & Configuration Generator diff --git a/CreamInstaller/Forms/InstallForm.cs b/CreamInstaller/Forms/InstallForm.cs index fa05a30..e7264b1 100644 --- a/CreamInstaller/Forms/InstallForm.cs +++ b/CreamInstaller/Forms/InstallForm.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; -using System.Threading; using System.Threading.Tasks; using CreamInstaller.Components; using CreamInstaller.Resources; @@ -15,7 +14,7 @@ namespace CreamInstaller.Forms; internal sealed partial class InstallForm : CustomForm { - private readonly List disabledSelections = new(); + private readonly HashSet disabledSelections = new(); private readonly int programCount = Selection.AllEnabled.Count; private readonly bool uninstalling; @@ -87,7 +86,6 @@ internal sealed partial class InstallForm : CustomForm UpdateUser("Uninstalling Koaloader from " + selection.Name + $" in incorrect directory \"{directory}\" . . . ", LogTextBox.Operation); await Koaloader.Uninstall(directory, selection.RootDirectory, this); } - Thread.Sleep(1); } if (uninstalling || !selection.Koaloader) foreach ((string directory, BinaryType _) in selection.ExecutableDirectories) @@ -101,7 +99,6 @@ internal sealed partial class InstallForm : CustomForm UpdateUser("Uninstalling Koaloader from " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation); await Koaloader.Uninstall(directory, selection.RootDirectory, this); } - Thread.Sleep(1); } bool uninstallProxy = uninstalling || selection.Koaloader; int count = selection.DllDirectories.Count, cur = 0; @@ -173,7 +170,6 @@ internal sealed partial class InstallForm : CustomForm } } UpdateProgress(++cur / count * 100); - Thread.Sleep(1); } if (selection.Koaloader && !uninstalling) foreach ((string directory, BinaryType binaryType) in selection.ExecutableDirectories) @@ -182,14 +178,13 @@ internal sealed partial class InstallForm : CustomForm throw new CustomMessageException("The operation was canceled."); UpdateUser("Installing Koaloader to " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation); await Koaloader.Install(directory, binaryType, selection, selection.RootDirectory, this); - Thread.Sleep(1); } UpdateProgress(100); } private async Task Operate() { - List programSelections = Selection.AllEnabled; + HashSet programSelections = Selection.AllEnabled; operationsCount = programSelections.Count; completeOperationsCount = 0; foreach (Selection selection in programSelections) @@ -201,7 +196,7 @@ internal sealed partial class InstallForm : CustomForm await OperateFor(selection); UpdateUser($"Operation succeeded for {selection.Name}.", LogTextBox.Success); selection.Enabled = false; - disabledSelections.Add(selection); + _ = disabledSelections.Add(selection); } catch (Exception exception) { @@ -210,7 +205,7 @@ internal sealed partial class InstallForm : CustomForm ++completeOperationsCount; } Program.Cleanup(); - List failedSelections = Selection.AllEnabled; + HashSet failedSelections = Selection.AllEnabled; if (failedSelections.Count > 0) if (failedSelections.Count == 1) throw new CustomMessageException($"Operation failed for {failedSelections.First().Name}."); diff --git a/CreamInstaller/Forms/SelectForm.cs b/CreamInstaller/Forms/SelectForm.cs index bc60842..9a656d8 100644 --- a/CreamInstaller/Forms/SelectForm.cs +++ b/CreamInstaller/Forms/SelectForm.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Drawing; +using System.IO; using System.Linq; using System.Text; using System.Threading; @@ -128,30 +129,16 @@ internal sealed partial class SelectForm : CustomForm HashSet dllDirectories = await ParadoxLauncher.InstallPath.GetDllDirectoriesFromGameDirectory(Platform.Paradox); if (dllDirectories is not null) { + Selection selection = Selection.GetOrCreate(Platform.Paradox, "PL", "Paradox Launcher", ParadoxLauncher.InstallPath, dllDirectories, + await ParadoxLauncher.InstallPath.GetExecutableDirectories(validFunc: path => !Path.GetFileName(path).Contains("bootstrapper"))); if (uninstallAll) - { - Selection bareSelection = Selection.FromPlatformId(Platform.Paradox, "PL") ?? new(); - bareSelection.Enabled = true; - bareSelection.Id = "PL"; - bareSelection.Name = "Paradox Launcher"; - bareSelection.RootDirectory = ParadoxLauncher.InstallPath; - bareSelection.ExecutableDirectories = await ParadoxLauncher.GetExecutableDirectories(bareSelection.RootDirectory); - bareSelection.DllDirectories = dllDirectories; - bareSelection.Platform = Platform.Paradox; - } + selection.Enabled = true; else { - Selection selection = Selection.FromPlatformId(Platform.Paradox, "PL") ?? new(); if (allCheckBox.Checked) selection.Enabled = true; if (koaloaderAllCheckBox.Checked) selection.Koaloader = true; - selection.Id = "PL"; - selection.Name = "Paradox Launcher"; - selection.RootDirectory = ParadoxLauncher.InstallPath; - selection.ExecutableDirectories = await ParadoxLauncher.GetExecutableDirectories(selection.RootDirectory); - selection.DllDirectories = dllDirectories; - selection.Platform = Platform.Paradox; TreeNode programNode = treeNodes.Find(s => s.Tag is Platform.Paradox && s.Name == selection.Id) ?? new TreeNode(); programNode.Tag = selection.Platform; programNode.Name = selection.Id; @@ -166,7 +153,7 @@ internal sealed partial class SelectForm : CustomForm int steamGamesToCheck; if (uninstallAll || programsToScan.Any(c => c.platform is Platform.Steam)) { - HashSet<(string appId, string name, string branch, int buildId, string gameDirectory)> steamGames = await SteamLibrary.GetGames(); + List<(string appId, string name, string branch, int buildId, string gameDirectory)> steamGames = await SteamLibrary.GetGames(); steamGamesToCheck = steamGames.Count; foreach ((string appId, string name, string branch, int buildId, string gameDirectory) in steamGames) { @@ -191,14 +178,9 @@ internal sealed partial class SelectForm : CustomForm } if (uninstallAll) { - Selection bareSelection = Selection.FromPlatformId(Platform.Steam, appId) ?? new Selection(); + Selection bareSelection = Selection.GetOrCreate(Platform.Steam, appId, name, gameDirectory, dllDirectories, + await gameDirectory.GetExecutableDirectories(true)); bareSelection.Enabled = true; - bareSelection.Id = appId; - bareSelection.Name = name; - bareSelection.RootDirectory = gameDirectory; - bareSelection.ExecutableDirectories = await SteamLibrary.GetExecutableDirectories(bareSelection.RootDirectory); - bareSelection.DllDirectories = dllDirectories; - bareSelection.Platform = Platform.Steam; RemoveFromRemainingGames(name); return; } @@ -214,14 +196,15 @@ internal sealed partial class SelectForm : CustomForm } if (Program.Canceled) return; - ConcurrentDictionary dlc = new(); + ConcurrentDictionary dlc = new(); List dlcTasks = new(); - List dlcIds = new(); + HashSet dlcIds = new(); if (appData is not null) - dlcIds.AddRange(await SteamStore.ParseDlcAppIds(appData)); + foreach (string dlcId in await SteamStore.ParseDlcAppIds(appData)) + _ = dlcIds.Add(dlcId); if (appInfo is not null) - dlcIds.AddRange(await SteamCMD.ParseDlcAppIds(appInfo)); - dlcIds = dlcIds.Distinct().ToList(); + foreach (string dlcId in await SteamCMD.ParseDlcAppIds(appInfo)) + _ = dlcIds.Add(dlcId); if (dlcIds.Count > 0) foreach (string dlcAppId in dlcIds) { @@ -291,20 +274,20 @@ internal sealed partial class SelectForm : CustomForm if (Program.Canceled) return; if (!string.IsNullOrWhiteSpace(fullGameName)) - dlc[fullGameAppId] = new() - { - Id = fullGameAppId, Type = fullGameOnSteamStore ? DLCType.Steam : DLCType.SteamHidden, Name = fullGameName, - Icon = fullGameIcon - }; + { + SelectionDLC fullGameDlc = SelectionDLC.GetOrCreate(fullGameOnSteamStore ? DLCType.Steam : DLCType.SteamHidden, + fullGameAppId, fullGameName); + fullGameDlc.Icon = fullGameIcon; + _ = dlc.TryAdd(fullGameDlc, default); + } } if (Program.Canceled) return; if (string.IsNullOrWhiteSpace(dlcName)) dlcName = "Unknown"; - dlc[dlcAppId] = new() - { - Id = dlcAppId, Type = onSteamStore ? DLCType.Steam : DLCType.SteamHidden, Name = dlcName, Icon = dlcIcon - }; + SelectionDLC _dlc = SelectionDLC.GetOrCreate(onSteamStore ? DLCType.Steam : DLCType.SteamHidden, dlcAppId, dlcName); + _dlc.Icon = dlcIcon; + _ = dlc.TryAdd(_dlc, default); RemoveFromRemainingDLCs(dlcAppId); }); dlcTasks.Add(task); @@ -328,17 +311,12 @@ internal sealed partial class SelectForm : CustomForm RemoveFromRemainingGames(name); return; } - Selection selection = Selection.FromPlatformId(Platform.Steam, appId) ?? new Selection(); + Selection selection = Selection.GetOrCreate(Platform.Steam, appId, appData?.Name ?? name, gameDirectory, dllDirectories, + await gameDirectory.GetExecutableDirectories(true)); selection.Enabled = allCheckBox.Checked || selection.DLC.Any(dlc => dlc.Enabled) || selection.ExtraSelections.Any(extraSelection => extraSelection.DLC.Any(dlc => dlc.Enabled)); if (koaloaderAllCheckBox.Checked) selection.Koaloader = true; - selection.Id = appId; - selection.Name = appData?.Name ?? name; - selection.RootDirectory = gameDirectory; - selection.ExecutableDirectories = await SteamLibrary.GetExecutableDirectories(selection.RootDirectory); - selection.DllDirectories = dllDirectories; - selection.Platform = Platform.Steam; selection.Product = "https://store.steampowered.com/app/" + appId; selection.Icon = IconGrabber.SteamAppImagesPath + @$"\{appId}\{appInfo?.Value.GetChild("common")?.GetChild("icon")}.jpg"; selection.SubIcon = appData?.HeaderImage ?? IconGrabber.SteamAppImagesPath @@ -351,21 +329,21 @@ internal sealed partial class SelectForm : CustomForm { if (Program.Canceled) return; - TreeNode programNode = treeNodes.Find(s => s.Tag is Platform.Steam && s.Name == appId) ?? new TreeNode(); + TreeNode programNode = treeNodes.Find(s => (Platform)s.Tag == selection.Platform && s.Name == appId) ?? new TreeNode(); programNode.Tag = selection.Platform; programNode.Name = appId; programNode.Text = appData?.Name ?? name; programNode.Checked = selection.Enabled; if (programNode.TreeView is null) _ = selectionTreeView.Nodes.Add(programNode); - foreach ((_, SelectionDLC dlc) in dlc) + foreach ((SelectionDLC dlc, _) in dlc) { if (Program.Canceled) return; dlc.Selection = selection; dlc.Enabled = dlc.Name != "Unknown" && allCheckBox.Checked; - TreeNode dlcNode = treeNodes.Find(s => s.Tag is Platform.Steam && s.Name == dlc.Id) ?? new TreeNode(); - dlcNode.Tag = selection.Platform; + TreeNode dlcNode = treeNodes.Find(s => (DLCType)s.Tag == dlc.Type && s.Name == dlc.Id) ?? new TreeNode(); + dlcNode.Tag = dlc.Type; dlcNode.Name = dlc.Id; dlcNode.Text = dlc.Name; dlcNode.Checked = dlc.Enabled; @@ -382,7 +360,7 @@ internal sealed partial class SelectForm : CustomForm } if (uninstallAll || programsToScan.Any(c => c.platform is Platform.Epic)) { - HashSet epicGames = await EpicLibrary.GetGames(); + List epicGames = await EpicLibrary.GetGames(); foreach (Manifest manifest in epicGames) { string @namespace = manifest.CatalogNamespace; @@ -405,22 +383,17 @@ internal sealed partial class SelectForm : CustomForm } if (uninstallAll) { - Selection bareSelection = Selection.FromPlatformId(Platform.Epic, @namespace) ?? new Selection(); + Selection bareSelection = Selection.GetOrCreate(Platform.Epic, @namespace, name, directory, dllDirectories, + await directory.GetExecutableDirectories(true)); bareSelection.Enabled = true; - bareSelection.Id = @namespace; - bareSelection.Name = name; - bareSelection.RootDirectory = directory; - bareSelection.ExecutableDirectories = await EpicLibrary.GetExecutableDirectories(bareSelection.RootDirectory); - bareSelection.DllDirectories = dllDirectories; - bareSelection.Platform = Platform.Epic; RemoveFromRemainingGames(name); return; } if (Program.Canceled) return; - ConcurrentDictionary catalogItems = new(); + ConcurrentDictionary catalogItems = new(); // get catalog items - ConcurrentDictionary entitlements = new(); + ConcurrentDictionary entitlements = new(); List dlcTasks = new(); List<(string id, string name, string product, string icon, string developer)> entitlementIds = await EpicStore.QueryEntitlements(@namespace); @@ -434,11 +407,11 @@ internal sealed partial class SelectForm : CustomForm { if (Program.Canceled) return; - entitlements[id] = new() - { - Id = id, Name = name, Product = product, Icon = icon, - Publisher = developer - }; + SelectionDLC entitlement = SelectionDLC.GetOrCreate(DLCType.EpicEntitlement, id, name); + entitlement.Icon = icon; + entitlement.Product = product; + entitlement.Publisher = developer; + _ = entitlements.TryAdd(entitlement, default); RemoveFromRemainingDLCs(id); }); dlcTasks.Add(task); @@ -456,24 +429,19 @@ internal sealed partial class SelectForm : CustomForm RemoveFromRemainingGames(name); return; } - Selection selection = Selection.FromPlatformId(Platform.Epic, @namespace) ?? new Selection(); + Selection selection = Selection.GetOrCreate(Platform.Epic, @namespace, name, directory, dllDirectories, + await directory.GetExecutableDirectories(true)); selection.Enabled = allCheckBox.Checked || selection.DLC.Any(dlc => dlc.Enabled) || selection.ExtraSelections.Any(extraSelection => extraSelection.DLC.Any(dlc => dlc.Enabled)); if (koaloaderAllCheckBox.Checked) selection.Koaloader = true; - selection.Id = @namespace; - selection.Name = name; - selection.RootDirectory = directory; - selection.ExecutableDirectories = await EpicLibrary.GetExecutableDirectories(selection.RootDirectory); - selection.DllDirectories = dllDirectories; - selection.Platform = Platform.Epic; - foreach (KeyValuePair dlc in entitlements.Where(dlc => dlc.Value.Name == selection.Name)) + foreach ((SelectionDLC dlc, _) in entitlements.Where(dlc => dlc.Key.Name == selection.Name)) { if (Program.Canceled) return; - selection.Product = "https://www.epicgames.com/store/product/" + dlc.Value.Product; - selection.Icon = dlc.Value.Icon; - selection.Publisher = dlc.Value.Publisher; + selection.Product = "https://www.epicgames.com/store/product/" + dlc.Product; + selection.Icon = dlc.Icon; + selection.Publisher = dlc.Publisher; } if (Program.Canceled) return; @@ -481,7 +449,7 @@ internal sealed partial class SelectForm : CustomForm { if (Program.Canceled) return; - TreeNode programNode = treeNodes.Find(s => s.Tag is Platform.Epic && s.Name == @namespace) ?? new TreeNode(); + TreeNode programNode = treeNodes.Find(s => (Platform)s.Tag == selection.Platform && s.Name == @namespace) ?? new TreeNode(); programNode.Tag = selection.Platform; programNode.Name = @namespace; programNode.Text = name; @@ -489,20 +457,21 @@ internal sealed partial class SelectForm : CustomForm if (programNode.TreeView is null) _ = selectionTreeView.Nodes.Add(programNode); if (!catalogItems.IsEmpty) - /*TreeNode catalogItemsNode = treeNodes.Find(node => node.Tag is Platform.Epic && node.Name == @namespace + "_catalogItems") ?? new(); + /*TreeNode catalogItemsNode = treeNodes.Find(node => (Platform)s.Tag == selection.Platform && node.Name == @namespace + "_catalogItems") ?? new(); + catalogItemsNode.Tag = selection.Platform; catalogItemsNode.Name = @namespace + "_catalogItems"; catalogItemsNode.Text = "Catalog Items"; catalogItemsNode.Checked = selection.DLC.Any(dlc => dlc.Type is DLCType.EpicCatalogItem && dlc.Enabled); if (catalogItemsNode.Parent is null) _ = programNode.Nodes.Add(catalogItemsNode);*/ - foreach ((_, SelectionDLC dlc) in catalogItems) + foreach ((SelectionDLC dlc, _) in catalogItems) { if (Program.Canceled) return; dlc.Selection = selection; dlc.Enabled = allCheckBox.Checked; - TreeNode dlcNode = treeNodes.Find(s => s.Tag is Platform.Epic && s.Name == dlc.Id) ?? new TreeNode(); - dlcNode.Tag = selection.Platform; + TreeNode dlcNode = treeNodes.Find(s => (DLCType)s.Tag == dlc.Type && s.Name == dlc.Id) ?? new TreeNode(); + dlcNode.Tag = dlc.Type; dlcNode.Name = dlc.Id; dlcNode.Text = dlc.Name; dlcNode.Checked = dlc.Enabled; @@ -511,20 +480,21 @@ internal sealed partial class SelectForm : CustomForm } if (entitlements.IsEmpty) return; - /*TreeNode entitlementsNode = treeNodes.Find(node => node.Tag is Platform.Epic && node.Name == @namespace + "_entitlements") ?? new(); + /*TreeNode entitlementsNode = treeNodes.Find(node => (Platform)s.Tag == selection.Platform && node.Name == @namespace + "_entitlements") ?? new(); + entitlementsNode.Name = selection.Platform; entitlementsNode.Name = @namespace + "_entitlements"; entitlementsNode.Text = "Entitlements"; entitlementsNode.Checked = selection.DLC.Any(dlc => dlc.Type is DLCType.EpicEntitlement && dlc.Enabled); if (entitlementsNode.Parent is null) _ = programNode.Nodes.Add(entitlementsNode);*/ - foreach ((_, SelectionDLC dlc) in entitlements) + foreach ((SelectionDLC dlc, _) in entitlements) { if (Program.Canceled) return; dlc.Selection = selection; dlc.Enabled = allCheckBox.Checked; - TreeNode dlcNode = treeNodes.Find(s => s.Tag is Platform.Epic && s.Name == dlc.Id) ?? new TreeNode(); - dlcNode.Tag = selection.Platform; + TreeNode dlcNode = treeNodes.Find(s => (DLCType)s.Tag == dlc.Type && s.Name == dlc.Id) ?? new TreeNode(); + dlcNode.Tag = dlc.Type; dlcNode.Name = dlc.Id; dlcNode.Text = dlc.Name; dlcNode.Checked = dlc.Enabled; @@ -541,7 +511,7 @@ internal sealed partial class SelectForm : CustomForm } if (uninstallAll || programsToScan.Any(c => c.platform is Platform.Ubisoft)) { - HashSet<(string gameId, string name, string gameDirectory)> ubisoftGames = await UbisoftLibrary.GetGames(); + List<(string gameId, string name, string gameDirectory)> ubisoftGames = await UbisoftLibrary.GetGames(); foreach ((string gameId, string name, string gameDirectory) in ubisoftGames) { if (Program.Canceled) @@ -561,36 +531,26 @@ internal sealed partial class SelectForm : CustomForm } if (uninstallAll) { - Selection bareSelection = Selection.FromPlatformId(Platform.Ubisoft, gameId) ?? new Selection(); + Selection bareSelection = Selection.GetOrCreate(Platform.Ubisoft, gameId, name, gameDirectory, dllDirectories, + await gameDirectory.GetExecutableDirectories(true)); bareSelection.Enabled = true; - bareSelection.Id = gameId; - bareSelection.Name = name; - bareSelection.RootDirectory = gameDirectory; - bareSelection.ExecutableDirectories = await UbisoftLibrary.GetExecutableDirectories(bareSelection.RootDirectory); - bareSelection.DllDirectories = dllDirectories; - bareSelection.Platform = Platform.Ubisoft; RemoveFromRemainingGames(name); return; } if (Program.Canceled) return; - Selection selection = Selection.FromPlatformId(Platform.Ubisoft, gameId) ?? new Selection(); + Selection selection = Selection.GetOrCreate(Platform.Ubisoft, gameId, name, gameDirectory, dllDirectories, + await gameDirectory.GetExecutableDirectories(true)); selection.Enabled = allCheckBox.Checked || selection.DLC.Any(dlc => dlc.Enabled) || selection.ExtraSelections.Any(extraSelection => extraSelection.DLC.Any(dlc => dlc.Enabled)); if (koaloaderAllCheckBox.Checked) selection.Koaloader = true; - selection.Id = gameId; - selection.Name = name; - selection.RootDirectory = gameDirectory; - selection.ExecutableDirectories = await UbisoftLibrary.GetExecutableDirectories(selection.RootDirectory); - selection.DllDirectories = dllDirectories; - selection.Platform = Platform.Ubisoft; selection.Icon = IconGrabber.GetDomainFaviconUrl("store.ubi.com"); selectionTreeView.Invoke(delegate { if (Program.Canceled) return; - TreeNode programNode = treeNodes.Find(s => s.Tag is Platform.Ubisoft && s.Name == gameId) ?? new TreeNode(); + TreeNode programNode = treeNodes.Find(s => (Platform)s.Tag == selection.Platform && s.Name == gameId) ?? new TreeNode(); programNode.Tag = selection.Platform; programNode.Name = gameId; programNode.Text = name; @@ -738,7 +698,7 @@ internal sealed partial class SelectForm : CustomForm OnLoadDlc(null, null); OnLoadKoaloader(null, null); HideProgressBar(); - selectionTreeView.Enabled = Selection.All.Count > 0; + selectionTreeView.Enabled = !Selection.All.IsEmpty; allCheckBox.Enabled = selectionTreeView.Enabled; koaloaderAllCheckBox.Enabled = selectionTreeView.Enabled; noneFoundLabel.Visible = !selectionTreeView.Enabled; @@ -791,14 +751,16 @@ internal sealed partial class SelectForm : CustomForm } private static void SyncNodeDescendants(TreeNode node) - => node.Nodes.Cast().ToList().ForEach(childNode => + { + foreach (TreeNode childNode in node.Nodes) { if (childNode.Text == "Unknown") return; childNode.Checked = node.Checked; SyncNode(childNode); SyncNodeDescendants(childNode); - }); + } + } private static void SyncNode(TreeNode node) { @@ -810,7 +772,8 @@ internal sealed partial class SelectForm : CustomForm selection.Enabled = node.Checked; return; } - SelectionDLC dlc = SelectionDLC.FromPlatformId(platform, id); + DLCType type = (DLCType)node.Tag; + SelectionDLC dlc = SelectionDLC.FromTypeId(type, id); if (dlc is not null) dlc.Enabled = node.Checked; } @@ -867,10 +830,10 @@ internal sealed partial class SelectForm : CustomForm Selection selection = Selection.FromPlatformId(platform, id); SelectionDLC dlc = null; if (selection is null) - dlc = SelectionDLC.FromPlatformId(platform, id); + dlc = SelectionDLC.FromTypeId((DLCType)node.Tag, id); Selection dlcParentSelection = null; if (dlc is not null) - dlcParentSelection = Selection.FromPlatformId(platform, dlc.Selection.Id); + dlcParentSelection = dlc.Selection; if (selection is null && dlcParentSelection is null) return; ContextMenuItem header = id == "PL" @@ -922,10 +885,10 @@ internal sealed partial class SelectForm : CustomForm _ = items.Add(new ContextMenuItem("Open Root Directory", "File Explorer", (_, _) => Diagnostics.OpenDirectoryInFileExplorer(selection.RootDirectory))); int executables = 0; - foreach ((string directory, BinaryType binaryType) in selection.ExecutableDirectories.ToList()) + foreach ((string directory, BinaryType binaryType) in selection.ExecutableDirectories) _ = items.Add(new ContextMenuItem($"Open Executable Directory #{++executables} ({(binaryType == BinaryType.BIT32 ? "32" : "64")}-bit)", "File Explorer", (_, _) => Diagnostics.OpenDirectoryInFileExplorer(directory))); - List directories = selection.DllDirectories.ToList(); + HashSet directories = selection.DllDirectories; int steam = 0, epic = 0, r1 = 0, r2 = 0; if (selection.Platform is Platform.Steam or Platform.Paradox) foreach (string directory in directories) @@ -1020,7 +983,7 @@ internal sealed partial class SelectForm : CustomForm private void OnAccept(bool uninstall = false) { - if (Selection.All.Count < 1) + if (Selection.All.IsEmpty) return; if (Selection.AllEnabled.Any(selection => !Program.AreDllsLockedDialog(this, selection))) return; @@ -1097,12 +1060,12 @@ internal sealed partial class SelectForm : CustomForm foreach (TreeNode node in TreeNodes) if (node.Parent is { } parent && node.Tag is Platform platform) { - if (node.Text == "Unknown" ? node.Checked : !node.Checked) + if ((node.Text == "Unknown" ? node.Checked : !node.Checked) + && !choices.Any(c => c.platform == platform && c.gameId == parent.Name && c.dlcId == node.Name)) choices.Add((platform, node.Parent.Name, node.Name)); else _ = choices.RemoveAll(n => n.platform == platform && n.gameId == parent.Name && n.dlcId == node.Name); } - choices = choices.Distinct().ToList(); ProgramData.WriteDlcChoices(choices); loadButton.Enabled = CanLoadDlc(); saveButton.Enabled = CanSaveDlc(); @@ -1206,7 +1169,7 @@ internal sealed partial class SelectForm : CustomForm saveKoaloaderButton.Enabled = CanSaveKoaloader(); resetKoaloaderButton.Enabled = CanResetKoaloader(); koaloaderAllCheckBox.CheckedChanged -= OnKoaloaderAllCheckBoxChanged; - koaloaderAllCheckBox.Checked = Selection.AllSafe.TrueForAll(selection => selection.Koaloader); + koaloaderAllCheckBox.Checked = Selection.AllSafe.All(selection => selection.Koaloader); koaloaderAllCheckBox.CheckedChanged += OnKoaloaderAllCheckBoxChanged; } diff --git a/CreamInstaller/Forms/UpdateForm.cs b/CreamInstaller/Forms/UpdateForm.cs index 6004675..6175fa6 100644 --- a/CreamInstaller/Forms/UpdateForm.cs +++ b/CreamInstaller/Forms/UpdateForm.cs @@ -146,7 +146,7 @@ internal sealed partial class UpdateForm : CustomForm throw new TaskCanceledException(); using HttpResponseMessage response = await HttpClientManager.HttpClient.GetAsync(latestRelease.Asset.BrowserDownloadUrl, HttpCompletionOption.ResponseHeadersRead, cancellation.Token); - response.EnsureSuccessStatusCode(); + _ = response.EnsureSuccessStatusCode(); if (cancellation is null || Program.Canceled) throw new TaskCanceledException(); await using Stream download = await response.Content.ReadAsStreamAsync(cancellation.Token); @@ -196,18 +196,18 @@ internal sealed partial class UpdateForm : CustomForm string properExecutable = Path.GetFileName(ExecutablePath); string properExecutablePath = Path.Combine(currentDirectory!, properExecutable!); StringBuilder commands = new(); - commands.AppendLine(CultureInfo.InvariantCulture, $"\nTASKKILL /F /T /PID {Program.CurrentProcessId}"); - commands.AppendLine(CultureInfo.InvariantCulture, $":LOOP"); - commands.AppendLine(CultureInfo.InvariantCulture, $"TASKLIST | FIND \" {Program.CurrentProcessId}\" "); - commands.AppendLine(CultureInfo.InvariantCulture, $"IF NOT ERRORLEVEL 1 ("); - commands.AppendLine(CultureInfo.InvariantCulture, $" TIMEOUT /T 1"); - commands.AppendLine(CultureInfo.InvariantCulture, $" GOTO LOOP"); - commands.AppendLine(CultureInfo.InvariantCulture, $")"); - commands.AppendLine(CultureInfo.InvariantCulture, $"DEL /F /Q \"{currentPath}\""); - commands.AppendLine(CultureInfo.InvariantCulture, $"DEL /F /Q \"{properExecutablePath}\""); - commands.AppendLine(CultureInfo.InvariantCulture, $"MOVE /Y \"{ExecutablePath}\" \"{properExecutablePath}\""); - commands.AppendLine(CultureInfo.InvariantCulture, $"START \"\" /D \"{currentDirectory}\" \"{properExecutable}\""); - commands.AppendLine(CultureInfo.InvariantCulture, $"EXIT"); + _ = commands.AppendLine(CultureInfo.InvariantCulture, $"\nTASKKILL /F /T /PID {Program.CurrentProcessId}"); + _ = commands.AppendLine(CultureInfo.InvariantCulture, $":LOOP"); + _ = commands.AppendLine(CultureInfo.InvariantCulture, $"TASKLIST | FIND \" {Program.CurrentProcessId}\" "); + _ = commands.AppendLine(CultureInfo.InvariantCulture, $"IF NOT ERRORLEVEL 1 ("); + _ = commands.AppendLine(CultureInfo.InvariantCulture, $" TIMEOUT /T 1"); + _ = commands.AppendLine(CultureInfo.InvariantCulture, $" GOTO LOOP"); + _ = commands.AppendLine(CultureInfo.InvariantCulture, $")"); + _ = commands.AppendLine(CultureInfo.InvariantCulture, $"DEL /F /Q \"{currentPath}\""); + _ = commands.AppendLine(CultureInfo.InvariantCulture, $"DEL /F /Q \"{properExecutablePath}\""); + _ = commands.AppendLine(CultureInfo.InvariantCulture, $"MOVE /Y \"{ExecutablePath}\" \"{properExecutablePath}\""); + _ = commands.AppendLine(CultureInfo.InvariantCulture, $"START \"\" /D \"{currentDirectory}\" \"{properExecutable}\""); + _ = commands.AppendLine(CultureInfo.InvariantCulture, $"EXIT"); UpdaterPath.WriteFile(commands.ToString(), true, this); Process process = new(); ProcessStartInfo startInfo = new() @@ -216,7 +216,7 @@ internal sealed partial class UpdateForm : CustomForm CreateNoWindow = true }; process.StartInfo = startInfo; - process.Start(); + _ = process.Start(); return; } if (!retry) diff --git a/CreamInstaller/Platforms/Epic/EpicLibrary.cs b/CreamInstaller/Platforms/Epic/EpicLibrary.cs index 2ae784f..0a009ac 100644 --- a/CreamInstaller/Platforms/Epic/EpicLibrary.cs +++ b/CreamInstaller/Platforms/Epic/EpicLibrary.cs @@ -6,7 +6,6 @@ using CreamInstaller.Platforms.Epic.Heroic; using CreamInstaller.Utility; using Microsoft.Win32; using Newtonsoft.Json; -using static CreamInstaller.Resources.Resources; namespace CreamInstaller.Platforms.Epic; @@ -26,13 +25,10 @@ internal static class EpicLibrary } } - internal static async Task> GetExecutableDirectories(string gameDirectory) - => await Task.Run(async () => await gameDirectory.GetExecutableDirectories(true)); - - internal static async Task> GetGames() + internal static async Task> GetGames() => await Task.Run(async () => { - HashSet games = new(); + List games = new(); string manifests = EpicManifestsPath; if (manifests.DirectoryExists()) foreach (string item in manifests.EnumerateDirectory("*.item")) @@ -45,7 +41,7 @@ internal static class EpicLibrary Manifest manifest = JsonConvert.DeserializeObject(json); if (manifest is not null && !games.Any(g => g.CatalogNamespace == manifest.CatalogNamespace && g.InstallLocation == manifest.InstallLocation)) - _ = games.Add(manifest); + games.Add(manifest); } catch { diff --git a/CreamInstaller/Platforms/Epic/Heroic/HeroicAppData.cs b/CreamInstaller/Platforms/Epic/Heroic/HeroicAppData.cs index 4f28d1e..1139a94 100644 --- a/CreamInstaller/Platforms/Epic/Heroic/HeroicAppData.cs +++ b/CreamInstaller/Platforms/Epic/Heroic/HeroicAppData.cs @@ -14,6 +14,4 @@ public class HeroicAppData [JsonProperty("namespace")] public string Namespace { get; set; } [JsonProperty("title")] public string Title { get; set; } - - [JsonProperty("runner")] public string Runner { get; set; } } \ No newline at end of file diff --git a/CreamInstaller/Platforms/Epic/Heroic/HeroicLibrary.cs b/CreamInstaller/Platforms/Epic/Heroic/HeroicLibrary.cs index 5960228..634532e 100644 --- a/CreamInstaller/Platforms/Epic/Heroic/HeroicLibrary.cs +++ b/CreamInstaller/Platforms/Epic/Heroic/HeroicLibrary.cs @@ -12,7 +12,7 @@ internal static class HeroicLibrary internal static readonly string HeroicLibraryPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\heroic\store_cache\legendary_library.json"; - internal static async Task GetGames(HashSet games) + internal static async Task GetGames(List games) => await Task.Run(() => { string libraryPath = HeroicLibraryPath; @@ -28,14 +28,14 @@ internal static class HeroicLibrary try { HeroicAppData appData = token.ToObject(); - if (appData is null || appData.Runner != "legendary" || string.IsNullOrWhiteSpace(appData.Install.InstallPath)) + if (appData is null || string.IsNullOrWhiteSpace(appData.Install.InstallPath)) continue; Manifest manifest = new() { DisplayName = appData.Title, CatalogNamespace = appData.Namespace, InstallLocation = appData.Install.InstallPath }; if (!games.Any(g => g.CatalogNamespace == manifest.CatalogNamespace && g.InstallLocation == manifest.InstallLocation)) - _ = games.Add(manifest); + games.Add(manifest); } catch { diff --git a/CreamInstaller/Platforms/Paradox/ParadoxLauncher.cs b/CreamInstaller/Platforms/Paradox/ParadoxLauncher.cs index dbd7800..279b5ee 100644 --- a/CreamInstaller/Platforms/Paradox/ParadoxLauncher.cs +++ b/CreamInstaller/Platforms/Paradox/ParadoxLauncher.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using System.Drawing; -using System.IO; +using System.Drawing; using System.Linq; using System.Threading.Tasks; using System.Windows.Forms; @@ -31,9 +29,6 @@ internal static class ParadoxLauncher } } - internal static async Task> GetExecutableDirectories(string gameDirectory) - => await Task.Run(async () => await gameDirectory.GetExecutableDirectories(validFunc: path => !Path.GetFileName(path).Contains("bootstrapper"))); - private static void PopulateDlc(Selection paradoxLauncher = null) { paradoxLauncher ??= Selection.FromPlatformId(Platform.Paradox, "PL"); diff --git a/CreamInstaller/Platforms/Steam/SteamCMD.cs b/CreamInstaller/Platforms/Steam/SteamCMD.cs index df197f3..b57fae7 100644 --- a/CreamInstaller/Platforms/Steam/SteamCMD.cs +++ b/CreamInstaller/Platforms/Steam/SteamCMD.cs @@ -59,7 +59,7 @@ internal static class SteamCMD { if (appId != null) { - AttemptCount.TryGetValue(appId, out int count); + _ = AttemptCount.TryGetValue(appId, out int count); AttemptCount[appId] = ++count; } if (Program.Canceled) @@ -113,7 +113,6 @@ internal static class SteamCMD _ = Interlocked.Decrement(ref Locks[i]); return appInfo.ToString(); } - Thread.Sleep(200); } Thread.Sleep(200); goto wait_for_lock; @@ -232,7 +231,7 @@ internal static class SteamCMD #endif continue; } - if (appInfo.Value.Children().ToList().Count == 0) + if (!appInfo.Value.Children().Any()) return appInfo; VToken type = appInfo.Value.GetChild("common")?.GetChild("type"); if (type is not null && type.ToString() != "Game") @@ -242,7 +241,7 @@ internal static class SteamCMD return appInfo; if (type is not null && (!int.TryParse(buildid, out int gamebuildId) || gamebuildId >= buildId)) return appInfo; - List dlcAppIds = await ParseDlcAppIds(appInfo); + HashSet dlcAppIds = await ParseDlcAppIds(appInfo); foreach (string dlcAppUpdateFile in dlcAppIds.Select(id => $@"{AppInfoPath}\{id}.vdf")) dlcAppUpdateFile.DeleteFile(); appUpdateFile.DeleteFile(); @@ -253,10 +252,10 @@ internal static class SteamCMD return null; } - internal static async Task> ParseDlcAppIds(VProperty appInfo) + internal static async Task> ParseDlcAppIds(VProperty appInfo) => await Task.Run(() => { - List dlcIds = new(); + HashSet dlcIds = new(); if (Program.Canceled || appInfo is null) return dlcIds; VToken extended = appInfo.Value.GetChild("extended"); @@ -265,8 +264,8 @@ internal static class SteamCMD { VProperty property = (VProperty)vToken; foreach (string id in property.Value.ToString().Split(",")) - if (int.TryParse(id, out int appId) && appId > 0 && !dlcIds.Contains("" + appId)) - dlcIds.Add("" + appId); + if (int.TryParse(id, out int appId) && appId > 0) + _ = dlcIds.Add("" + appId); } VToken depots = appInfo.Value.GetChild("depots"); if (depots is null) @@ -274,8 +273,8 @@ internal static class SteamCMD foreach (VToken vToken in depots.Where(p => p is VProperty property && int.TryParse(property.Key, out int _))) { VProperty property = (VProperty)vToken; - if (int.TryParse(property.Value.GetChild("dlcappid")?.ToString(), out int appId) && appId > 0 && !dlcIds.Contains("" + appId)) - dlcIds.Add("" + appId); + if (int.TryParse(property.Value.GetChild("dlcappid")?.ToString(), out int appId) && appId > 0) + _ = dlcIds.Add("" + appId); } return dlcIds; }); diff --git a/CreamInstaller/Platforms/Steam/SteamLibrary.cs b/CreamInstaller/Platforms/Steam/SteamLibrary.cs index 21151b2..77c7fcc 100644 --- a/CreamInstaller/Platforms/Steam/SteamLibrary.cs +++ b/CreamInstaller/Platforms/Steam/SteamLibrary.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using CreamInstaller.Utility; using Gameloop.Vdf.Linq; using Microsoft.Win32; -using static CreamInstaller.Resources.Resources; namespace CreamInstaller.Platforms.Steam; @@ -22,13 +21,10 @@ internal static class SteamLibrary } } - internal static async Task> GetExecutableDirectories(string gameDirectory) - => await Task.Run(async () => await gameDirectory.GetExecutableDirectories(true)); - - internal static async Task> GetGames() + internal static async Task> GetGames() => await Task.Run(async () => { - HashSet<(string appId, string name, string branch, int buildId, string gameDirectory)> games = new(); + List<(string appId, string name, string branch, int buildId, string gameDirectory)> games = new(); HashSet gameLibraryDirectories = await GetLibraryDirectories(); foreach (string libraryDirectory in gameLibraryDirectories) { @@ -37,7 +33,7 @@ internal static class SteamLibrary foreach ((string appId, string name, string branch, int buildId, string gameDirectory) game in (await GetGamesFromLibraryDirectory(libraryDirectory)).Where(game => !games.Any(_game => _game.appId == game.appId && _game.gameDirectory == game.gameDirectory))) - _ = games.Add(game); + games.Add(game); } return games; }); diff --git a/CreamInstaller/Platforms/Steam/SteamStore.cs b/CreamInstaller/Platforms/Steam/SteamStore.cs index db10308..deb5672 100644 --- a/CreamInstaller/Platforms/Steam/SteamStore.cs +++ b/CreamInstaller/Platforms/Steam/SteamStore.cs @@ -18,13 +18,14 @@ internal static class SteamStore private const int CooldownGame = 600; private const int CooldownDlc = 1200; - internal static async Task> ParseDlcAppIds(AppData appData) + internal static async Task> ParseDlcAppIds(AppData appData) => await Task.Run(() => { - List dlcIds = new(); + HashSet dlcIds = new(); if (appData.DLC is null) return dlcIds; - dlcIds.AddRange(from appId in appData.DLC where appId > 0 select appId.ToString(CultureInfo.InvariantCulture)); + foreach (string dlcId in from appId in appData.DLC where appId > 0 select appId.ToString(CultureInfo.InvariantCulture)) + _ = dlcIds.Add(dlcId); return dlcIds; }); diff --git a/CreamInstaller/Platforms/Ubisoft/UbisoftLibrary.cs b/CreamInstaller/Platforms/Ubisoft/UbisoftLibrary.cs index 779cc9a..b63b4b2 100644 --- a/CreamInstaller/Platforms/Ubisoft/UbisoftLibrary.cs +++ b/CreamInstaller/Platforms/Ubisoft/UbisoftLibrary.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Threading.Tasks; using CreamInstaller.Utility; using Microsoft.Win32; -using static CreamInstaller.Resources.Resources; namespace CreamInstaller.Platforms.Ubisoft; @@ -21,13 +20,10 @@ internal static class UbisoftLibrary } } - internal static async Task> GetExecutableDirectories(string gameDirectory) - => await Task.Run(async () => await gameDirectory.GetExecutableDirectories(true)); - - internal static async Task> GetGames() + internal static async Task> GetGames() => await Task.Run(() => { - HashSet<(string gameId, string name, string gameDirectory)> games = new(); + List<(string gameId, string name, string gameDirectory)> games = new(); RegistryKey installsKey = InstallsKey; if (installsKey is null) return games; @@ -36,7 +32,7 @@ internal static class UbisoftLibrary RegistryKey installKey = installsKey.OpenSubKey(gameId); string installDir = installKey?.GetValue("InstallDir")?.ToString()?.BeautifyPath(); if (installDir is not null && !games.Any(g => g.gameId == gameId && g.gameDirectory == installDir)) - _ = games.Add((gameId, new DirectoryInfo(installDir).Name, installDir)); + games.Add((gameId, new DirectoryInfo(installDir).Name, installDir)); } return games; }); diff --git a/CreamInstaller/Resources/Koaloader.cs b/CreamInstaller/Resources/Koaloader.cs index 8375a1c..fa7f5f3 100644 --- a/CreamInstaller/Resources/Koaloader.cs +++ b/CreamInstaller/Resources/Koaloader.cs @@ -38,7 +38,7 @@ internal static class Koaloader private static void WriteProxy(this string path, string proxyName, BinaryType binaryType) { - foreach (string resourceIdentifier in EmbeddedResources.FindAll(r => r.StartsWith("Koaloader", StringComparison.Ordinal))) + foreach (string resourceIdentifier in EmbeddedResources.Where(r => r.StartsWith("Koaloader", StringComparison.Ordinal))) { resourceIdentifier.GetProxyInfoFromIdentifier(out string _proxyName, out BinaryType _binaryType); if (_proxyName != proxyName || _binaryType != binaryType) diff --git a/CreamInstaller/Resources/Resources.cs b/CreamInstaller/Resources/Resources.cs index cbaad1e..0ca71df 100644 --- a/CreamInstaller/Resources/Resources.cs +++ b/CreamInstaller/Resources/Resources.cs @@ -4,7 +4,6 @@ using System.IO; using System.Linq; using System.Reflection; using System.Security.Cryptography; -using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using CreamInstaller.Utility; @@ -13,12 +12,12 @@ namespace CreamInstaller.Resources; internal static class Resources { - private static List embeddedResources; + private static HashSet embeddedResources; - private static readonly Dictionary> ResourceMD5s = new() + private static readonly Dictionary> ResourceMD5s = new() { { - ResourceIdentifier.Koaloader, new List + ResourceIdentifier.Koaloader, new() { "8A0958687B5ED7C34DAD037856DD1530", // Koaloader v2.0.0 "8FECDEB40980F4E687C10E056232D96B", // Koaloader v2.0.0 @@ -355,7 +354,7 @@ internal static class Resources } }, { - ResourceIdentifier.EpicOnlineServices32, new List + ResourceIdentifier.EpicOnlineServices32, new() { "069A57B1834A960193D2AD6B96926D70", // ScreamAPI v3.0.0 "E2FB3A4A9583FDC215832E5F935E4440", // ScreamAPI v3.0.1 @@ -363,7 +362,7 @@ internal static class Resources } }, { - ResourceIdentifier.EpicOnlineServices64, new List + ResourceIdentifier.EpicOnlineServices64, new() { "0D62E57139F1A64F807A9934946A9474", // ScreamAPI v3.0.0 "3875C7B735EE80C23239CC4749FDCBE6", // ScreamAPI v3.0.1 @@ -371,7 +370,7 @@ internal static class Resources } }, { - ResourceIdentifier.Steamworks32, new List + ResourceIdentifier.Steamworks32, new() { "02594110FE56B2945955D46670B9A094", // CreamAPI v4.5.0.0 Hotfix "B2434578957CBE38BDCE0A671C1262FC", // SmokeAPI v1.0.0 @@ -389,7 +388,7 @@ internal static class Resources } }, { - ResourceIdentifier.Steamworks64, new List + ResourceIdentifier.Steamworks64, new() { "30091B91923D9583A54A93ED1145554B", // CreamAPI v4.5.0.0 Hotfix "08713035CAD6F52548FF324D0487B88D", // SmokeAPI v1.0.0 @@ -407,26 +406,26 @@ internal static class Resources } }, { - ResourceIdentifier.Uplay32, new List + ResourceIdentifier.Uplay32, new() { "1977967B2549A38EC2DB39D4C8ED499B" // Uplay R1 Unlocker v2.0.0 } }, { - ResourceIdentifier.Uplay64, new List + ResourceIdentifier.Uplay64, new() { "333FEDD9DC2B299419B37ED1624FF8DB" // Uplay R1 Unlocker v2.0.0 } }, { - ResourceIdentifier.Upc32, new List + ResourceIdentifier.Upc32, new() { "C14368BC4EE19FDE8DBAC07E31C67AE4", // Uplay R2 Unlocker v3.0.0 "DED3A3EA1876E3110D7D87B9A22946B0" // Uplay R2 Unlocker v3.0.1 } }, { - ResourceIdentifier.Upc64, new List + ResourceIdentifier.Upc64, new() { "7D9A4C12972BAABCB6C181920CC0F19B", // Uplay R2 Unlocker v3.0.0 "D7FDBFE0FC8D7600FEB8EC0A97713184" // Uplay R2 Unlocker v3.0.1 @@ -434,7 +433,7 @@ internal static class Resources } }; - internal static List EmbeddedResources + internal static HashSet EmbeddedResources { get { @@ -443,7 +442,7 @@ internal static class Resources string[] names = Assembly.GetExecutingAssembly().GetManifestResourceNames(); embeddedResources = new(); foreach (string resourceName in names.Where(n => n.StartsWith("CreamInstaller.Resources.", StringComparison.Ordinal))) - embeddedResources.Add(resourceName[25..]); + _ = embeddedResources.Add(resourceName[25..]); return embeddedResources; } } @@ -483,21 +482,21 @@ internal static class Resources internal static bool TryGetFileBinaryType(this string path, out BinaryType binaryType) => NativeImports.GetBinaryType(path, out binaryType); - internal static async Task> GetExecutableDirectories(this string rootDirectory, - bool filterCommon = false, Func validFunc = null) + internal static async Task> GetExecutableDirectories(this string rootDirectory, bool filterCommon = false, + Func validFunc = null) => await Task.Run(async () => (await rootDirectory.GetExecutables(filterCommon, validFunc) ?? (filterCommon || validFunc is not null ? await rootDirectory.GetExecutables() : null))?.Select(e => { e.path = Path.GetDirectoryName(e.path); return e; - }).DistinctBy(e => e.path).ToHashSet()); + }).DistinctBy(e => e.path).ToList()); - internal static async Task> GetExecutables(this string rootDirectory, bool filterCommon = false, + internal static async Task> GetExecutables(this string rootDirectory, bool filterCommon = false, Func validFunc = null) => await Task.Run(() => { - HashSet<(string path, BinaryType binaryType)> executables = new(); + List<(string path, BinaryType binaryType)> executables = new(); if (Program.Canceled || !rootDirectory.DirectoryExists()) return null; foreach (string path in rootDirectory.EnumerateDirectory("*.exe", true)) @@ -507,8 +506,7 @@ internal static class Resources if (executables.All(e => e.path != path) && (!filterCommon || !rootDirectory.IsCommonIncorrectExecutable(path)) && (validFunc is null || validFunc(path)) && path.TryGetFileBinaryType(out BinaryType binaryType) && binaryType is BinaryType.BIT64) - _ = executables.Add((path, binaryType)); - Thread.Sleep(1); + executables.Add((path, binaryType)); } foreach (string path in rootDirectory.EnumerateDirectory("*.exe", true)) { @@ -517,8 +515,7 @@ internal static class Resources if (executables.All(e => e.path != path) && (!filterCommon || !rootDirectory.IsCommonIncorrectExecutable(path)) && (validFunc is null || validFunc(path)) && path.TryGetFileBinaryType(out BinaryType binaryType) && binaryType is BinaryType.BIT32) - _ = executables.Add((path, binaryType)); - Thread.Sleep(1); + executables.Add((path, binaryType)); } return executables.Count > 0 ? executables : null; }); diff --git a/CreamInstaller/Resources/ScreamAPI.cs b/CreamInstaller/Resources/ScreamAPI.cs index 3950029..74f50d5 100644 --- a/CreamInstaller/Resources/ScreamAPI.cs +++ b/CreamInstaller/Resources/ScreamAPI.cs @@ -25,12 +25,14 @@ internal static class ScreamAPI internal static void CheckConfig(string directory, Selection selection, InstallForm installForm = null) { directory.GetScreamApiComponents(out _, out _, out _, out _, out string config, out _); - List overrideCatalogItems = selection.DLC.Where(dlc => dlc.Type is DLCType.EpicCatalogItem && !dlc.Enabled).ToList(); - List overrideEntitlements = selection.DLC.Where(dlc => dlc.Type is DLCType.EpicEntitlement && !dlc.Enabled).ToList(); + HashSet overrideCatalogItems = selection.DLC.Where(dlc => dlc.Type is DLCType.EpicCatalogItem && !dlc.Enabled).ToHashSet(); + HashSet overrideEntitlements = selection.DLC.Where(dlc => dlc.Type is DLCType.EpicEntitlement && !dlc.Enabled).ToHashSet(); foreach (Selection extraSelection in selection.ExtraSelections) { - overrideCatalogItems.AddRange(extraSelection.DLC.Where(dlc => dlc.Type is DLCType.EpicCatalogItem && !dlc.Enabled)); - overrideEntitlements.AddRange(extraSelection.DLC.Where(dlc => dlc.Type is DLCType.EpicEntitlement && !dlc.Enabled)); + foreach (SelectionDLC extraDlc in extraSelection.DLC.Where(dlc => dlc.Type is DLCType.EpicCatalogItem && !dlc.Enabled)) + _ = overrideCatalogItems.Add(extraDlc); + foreach (SelectionDLC extraDlc in extraSelection.DLC.Where(dlc => dlc.Type is DLCType.EpicEntitlement && !dlc.Enabled)) + _ = overrideEntitlements.Add(extraDlc); } if (overrideCatalogItems.Count > 0 || overrideEntitlements.Count > 0) { diff --git a/CreamInstaller/Resources/SmokeAPI.cs b/CreamInstaller/Resources/SmokeAPI.cs index 538b7cb..07fd865 100644 --- a/CreamInstaller/Resources/SmokeAPI.cs +++ b/CreamInstaller/Resources/SmokeAPI.cs @@ -28,12 +28,13 @@ internal static class SmokeAPI internal static void CheckConfig(string directory, Selection selection, InstallForm installForm = null) { directory.GetSmokeApiComponents(out _, out _, out _, out _, out string old_config, out string config, out _, out _, out _); - List overrideDlc = selection.DLC.Where(dlc => !dlc.Enabled).ToList(); - foreach (Selection extraSelection in selection.ExtraSelections) - overrideDlc.AddRange(extraSelection.DLC.Where(dlc => !dlc.Enabled)); - List injectDlc = new(); + HashSet overrideDlc = selection.DLC.Where(dlc => !dlc.Enabled).ToHashSet(); + foreach (SelectionDLC extraDlc in selection.ExtraSelections.SelectMany(extraSelection => extraSelection.DLC.Where(dlc => !dlc.Enabled))) + _ = overrideDlc.Add(extraDlc); + HashSet injectDlc = new(); if (selection.DLC.Count() > 64) - injectDlc.AddRange(selection.DLC.Where(dlc => dlc.Enabled && dlc.Type is DLCType.SteamHidden)); + foreach (SelectionDLC hiddenDlc in selection.DLC.Where(dlc => dlc.Enabled && dlc.Type is DLCType.SteamHidden)) + _ = injectDlc.Add(hiddenDlc); List injectDlc)>> extraApps = new(); foreach (Selection extraSelection in selection.ExtraSelections.Where(extraSelection => extraSelection.DLC.Count() > 64)) { diff --git a/CreamInstaller/Resources/UplayR1.cs b/CreamInstaller/Resources/UplayR1.cs index 73f00cb..eed5ab6 100644 --- a/CreamInstaller/Resources/UplayR1.cs +++ b/CreamInstaller/Resources/UplayR1.cs @@ -25,9 +25,9 @@ internal static class UplayR1 internal static void CheckConfig(string directory, Selection selection, InstallForm installForm = null) { directory.GetUplayR1Components(out _, out _, out _, out _, out string config, out _); - List blacklistDlc = selection.DLC.Where(dlc => !dlc.Enabled).ToList(); - foreach (Selection extraSelection in selection.ExtraSelections) - blacklistDlc.AddRange(extraSelection.DLC.Where(dlc => !dlc.Enabled)); + HashSet blacklistDlc = selection.DLC.Where(dlc => !dlc.Enabled).ToHashSet(); + foreach (SelectionDLC extraDlc in selection.ExtraSelections.SelectMany(extraSelection => extraSelection.DLC.Where(dlc => !dlc.Enabled))) + _ = blacklistDlc.Add(extraDlc); if (blacklistDlc.Count > 0) { /*if (installForm is not null) @@ -100,11 +100,10 @@ internal static class UplayR1 config.DeleteFile(); installForm?.UpdateUser($"Deleted configuration: {Path.GetFileName(config)}", LogTextBox.Action, false); } - if (log.FileExists()) - { - log.DeleteFile(); - installForm?.UpdateUser($"Deleted log: {Path.GetFileName(log)}", LogTextBox.Action, false); - } + if (!log.FileExists()) + return; + log.DeleteFile(); + installForm?.UpdateUser($"Deleted log: {Path.GetFileName(log)}", LogTextBox.Action, false); }); internal static async Task Install(string directory, Selection selection, InstallForm installForm = null, bool generateConfig = true) diff --git a/CreamInstaller/Resources/UplayR2.cs b/CreamInstaller/Resources/UplayR2.cs index 41adcb6..09d49d2 100644 --- a/CreamInstaller/Resources/UplayR2.cs +++ b/CreamInstaller/Resources/UplayR2.cs @@ -27,9 +27,9 @@ internal static class UplayR2 internal static void CheckConfig(string directory, Selection selection, InstallForm installForm = null) { directory.GetUplayR2Components(out _, out _, out _, out _, out _, out _, out string config, out _); - List blacklistDlc = selection.DLC.Where(dlc => !dlc.Enabled).ToList(); - foreach (Selection extraSelection in selection.ExtraSelections) - blacklistDlc.AddRange(extraSelection.DLC.Where(dlc => !dlc.Enabled)); + HashSet blacklistDlc = selection.DLC.Where(dlc => !dlc.Enabled).ToHashSet(); + foreach (SelectionDLC extraDlc in selection.ExtraSelections.SelectMany(extraSelection => extraSelection.DLC.Where(dlc => !dlc.Enabled))) + _ = blacklistDlc.Add(extraDlc); if (blacklistDlc.Count > 0) { /*if (installForm is not null) diff --git a/CreamInstaller/Selection.cs b/CreamInstaller/Selection.cs index f7b8c13..812c42d 100644 --- a/CreamInstaller/Selection.cs +++ b/CreamInstaller/Selection.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; using System.Linq; using CreamInstaller.Resources; using CreamInstaller.Utility; @@ -12,31 +14,42 @@ public enum Platform Epic, Ubisoft } -internal sealed class Selection +internal sealed class Selection : IEquatable { internal const string DefaultKoaloaderProxy = "version"; - internal static readonly HashSet All = new(); - internal readonly HashSet ExtraSelections = new(); + internal static readonly ConcurrentDictionary All = new(); + + internal readonly HashSet DllDirectories; + internal readonly List<(string directory, BinaryType binaryType)> ExecutableDirectories; + internal readonly HashSet ExtraSelections = new(); + internal readonly string Id; + internal readonly string Name; + internal readonly Platform Platform; + internal readonly string RootDirectory; - internal HashSet DllDirectories; internal bool Enabled; - internal HashSet<(string directory, BinaryType binaryType)> ExecutableDirectories; internal string Icon; - internal string Id = "0"; internal bool Koaloader; internal string KoaloaderProxy; - internal string Name = "Program"; - internal Platform Platform; internal string Product; internal string Publisher; - internal string RootDirectory; internal string SubIcon; internal string Website; - internal Selection() => All.Add(this); + private Selection(Platform platform, string id, string name, string rootDirectory, HashSet dllDirectories, + List<(string directory, BinaryType binaryType)> executableDirectories) + { + Platform = platform; + Id = id; + Name = name; + RootDirectory = rootDirectory; + DllDirectories = dllDirectories; + ExecutableDirectories = executableDirectories; + _ = All.TryAdd(this, default); + } - internal IEnumerable DLC => SelectionDLC.AllSafe.Where(dlc => dlc.Selection == this); + internal IEnumerable DLC => SelectionDLC.AllSafe.Where(dlc => dlc.Selection.Equals(this)); internal bool AreDllsLocked { @@ -79,13 +92,20 @@ internal sealed class Selection } } - internal static List AllSafe => All.ToList(); + internal static HashSet AllSafe => All.Keys.ToHashSet(); - internal static List AllEnabled => AllSafe.FindAll(s => s.Enabled); + internal static HashSet AllEnabled => All.Keys.Where(s => s.Enabled).ToHashSet(); + + public bool Equals(Selection other) + => other is not null && (ReferenceEquals(this, other) || Id == other.Id && Platform == other.Platform && RootDirectory == other.RootDirectory); + + internal static Selection GetOrCreate(Platform platform, string id, string name, string rootDirectory, HashSet dllDirectories, + List<(string directory, BinaryType binaryType)> executableDirectories) + => FromPlatformId(platform, id) ?? new Selection(platform, id, name, rootDirectory, dllDirectories, executableDirectories); private void Remove() { - _ = All.Remove(this); + _ = All.TryRemove(this, out _); foreach (SelectionDLC dlc in DLC) dlc.Selection = null; } @@ -113,7 +133,14 @@ internal sealed class Selection } internal static void ValidateAll(List<(Platform platform, string id, string name)> programsToScan) - => AllSafe.ForEach(selection => selection.Validate(programsToScan)); + { + foreach (Selection selection in AllSafe) + selection.Validate(programsToScan); + } - internal static Selection FromPlatformId(Platform platform, string gameId) => AllSafe.Find(s => s.Platform == platform && s.Id == gameId); + internal static Selection FromPlatformId(Platform platform, string gameId) => AllSafe.FirstOrDefault(s => s.Platform == platform && s.Id == gameId); + + public override bool Equals(object obj) => ReferenceEquals(this, obj) || obj is Selection other && Equals(other); + + public override int GetHashCode() => HashCode.Combine(Id, (int)Platform, RootDirectory); } \ No newline at end of file diff --git a/CreamInstaller/SelectionDLC.cs b/CreamInstaller/SelectionDLC.cs index 4727f42..b83cad8 100644 --- a/CreamInstaller/SelectionDLC.cs +++ b/CreamInstaller/SelectionDLC.cs @@ -1,26 +1,36 @@ -using System.Collections.Generic; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; using System.Linq; namespace CreamInstaller; public enum DLCType { - Steam, SteamHidden, EpicCatalogItem, + None = 0, Steam, SteamHidden, EpicCatalogItem, EpicEntitlement } -internal sealed class SelectionDLC +internal sealed class SelectionDLC : IEquatable { - private static readonly HashSet All = new(); + private static readonly ConcurrentDictionary All = new(); + + internal readonly string Id; + internal readonly string Name; + internal readonly DLCType Type; internal bool Enabled; internal string Icon; - internal string Id; - internal string Name; internal string Product; internal string Publisher; private Selection selection; - internal DLCType Type; + + private SelectionDLC(DLCType type, string id, string name) + { + Type = type; + Id = id; + Name = name; + } internal Selection Selection { @@ -28,11 +38,19 @@ internal sealed class SelectionDLC set { selection = value; - _ = value is null ? All.Remove(this) : All.Add(this); + _ = value is null ? All.TryRemove(this, out _) : All.TryAdd(this, default); } } - internal static List AllSafe => All.ToList(); + internal static HashSet AllSafe => All.Keys.ToHashSet(); - internal static SelectionDLC FromPlatformId(Platform platform, string dlcId) => AllSafe.Find(dlc => dlc.Selection.Platform == platform && dlc.Id == dlcId); + public bool Equals(SelectionDLC other) => other is not null && (ReferenceEquals(this, other) || Id == other.Id && Type == other.Type); + + internal static SelectionDLC GetOrCreate(DLCType type, string id, string name) => FromTypeId(type, id) ?? new SelectionDLC(type, id, name); + + internal static SelectionDLC FromTypeId(DLCType Type, string dlcId) => AllSafe.FirstOrDefault(dlc => dlc.Type == Type && dlc.Id == dlcId); + + public override bool Equals(object obj) => ReferenceEquals(this, obj) || obj is SelectionDLC other && Equals(other); + + public override int GetHashCode() => HashCode.Combine(Id, (int)Type); } \ No newline at end of file