- Hash & concurrency fixes to stop duplicate games & DLC
- Threading fixes for significantly faster operations
- More significant refactoring to prevent possible issues
This commit is contained in:
pointfeev 2023-05-28 21:20:35 -04:00
parent 0974f78889
commit 1669f8c887
22 changed files with 263 additions and 272 deletions

View file

@ -76,16 +76,24 @@ internal sealed class CustomTreeView : TreeView
Form form = FindForm(); Form form = FindForm();
if (form is not SelectForm and not SelectDialogForm) if (form is not SelectForm and not SelectDialogForm)
return; return;
string platformId = node.Name; string id = node.Name;
Platform platform = (node.Tag as Platform?).GetValueOrDefault(Platform.None); 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; return;
Color color = highlighted Color color = highlighted
? C1 ? C1
: Enabled : Enabled
? C2 ? C2
: C3; : 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); Size size = TextRenderer.MeasureText(graphics, text, font);
bounds = bounds with { X = bounds.X + bounds.Width, Width = size.Width }; bounds = bounds with { X = bounds.X + bounds.Width, Width = size.Width };
selectionBounds = new(selectionBounds.Location, selectionBounds.Size + bounds.Size with { Height = 0 }); selectionBounds = new(selectionBounds.Location, selectionBounds.Size + bounds.Size with { Height = 0 });
@ -99,7 +107,7 @@ internal sealed class CustomTreeView : TreeView
: Enabled : Enabled
? C5 ? C5
: C6; : C6;
text = platformId; text = id;
size = TextRenderer.MeasureText(graphics, text, font); size = TextRenderer.MeasureText(graphics, text, font);
const int left = -4; const int left = -4;
bounds = bounds with { X = bounds.X + bounds.Width + left, Width = size.Width }; 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) if (form is SelectForm)
{ {
Selection selection = Selection.FromPlatformId(platform, platformId); Selection selection = Selection.FromPlatformId(platform, id);
if (selection is not null) if (selection is not null)
{ {
if (bounds == node.Bounds) if (bounds == node.Bounds)
@ -188,15 +196,15 @@ internal sealed class CustomTreeView : TreeView
return; return;
if (comboBoxBounds.Count > 0 && selectForm is not null) if (comboBoxBounds.Count > 0 && selectForm is not null)
foreach (KeyValuePair<Selection, Rectangle> pair in comboBoxBounds) foreach (KeyValuePair<Selection, Rectangle> pair in comboBoxBounds)
if (!Selection.All.Contains(pair.Key)) if (!Selection.All.ContainsKey(pair.Key))
_ = comboBoxBounds.Remove(pair.Key); _ = comboBoxBounds.Remove(pair.Key);
else if (pair.Value.Contains(clickPoint)) else if (pair.Value.Contains(clickPoint))
{ {
List<string> proxies = EmbeddedResources.FindAll(r => r.StartsWith("Koaloader", StringComparison.Ordinal)).Select(p => HashSet<string> proxies = EmbeddedResources.Where(r => r.StartsWith("Koaloader", StringComparison.Ordinal)).Select(p =>
{ {
p.GetProxyInfoFromIdentifier(out string proxyName, out _); p.GetProxyInfoFromIdentifier(out string proxyName, out _);
return proxyName; return proxyName;
}).Distinct().ToList(); }).ToHashSet();
comboBoxDropDown ??= new(); comboBoxDropDown ??= new();
comboBoxDropDown.ShowItemToolTips = false; comboBoxDropDown.ShowItemToolTips = false;
comboBoxDropDown.Items.Clear(); comboBoxDropDown.Items.Clear();
@ -222,7 +230,7 @@ internal sealed class CustomTreeView : TreeView
break; break;
} }
foreach (KeyValuePair<Selection, Rectangle> pair in checkBoxBounds) foreach (KeyValuePair<Selection, Rectangle> pair in checkBoxBounds)
if (!Selection.All.Contains(pair.Key)) if (!Selection.All.ContainsKey(pair.Key))
_ = checkBoxBounds.Remove(pair.Key); _ = checkBoxBounds.Remove(pair.Key);
else if (pair.Value.Contains(clickPoint)) else if (pair.Value.Contains(clickPoint))
{ {

View file

@ -8,12 +8,10 @@ namespace CreamInstaller.Components;
internal static class PlatformIdComparer internal static class PlatformIdComparer
{ {
private static StringComparer stringComparer; private static StringComparer stringComparer;
private static NodeComparer nodeComparer; private static NodeComparer nodeComparer;
private static NodeNameComparer nodeNameComparer; private static NodeNameComparer nodeNameComparer;
private static NodeTextComparer nodeTextComparer; private static NodeTextComparer nodeTextComparer;
internal static StringComparer String => stringComparer ??= new(); internal static StringComparer String => stringComparer ??= new();
internal static NodeComparer Node => nodeComparer ??= new(); internal static NodeComparer Node => nodeComparer ??= new();
internal static NodeNameComparer NodeName => nodeNameComparer ??= new(); internal static NodeNameComparer NodeName => nodeNameComparer ??= new();
@ -39,15 +37,17 @@ internal sealed class StringComparer : IComparer<string>
internal sealed class NodeComparer : IComparer<TreeNode> internal sealed class NodeComparer : IComparer<TreeNode>
{ {
public int Compare(TreeNode a, TreeNode b) public int Compare(TreeNode a, TreeNode b)
=> a?.Tag is not Platform A => a is null
? 1 ? 1
: b?.Tag is not Platform B : b is null
? -1 ? -1
: A > B : a.Tag is not Platform pA || b.Tag is not Platform pB
? 1 ? 0
: A < B : pA > pB
? -1 ? 1
: 0; : pA < pB
? -1
: 0;
} }
internal sealed class NodeNameComparer : IComparer internal sealed class NodeNameComparer : IComparer

View file

@ -4,7 +4,7 @@
<TargetFramework>net7.0-windows</TargetFramework> <TargetFramework>net7.0-windows</TargetFramework>
<UseWindowsForms>True</UseWindowsForms> <UseWindowsForms>True</UseWindowsForms>
<ApplicationIcon>Resources\ini.ico</ApplicationIcon> <ApplicationIcon>Resources\ini.ico</ApplicationIcon>
<Version>4.9.0</Version> <Version>4.9.1</Version>
<Copyright>2021, pointfeev (https://github.com/pointfeev)</Copyright> <Copyright>2021, pointfeev (https://github.com/pointfeev)</Copyright>
<Company>CreamInstaller</Company> <Company>CreamInstaller</Company>
<Product>Automatic DLC Unlocker Installer &amp; Configuration Generator</Product> <Product>Automatic DLC Unlocker Installer &amp; Configuration Generator</Product>

View file

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CreamInstaller.Components; using CreamInstaller.Components;
using CreamInstaller.Resources; using CreamInstaller.Resources;
@ -15,7 +14,7 @@ namespace CreamInstaller.Forms;
internal sealed partial class InstallForm : CustomForm internal sealed partial class InstallForm : CustomForm
{ {
private readonly List<Selection> disabledSelections = new(); private readonly HashSet<Selection> disabledSelections = new();
private readonly int programCount = Selection.AllEnabled.Count; private readonly int programCount = Selection.AllEnabled.Count;
private readonly bool uninstalling; 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); UpdateUser("Uninstalling Koaloader from " + selection.Name + $" in incorrect directory \"{directory}\" . . . ", LogTextBox.Operation);
await Koaloader.Uninstall(directory, selection.RootDirectory, this); await Koaloader.Uninstall(directory, selection.RootDirectory, this);
} }
Thread.Sleep(1);
} }
if (uninstalling || !selection.Koaloader) if (uninstalling || !selection.Koaloader)
foreach ((string directory, BinaryType _) in selection.ExecutableDirectories) 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); UpdateUser("Uninstalling Koaloader from " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation);
await Koaloader.Uninstall(directory, selection.RootDirectory, this); await Koaloader.Uninstall(directory, selection.RootDirectory, this);
} }
Thread.Sleep(1);
} }
bool uninstallProxy = uninstalling || selection.Koaloader; bool uninstallProxy = uninstalling || selection.Koaloader;
int count = selection.DllDirectories.Count, cur = 0; int count = selection.DllDirectories.Count, cur = 0;
@ -173,7 +170,6 @@ internal sealed partial class InstallForm : CustomForm
} }
} }
UpdateProgress(++cur / count * 100); UpdateProgress(++cur / count * 100);
Thread.Sleep(1);
} }
if (selection.Koaloader && !uninstalling) if (selection.Koaloader && !uninstalling)
foreach ((string directory, BinaryType binaryType) in selection.ExecutableDirectories) 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."); throw new CustomMessageException("The operation was canceled.");
UpdateUser("Installing Koaloader to " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation); UpdateUser("Installing Koaloader to " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation);
await Koaloader.Install(directory, binaryType, selection, selection.RootDirectory, this); await Koaloader.Install(directory, binaryType, selection, selection.RootDirectory, this);
Thread.Sleep(1);
} }
UpdateProgress(100); UpdateProgress(100);
} }
private async Task Operate() private async Task Operate()
{ {
List<Selection> programSelections = Selection.AllEnabled; HashSet<Selection> programSelections = Selection.AllEnabled;
operationsCount = programSelections.Count; operationsCount = programSelections.Count;
completeOperationsCount = 0; completeOperationsCount = 0;
foreach (Selection selection in programSelections) foreach (Selection selection in programSelections)
@ -201,7 +196,7 @@ internal sealed partial class InstallForm : CustomForm
await OperateFor(selection); await OperateFor(selection);
UpdateUser($"Operation succeeded for {selection.Name}.", LogTextBox.Success); UpdateUser($"Operation succeeded for {selection.Name}.", LogTextBox.Success);
selection.Enabled = false; selection.Enabled = false;
disabledSelections.Add(selection); _ = disabledSelections.Add(selection);
} }
catch (Exception exception) catch (Exception exception)
{ {
@ -210,7 +205,7 @@ internal sealed partial class InstallForm : CustomForm
++completeOperationsCount; ++completeOperationsCount;
} }
Program.Cleanup(); Program.Cleanup();
List<Selection> failedSelections = Selection.AllEnabled; HashSet<Selection> failedSelections = Selection.AllEnabled;
if (failedSelections.Count > 0) if (failedSelections.Count > 0)
if (failedSelections.Count == 1) if (failedSelections.Count == 1)
throw new CustomMessageException($"Operation failed for {failedSelections.First().Name}."); throw new CustomMessageException($"Operation failed for {failedSelections.First().Name}.");

View file

@ -2,6 +2,7 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
@ -128,30 +129,16 @@ internal sealed partial class SelectForm : CustomForm
HashSet<string> dllDirectories = await ParadoxLauncher.InstallPath.GetDllDirectoriesFromGameDirectory(Platform.Paradox); HashSet<string> dllDirectories = await ParadoxLauncher.InstallPath.GetDllDirectoriesFromGameDirectory(Platform.Paradox);
if (dllDirectories is not null) 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) if (uninstallAll)
{ selection.Enabled = true;
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;
}
else else
{ {
Selection selection = Selection.FromPlatformId(Platform.Paradox, "PL") ?? new();
if (allCheckBox.Checked) if (allCheckBox.Checked)
selection.Enabled = true; selection.Enabled = true;
if (koaloaderAllCheckBox.Checked) if (koaloaderAllCheckBox.Checked)
selection.Koaloader = true; 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(); TreeNode programNode = treeNodes.Find(s => s.Tag is Platform.Paradox && s.Name == selection.Id) ?? new TreeNode();
programNode.Tag = selection.Platform; programNode.Tag = selection.Platform;
programNode.Name = selection.Id; programNode.Name = selection.Id;
@ -166,7 +153,7 @@ internal sealed partial class SelectForm : CustomForm
int steamGamesToCheck; int steamGamesToCheck;
if (uninstallAll || programsToScan.Any(c => c.platform is Platform.Steam)) 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; steamGamesToCheck = steamGames.Count;
foreach ((string appId, string name, string branch, int buildId, string gameDirectory) in steamGames) 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) 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.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); RemoveFromRemainingGames(name);
return; return;
} }
@ -214,14 +196,15 @@ internal sealed partial class SelectForm : CustomForm
} }
if (Program.Canceled) if (Program.Canceled)
return; return;
ConcurrentDictionary<string, SelectionDLC> dlc = new(); ConcurrentDictionary<SelectionDLC, byte> dlc = new();
List<Task> dlcTasks = new(); List<Task> dlcTasks = new();
List<string> dlcIds = new(); HashSet<string> dlcIds = new();
if (appData is not null) 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) if (appInfo is not null)
dlcIds.AddRange(await SteamCMD.ParseDlcAppIds(appInfo)); foreach (string dlcId in await SteamCMD.ParseDlcAppIds(appInfo))
dlcIds = dlcIds.Distinct().ToList(); _ = dlcIds.Add(dlcId);
if (dlcIds.Count > 0) if (dlcIds.Count > 0)
foreach (string dlcAppId in dlcIds) foreach (string dlcAppId in dlcIds)
{ {
@ -291,20 +274,20 @@ internal sealed partial class SelectForm : CustomForm
if (Program.Canceled) if (Program.Canceled)
return; return;
if (!string.IsNullOrWhiteSpace(fullGameName)) if (!string.IsNullOrWhiteSpace(fullGameName))
dlc[fullGameAppId] = new() {
{ SelectionDLC fullGameDlc = SelectionDLC.GetOrCreate(fullGameOnSteamStore ? DLCType.Steam : DLCType.SteamHidden,
Id = fullGameAppId, Type = fullGameOnSteamStore ? DLCType.Steam : DLCType.SteamHidden, Name = fullGameName, fullGameAppId, fullGameName);
Icon = fullGameIcon fullGameDlc.Icon = fullGameIcon;
}; _ = dlc.TryAdd(fullGameDlc, default);
}
} }
if (Program.Canceled) if (Program.Canceled)
return; return;
if (string.IsNullOrWhiteSpace(dlcName)) if (string.IsNullOrWhiteSpace(dlcName))
dlcName = "Unknown"; dlcName = "Unknown";
dlc[dlcAppId] = new() SelectionDLC _dlc = SelectionDLC.GetOrCreate(onSteamStore ? DLCType.Steam : DLCType.SteamHidden, dlcAppId, dlcName);
{ _dlc.Icon = dlcIcon;
Id = dlcAppId, Type = onSteamStore ? DLCType.Steam : DLCType.SteamHidden, Name = dlcName, Icon = dlcIcon _ = dlc.TryAdd(_dlc, default);
};
RemoveFromRemainingDLCs(dlcAppId); RemoveFromRemainingDLCs(dlcAppId);
}); });
dlcTasks.Add(task); dlcTasks.Add(task);
@ -328,17 +311,12 @@ internal sealed partial class SelectForm : CustomForm
RemoveFromRemainingGames(name); RemoveFromRemainingGames(name);
return; 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.Enabled = allCheckBox.Checked || selection.DLC.Any(dlc => dlc.Enabled)
|| selection.ExtraSelections.Any(extraSelection => extraSelection.DLC.Any(dlc => dlc.Enabled)); || selection.ExtraSelections.Any(extraSelection => extraSelection.DLC.Any(dlc => dlc.Enabled));
if (koaloaderAllCheckBox.Checked) if (koaloaderAllCheckBox.Checked)
selection.Koaloader = true; 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.Product = "https://store.steampowered.com/app/" + appId;
selection.Icon = IconGrabber.SteamAppImagesPath + @$"\{appId}\{appInfo?.Value.GetChild("common")?.GetChild("icon")}.jpg"; selection.Icon = IconGrabber.SteamAppImagesPath + @$"\{appId}\{appInfo?.Value.GetChild("common")?.GetChild("icon")}.jpg";
selection.SubIcon = appData?.HeaderImage ?? IconGrabber.SteamAppImagesPath selection.SubIcon = appData?.HeaderImage ?? IconGrabber.SteamAppImagesPath
@ -351,21 +329,21 @@ internal sealed partial class SelectForm : CustomForm
{ {
if (Program.Canceled) if (Program.Canceled)
return; 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.Tag = selection.Platform;
programNode.Name = appId; programNode.Name = appId;
programNode.Text = appData?.Name ?? name; programNode.Text = appData?.Name ?? name;
programNode.Checked = selection.Enabled; programNode.Checked = selection.Enabled;
if (programNode.TreeView is null) if (programNode.TreeView is null)
_ = selectionTreeView.Nodes.Add(programNode); _ = selectionTreeView.Nodes.Add(programNode);
foreach ((_, SelectionDLC dlc) in dlc) foreach ((SelectionDLC dlc, _) in dlc)
{ {
if (Program.Canceled) if (Program.Canceled)
return; return;
dlc.Selection = selection; dlc.Selection = selection;
dlc.Enabled = dlc.Name != "Unknown" && allCheckBox.Checked; dlc.Enabled = dlc.Name != "Unknown" && allCheckBox.Checked;
TreeNode dlcNode = treeNodes.Find(s => s.Tag is Platform.Steam && s.Name == dlc.Id) ?? new TreeNode(); TreeNode dlcNode = treeNodes.Find(s => (DLCType)s.Tag == dlc.Type && s.Name == dlc.Id) ?? new TreeNode();
dlcNode.Tag = selection.Platform; dlcNode.Tag = dlc.Type;
dlcNode.Name = dlc.Id; dlcNode.Name = dlc.Id;
dlcNode.Text = dlc.Name; dlcNode.Text = dlc.Name;
dlcNode.Checked = dlc.Enabled; dlcNode.Checked = dlc.Enabled;
@ -382,7 +360,7 @@ internal sealed partial class SelectForm : CustomForm
} }
if (uninstallAll || programsToScan.Any(c => c.platform is Platform.Epic)) if (uninstallAll || programsToScan.Any(c => c.platform is Platform.Epic))
{ {
HashSet<Manifest> epicGames = await EpicLibrary.GetGames(); List<Manifest> epicGames = await EpicLibrary.GetGames();
foreach (Manifest manifest in epicGames) foreach (Manifest manifest in epicGames)
{ {
string @namespace = manifest.CatalogNamespace; string @namespace = manifest.CatalogNamespace;
@ -405,22 +383,17 @@ internal sealed partial class SelectForm : CustomForm
} }
if (uninstallAll) 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.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); RemoveFromRemainingGames(name);
return; return;
} }
if (Program.Canceled) if (Program.Canceled)
return; return;
ConcurrentDictionary<string, SelectionDLC> catalogItems = new(); ConcurrentDictionary<SelectionDLC, byte> catalogItems = new();
// get catalog items // get catalog items
ConcurrentDictionary<string, SelectionDLC> entitlements = new(); ConcurrentDictionary<SelectionDLC, byte> entitlements = new();
List<Task> dlcTasks = new(); List<Task> dlcTasks = new();
List<(string id, string name, string product, string icon, string developer)> List<(string id, string name, string product, string icon, string developer)>
entitlementIds = await EpicStore.QueryEntitlements(@namespace); entitlementIds = await EpicStore.QueryEntitlements(@namespace);
@ -434,11 +407,11 @@ internal sealed partial class SelectForm : CustomForm
{ {
if (Program.Canceled) if (Program.Canceled)
return; return;
entitlements[id] = new() SelectionDLC entitlement = SelectionDLC.GetOrCreate(DLCType.EpicEntitlement, id, name);
{ entitlement.Icon = icon;
Id = id, Name = name, Product = product, Icon = icon, entitlement.Product = product;
Publisher = developer entitlement.Publisher = developer;
}; _ = entitlements.TryAdd(entitlement, default);
RemoveFromRemainingDLCs(id); RemoveFromRemainingDLCs(id);
}); });
dlcTasks.Add(task); dlcTasks.Add(task);
@ -456,24 +429,19 @@ internal sealed partial class SelectForm : CustomForm
RemoveFromRemainingGames(name); RemoveFromRemainingGames(name);
return; 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.Enabled = allCheckBox.Checked || selection.DLC.Any(dlc => dlc.Enabled)
|| selection.ExtraSelections.Any(extraSelection => extraSelection.DLC.Any(dlc => dlc.Enabled)); || selection.ExtraSelections.Any(extraSelection => extraSelection.DLC.Any(dlc => dlc.Enabled));
if (koaloaderAllCheckBox.Checked) if (koaloaderAllCheckBox.Checked)
selection.Koaloader = true; selection.Koaloader = true;
selection.Id = @namespace; foreach ((SelectionDLC dlc, _) in entitlements.Where(dlc => dlc.Key.Name == selection.Name))
selection.Name = name;
selection.RootDirectory = directory;
selection.ExecutableDirectories = await EpicLibrary.GetExecutableDirectories(selection.RootDirectory);
selection.DllDirectories = dllDirectories;
selection.Platform = Platform.Epic;
foreach (KeyValuePair<string, SelectionDLC> dlc in entitlements.Where(dlc => dlc.Value.Name == selection.Name))
{ {
if (Program.Canceled) if (Program.Canceled)
return; return;
selection.Product = "https://www.epicgames.com/store/product/" + dlc.Value.Product; selection.Product = "https://www.epicgames.com/store/product/" + dlc.Product;
selection.Icon = dlc.Value.Icon; selection.Icon = dlc.Icon;
selection.Publisher = dlc.Value.Publisher; selection.Publisher = dlc.Publisher;
} }
if (Program.Canceled) if (Program.Canceled)
return; return;
@ -481,7 +449,7 @@ internal sealed partial class SelectForm : CustomForm
{ {
if (Program.Canceled) if (Program.Canceled)
return; 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.Tag = selection.Platform;
programNode.Name = @namespace; programNode.Name = @namespace;
programNode.Text = name; programNode.Text = name;
@ -489,20 +457,21 @@ internal sealed partial class SelectForm : CustomForm
if (programNode.TreeView is null) if (programNode.TreeView is null)
_ = selectionTreeView.Nodes.Add(programNode); _ = selectionTreeView.Nodes.Add(programNode);
if (!catalogItems.IsEmpty) 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.Name = @namespace + "_catalogItems";
catalogItemsNode.Text = "Catalog Items"; catalogItemsNode.Text = "Catalog Items";
catalogItemsNode.Checked = selection.DLC.Any(dlc => dlc.Type is DLCType.EpicCatalogItem && dlc.Enabled); catalogItemsNode.Checked = selection.DLC.Any(dlc => dlc.Type is DLCType.EpicCatalogItem && dlc.Enabled);
if (catalogItemsNode.Parent is null) if (catalogItemsNode.Parent is null)
_ = programNode.Nodes.Add(catalogItemsNode);*/ _ = programNode.Nodes.Add(catalogItemsNode);*/
foreach ((_, SelectionDLC dlc) in catalogItems) foreach ((SelectionDLC dlc, _) in catalogItems)
{ {
if (Program.Canceled) if (Program.Canceled)
return; return;
dlc.Selection = selection; dlc.Selection = selection;
dlc.Enabled = allCheckBox.Checked; dlc.Enabled = allCheckBox.Checked;
TreeNode dlcNode = treeNodes.Find(s => s.Tag is Platform.Epic && s.Name == dlc.Id) ?? new TreeNode(); TreeNode dlcNode = treeNodes.Find(s => (DLCType)s.Tag == dlc.Type && s.Name == dlc.Id) ?? new TreeNode();
dlcNode.Tag = selection.Platform; dlcNode.Tag = dlc.Type;
dlcNode.Name = dlc.Id; dlcNode.Name = dlc.Id;
dlcNode.Text = dlc.Name; dlcNode.Text = dlc.Name;
dlcNode.Checked = dlc.Enabled; dlcNode.Checked = dlc.Enabled;
@ -511,20 +480,21 @@ internal sealed partial class SelectForm : CustomForm
} }
if (entitlements.IsEmpty) if (entitlements.IsEmpty)
return; 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.Name = @namespace + "_entitlements";
entitlementsNode.Text = "Entitlements"; entitlementsNode.Text = "Entitlements";
entitlementsNode.Checked = selection.DLC.Any(dlc => dlc.Type is DLCType.EpicEntitlement && dlc.Enabled); entitlementsNode.Checked = selection.DLC.Any(dlc => dlc.Type is DLCType.EpicEntitlement && dlc.Enabled);
if (entitlementsNode.Parent is null) if (entitlementsNode.Parent is null)
_ = programNode.Nodes.Add(entitlementsNode);*/ _ = programNode.Nodes.Add(entitlementsNode);*/
foreach ((_, SelectionDLC dlc) in entitlements) foreach ((SelectionDLC dlc, _) in entitlements)
{ {
if (Program.Canceled) if (Program.Canceled)
return; return;
dlc.Selection = selection; dlc.Selection = selection;
dlc.Enabled = allCheckBox.Checked; dlc.Enabled = allCheckBox.Checked;
TreeNode dlcNode = treeNodes.Find(s => s.Tag is Platform.Epic && s.Name == dlc.Id) ?? new TreeNode(); TreeNode dlcNode = treeNodes.Find(s => (DLCType)s.Tag == dlc.Type && s.Name == dlc.Id) ?? new TreeNode();
dlcNode.Tag = selection.Platform; dlcNode.Tag = dlc.Type;
dlcNode.Name = dlc.Id; dlcNode.Name = dlc.Id;
dlcNode.Text = dlc.Name; dlcNode.Text = dlc.Name;
dlcNode.Checked = dlc.Enabled; dlcNode.Checked = dlc.Enabled;
@ -541,7 +511,7 @@ internal sealed partial class SelectForm : CustomForm
} }
if (uninstallAll || programsToScan.Any(c => c.platform is Platform.Ubisoft)) 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) foreach ((string gameId, string name, string gameDirectory) in ubisoftGames)
{ {
if (Program.Canceled) if (Program.Canceled)
@ -561,36 +531,26 @@ internal sealed partial class SelectForm : CustomForm
} }
if (uninstallAll) 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.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); RemoveFromRemainingGames(name);
return; return;
} }
if (Program.Canceled) if (Program.Canceled)
return; 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.Enabled = allCheckBox.Checked || selection.DLC.Any(dlc => dlc.Enabled)
|| selection.ExtraSelections.Any(extraSelection => extraSelection.DLC.Any(dlc => dlc.Enabled)); || selection.ExtraSelections.Any(extraSelection => extraSelection.DLC.Any(dlc => dlc.Enabled));
if (koaloaderAllCheckBox.Checked) if (koaloaderAllCheckBox.Checked)
selection.Koaloader = true; 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"); selection.Icon = IconGrabber.GetDomainFaviconUrl("store.ubi.com");
selectionTreeView.Invoke(delegate selectionTreeView.Invoke(delegate
{ {
if (Program.Canceled) if (Program.Canceled)
return; 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.Tag = selection.Platform;
programNode.Name = gameId; programNode.Name = gameId;
programNode.Text = name; programNode.Text = name;
@ -738,7 +698,7 @@ internal sealed partial class SelectForm : CustomForm
OnLoadDlc(null, null); OnLoadDlc(null, null);
OnLoadKoaloader(null, null); OnLoadKoaloader(null, null);
HideProgressBar(); HideProgressBar();
selectionTreeView.Enabled = Selection.All.Count > 0; selectionTreeView.Enabled = !Selection.All.IsEmpty;
allCheckBox.Enabled = selectionTreeView.Enabled; allCheckBox.Enabled = selectionTreeView.Enabled;
koaloaderAllCheckBox.Enabled = selectionTreeView.Enabled; koaloaderAllCheckBox.Enabled = selectionTreeView.Enabled;
noneFoundLabel.Visible = !selectionTreeView.Enabled; noneFoundLabel.Visible = !selectionTreeView.Enabled;
@ -791,14 +751,16 @@ internal sealed partial class SelectForm : CustomForm
} }
private static void SyncNodeDescendants(TreeNode node) private static void SyncNodeDescendants(TreeNode node)
=> node.Nodes.Cast<TreeNode>().ToList().ForEach(childNode => {
foreach (TreeNode childNode in node.Nodes)
{ {
if (childNode.Text == "Unknown") if (childNode.Text == "Unknown")
return; return;
childNode.Checked = node.Checked; childNode.Checked = node.Checked;
SyncNode(childNode); SyncNode(childNode);
SyncNodeDescendants(childNode); SyncNodeDescendants(childNode);
}); }
}
private static void SyncNode(TreeNode node) private static void SyncNode(TreeNode node)
{ {
@ -810,7 +772,8 @@ internal sealed partial class SelectForm : CustomForm
selection.Enabled = node.Checked; selection.Enabled = node.Checked;
return; return;
} }
SelectionDLC dlc = SelectionDLC.FromPlatformId(platform, id); DLCType type = (DLCType)node.Tag;
SelectionDLC dlc = SelectionDLC.FromTypeId(type, id);
if (dlc is not null) if (dlc is not null)
dlc.Enabled = node.Checked; dlc.Enabled = node.Checked;
} }
@ -867,10 +830,10 @@ internal sealed partial class SelectForm : CustomForm
Selection selection = Selection.FromPlatformId(platform, id); Selection selection = Selection.FromPlatformId(platform, id);
SelectionDLC dlc = null; SelectionDLC dlc = null;
if (selection is null) if (selection is null)
dlc = SelectionDLC.FromPlatformId(platform, id); dlc = SelectionDLC.FromTypeId((DLCType)node.Tag, id);
Selection dlcParentSelection = null; Selection dlcParentSelection = null;
if (dlc is not null) if (dlc is not null)
dlcParentSelection = Selection.FromPlatformId(platform, dlc.Selection.Id); dlcParentSelection = dlc.Selection;
if (selection is null && dlcParentSelection is null) if (selection is null && dlcParentSelection is null)
return; return;
ContextMenuItem header = id == "PL" ContextMenuItem header = id == "PL"
@ -922,10 +885,10 @@ internal sealed partial class SelectForm : CustomForm
_ = items.Add(new ContextMenuItem("Open Root Directory", "File Explorer", _ = items.Add(new ContextMenuItem("Open Root Directory", "File Explorer",
(_, _) => Diagnostics.OpenDirectoryInFileExplorer(selection.RootDirectory))); (_, _) => Diagnostics.OpenDirectoryInFileExplorer(selection.RootDirectory)));
int executables = 0; 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)", _ = items.Add(new ContextMenuItem($"Open Executable Directory #{++executables} ({(binaryType == BinaryType.BIT32 ? "32" : "64")}-bit)",
"File Explorer", (_, _) => Diagnostics.OpenDirectoryInFileExplorer(directory))); "File Explorer", (_, _) => Diagnostics.OpenDirectoryInFileExplorer(directory)));
List<string> directories = selection.DllDirectories.ToList(); HashSet<string> directories = selection.DllDirectories;
int steam = 0, epic = 0, r1 = 0, r2 = 0; int steam = 0, epic = 0, r1 = 0, r2 = 0;
if (selection.Platform is Platform.Steam or Platform.Paradox) if (selection.Platform is Platform.Steam or Platform.Paradox)
foreach (string directory in directories) foreach (string directory in directories)
@ -1020,7 +983,7 @@ internal sealed partial class SelectForm : CustomForm
private void OnAccept(bool uninstall = false) private void OnAccept(bool uninstall = false)
{ {
if (Selection.All.Count < 1) if (Selection.All.IsEmpty)
return; return;
if (Selection.AllEnabled.Any(selection => !Program.AreDllsLockedDialog(this, selection))) if (Selection.AllEnabled.Any(selection => !Program.AreDllsLockedDialog(this, selection)))
return; return;
@ -1097,12 +1060,12 @@ internal sealed partial class SelectForm : CustomForm
foreach (TreeNode node in TreeNodes) foreach (TreeNode node in TreeNodes)
if (node.Parent is { } parent && node.Tag is Platform platform) 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)); choices.Add((platform, node.Parent.Name, node.Name));
else else
_ = choices.RemoveAll(n => n.platform == platform && n.gameId == parent.Name && n.dlcId == node.Name); _ = choices.RemoveAll(n => n.platform == platform && n.gameId == parent.Name && n.dlcId == node.Name);
} }
choices = choices.Distinct().ToList();
ProgramData.WriteDlcChoices(choices); ProgramData.WriteDlcChoices(choices);
loadButton.Enabled = CanLoadDlc(); loadButton.Enabled = CanLoadDlc();
saveButton.Enabled = CanSaveDlc(); saveButton.Enabled = CanSaveDlc();
@ -1206,7 +1169,7 @@ internal sealed partial class SelectForm : CustomForm
saveKoaloaderButton.Enabled = CanSaveKoaloader(); saveKoaloaderButton.Enabled = CanSaveKoaloader();
resetKoaloaderButton.Enabled = CanResetKoaloader(); resetKoaloaderButton.Enabled = CanResetKoaloader();
koaloaderAllCheckBox.CheckedChanged -= OnKoaloaderAllCheckBoxChanged; koaloaderAllCheckBox.CheckedChanged -= OnKoaloaderAllCheckBoxChanged;
koaloaderAllCheckBox.Checked = Selection.AllSafe.TrueForAll(selection => selection.Koaloader); koaloaderAllCheckBox.Checked = Selection.AllSafe.All(selection => selection.Koaloader);
koaloaderAllCheckBox.CheckedChanged += OnKoaloaderAllCheckBoxChanged; koaloaderAllCheckBox.CheckedChanged += OnKoaloaderAllCheckBoxChanged;
} }

View file

@ -146,7 +146,7 @@ internal sealed partial class UpdateForm : CustomForm
throw new TaskCanceledException(); throw new TaskCanceledException();
using HttpResponseMessage response = await HttpClientManager.HttpClient.GetAsync(latestRelease.Asset.BrowserDownloadUrl, using HttpResponseMessage response = await HttpClientManager.HttpClient.GetAsync(latestRelease.Asset.BrowserDownloadUrl,
HttpCompletionOption.ResponseHeadersRead, cancellation.Token); HttpCompletionOption.ResponseHeadersRead, cancellation.Token);
response.EnsureSuccessStatusCode(); _ = response.EnsureSuccessStatusCode();
if (cancellation is null || Program.Canceled) if (cancellation is null || Program.Canceled)
throw new TaskCanceledException(); throw new TaskCanceledException();
await using Stream download = await response.Content.ReadAsStreamAsync(cancellation.Token); 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 properExecutable = Path.GetFileName(ExecutablePath);
string properExecutablePath = Path.Combine(currentDirectory!, properExecutable!); string properExecutablePath = Path.Combine(currentDirectory!, properExecutable!);
StringBuilder commands = new(); StringBuilder commands = new();
commands.AppendLine(CultureInfo.InvariantCulture, $"\nTASKKILL /F /T /PID {Program.CurrentProcessId}"); _ = commands.AppendLine(CultureInfo.InvariantCulture, $"\nTASKKILL /F /T /PID {Program.CurrentProcessId}");
commands.AppendLine(CultureInfo.InvariantCulture, $":LOOP"); _ = commands.AppendLine(CultureInfo.InvariantCulture, $":LOOP");
commands.AppendLine(CultureInfo.InvariantCulture, $"TASKLIST | FIND \" {Program.CurrentProcessId}\" "); _ = commands.AppendLine(CultureInfo.InvariantCulture, $"TASKLIST | FIND \" {Program.CurrentProcessId}\" ");
commands.AppendLine(CultureInfo.InvariantCulture, $"IF NOT ERRORLEVEL 1 ("); _ = commands.AppendLine(CultureInfo.InvariantCulture, $"IF NOT ERRORLEVEL 1 (");
commands.AppendLine(CultureInfo.InvariantCulture, $" TIMEOUT /T 1"); _ = commands.AppendLine(CultureInfo.InvariantCulture, $" TIMEOUT /T 1");
commands.AppendLine(CultureInfo.InvariantCulture, $" GOTO LOOP"); _ = commands.AppendLine(CultureInfo.InvariantCulture, $" GOTO LOOP");
commands.AppendLine(CultureInfo.InvariantCulture, $")"); _ = commands.AppendLine(CultureInfo.InvariantCulture, $")");
commands.AppendLine(CultureInfo.InvariantCulture, $"DEL /F /Q \"{currentPath}\""); _ = commands.AppendLine(CultureInfo.InvariantCulture, $"DEL /F /Q \"{currentPath}\"");
commands.AppendLine(CultureInfo.InvariantCulture, $"DEL /F /Q \"{properExecutablePath}\""); _ = commands.AppendLine(CultureInfo.InvariantCulture, $"DEL /F /Q \"{properExecutablePath}\"");
commands.AppendLine(CultureInfo.InvariantCulture, $"MOVE /Y \"{ExecutablePath}\" \"{properExecutablePath}\""); _ = commands.AppendLine(CultureInfo.InvariantCulture, $"MOVE /Y \"{ExecutablePath}\" \"{properExecutablePath}\"");
commands.AppendLine(CultureInfo.InvariantCulture, $"START \"\" /D \"{currentDirectory}\" \"{properExecutable}\""); _ = commands.AppendLine(CultureInfo.InvariantCulture, $"START \"\" /D \"{currentDirectory}\" \"{properExecutable}\"");
commands.AppendLine(CultureInfo.InvariantCulture, $"EXIT"); _ = commands.AppendLine(CultureInfo.InvariantCulture, $"EXIT");
UpdaterPath.WriteFile(commands.ToString(), true, this); UpdaterPath.WriteFile(commands.ToString(), true, this);
Process process = new(); Process process = new();
ProcessStartInfo startInfo = new() ProcessStartInfo startInfo = new()
@ -216,7 +216,7 @@ internal sealed partial class UpdateForm : CustomForm
CreateNoWindow = true CreateNoWindow = true
}; };
process.StartInfo = startInfo; process.StartInfo = startInfo;
process.Start(); _ = process.Start();
return; return;
} }
if (!retry) if (!retry)

View file

@ -6,7 +6,6 @@ using CreamInstaller.Platforms.Epic.Heroic;
using CreamInstaller.Utility; using CreamInstaller.Utility;
using Microsoft.Win32; using Microsoft.Win32;
using Newtonsoft.Json; using Newtonsoft.Json;
using static CreamInstaller.Resources.Resources;
namespace CreamInstaller.Platforms.Epic; namespace CreamInstaller.Platforms.Epic;
@ -26,13 +25,10 @@ internal static class EpicLibrary
} }
} }
internal static async Task<HashSet<(string directory, BinaryType binaryType)>> GetExecutableDirectories(string gameDirectory) internal static async Task<List<Manifest>> GetGames()
=> await Task.Run(async () => await gameDirectory.GetExecutableDirectories(true));
internal static async Task<HashSet<Manifest>> GetGames()
=> await Task.Run(async () => => await Task.Run(async () =>
{ {
HashSet<Manifest> games = new(); List<Manifest> games = new();
string manifests = EpicManifestsPath; string manifests = EpicManifestsPath;
if (manifests.DirectoryExists()) if (manifests.DirectoryExists())
foreach (string item in manifests.EnumerateDirectory("*.item")) foreach (string item in manifests.EnumerateDirectory("*.item"))
@ -45,7 +41,7 @@ internal static class EpicLibrary
Manifest manifest = JsonConvert.DeserializeObject<Manifest>(json); Manifest manifest = JsonConvert.DeserializeObject<Manifest>(json);
if (manifest is not null && !games.Any(g if (manifest is not null && !games.Any(g
=> g.CatalogNamespace == manifest.CatalogNamespace && g.InstallLocation == manifest.InstallLocation)) => g.CatalogNamespace == manifest.CatalogNamespace && g.InstallLocation == manifest.InstallLocation))
_ = games.Add(manifest); games.Add(manifest);
} }
catch catch
{ {

View file

@ -14,6 +14,4 @@ public class HeroicAppData
[JsonProperty("namespace")] public string Namespace { get; set; } [JsonProperty("namespace")] public string Namespace { get; set; }
[JsonProperty("title")] public string Title { get; set; } [JsonProperty("title")] public string Title { get; set; }
[JsonProperty("runner")] public string Runner { get; set; }
} }

View file

@ -12,7 +12,7 @@ internal static class HeroicLibrary
internal static readonly string HeroicLibraryPath internal static readonly string HeroicLibraryPath
= Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\heroic\store_cache\legendary_library.json"; = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\heroic\store_cache\legendary_library.json";
internal static async Task GetGames(HashSet<Manifest> games) internal static async Task GetGames(List<Manifest> games)
=> await Task.Run(() => => await Task.Run(() =>
{ {
string libraryPath = HeroicLibraryPath; string libraryPath = HeroicLibraryPath;
@ -28,14 +28,14 @@ internal static class HeroicLibrary
try try
{ {
HeroicAppData appData = token.ToObject<HeroicAppData>(); HeroicAppData appData = token.ToObject<HeroicAppData>();
if (appData is null || appData.Runner != "legendary" || string.IsNullOrWhiteSpace(appData.Install.InstallPath)) if (appData is null || string.IsNullOrWhiteSpace(appData.Install.InstallPath))
continue; continue;
Manifest manifest = new() Manifest manifest = new()
{ {
DisplayName = appData.Title, CatalogNamespace = appData.Namespace, InstallLocation = appData.Install.InstallPath DisplayName = appData.Title, CatalogNamespace = appData.Namespace, InstallLocation = appData.Install.InstallPath
}; };
if (!games.Any(g => g.CatalogNamespace == manifest.CatalogNamespace && g.InstallLocation == manifest.InstallLocation)) if (!games.Any(g => g.CatalogNamespace == manifest.CatalogNamespace && g.InstallLocation == manifest.InstallLocation))
_ = games.Add(manifest); games.Add(manifest);
} }
catch catch
{ {

View file

@ -1,6 +1,4 @@
using System.Collections.Generic; using System.Drawing;
using System.Drawing;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
@ -31,9 +29,6 @@ internal static class ParadoxLauncher
} }
} }
internal static async Task<HashSet<(string directory, BinaryType binaryType)>> GetExecutableDirectories(string gameDirectory)
=> await Task.Run(async () => await gameDirectory.GetExecutableDirectories(validFunc: path => !Path.GetFileName(path).Contains("bootstrapper")));
private static void PopulateDlc(Selection paradoxLauncher = null) private static void PopulateDlc(Selection paradoxLauncher = null)
{ {
paradoxLauncher ??= Selection.FromPlatformId(Platform.Paradox, "PL"); paradoxLauncher ??= Selection.FromPlatformId(Platform.Paradox, "PL");

View file

@ -59,7 +59,7 @@ internal static class SteamCMD
{ {
if (appId != null) if (appId != null)
{ {
AttemptCount.TryGetValue(appId, out int count); _ = AttemptCount.TryGetValue(appId, out int count);
AttemptCount[appId] = ++count; AttemptCount[appId] = ++count;
} }
if (Program.Canceled) if (Program.Canceled)
@ -113,7 +113,6 @@ internal static class SteamCMD
_ = Interlocked.Decrement(ref Locks[i]); _ = Interlocked.Decrement(ref Locks[i]);
return appInfo.ToString(); return appInfo.ToString();
} }
Thread.Sleep(200);
} }
Thread.Sleep(200); Thread.Sleep(200);
goto wait_for_lock; goto wait_for_lock;
@ -232,7 +231,7 @@ internal static class SteamCMD
#endif #endif
continue; continue;
} }
if (appInfo.Value.Children().ToList().Count == 0) if (!appInfo.Value.Children().Any())
return appInfo; return appInfo;
VToken type = appInfo.Value.GetChild("common")?.GetChild("type"); VToken type = appInfo.Value.GetChild("common")?.GetChild("type");
if (type is not null && type.ToString() != "Game") if (type is not null && type.ToString() != "Game")
@ -242,7 +241,7 @@ internal static class SteamCMD
return appInfo; return appInfo;
if (type is not null && (!int.TryParse(buildid, out int gamebuildId) || gamebuildId >= buildId)) if (type is not null && (!int.TryParse(buildid, out int gamebuildId) || gamebuildId >= buildId))
return appInfo; return appInfo;
List<string> dlcAppIds = await ParseDlcAppIds(appInfo); HashSet<string> dlcAppIds = await ParseDlcAppIds(appInfo);
foreach (string dlcAppUpdateFile in dlcAppIds.Select(id => $@"{AppInfoPath}\{id}.vdf")) foreach (string dlcAppUpdateFile in dlcAppIds.Select(id => $@"{AppInfoPath}\{id}.vdf"))
dlcAppUpdateFile.DeleteFile(); dlcAppUpdateFile.DeleteFile();
appUpdateFile.DeleteFile(); appUpdateFile.DeleteFile();
@ -253,10 +252,10 @@ internal static class SteamCMD
return null; return null;
} }
internal static async Task<List<string>> ParseDlcAppIds(VProperty appInfo) internal static async Task<HashSet<string>> ParseDlcAppIds(VProperty appInfo)
=> await Task.Run(() => => await Task.Run(() =>
{ {
List<string> dlcIds = new(); HashSet<string> dlcIds = new();
if (Program.Canceled || appInfo is null) if (Program.Canceled || appInfo is null)
return dlcIds; return dlcIds;
VToken extended = appInfo.Value.GetChild("extended"); VToken extended = appInfo.Value.GetChild("extended");
@ -265,8 +264,8 @@ internal static class SteamCMD
{ {
VProperty property = (VProperty)vToken; VProperty property = (VProperty)vToken;
foreach (string id in property.Value.ToString().Split(",")) foreach (string id in property.Value.ToString().Split(","))
if (int.TryParse(id, out int appId) && appId > 0 && !dlcIds.Contains("" + appId)) if (int.TryParse(id, out int appId) && appId > 0)
dlcIds.Add("" + appId); _ = dlcIds.Add("" + appId);
} }
VToken depots = appInfo.Value.GetChild("depots"); VToken depots = appInfo.Value.GetChild("depots");
if (depots is null) 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 _))) foreach (VToken vToken in depots.Where(p => p is VProperty property && int.TryParse(property.Key, out int _)))
{ {
VProperty property = (VProperty)vToken; VProperty property = (VProperty)vToken;
if (int.TryParse(property.Value.GetChild("dlcappid")?.ToString(), out int appId) && appId > 0 && !dlcIds.Contains("" + appId)) if (int.TryParse(property.Value.GetChild("dlcappid")?.ToString(), out int appId) && appId > 0)
dlcIds.Add("" + appId); _ = dlcIds.Add("" + appId);
} }
return dlcIds; return dlcIds;
}); });

View file

@ -4,7 +4,6 @@ using System.Threading.Tasks;
using CreamInstaller.Utility; using CreamInstaller.Utility;
using Gameloop.Vdf.Linq; using Gameloop.Vdf.Linq;
using Microsoft.Win32; using Microsoft.Win32;
using static CreamInstaller.Resources.Resources;
namespace CreamInstaller.Platforms.Steam; namespace CreamInstaller.Platforms.Steam;
@ -22,13 +21,10 @@ internal static class SteamLibrary
} }
} }
internal static async Task<HashSet<(string directory, BinaryType binaryType)>> GetExecutableDirectories(string gameDirectory) internal static async Task<List<(string appId, string name, string branch, int buildId, string gameDirectory)>> GetGames()
=> await Task.Run(async () => await gameDirectory.GetExecutableDirectories(true));
internal static async Task<HashSet<(string appId, string name, string branch, int buildId, string gameDirectory)>> GetGames()
=> await Task.Run(async () => => 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<string> gameLibraryDirectories = await GetLibraryDirectories(); HashSet<string> gameLibraryDirectories = await GetLibraryDirectories();
foreach (string libraryDirectory in gameLibraryDirectories) 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 foreach ((string appId, string name, string branch, int buildId, string gameDirectory) game in
(await GetGamesFromLibraryDirectory(libraryDirectory)).Where(game (await GetGamesFromLibraryDirectory(libraryDirectory)).Where(game
=> !games.Any(_game => _game.appId == game.appId && _game.gameDirectory == game.gameDirectory))) => !games.Any(_game => _game.appId == game.appId && _game.gameDirectory == game.gameDirectory)))
_ = games.Add(game); games.Add(game);
} }
return games; return games;
}); });

View file

@ -18,13 +18,14 @@ internal static class SteamStore
private const int CooldownGame = 600; private const int CooldownGame = 600;
private const int CooldownDlc = 1200; private const int CooldownDlc = 1200;
internal static async Task<List<string>> ParseDlcAppIds(AppData appData) internal static async Task<HashSet<string>> ParseDlcAppIds(AppData appData)
=> await Task.Run(() => => await Task.Run(() =>
{ {
List<string> dlcIds = new(); HashSet<string> dlcIds = new();
if (appData.DLC is null) if (appData.DLC is null)
return dlcIds; 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; return dlcIds;
}); });

View file

@ -4,7 +4,6 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using CreamInstaller.Utility; using CreamInstaller.Utility;
using Microsoft.Win32; using Microsoft.Win32;
using static CreamInstaller.Resources.Resources;
namespace CreamInstaller.Platforms.Ubisoft; namespace CreamInstaller.Platforms.Ubisoft;
@ -21,13 +20,10 @@ internal static class UbisoftLibrary
} }
} }
internal static async Task<HashSet<(string directory, BinaryType binaryType)>> GetExecutableDirectories(string gameDirectory) internal static async Task<List<(string gameId, string name, string gameDirectory)>> GetGames()
=> await Task.Run(async () => await gameDirectory.GetExecutableDirectories(true));
internal static async Task<HashSet<(string gameId, string name, string gameDirectory)>> GetGames()
=> await Task.Run(() => => await Task.Run(() =>
{ {
HashSet<(string gameId, string name, string gameDirectory)> games = new(); List<(string gameId, string name, string gameDirectory)> games = new();
RegistryKey installsKey = InstallsKey; RegistryKey installsKey = InstallsKey;
if (installsKey is null) if (installsKey is null)
return games; return games;
@ -36,7 +32,7 @@ internal static class UbisoftLibrary
RegistryKey installKey = installsKey.OpenSubKey(gameId); RegistryKey installKey = installsKey.OpenSubKey(gameId);
string installDir = installKey?.GetValue("InstallDir")?.ToString()?.BeautifyPath(); string installDir = installKey?.GetValue("InstallDir")?.ToString()?.BeautifyPath();
if (installDir is not null && !games.Any(g => g.gameId == gameId && g.gameDirectory == installDir)) 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; return games;
}); });

View file

@ -38,7 +38,7 @@ internal static class Koaloader
private static void WriteProxy(this string path, string proxyName, BinaryType binaryType) 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); resourceIdentifier.GetProxyInfoFromIdentifier(out string _proxyName, out BinaryType _binaryType);
if (_proxyName != proxyName || _binaryType != binaryType) if (_proxyName != proxyName || _binaryType != binaryType)

View file

@ -4,7 +4,6 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
using CreamInstaller.Utility; using CreamInstaller.Utility;
@ -13,12 +12,12 @@ namespace CreamInstaller.Resources;
internal static class Resources internal static class Resources
{ {
private static List<string> embeddedResources; private static HashSet<string> embeddedResources;
private static readonly Dictionary<ResourceIdentifier, IReadOnlyList<string>> ResourceMD5s = new() private static readonly Dictionary<ResourceIdentifier, HashSet<string>> ResourceMD5s = new()
{ {
{ {
ResourceIdentifier.Koaloader, new List<string> ResourceIdentifier.Koaloader, new()
{ {
"8A0958687B5ED7C34DAD037856DD1530", // Koaloader v2.0.0 "8A0958687B5ED7C34DAD037856DD1530", // Koaloader v2.0.0
"8FECDEB40980F4E687C10E056232D96B", // Koaloader v2.0.0 "8FECDEB40980F4E687C10E056232D96B", // Koaloader v2.0.0
@ -355,7 +354,7 @@ internal static class Resources
} }
}, },
{ {
ResourceIdentifier.EpicOnlineServices32, new List<string> ResourceIdentifier.EpicOnlineServices32, new()
{ {
"069A57B1834A960193D2AD6B96926D70", // ScreamAPI v3.0.0 "069A57B1834A960193D2AD6B96926D70", // ScreamAPI v3.0.0
"E2FB3A4A9583FDC215832E5F935E4440", // ScreamAPI v3.0.1 "E2FB3A4A9583FDC215832E5F935E4440", // ScreamAPI v3.0.1
@ -363,7 +362,7 @@ internal static class Resources
} }
}, },
{ {
ResourceIdentifier.EpicOnlineServices64, new List<string> ResourceIdentifier.EpicOnlineServices64, new()
{ {
"0D62E57139F1A64F807A9934946A9474", // ScreamAPI v3.0.0 "0D62E57139F1A64F807A9934946A9474", // ScreamAPI v3.0.0
"3875C7B735EE80C23239CC4749FDCBE6", // ScreamAPI v3.0.1 "3875C7B735EE80C23239CC4749FDCBE6", // ScreamAPI v3.0.1
@ -371,7 +370,7 @@ internal static class Resources
} }
}, },
{ {
ResourceIdentifier.Steamworks32, new List<string> ResourceIdentifier.Steamworks32, new()
{ {
"02594110FE56B2945955D46670B9A094", // CreamAPI v4.5.0.0 Hotfix "02594110FE56B2945955D46670B9A094", // CreamAPI v4.5.0.0 Hotfix
"B2434578957CBE38BDCE0A671C1262FC", // SmokeAPI v1.0.0 "B2434578957CBE38BDCE0A671C1262FC", // SmokeAPI v1.0.0
@ -389,7 +388,7 @@ internal static class Resources
} }
}, },
{ {
ResourceIdentifier.Steamworks64, new List<string> ResourceIdentifier.Steamworks64, new()
{ {
"30091B91923D9583A54A93ED1145554B", // CreamAPI v4.5.0.0 Hotfix "30091B91923D9583A54A93ED1145554B", // CreamAPI v4.5.0.0 Hotfix
"08713035CAD6F52548FF324D0487B88D", // SmokeAPI v1.0.0 "08713035CAD6F52548FF324D0487B88D", // SmokeAPI v1.0.0
@ -407,26 +406,26 @@ internal static class Resources
} }
}, },
{ {
ResourceIdentifier.Uplay32, new List<string> ResourceIdentifier.Uplay32, new()
{ {
"1977967B2549A38EC2DB39D4C8ED499B" // Uplay R1 Unlocker v2.0.0 "1977967B2549A38EC2DB39D4C8ED499B" // Uplay R1 Unlocker v2.0.0
} }
}, },
{ {
ResourceIdentifier.Uplay64, new List<string> ResourceIdentifier.Uplay64, new()
{ {
"333FEDD9DC2B299419B37ED1624FF8DB" // Uplay R1 Unlocker v2.0.0 "333FEDD9DC2B299419B37ED1624FF8DB" // Uplay R1 Unlocker v2.0.0
} }
}, },
{ {
ResourceIdentifier.Upc32, new List<string> ResourceIdentifier.Upc32, new()
{ {
"C14368BC4EE19FDE8DBAC07E31C67AE4", // Uplay R2 Unlocker v3.0.0 "C14368BC4EE19FDE8DBAC07E31C67AE4", // Uplay R2 Unlocker v3.0.0
"DED3A3EA1876E3110D7D87B9A22946B0" // Uplay R2 Unlocker v3.0.1 "DED3A3EA1876E3110D7D87B9A22946B0" // Uplay R2 Unlocker v3.0.1
} }
}, },
{ {
ResourceIdentifier.Upc64, new List<string> ResourceIdentifier.Upc64, new()
{ {
"7D9A4C12972BAABCB6C181920CC0F19B", // Uplay R2 Unlocker v3.0.0 "7D9A4C12972BAABCB6C181920CC0F19B", // Uplay R2 Unlocker v3.0.0
"D7FDBFE0FC8D7600FEB8EC0A97713184" // Uplay R2 Unlocker v3.0.1 "D7FDBFE0FC8D7600FEB8EC0A97713184" // Uplay R2 Unlocker v3.0.1
@ -434,7 +433,7 @@ internal static class Resources
} }
}; };
internal static List<string> EmbeddedResources internal static HashSet<string> EmbeddedResources
{ {
get get
{ {
@ -443,7 +442,7 @@ internal static class Resources
string[] names = Assembly.GetExecutingAssembly().GetManifestResourceNames(); string[] names = Assembly.GetExecutingAssembly().GetManifestResourceNames();
embeddedResources = new(); embeddedResources = new();
foreach (string resourceName in names.Where(n => n.StartsWith("CreamInstaller.Resources.", StringComparison.Ordinal))) foreach (string resourceName in names.Where(n => n.StartsWith("CreamInstaller.Resources.", StringComparison.Ordinal)))
embeddedResources.Add(resourceName[25..]); _ = embeddedResources.Add(resourceName[25..]);
return embeddedResources; 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 bool TryGetFileBinaryType(this string path, out BinaryType binaryType) => NativeImports.GetBinaryType(path, out binaryType);
internal static async Task<HashSet<(string directory, BinaryType binaryType)>> GetExecutableDirectories(this string rootDirectory, internal static async Task<List<(string directory, BinaryType binaryType)>> GetExecutableDirectories(this string rootDirectory, bool filterCommon = false,
bool filterCommon = false, Func<string, bool> validFunc = null) Func<string, bool> validFunc = null)
=> await Task.Run(async () => await Task.Run(async ()
=> (await rootDirectory.GetExecutables(filterCommon, validFunc) => (await rootDirectory.GetExecutables(filterCommon, validFunc)
?? (filterCommon || validFunc is not null ? await rootDirectory.GetExecutables() : null))?.Select(e => ?? (filterCommon || validFunc is not null ? await rootDirectory.GetExecutables() : null))?.Select(e =>
{ {
e.path = Path.GetDirectoryName(e.path); e.path = Path.GetDirectoryName(e.path);
return e; return e;
}).DistinctBy(e => e.path).ToHashSet()); }).DistinctBy(e => e.path).ToList());
internal static async Task<HashSet<(string path, BinaryType binaryType)>> GetExecutables(this string rootDirectory, bool filterCommon = false, internal static async Task<List<(string path, BinaryType binaryType)>> GetExecutables(this string rootDirectory, bool filterCommon = false,
Func<string, bool> validFunc = null) Func<string, bool> validFunc = null)
=> await Task.Run(() => => await Task.Run(() =>
{ {
HashSet<(string path, BinaryType binaryType)> executables = new(); List<(string path, BinaryType binaryType)> executables = new();
if (Program.Canceled || !rootDirectory.DirectoryExists()) if (Program.Canceled || !rootDirectory.DirectoryExists())
return null; return null;
foreach (string path in rootDirectory.EnumerateDirectory("*.exe", true)) 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)) if (executables.All(e => e.path != path) && (!filterCommon || !rootDirectory.IsCommonIncorrectExecutable(path))
&& (validFunc is null || validFunc(path)) && path.TryGetFileBinaryType(out BinaryType binaryType) && (validFunc is null || validFunc(path)) && path.TryGetFileBinaryType(out BinaryType binaryType)
&& binaryType is BinaryType.BIT64) && binaryType is BinaryType.BIT64)
_ = executables.Add((path, binaryType)); executables.Add((path, binaryType));
Thread.Sleep(1);
} }
foreach (string path in rootDirectory.EnumerateDirectory("*.exe", true)) 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)) if (executables.All(e => e.path != path) && (!filterCommon || !rootDirectory.IsCommonIncorrectExecutable(path))
&& (validFunc is null || validFunc(path)) && path.TryGetFileBinaryType(out BinaryType binaryType) && (validFunc is null || validFunc(path)) && path.TryGetFileBinaryType(out BinaryType binaryType)
&& binaryType is BinaryType.BIT32) && binaryType is BinaryType.BIT32)
_ = executables.Add((path, binaryType)); executables.Add((path, binaryType));
Thread.Sleep(1);
} }
return executables.Count > 0 ? executables : null; return executables.Count > 0 ? executables : null;
}); });

View file

@ -25,12 +25,14 @@ internal static class ScreamAPI
internal static void CheckConfig(string directory, Selection selection, InstallForm installForm = null) internal static void CheckConfig(string directory, Selection selection, InstallForm installForm = null)
{ {
directory.GetScreamApiComponents(out _, out _, out _, out _, out string config, out _); directory.GetScreamApiComponents(out _, out _, out _, out _, out string config, out _);
List<SelectionDLC> overrideCatalogItems = selection.DLC.Where(dlc => dlc.Type is DLCType.EpicCatalogItem && !dlc.Enabled).ToList(); HashSet<SelectionDLC> overrideCatalogItems = selection.DLC.Where(dlc => dlc.Type is DLCType.EpicCatalogItem && !dlc.Enabled).ToHashSet();
List<SelectionDLC> overrideEntitlements = selection.DLC.Where(dlc => dlc.Type is DLCType.EpicEntitlement && !dlc.Enabled).ToList(); HashSet<SelectionDLC> overrideEntitlements = selection.DLC.Where(dlc => dlc.Type is DLCType.EpicEntitlement && !dlc.Enabled).ToHashSet();
foreach (Selection extraSelection in selection.ExtraSelections) foreach (Selection extraSelection in selection.ExtraSelections)
{ {
overrideCatalogItems.AddRange(extraSelection.DLC.Where(dlc => dlc.Type is DLCType.EpicCatalogItem && !dlc.Enabled)); foreach (SelectionDLC extraDlc in extraSelection.DLC.Where(dlc => dlc.Type is DLCType.EpicCatalogItem && !dlc.Enabled))
overrideEntitlements.AddRange(extraSelection.DLC.Where(dlc => dlc.Type is DLCType.EpicEntitlement && !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) if (overrideCatalogItems.Count > 0 || overrideEntitlements.Count > 0)
{ {

View file

@ -28,12 +28,13 @@ internal static class SmokeAPI
internal static void CheckConfig(string directory, Selection selection, InstallForm installForm = null) 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 _); directory.GetSmokeApiComponents(out _, out _, out _, out _, out string old_config, out string config, out _, out _, out _);
List<SelectionDLC> overrideDlc = selection.DLC.Where(dlc => !dlc.Enabled).ToList(); HashSet<SelectionDLC> overrideDlc = selection.DLC.Where(dlc => !dlc.Enabled).ToHashSet();
foreach (Selection extraSelection in selection.ExtraSelections) foreach (SelectionDLC extraDlc in selection.ExtraSelections.SelectMany(extraSelection => extraSelection.DLC.Where(dlc => !dlc.Enabled)))
overrideDlc.AddRange(extraSelection.DLC.Where(dlc => !dlc.Enabled)); _ = overrideDlc.Add(extraDlc);
List<SelectionDLC> injectDlc = new(); HashSet<SelectionDLC> injectDlc = new();
if (selection.DLC.Count() > 64) 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<KeyValuePair<string, (string name, SortedList<string, SelectionDLC> injectDlc)>> extraApps = new(); List<KeyValuePair<string, (string name, SortedList<string, SelectionDLC> injectDlc)>> extraApps = new();
foreach (Selection extraSelection in selection.ExtraSelections.Where(extraSelection => extraSelection.DLC.Count() > 64)) foreach (Selection extraSelection in selection.ExtraSelections.Where(extraSelection => extraSelection.DLC.Count() > 64))
{ {

View file

@ -25,9 +25,9 @@ internal static class UplayR1
internal static void CheckConfig(string directory, Selection selection, InstallForm installForm = null) internal static void CheckConfig(string directory, Selection selection, InstallForm installForm = null)
{ {
directory.GetUplayR1Components(out _, out _, out _, out _, out string config, out _); directory.GetUplayR1Components(out _, out _, out _, out _, out string config, out _);
List<SelectionDLC> blacklistDlc = selection.DLC.Where(dlc => !dlc.Enabled).ToList(); HashSet<SelectionDLC> blacklistDlc = selection.DLC.Where(dlc => !dlc.Enabled).ToHashSet();
foreach (Selection extraSelection in selection.ExtraSelections) foreach (SelectionDLC extraDlc in selection.ExtraSelections.SelectMany(extraSelection => extraSelection.DLC.Where(dlc => !dlc.Enabled)))
blacklistDlc.AddRange(extraSelection.DLC.Where(dlc => !dlc.Enabled)); _ = blacklistDlc.Add(extraDlc);
if (blacklistDlc.Count > 0) if (blacklistDlc.Count > 0)
{ {
/*if (installForm is not null) /*if (installForm is not null)
@ -100,11 +100,10 @@ internal static class UplayR1
config.DeleteFile(); config.DeleteFile();
installForm?.UpdateUser($"Deleted configuration: {Path.GetFileName(config)}", LogTextBox.Action, false); installForm?.UpdateUser($"Deleted configuration: {Path.GetFileName(config)}", LogTextBox.Action, false);
} }
if (log.FileExists()) if (!log.FileExists())
{ return;
log.DeleteFile(); log.DeleteFile();
installForm?.UpdateUser($"Deleted log: {Path.GetFileName(log)}", LogTextBox.Action, false); 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) internal static async Task Install(string directory, Selection selection, InstallForm installForm = null, bool generateConfig = true)

View file

@ -27,9 +27,9 @@ internal static class UplayR2
internal static void CheckConfig(string directory, Selection selection, InstallForm installForm = null) internal static void CheckConfig(string directory, Selection selection, InstallForm installForm = null)
{ {
directory.GetUplayR2Components(out _, out _, out _, out _, out _, out _, out string config, out _); directory.GetUplayR2Components(out _, out _, out _, out _, out _, out _, out string config, out _);
List<SelectionDLC> blacklistDlc = selection.DLC.Where(dlc => !dlc.Enabled).ToList(); HashSet<SelectionDLC> blacklistDlc = selection.DLC.Where(dlc => !dlc.Enabled).ToHashSet();
foreach (Selection extraSelection in selection.ExtraSelections) foreach (SelectionDLC extraDlc in selection.ExtraSelections.SelectMany(extraSelection => extraSelection.DLC.Where(dlc => !dlc.Enabled)))
blacklistDlc.AddRange(extraSelection.DLC.Where(dlc => !dlc.Enabled)); _ = blacklistDlc.Add(extraDlc);
if (blacklistDlc.Count > 0) if (blacklistDlc.Count > 0)
{ {
/*if (installForm is not null) /*if (installForm is not null)

View file

@ -1,4 +1,6 @@
using System.Collections.Generic; using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using CreamInstaller.Resources; using CreamInstaller.Resources;
using CreamInstaller.Utility; using CreamInstaller.Utility;
@ -12,31 +14,42 @@ public enum Platform
Epic, Ubisoft Epic, Ubisoft
} }
internal sealed class Selection internal sealed class Selection : IEquatable<Selection>
{ {
internal const string DefaultKoaloaderProxy = "version"; internal const string DefaultKoaloaderProxy = "version";
internal static readonly HashSet<Selection> All = new(); internal static readonly ConcurrentDictionary<Selection, byte> All = new();
internal readonly HashSet<Selection> ExtraSelections = new();
internal readonly HashSet<string> DllDirectories;
internal readonly List<(string directory, BinaryType binaryType)> ExecutableDirectories;
internal readonly HashSet<Selection> ExtraSelections = new();
internal readonly string Id;
internal readonly string Name;
internal readonly Platform Platform;
internal readonly string RootDirectory;
internal HashSet<string> DllDirectories;
internal bool Enabled; internal bool Enabled;
internal HashSet<(string directory, BinaryType binaryType)> ExecutableDirectories;
internal string Icon; internal string Icon;
internal string Id = "0";
internal bool Koaloader; internal bool Koaloader;
internal string KoaloaderProxy; internal string KoaloaderProxy;
internal string Name = "Program";
internal Platform Platform;
internal string Product; internal string Product;
internal string Publisher; internal string Publisher;
internal string RootDirectory;
internal string SubIcon; internal string SubIcon;
internal string Website; internal string Website;
internal Selection() => All.Add(this); private Selection(Platform platform, string id, string name, string rootDirectory, HashSet<string> 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<SelectionDLC> DLC => SelectionDLC.AllSafe.Where(dlc => dlc.Selection == this); internal IEnumerable<SelectionDLC> DLC => SelectionDLC.AllSafe.Where(dlc => dlc.Selection.Equals(this));
internal bool AreDllsLocked internal bool AreDllsLocked
{ {
@ -79,13 +92,20 @@ internal sealed class Selection
} }
} }
internal static List<Selection> AllSafe => All.ToList(); internal static HashSet<Selection> AllSafe => All.Keys.ToHashSet();
internal static List<Selection> AllEnabled => AllSafe.FindAll(s => s.Enabled); internal static HashSet<Selection> 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<string> dllDirectories,
List<(string directory, BinaryType binaryType)> executableDirectories)
=> FromPlatformId(platform, id) ?? new Selection(platform, id, name, rootDirectory, dllDirectories, executableDirectories);
private void Remove() private void Remove()
{ {
_ = All.Remove(this); _ = All.TryRemove(this, out _);
foreach (SelectionDLC dlc in DLC) foreach (SelectionDLC dlc in DLC)
dlc.Selection = null; dlc.Selection = null;
} }
@ -113,7 +133,14 @@ internal sealed class Selection
} }
internal static void ValidateAll(List<(Platform platform, string id, string name)> programsToScan) 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);
} }

View file

@ -1,26 +1,36 @@
using System.Collections.Generic; using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace CreamInstaller; namespace CreamInstaller;
public enum DLCType public enum DLCType
{ {
Steam, SteamHidden, EpicCatalogItem, None = 0, Steam, SteamHidden, EpicCatalogItem,
EpicEntitlement EpicEntitlement
} }
internal sealed class SelectionDLC internal sealed class SelectionDLC : IEquatable<SelectionDLC>
{ {
private static readonly HashSet<SelectionDLC> All = new(); private static readonly ConcurrentDictionary<SelectionDLC, byte> All = new();
internal readonly string Id;
internal readonly string Name;
internal readonly DLCType Type;
internal bool Enabled; internal bool Enabled;
internal string Icon; internal string Icon;
internal string Id;
internal string Name;
internal string Product; internal string Product;
internal string Publisher; internal string Publisher;
private Selection selection; private Selection selection;
internal DLCType Type;
private SelectionDLC(DLCType type, string id, string name)
{
Type = type;
Id = id;
Name = name;
}
internal Selection Selection internal Selection Selection
{ {
@ -28,11 +38,19 @@ internal sealed class SelectionDLC
set set
{ {
selection = value; 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<SelectionDLC> AllSafe => All.ToList(); internal static HashSet<SelectionDLC> 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);
} }