start of v4.9.0

- Large refactoring & optimization
- Fixed & optimized right-click context menu
- Fixed silent HTTP cache concurrency exception
- Added a better SafeIO exception output for anti-virus detections
- Updated the help button dialog
This commit is contained in:
pointfeev 2023-05-27 17:50:03 -04:00
parent 324b0606e5
commit 9318fd7768
23 changed files with 484 additions and 489 deletions

View file

@ -51,14 +51,10 @@ internal class CustomForm : Form
_ = helpDialog.Show(SystemIcons.Information, _ = helpDialog.Show(SystemIcons.Information,
"Automatically finds all installed Steam, Epic and Ubisoft games with their respective DLC-related DLL locations on the user's computer,\n" "Automatically finds all installed Steam, Epic and Ubisoft games with their respective DLC-related DLL locations on the user's computer,\n"
+ "parses SteamCMD, Steam Store and Epic Games Store for user-selected games' DLCs, then provides a very simple graphical interface\n" + "parses SteamCMD, Steam Store and Epic Games Store for user-selected games' DLCs, then provides a very simple graphical interface\n"
+ "utilizing the gathered information for the maintenance of DLC unlockers.\n" + "\n" + "utilizing the gathered information for the maintenance of DLC unlockers.\n\n"
+ $"The program utilizes the latest versions of [Koaloader]({acidicoala}/Koaloader), [SmokeAPI]({acidicoala}/SmokeAPI), [ScreamAPI]({acidicoala}/ScreamAPI), [Uplay R1 Unlocker]({acidicoala}/UplayR1Unlocker) and [Uplay R2 Unlocker]({acidicoala}/UplayR2Unlocker), all by\n" + $"The program utilizes the latest versions of [Koaloader]({acidicoala}/Koaloader), [SmokeAPI]({acidicoala}/SmokeAPI), [ScreamAPI]({acidicoala}/ScreamAPI), [Uplay R1 Unlocker]({acidicoala}/UplayR1Unlocker) and [Uplay R2 Unlocker]({acidicoala}/UplayR2Unlocker), all by\n"
+ $"the wonderful [acidicoala]({acidicoala}), and all downloaded and embedded into the program itself; no further downloads necessary on your part!\n" + $"the wonderful [acidicoala]({acidicoala}), and all downloaded and embedded into the program itself; no further downloads necessary on your part!\n\n"
+ "\n" + "NOTE: This program does not automatically download nor install actual DLC files for you. As the title of the program says, it's\n" + "USAGE:\n" + " 1. Choose which programs and/or games the program should scan for DLC.\n"
+ "only a DLC Unlocker installer. Should the game you wish to unlock DLC for not already come with the DLCs installed (very many\n"
+ "do not), you have to find, download, and install those yourself. Preferably, you should be referring to the proper cs.rin.ru post for\n"
+ "the game(s) you're tinkering with; you'll usually find any answer to your problems there.\n" + "\n" + "USAGE:\n"
+ " 1. Choose which programs and/or games the program should scan for DLC.\n"
+ " The program automatically gathers all installed games from Steam, Epic and Ubisoft directories.\n" + " The program automatically gathers all installed games from Steam, Epic and Ubisoft directories.\n"
+ " 2. Wait for the program to download and install SteamCMD (if you chose a Steam game).\n" + " 2. Wait for the program to download and install SteamCMD (if you chose a Steam game).\n"
+ " 3. Wait for the program to gather and cache the chosen games' information && DLCs.\n" + " 3. Wait for the program to gather and cache the chosen games' information && DLCs.\n"
@ -69,10 +65,17 @@ internal class CustomForm : Form
+ " If the default \'version.dll\' doesn't work, then see [here](https://cs.rin.ru/forum/viewtopic.php?p=2552172#p2552172) to find one that does.\n" + " If the default \'version.dll\' doesn't work, then see [here](https://cs.rin.ru/forum/viewtopic.php?p=2552172#p2552172) to find one that does.\n"
+ " 6. Click the \"Generate and Install\" button.\n" + " 7. Click the \"OK\" button to close the program.\n" + " 6. Click the \"Generate and Install\" button.\n" + " 7. Click the \"OK\" button to close the program.\n"
+ " 8. If any of the DLC unlockers cause problems with any of the games you installed them on, simply go back\n" + " 8. If any of the DLC unlockers cause problems with any of the games you installed them on, simply go back\n"
+ " to step 5 and select what games you wish you revert changes to, and instead click the \"Uninstall\" button this time.\n" + "\n" + " to step 5 and select what games you wish you revert changes to, and instead click the \"Uninstall\" button this time.\n\n"
+ $"For reliable and quick assistance, all bugs, crashes and other issues should be referred to the [GitHub Issues]({repository}/issues) page!\n" + "NOTE: This program does not automatically download nor install actual DLC files for you; as the title of the program states, this program\n"
+ "\n" + "SteamCMD installation and appinfo cache can be found at [C:\\ProgramData\\CreamInstaller]().\n" + "is only a DLC Unlocker installer. Should the game you wish to unlock DLC for not already come with the DLCs installed, as is the case with\n"
+ $"The program automatically and very quickly updates from [GitHub]({repository}) using [Onova](https://github.com/Tyrrrz/Onova). (updates can be ignored)\n" + "a good majority of games, you must find, download and install those to the game yourself. This process includes manually installing new\n"
+ "DLCs and manually updating the previously manually installed DLCs after game updates.\n\n"
+ $"For reliable and quick assistance, all bugs, crashes and other issues should be referred to the [GitHub Issues]({repository}/issues) page!\n\n"
+ $"HOWEVER: Please read the template issue corresponding to your problem should one exist! Also, note that the [GitHub Issues]({repository}/issues) page is not\n"
+ "your personal assistance hotline, rather it is for genuine bugs/crashes/issues with the program itself. If you post an issue which has already\n"
+ "been explained within the template issues and/or within this text, I will just close it.\n\n"
+ "SteamCMD installation and appinfo cache can be found at [C:\\ProgramData\\CreamInstaller]().\n"
+ $"The program automatically and very quickly updates from [GitHub]({repository}) by choice of the user through a dialog on startup.\n"
+ $"The program source and other information can be found on [GitHub]({repository})."); + $"The program source and other information can be found on [GitHub]({repository}).");
} }

View file

@ -24,8 +24,8 @@ internal sealed class CustomTreeView : TreeView
private static readonly Color C7 = ColorTranslator.FromHtml("#006900"); private static readonly Color C7 = ColorTranslator.FromHtml("#006900");
private static readonly Color C8 = ColorTranslator.FromHtml("#69AA69"); private static readonly Color C8 = ColorTranslator.FromHtml("#69AA69");
private readonly Dictionary<ProgramSelection, Rectangle> checkBoxBounds = new(); private readonly Dictionary<Selection, Rectangle> checkBoxBounds = new();
private readonly Dictionary<ProgramSelection, Rectangle> comboBoxBounds = new(); private readonly Dictionary<Selection, Rectangle> comboBoxBounds = new();
private readonly Dictionary<TreeNode, Rectangle> selectionBounds = new(); private readonly Dictionary<TreeNode, Rectangle> selectionBounds = new();
private SolidBrush backBrush; private SolidBrush backBrush;
@ -110,7 +110,7 @@ internal sealed class CustomTreeView : TreeView
} }
if (form is SelectForm) if (form is SelectForm)
{ {
ProgramSelection selection = ProgramSelection.FromPlatformId(platform, platformId); Selection selection = Selection.FromPlatformId(platform, platformId);
if (selection is not null) if (selection is not null)
{ {
if (bounds == node.Bounds) if (bounds == node.Bounds)
@ -145,7 +145,7 @@ internal sealed class CustomTreeView : TreeView
{ {
comboBoxFont ??= new(font.FontFamily, 6, font.Style, font.Unit, font.GdiCharSet, font.GdiVerticalFont); comboBoxFont ??= new(font.FontFamily, 6, font.Style, font.Unit, font.GdiCharSet, font.GdiVerticalFont);
ComboBoxState comboBoxState = Enabled ? ComboBoxState.Normal : ComboBoxState.Disabled; ComboBoxState comboBoxState = Enabled ? ComboBoxState.Normal : ComboBoxState.Disabled;
text = (selection.KoaloaderProxy ?? ProgramSelection.DefaultKoaloaderProxy) + ".dll"; text = (selection.KoaloaderProxy ?? Selection.DefaultKoaloaderProxy) + ".dll";
size = TextRenderer.MeasureText(graphics, text, comboBoxFont) + new Size(6, 0); size = TextRenderer.MeasureText(graphics, text, comboBoxFont) + new Size(6, 0);
const int padding = 2; const int padding = 2;
bounds = new(bounds.X + bounds.Width, bounds.Y + padding / 2, size.Width, bounds.Height - padding); bounds = new(bounds.X + bounds.Width, bounds.Y + padding / 2, size.Width, bounds.Height - padding);
@ -186,9 +186,9 @@ internal sealed class CustomTreeView : TreeView
} }
if (e.Button is not MouseButtons.Left) if (e.Button is not MouseButtons.Left)
return; return;
if (comboBoxBounds.Any() && selectForm is not null) if (comboBoxBounds.Count > 0 && selectForm is not null)
foreach (KeyValuePair<ProgramSelection, Rectangle> pair in comboBoxBounds) foreach (KeyValuePair<Selection, Rectangle> pair in comboBoxBounds)
if (!ProgramSelection.All.Contains(pair.Key)) if (!Selection.All.Contains(pair.Key))
_ = comboBoxBounds.Remove(pair.Key); _ = comboBoxBounds.Remove(pair.Key);
else if (pair.Value.Contains(clickPoint)) else if (pair.Value.Contains(clickPoint))
{ {
@ -214,15 +214,15 @@ internal sealed class CustomTreeView : TreeView
if (canUse) if (canUse)
_ = comboBoxDropDown.Items.Add(new ToolStripButton(proxy + ".dll", null, (_, _) => _ = comboBoxDropDown.Items.Add(new ToolStripButton(proxy + ".dll", null, (_, _) =>
{ {
pair.Key.KoaloaderProxy = proxy == ProgramSelection.DefaultKoaloaderProxy ? null : proxy; pair.Key.KoaloaderProxy = proxy == Selection.DefaultKoaloaderProxy ? null : proxy;
selectForm.OnKoaloaderChanged(); selectForm.OnKoaloaderChanged();
}) { Font = comboBoxFont }); }) { Font = comboBoxFont });
} }
comboBoxDropDown.Show(this, PointToScreen(new(pair.Value.Left, pair.Value.Bottom - 1))); comboBoxDropDown.Show(this, PointToScreen(new(pair.Value.Left, pair.Value.Bottom - 1)));
break; break;
} }
foreach (KeyValuePair<ProgramSelection, Rectangle> pair in checkBoxBounds) foreach (KeyValuePair<Selection, Rectangle> pair in checkBoxBounds)
if (!ProgramSelection.All.Contains(pair.Key)) if (!Selection.All.Contains(pair.Key))
_ = checkBoxBounds.Remove(pair.Key); _ = checkBoxBounds.Remove(pair.Key);
else if (pair.Value.Contains(clickPoint)) else if (pair.Value.Contains(clickPoint))
{ {

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.8.1</Version> <Version>4.9.0</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

@ -2,7 +2,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Drawing; using System.Drawing;
using System.Linq;
using System.Windows.Forms; using System.Windows.Forms;
using CreamInstaller.Components; using CreamInstaller.Components;
@ -52,14 +51,14 @@ internal sealed partial class DialogForm : CustomForm
} }
if (customFormIcon is not null) if (customFormIcon is not null)
Icon = customFormIcon; Icon = customFormIcon;
if (!links.Any()) if (links.Count < 1)
return ShowDialog(); return ShowDialog();
foreach (LinkLabel.Link link in links) foreach (LinkLabel.Link link in links)
_ = descriptionLabel.Links.Add(link); _ = descriptionLabel.Links.Add(link);
descriptionLabel.LinkClicked += (_, e) => descriptionLabel.LinkClicked += (s, e) =>
{ {
if (e.Link != null) if (e.Link != null)
Process.Start(new ProcessStartInfo((string)e.Link.LinkData) { UseShellExecute = true }); _ = Process.Start(new ProcessStartInfo((string)e.Link.LinkData) { UseShellExecute = true });
}; };
return ShowDialog(); return ShowDialog();
} }

View file

@ -15,9 +15,9 @@ namespace CreamInstaller.Forms;
internal sealed partial class InstallForm : CustomForm internal sealed partial class InstallForm : CustomForm
{ {
private readonly List<ProgramSelection> disabledSelections = new(); private readonly List<Selection> disabledSelections = new();
private readonly int programCount = ProgramSelection.AllEnabled.Count; private readonly int programCount = Selection.AllEnabled.Count;
private readonly bool uninstalling; private readonly bool uninstalling;
private int completeOperationsCount; private int completeOperationsCount;
@ -58,7 +58,7 @@ internal sealed partial class InstallForm : CustomForm
}); });
} }
private async Task OperateFor(ProgramSelection selection) private async Task OperateFor(Selection selection)
{ {
UpdateProgress(0); UpdateProgress(0);
if (selection.Id == "PL") if (selection.Id == "PL")
@ -189,10 +189,10 @@ internal sealed partial class InstallForm : CustomForm
private async Task Operate() private async Task Operate()
{ {
List<ProgramSelection> programSelections = ProgramSelection.AllEnabled; List<Selection> programSelections = Selection.AllEnabled;
operationsCount = programSelections.Count; operationsCount = programSelections.Count;
completeOperationsCount = 0; completeOperationsCount = 0;
foreach (ProgramSelection selection in programSelections) foreach (Selection selection in programSelections)
{ {
if (Program.Canceled || !Program.AreDllsLockedDialog(this, selection)) if (Program.Canceled || !Program.AreDllsLockedDialog(this, selection))
throw new CustomMessageException("The operation was canceled."); throw new CustomMessageException("The operation was canceled.");
@ -210,13 +210,13 @@ internal sealed partial class InstallForm : CustomForm
++completeOperationsCount; ++completeOperationsCount;
} }
Program.Cleanup(); Program.Cleanup();
List<ProgramSelection> failedSelections = ProgramSelection.AllEnabled; List<Selection> failedSelections = Selection.AllEnabled;
if (failedSelections.Any()) 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}.");
else else
throw new CustomMessageException($"Operation failed for {failedSelections.Count} programs."); throw new CustomMessageException($"Operation failed for {failedSelections.Count} programs.");
foreach (ProgramSelection selection in disabledSelections) foreach (Selection selection in disabledSelections)
selection.Enabled = true; selection.Enabled = true;
disabledSelections.Clear(); disabledSelections.Clear();
} }
@ -281,7 +281,7 @@ internal sealed partial class InstallForm : CustomForm
{ {
Program.Cleanup(); Program.Cleanup();
Reselecting = true; Reselecting = true;
foreach (ProgramSelection selection in disabledSelections) foreach (Selection selection in disabledSelections)
selection.Enabled = true; selection.Enabled = true;
disabledSelections.Clear(); disabledSelections.Clear();
Close(); Close();

View file

@ -16,7 +16,7 @@ internal sealed partial class SelectDialogForm : CustomForm
out List<(Platform platform, string id, string name)> choices) out List<(Platform platform, string id, string name)> choices)
{ {
choices = null; choices = null;
if (!potentialChoices.Any()) if (potentialChoices.Count < 1)
return DialogResult.Cancel; return DialogResult.Cancel;
groupBox.Text = groupBoxText; groupBox.Text = groupBoxText;
allCheckBox.Enabled = false; allCheckBox.Enabled = false;
@ -28,13 +28,13 @@ internal sealed partial class SelectDialogForm : CustomForm
OnTreeNodeChecked(node); OnTreeNodeChecked(node);
_ = selectionTreeView.Nodes.Add(node); _ = selectionTreeView.Nodes.Add(node);
} }
if (!selected.Any()) if (selected.Count < 1)
OnLoad(null, null); OnLoad(null, null);
allCheckBox.CheckedChanged -= OnAllCheckBoxChanged; allCheckBox.CheckedChanged -= OnAllCheckBoxChanged;
allCheckBox.Checked = selectionTreeView.Nodes.Cast<TreeNode>().All(n => n.Checked); allCheckBox.Checked = selectionTreeView.Nodes.Cast<TreeNode>().All(n => n.Checked);
allCheckBox.CheckedChanged += OnAllCheckBoxChanged; allCheckBox.CheckedChanged += OnAllCheckBoxChanged;
allCheckBox.Enabled = true; allCheckBox.Enabled = true;
acceptButton.Enabled = selected.Any(); acceptButton.Enabled = selected.Count > 0;
saveButton.Enabled = acceptButton.Enabled; saveButton.Enabled = acceptButton.Enabled;
loadButton.Enabled = ProgramData.ReadProgramChoices() is not null; loadButton.Enabled = ProgramData.ReadProgramChoices() is not null;
OnResize(null, null); OnResize(null, null);
@ -46,7 +46,7 @@ internal sealed partial class SelectDialogForm : CustomForm
private void OnTreeNodeChecked(object sender, TreeViewEventArgs e) private void OnTreeNodeChecked(object sender, TreeViewEventArgs e)
{ {
OnTreeNodeChecked(e.Node); OnTreeNodeChecked(e.Node);
acceptButton.Enabled = selected.Any(); acceptButton.Enabled = selected.Count > 0;
saveButton.Enabled = acceptButton.Enabled; saveButton.Enabled = acceptButton.Enabled;
} }
@ -85,7 +85,7 @@ internal sealed partial class SelectDialogForm : CustomForm
private void OnLoad(object sender, EventArgs e) private void OnLoad(object sender, EventArgs e)
{ {
List<(Platform platform, string id)> choices = ProgramData.ReadProgramChoices().ToList(); List<(Platform platform, string id)> choices = ProgramData.ReadProgramChoices().ToList();
if (!choices.Any()) if (choices.Count < 1)
return; return;
foreach (TreeNode node in selectionTreeView.Nodes) foreach (TreeNode node in selectionTreeView.Nodes)
{ {

View file

@ -23,9 +23,9 @@ internal sealed partial class SelectForm : CustomForm
{ {
private const string HelpButtonListPrefix = "\n • "; private const string HelpButtonListPrefix = "\n • ";
private readonly SynchronizedCollection<string> remainingDLCs = new(); private readonly ConcurrentDictionary<string, string> remainingDLCs = new();
private readonly SynchronizedCollection<string> remainingGames = new(); private readonly ConcurrentDictionary<string, string> remainingGames = new();
private List<(Platform platform, string id, string name)> programsToScan; private List<(Platform platform, string id, string name)> programsToScan;
@ -39,8 +39,8 @@ internal sealed partial class SelectForm : CustomForm
private List<TreeNode> TreeNodes => GatherTreeNodes(selectionTreeView.Nodes); private List<TreeNode> TreeNodes => GatherTreeNodes(selectionTreeView.Nodes);
private static void UpdateRemaining(Label label, SynchronizedCollection<string> list, string descriptor) private static void UpdateRemaining(Label label, ConcurrentDictionary<string, string> list, string descriptor)
=> label.Text = list.Any() ? $"Remaining {descriptor} ({list.Count}): " + string.Join(", ", list).Replace("&", "&&") : ""; => label.Text = list.IsEmpty ? "" : $"Remaining {descriptor} ({list.Count}): " + string.Join(", ", list.Values).Replace("&", "&&");
private void UpdateRemainingGames() => UpdateRemaining(progressLabelGames, remainingGames, "games"); private void UpdateRemainingGames() => UpdateRemaining(progressLabelGames, remainingGames, "games");
@ -52,8 +52,7 @@ internal sealed partial class SelectForm : CustomForm
{ {
if (Program.Canceled) if (Program.Canceled)
return; return;
if (!remainingGames.Contains(gameName)) remainingGames[gameName] = gameName;
remainingGames.Add(gameName);
UpdateRemainingGames(); UpdateRemainingGames();
}); });
} }
@ -66,7 +65,7 @@ internal sealed partial class SelectForm : CustomForm
{ {
if (Program.Canceled) if (Program.Canceled)
return; return;
remainingGames.Remove(gameName); _ = remainingGames.Remove(gameName, out _);
UpdateRemainingGames(); UpdateRemainingGames();
}); });
} }
@ -81,8 +80,7 @@ internal sealed partial class SelectForm : CustomForm
{ {
if (Program.Canceled) if (Program.Canceled)
return; return;
if (!remainingDLCs.Contains(dlcId)) remainingDLCs[dlcId] = dlcId;
remainingDLCs.Add(dlcId);
UpdateRemainingDLCs(); UpdateRemainingDLCs();
}); });
} }
@ -95,14 +93,14 @@ internal sealed partial class SelectForm : CustomForm
{ {
if (Program.Canceled) if (Program.Canceled)
return; return;
remainingDLCs.Remove(dlcId); _ = remainingDLCs.Remove(dlcId, out _);
UpdateRemainingDLCs(); UpdateRemainingDLCs();
}); });
} }
private async Task GetApplicablePrograms(IProgress<int> progress, bool uninstallAll = false) private async Task GetApplicablePrograms(IProgress<int> progress, bool uninstallAll = false)
{ {
if (!uninstallAll && (programsToScan is null || !programsToScan.Any())) if (!uninstallAll && (programsToScan is null || programsToScan.Count < 1))
return; return;
int totalGameCount = 0; int totalGameCount = 0;
int completeGameCount = 0; int completeGameCount = 0;
@ -126,12 +124,12 @@ internal sealed partial class SelectForm : CustomForm
if (uninstallAll || programsToScan.Any(c => c.platform is Platform.Paradox)) if (uninstallAll || programsToScan.Any(c => c.platform is Platform.Paradox))
{ {
AddToRemainingGames("Paradox Launcher"); AddToRemainingGames("Paradox Launcher");
List<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)
{ {
if (uninstallAll) if (uninstallAll)
{ {
ProgramSelection bareSelection = ProgramSelection.FromPlatformId(Platform.Paradox, "PL") ?? new(); Selection bareSelection = Selection.FromPlatformId(Platform.Paradox, "PL") ?? new();
bareSelection.Enabled = true; bareSelection.Enabled = true;
bareSelection.Id = "PL"; bareSelection.Id = "PL";
bareSelection.Name = "Paradox Launcher"; bareSelection.Name = "Paradox Launcher";
@ -142,7 +140,7 @@ internal sealed partial class SelectForm : CustomForm
} }
else else
{ {
ProgramSelection selection = ProgramSelection.FromPlatformId(Platform.Paradox, "PL") ?? new(); 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)
@ -167,7 +165,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))
{ {
List<(string appId, string name, string branch, int buildId, string gameDirectory)> steamGames = await SteamLibrary.GetGames(); HashSet<(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)
{ {
@ -175,7 +173,7 @@ internal sealed partial class SelectForm : CustomForm
return; return;
if (!uninstallAll && (Program.IsGameBlocked(name, gameDirectory) || !programsToScan.Any(c => c.platform is Platform.Steam && c.id == appId))) if (!uninstallAll && (Program.IsGameBlocked(name, gameDirectory) || !programsToScan.Any(c => c.platform is Platform.Steam && c.id == appId)))
{ {
Interlocked.Decrement(ref steamGamesToCheck); _ = Interlocked.Decrement(ref steamGamesToCheck);
continue; continue;
} }
AddToRemainingGames(name); AddToRemainingGames(name);
@ -183,16 +181,16 @@ internal sealed partial class SelectForm : CustomForm
{ {
if (Program.Canceled) if (Program.Canceled)
return; return;
List<string> dllDirectories = await gameDirectory.GetDllDirectoriesFromGameDirectory(Platform.Steam); HashSet<string> dllDirectories = await gameDirectory.GetDllDirectoriesFromGameDirectory(Platform.Steam);
if (dllDirectories is null) if (dllDirectories is null)
{ {
Interlocked.Decrement(ref steamGamesToCheck); _ = Interlocked.Decrement(ref steamGamesToCheck);
RemoveFromRemainingGames(name); RemoveFromRemainingGames(name);
return; return;
} }
if (uninstallAll) if (uninstallAll)
{ {
ProgramSelection bareSelection = ProgramSelection.FromPlatformId(Platform.Steam, appId) ?? new ProgramSelection(); Selection bareSelection = Selection.FromPlatformId(Platform.Steam, appId) ?? new Selection();
bareSelection.Enabled = true; bareSelection.Enabled = true;
bareSelection.Id = appId; bareSelection.Id = appId;
bareSelection.Name = name; bareSelection.Name = name;
@ -206,7 +204,7 @@ internal sealed partial class SelectForm : CustomForm
if (Program.Canceled) if (Program.Canceled)
return; return;
AppData appData = await SteamStore.QueryStoreAPI(appId); AppData appData = await SteamStore.QueryStoreAPI(appId);
Interlocked.Decrement(ref steamGamesToCheck); _ = Interlocked.Decrement(ref steamGamesToCheck);
VProperty appInfo = await SteamCMD.GetAppInfo(appId, branch, buildId); VProperty appInfo = await SteamCMD.GetAppInfo(appId, branch, buildId);
if (appData is null && appInfo is null) if (appData is null && appInfo is null)
{ {
@ -215,7 +213,7 @@ internal sealed partial class SelectForm : CustomForm
} }
if (Program.Canceled) if (Program.Canceled)
return; return;
ConcurrentDictionary<string, (DlcType type, string name, string icon)> dlc = new(); ConcurrentDictionary<string, SelectionDLC> dlc = new();
List<Task> dlcTasks = new(); List<Task> dlcTasks = new();
List<string> dlcIds = new(); List<string> dlcIds = new();
if (appData is not null) if (appData is not null)
@ -292,13 +290,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] = (fullGameOnSteamStore ? DlcType.Steam : DlcType.SteamHidden, fullGameName, fullGameIcon); dlc[fullGameAppId] = new()
{
Id = fullGameAppId, Type = fullGameOnSteamStore ? DLCType.Steam : DLCType.SteamHidden, Name = fullGameName,
Icon = fullGameIcon
};
} }
if (Program.Canceled) if (Program.Canceled)
return; return;
if (string.IsNullOrWhiteSpace(dlcName)) if (string.IsNullOrWhiteSpace(dlcName))
dlcName = "Unknown"; dlcName = "Unknown";
dlc[dlcAppId] = (onSteamStore ? DlcType.Steam : DlcType.SteamHidden, dlcName, dlcIcon); dlc[dlcAppId] = new()
{
Id = dlcAppId, Type = onSteamStore ? DLCType.Steam : DLCType.SteamHidden, Name = dlcName, Icon = dlcIcon
};
RemoveFromRemainingDLCs(dlcAppId); RemoveFromRemainingDLCs(dlcAppId);
}); });
dlcTasks.Add(task); dlcTasks.Add(task);
@ -317,8 +322,14 @@ internal sealed partial class SelectForm : CustomForm
await task; await task;
} }
steamGamesToCheck = 0; steamGamesToCheck = 0;
ProgramSelection selection = ProgramSelection.FromPlatformId(Platform.Steam, appId) ?? new ProgramSelection(); if (dlc.IsEmpty)
selection.Enabled = allCheckBox.Checked || selection.SelectedDlc.Any() || selection.ExtraSelectedDlc.Any(); {
RemoveFromRemainingGames(name);
return;
}
Selection selection = Selection.FromPlatformId(Platform.Steam, appId) ?? new Selection();
selection.Enabled = allCheckBox.Checked || selection.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.Id = appId;
@ -327,12 +338,12 @@ internal sealed partial class SelectForm : CustomForm
selection.ExecutableDirectories = await SteamLibrary.GetExecutableDirectories(selection.RootDirectory); selection.ExecutableDirectories = await SteamLibrary.GetExecutableDirectories(selection.RootDirectory);
selection.DllDirectories = dllDirectories; selection.DllDirectories = dllDirectories;
selection.Platform = Platform.Steam; selection.Platform = Platform.Steam;
selection.ProductUrl = "https://store.steampowered.com/app/" + appId; selection.Product = "https://store.steampowered.com/app/" + appId;
selection.IconUrl = IconGrabber.SteamAppImagesPath + @$"\{appId}\{appInfo?.Value.GetChild("common")?.GetChild("icon")}.jpg"; selection.Icon = IconGrabber.SteamAppImagesPath + @$"\{appId}\{appInfo?.Value.GetChild("common")?.GetChild("icon")}.jpg";
selection.SubIconUrl = appData?.HeaderImage ?? IconGrabber.SteamAppImagesPath selection.SubIcon = appData?.HeaderImage ?? IconGrabber.SteamAppImagesPath
+ @$"\{appId}\{appInfo?.Value.GetChild("common")?.GetChild("clienticon")}.ico"; + @$"\{appId}\{appInfo?.Value.GetChild("common")?.GetChild("clienticon")}.ico";
selection.Publisher = appData?.Publishers[0] ?? appInfo?.Value.GetChild("extended")?.GetChild("publisher")?.ToString(); selection.Publisher = appData?.Publishers[0] ?? appInfo?.Value.GetChild("extended")?.GetChild("publisher")?.ToString();
selection.WebsiteUrl = appData?.Website; selection.Website = appData?.Website;
if (Program.Canceled) if (Program.Canceled)
return; return;
selectionTreeView.Invoke(delegate selectionTreeView.Invoke(delegate
@ -346,18 +357,17 @@ internal sealed partial class SelectForm : CustomForm
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 ((string appId, (DlcType type, string name, string icon) dlcApp) in dlc) foreach ((_, SelectionDLC dlc) in dlc)
{ {
if (Program.Canceled) if (Program.Canceled)
return; return;
selection.AllDlc[appId] = dlcApp; dlc.Selection = selection;
if (allCheckBox.Checked && dlcApp.name != "Unknown") dlc.Enabled = dlc.Name != "Unknown" && allCheckBox.Checked;
selection.SelectedDlc[appId] = dlcApp; TreeNode dlcNode = treeNodes.Find(s => s.Tag is Platform.Steam && s.Name == dlc.Id) ?? new TreeNode();
TreeNode dlcNode = treeNodes.Find(s => s.Tag is Platform.Steam && s.Name == appId) ?? new TreeNode();
dlcNode.Tag = selection.Platform; dlcNode.Tag = selection.Platform;
dlcNode.Name = appId; dlcNode.Name = dlc.Id;
dlcNode.Text = dlcApp.name; dlcNode.Text = dlc.Name;
dlcNode.Checked = selection.SelectedDlc.ContainsKey(appId); dlcNode.Checked = dlc.Enabled;
if (dlcNode.Parent is null) if (dlcNode.Parent is null)
_ = programNode.Nodes.Add(dlcNode); _ = programNode.Nodes.Add(dlcNode);
} }
@ -371,7 +381,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))
{ {
List<Manifest> epicGames = await EpicLibrary.GetGames(); HashSet<Manifest> epicGames = await EpicLibrary.GetGames();
foreach (Manifest manifest in epicGames) foreach (Manifest manifest in epicGames)
{ {
string @namespace = manifest.CatalogNamespace; string @namespace = manifest.CatalogNamespace;
@ -386,7 +396,7 @@ internal sealed partial class SelectForm : CustomForm
{ {
if (Program.Canceled) if (Program.Canceled)
return; return;
List<string> dllDirectories = await directory.GetDllDirectoriesFromGameDirectory(Platform.Epic); HashSet<string> dllDirectories = await directory.GetDllDirectoriesFromGameDirectory(Platform.Epic);
if (dllDirectories is null) if (dllDirectories is null)
{ {
RemoveFromRemainingGames(name); RemoveFromRemainingGames(name);
@ -394,7 +404,7 @@ internal sealed partial class SelectForm : CustomForm
} }
if (uninstallAll) if (uninstallAll)
{ {
ProgramSelection bareSelection = ProgramSelection.FromPlatformId(Platform.Epic, @namespace) ?? new ProgramSelection(); Selection bareSelection = Selection.FromPlatformId(Platform.Epic, @namespace) ?? new Selection();
bareSelection.Enabled = true; bareSelection.Enabled = true;
bareSelection.Id = @namespace; bareSelection.Id = @namespace;
bareSelection.Name = name; bareSelection.Name = name;
@ -407,11 +417,13 @@ internal sealed partial class SelectForm : CustomForm
} }
if (Program.Canceled) if (Program.Canceled)
return; return;
ConcurrentDictionary<string, (string name, string product, string icon, string developer)> entitlements = new(); ConcurrentDictionary<string, SelectionDLC> catalogItems = new();
// get catalog items
ConcurrentDictionary<string, SelectionDLC> entitlements = new();
List<Task> dlcTasks = new(); List<Task> dlcTasks = new();
List<(string id, string name, string product, string icon, string developer)> entitlementIds List<(string id, string name, string product, string icon, string developer)>
= await EpicStore.QueryEntitlements(@namespace); entitlementIds = await EpicStore.QueryEntitlements(@namespace);
if (entitlementIds.Any()) if (entitlementIds.Count > 0)
foreach ((string id, string name, string product, string icon, string developer) in entitlementIds) foreach ((string id, string name, string product, string icon, string developer) in entitlementIds)
{ {
if (Program.Canceled) if (Program.Canceled)
@ -421,16 +433,15 @@ internal sealed partial class SelectForm : CustomForm
{ {
if (Program.Canceled) if (Program.Canceled)
return; return;
entitlements[id] = (name, product, icon, developer); entitlements[id] = new()
{
Id = id, Name = name, Product = product, Icon = icon,
Publisher = developer
};
RemoveFromRemainingDLCs(id); RemoveFromRemainingDLCs(id);
}); });
dlcTasks.Add(task); dlcTasks.Add(task);
} }
if ( /*!catalogItems.Any() && */!entitlements.Any())
{
RemoveFromRemainingGames(name);
return;
}
if (Program.Canceled) if (Program.Canceled)
return; return;
foreach (Task task in dlcTasks) foreach (Task task in dlcTasks)
@ -439,8 +450,14 @@ internal sealed partial class SelectForm : CustomForm
return; return;
await task; await task;
} }
ProgramSelection selection = ProgramSelection.FromPlatformId(Platform.Epic, @namespace) ?? new ProgramSelection(); if (catalogItems.IsEmpty && entitlements.IsEmpty)
selection.Enabled = allCheckBox.Checked || selection.SelectedDlc.Any() || selection.ExtraSelectedDlc.Any(); {
RemoveFromRemainingGames(name);
return;
}
Selection selection = Selection.FromPlatformId(Platform.Epic, @namespace) ?? new Selection();
selection.Enabled = allCheckBox.Checked || selection.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; selection.Id = @namespace;
@ -449,12 +466,13 @@ internal sealed partial class SelectForm : CustomForm
selection.ExecutableDirectories = await EpicLibrary.GetExecutableDirectories(selection.RootDirectory); selection.ExecutableDirectories = await EpicLibrary.GetExecutableDirectories(selection.RootDirectory);
selection.DllDirectories = dllDirectories; selection.DllDirectories = dllDirectories;
selection.Platform = Platform.Epic; selection.Platform = Platform.Epic;
foreach (KeyValuePair<string, (string name, string product, string icon, string developer)> pair in entitlements.Where(p foreach (KeyValuePair<string, SelectionDLC> dlc in entitlements.Where(dlc => dlc.Value.Name == selection.Name))
=> p.Value.name == selection.Name))
{ {
selection.ProductUrl = "https://www.epicgames.com/store/product/" + pair.Value.product; if (Program.Canceled)
selection.IconUrl = pair.Value.icon; return;
selection.Publisher = pair.Value.developer; selection.Product = "https://www.epicgames.com/store/product/" + dlc.Value.Product;
selection.Icon = dlc.Value.Icon;
selection.Publisher = dlc.Value.Publisher;
} }
if (Program.Canceled) if (Program.Canceled)
return; return;
@ -469,35 +487,49 @@ internal sealed partial class SelectForm : CustomForm
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);
/*TreeNode catalogItemsNode = treeNodes.Find(s => s.Tag is Platform.Epic && s.Name == @namespace + "_catalogItems") ?? new(); if (!catalogItems.IsEmpty)
catalogItemsNode.Tag = selection.Platform; /*TreeNode catalogItemsNode = treeNodes.Find(node => node.Tag is Platform.Epic && node.Name == @namespace + "_catalogItems") ?? new();
catalogItemsNode.Name = @namespace + "_catalogItems"; catalogItemsNode.Name = @namespace + "_catalogItems";
catalogItemsNode.Text = "Catalog Items"; catalogItemsNode.Text = "Catalog Items";
catalogItemsNode.Checked = selection.SelectedDlc.Any(pair => pair.Value.type == DlcType.CatalogItem); 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);*/
if (entitlements.Any()) foreach ((_, SelectionDLC dlc) in catalogItems)
/*TreeNode entitlementsNode = treeNodes.Find(s => s.Tag is Platform.Epic && s.Name == @namespace + "_entitlements") ?? new();
entitlementsNode.Tag = selection.Platform;
entitlementsNode.Name = @namespace + "_entitlements";
entitlementsNode.Text = "Entitlements";
entitlementsNode.Checked = selection.SelectedDlc.Any(pair => pair.Value.type == DlcType.Entitlement);
if (entitlementsNode.Parent is null)
programNode.Nodes.Add(entitlementsNode);*/
foreach ((string dlcId, (string name, string product, string icon, string developer) value) in entitlements)
{ {
(DlcType type, string name, string icon) dlcApp = (DlcType.EpicEntitlement, value.name, value.icon); if (Program.Canceled)
selection.AllDlc[dlcId] = dlcApp; return;
if (allCheckBox.Checked) dlc.Selection = selection;
selection.SelectedDlc[dlcId] = dlcApp; dlc.Enabled = allCheckBox.Checked;
TreeNode dlcNode = treeNodes.Find(s => s.Tag is Platform.Epic && s.Name == dlcId) ?? new TreeNode(); TreeNode dlcNode = treeNodes.Find(s => s.Tag is Platform.Epic && s.Name == dlc.Id) ?? new TreeNode();
dlcNode.Tag = selection.Platform; dlcNode.Tag = selection.Platform;
dlcNode.Name = dlcId; dlcNode.Name = dlc.Id;
dlcNode.Text = dlcApp.name; dlcNode.Text = dlc.Name;
dlcNode.Checked = selection.SelectedDlc.ContainsKey(dlcId); dlcNode.Checked = dlc.Enabled;
if (dlcNode.Parent is null) if (dlcNode.Parent is null)
_ = programNode.Nodes.Add(dlcNode); //entitlementsNode.Nodes.Add(dlcNode); _ = programNode.Nodes.Add(dlcNode); //_ = catalogItemsNode.Nodes.Add(dlcNode);
} }
if (entitlements.IsEmpty)
return;
/*TreeNode entitlementsNode = treeNodes.Find(node => node.Tag is Platform.Epic && node.Name == @namespace + "_entitlements") ?? new();
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)
{
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;
dlcNode.Name = dlc.Id;
dlcNode.Text = dlc.Name;
dlcNode.Checked = dlc.Enabled;
if (dlcNode.Parent is null)
_ = programNode.Nodes.Add(dlcNode); //_ = entitlementsNode.Nodes.Add(dlcNode);
}
}); });
if (Program.Canceled) if (Program.Canceled)
return; return;
@ -508,7 +540,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))
{ {
List<(string gameId, string name, string gameDirectory)> ubisoftGames = await UbisoftLibrary.GetGames(); HashSet<(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)
@ -520,7 +552,7 @@ internal sealed partial class SelectForm : CustomForm
{ {
if (Program.Canceled) if (Program.Canceled)
return; return;
List<string> dllDirectories = await gameDirectory.GetDllDirectoriesFromGameDirectory(Platform.Ubisoft); HashSet<string> dllDirectories = await gameDirectory.GetDllDirectoriesFromGameDirectory(Platform.Ubisoft);
if (dllDirectories is null) if (dllDirectories is null)
{ {
RemoveFromRemainingGames(name); RemoveFromRemainingGames(name);
@ -528,7 +560,7 @@ internal sealed partial class SelectForm : CustomForm
} }
if (uninstallAll) if (uninstallAll)
{ {
ProgramSelection bareSelection = ProgramSelection.FromPlatformId(Platform.Ubisoft, gameId) ?? new ProgramSelection(); Selection bareSelection = Selection.FromPlatformId(Platform.Ubisoft, gameId) ?? new Selection();
bareSelection.Enabled = true; bareSelection.Enabled = true;
bareSelection.Id = gameId; bareSelection.Id = gameId;
bareSelection.Name = name; bareSelection.Name = name;
@ -541,8 +573,9 @@ internal sealed partial class SelectForm : CustomForm
} }
if (Program.Canceled) if (Program.Canceled)
return; return;
ProgramSelection selection = ProgramSelection.FromPlatformId(Platform.Ubisoft, gameId) ?? new ProgramSelection(); Selection selection = Selection.FromPlatformId(Platform.Ubisoft, gameId) ?? new Selection();
selection.Enabled = allCheckBox.Checked || selection.SelectedDlc.Any() || selection.ExtraSelectedDlc.Any(); selection.Enabled = allCheckBox.Checked || selection.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.Id = gameId;
@ -551,7 +584,7 @@ internal sealed partial class SelectForm : CustomForm
selection.ExecutableDirectories = await UbisoftLibrary.GetExecutableDirectories(selection.RootDirectory); selection.ExecutableDirectories = await UbisoftLibrary.GetExecutableDirectories(selection.RootDirectory);
selection.DllDirectories = dllDirectories; selection.DllDirectories = dllDirectories;
selection.Platform = Platform.Ubisoft; selection.Platform = Platform.Ubisoft;
selection.IconUrl = IconGrabber.GetDomainFaviconUrl("store.ubi.com"); selection.Icon = IconGrabber.GetDomainFaviconUrl("store.ubi.com");
selectionTreeView.Invoke(delegate selectionTreeView.Invoke(delegate
{ {
if (Program.Canceled) if (Program.Canceled)
@ -603,7 +636,7 @@ internal sealed partial class SelectForm : CustomForm
ShowProgressBar(); ShowProgressBar();
await ProgramData.Setup(this); await ProgramData.Setup(this);
bool scan = forceScan; bool scan = forceScan;
if (!scan && (programsToScan is null || !programsToScan.Any() || forceProvideChoices)) if (!scan && (programsToScan is null || programsToScan.Count < 1 || forceProvideChoices))
{ {
List<(Platform platform, string id, string name, bool alreadySelected)> gameChoices = new(); List<(Platform platform, string id, string name, bool alreadySelected)> gameChoices = new();
if (ParadoxLauncher.InstallPath.DirectoryExists()) if (ParadoxLauncher.InstallPath.DirectoryExists())
@ -621,7 +654,7 @@ internal sealed partial class SelectForm : CustomForm
foreach ((string gameId, string name, string _) in (await UbisoftLibrary.GetGames()).Where(g => !Program.IsGameBlocked(g.name, g.gameDirectory))) foreach ((string gameId, string name, string _) in (await UbisoftLibrary.GetGames()).Where(g => !Program.IsGameBlocked(g.name, g.gameDirectory)))
gameChoices.Add((Platform.Ubisoft, gameId, name, gameChoices.Add((Platform.Ubisoft, gameId, name,
programsToScan is not null && programsToScan.Any(p => p.platform is Platform.Ubisoft && p.id == gameId))); programsToScan is not null && programsToScan.Any(p => p.platform is Platform.Ubisoft && p.id == gameId)));
if (gameChoices.Any()) if (gameChoices.Count > 0)
{ {
using SelectDialogForm form = new(this); using SelectDialogForm form = new(this);
DialogResult selectResult = form.QueryUser("Choose which programs and/or games to scan:", gameChoices, DialogResult selectResult = form.QueryUser("Choose which programs and/or games to scan:", gameChoices,
@ -649,11 +682,11 @@ internal sealed partial class SelectForm : CustomForm
await GetApplicablePrograms(iProgress, true); await GetApplicablePrograms(iProgress, true);
if (!Program.Canceled) if (!Program.Canceled)
OnUninstall(null, null); OnUninstall(null, null);
ProgramSelection.All.Clear(); Selection.All.Clear();
programsToScan = null; programsToScan = null;
} }
else else
scan = selectResult == DialogResult.OK && choices is not null && choices.Any(); scan = selectResult == DialogResult.OK && choices is not null && choices.Count > 0;
const string retry = "\n\nPress the \"Rescan\" button to re-choose."; const string retry = "\n\nPress the \"Rescan\" button to re-choose.";
if (scan) if (scan)
{ {
@ -696,7 +729,7 @@ internal sealed partial class SelectForm : CustomForm
} }
setup = false; setup = false;
progressLabel.Text = "Gathering and caching your applicable games and their DLCs . . . "; progressLabel.Text = "Gathering and caching your applicable games and their DLCs . . . ";
ProgramSelection.ValidateAll(programsToScan); Selection.ValidateAll(programsToScan);
TreeNodes.ForEach(node => node.Remove()); TreeNodes.ForEach(node => node.Remove());
await GetApplicablePrograms(iProgress); await GetApplicablePrograms(iProgress);
await SteamCMD.Cleanup(); await SteamCMD.Cleanup();
@ -704,11 +737,11 @@ internal sealed partial class SelectForm : CustomForm
OnLoadDlc(null, null); OnLoadDlc(null, null);
OnLoadKoaloader(null, null); OnLoadKoaloader(null, null);
HideProgressBar(); HideProgressBar();
selectionTreeView.Enabled = ProgramSelection.All.Any(); selectionTreeView.Enabled = Selection.All.Count > 0;
allCheckBox.Enabled = selectionTreeView.Enabled; allCheckBox.Enabled = selectionTreeView.Enabled;
koaloaderAllCheckBox.Enabled = selectionTreeView.Enabled; koaloaderAllCheckBox.Enabled = selectionTreeView.Enabled;
noneFoundLabel.Visible = !selectionTreeView.Enabled; noneFoundLabel.Visible = !selectionTreeView.Enabled;
installButton.Enabled = ProgramSelection.AllEnabled.Any(); installButton.Enabled = Selection.AllEnabled.Count > 0;
uninstallButton.Enabled = installButton.Enabled; uninstallButton.Enabled = installButton.Enabled;
saveButton.Enabled = CanSaveDlc(); saveButton.Enabled = CanSaveDlc();
loadButton.Enabled = CanLoadDlc(); loadButton.Enabled = CanLoadDlc();
@ -735,7 +768,7 @@ internal sealed partial class SelectForm : CustomForm
allCheckBox.CheckedChanged -= OnAllCheckBoxChanged; allCheckBox.CheckedChanged -= OnAllCheckBoxChanged;
allCheckBox.Checked = TreeNodes.TrueForAll(node => node.Text == "Unknown" || node.Checked); allCheckBox.Checked = TreeNodes.TrueForAll(node => node.Text == "Unknown" || node.Checked);
allCheckBox.CheckedChanged += OnAllCheckBoxChanged; allCheckBox.CheckedChanged += OnAllCheckBoxChanged;
installButton.Enabled = ProgramSelection.AllEnabled.Any(); installButton.Enabled = Selection.AllEnabled.Count > 0;
uninstallButton.Enabled = installButton.Enabled; uninstallButton.Enabled = installButton.Enabled;
saveButton.Enabled = CanSaveDlc(); saveButton.Enabled = CanSaveDlc();
resetButton.Enabled = CanResetDlc(); resetButton.Enabled = CanResetDlc();
@ -770,19 +803,15 @@ internal sealed partial class SelectForm : CustomForm
{ {
string id = node.Name; string id = node.Name;
Platform platform = (Platform)node.Tag; Platform platform = (Platform)node.Tag;
(string gameId, (DlcType type, string name, string icon) app)? dlc = ProgramSelection.GetDlcFromPlatformId(platform, id); Selection selection = Selection.FromPlatformId(platform, id);
if (dlc.HasValue) if (selection is not null)
{ {
(string gameId, _) = dlc.Value; selection.Enabled = node.Checked;
ProgramSelection selection = ProgramSelection.FromPlatformId(platform, gameId); return;
selection?.ToggleDlc(node.Name, node.Checked);
}
else
{
ProgramSelection selection = ProgramSelection.FromPlatformId(platform, id);
if (selection is not null)
selection.Enabled = node.Checked;
} }
SelectionDLC dlc = SelectionDLC.FromPlatformId(platform, id);
if (dlc is not null)
dlc.Enabled = node.Checked;
} }
private static List<TreeNode> GatherTreeNodes(TreeNodeCollection nodeCollection) private static List<TreeNode> GatherTreeNodes(TreeNodeCollection nodeCollection)
@ -830,31 +859,25 @@ internal sealed partial class SelectForm : CustomForm
internal void OnNodeRightClick(TreeNode node, Point location) internal void OnNodeRightClick(TreeNode node, Point location)
=> Invoke(() => => Invoke(() =>
{ {
ContextMenuStrip contextMenuStrip = ContextMenuStrip; ContextMenuStrip contextMenuStrip = new();
while (ContextMenuStrip.Tag is true)
Thread.Sleep(100);
ContextMenuStrip.Tag = true;
ToolStripItemCollection items = contextMenuStrip.Items; ToolStripItemCollection items = contextMenuStrip.Items;
items.Clear();
string id = node.Name; string id = node.Name;
Platform platform = (Platform)node.Tag; Platform platform = (Platform)node.Tag;
ProgramSelection selection = ProgramSelection.FromPlatformId(platform, id); Selection selection = Selection.FromPlatformId(platform, id);
(string gameAppId, (DlcType type, string name, string icon) app)? dlc = null; SelectionDLC dlc = null;
if (selection is null) if (selection is null)
dlc = ProgramSelection.GetDlcFromPlatformId(platform, id); dlc = SelectionDLC.FromPlatformId(platform, id);
ProgramSelection dlcParentSelection = null; Selection dlcParentSelection = null;
if (dlc is not null) if (dlc is not null)
dlcParentSelection = ProgramSelection.FromPlatformId(platform, dlc.Value.gameAppId); dlcParentSelection = Selection.FromPlatformId(platform, dlc.Selection.Id);
if (selection is null && dlcParentSelection is null) if (selection is null && dlcParentSelection is null)
return; return;
ContextMenuItem header; ContextMenuItem header = id == "PL"
if (id == "PL") ? new(node.Text, "Paradox Launcher")
header = new(node.Text, "Paradox Launcher"); : selection is not null
else if (selection is not null) ? new(node.Text, (id, selection.Icon))
header = new(node.Text, (id, selection.IconUrl)); : new(node.Text, (id, dlc.Icon), (id, dlcParentSelection.Icon));
else _ = items.Add(header);
header = new(node.Text, (id, dlc.Value.app.icon), (id, dlcParentSelection.IconUrl));
items.Add(header);
string appInfoVDF = $@"{SteamCMD.AppInfoPath}\{id}.vdf"; string appInfoVDF = $@"{SteamCMD.AppInfoPath}\{id}.vdf";
string appInfoJSON = $@"{SteamCMD.AppInfoPath}\{id}.json"; string appInfoJSON = $@"{SteamCMD.AppInfoPath}\{id}.json";
string cooldown = $@"{ProgramData.CooldownPath}\{id}.txt"; string cooldown = $@"{ProgramData.CooldownPath}\{id}.txt";
@ -872,12 +895,12 @@ internal sealed partial class SelectForm : CustomForm
} }
if (appInfoVDF.FileExists()) if (appInfoVDF.FileExists())
queries.Add(new("Open SteamCMD Query", "Notepad", (_, _) => Diagnostics.OpenFileInNotepad(appInfoVDF))); queries.Add(new("Open SteamCMD Query", "Notepad", (_, _) => Diagnostics.OpenFileInNotepad(appInfoVDF)));
if (queries.Any()) if (queries.Count > 0)
{ {
items.Add(new ToolStripSeparator()); _ = items.Add(new ToolStripSeparator());
foreach (ContextMenuItem query in queries) foreach (ContextMenuItem query in queries)
items.Add(query); _ = items.Add(query);
items.Add(new ContextMenuItem("Refresh Queries", "Command Prompt", (_, _) => _ = items.Add(new ContextMenuItem("Refresh Queries", "Command Prompt", (_, _) =>
{ {
appInfoVDF.DeleteFile(); appInfoVDF.DeleteFile();
appInfoJSON.DeleteFile(); appInfoJSON.DeleteFile();
@ -890,16 +913,16 @@ internal sealed partial class SelectForm : CustomForm
{ {
if (id == "PL") if (id == "PL")
{ {
items.Add(new ToolStripSeparator()); _ = items.Add(new ToolStripSeparator());
async void EventHandler(object sender, EventArgs e) => await ParadoxLauncher.Repair(this, selection); async void EventHandler(object sender, EventArgs e) => await ParadoxLauncher.Repair(this, selection);
items.Add(new ContextMenuItem("Repair", "Command Prompt", EventHandler)); _ = items.Add(new ContextMenuItem("Repair", "Command Prompt", EventHandler));
} }
items.Add(new ToolStripSeparator()); _ = items.Add(new ToolStripSeparator());
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.ToList())
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(); List<string> directories = selection.DllDirectories.ToList();
int steam = 0, epic = 0, r1 = 0, r2 = 0; int steam = 0, epic = 0, r1 = 0, r2 = 0;
@ -910,7 +933,7 @@ internal sealed partial class SelectForm : CustomForm
out string config, out string old_log, out string log, out string cache); out string config, out string old_log, out string log, out string cache);
if (api32.FileExists() || api32_o.FileExists() || api64.FileExists() || api64_o.FileExists() || old_config.FileExists() if (api32.FileExists() || api32_o.FileExists() || api64.FileExists() || api64_o.FileExists() || old_config.FileExists()
|| config.FileExists() || old_log.FileExists() || log.FileExists() || cache.FileExists()) || config.FileExists() || old_log.FileExists() || log.FileExists() || cache.FileExists())
items.Add(new ContextMenuItem($"Open Steamworks Directory #{++steam}", "File Explorer", _ = items.Add(new ContextMenuItem($"Open Steamworks Directory #{++steam}", "File Explorer",
(_, _) => Diagnostics.OpenDirectoryInFileExplorer(directory))); (_, _) => Diagnostics.OpenDirectoryInFileExplorer(directory)));
} }
if (selection.Platform is Platform.Epic or Platform.Paradox) if (selection.Platform is Platform.Epic or Platform.Paradox)
@ -919,7 +942,7 @@ internal sealed partial class SelectForm : CustomForm
directory.GetScreamApiComponents(out string api32, out string api32_o, out string api64, out string api64_o, out string config, directory.GetScreamApiComponents(out string api32, out string api32_o, out string api64, out string api64_o, out string config,
out string log); out string log);
if (api32.FileExists() || api32_o.FileExists() || api64.FileExists() || api64_o.FileExists() || config.FileExists() || log.FileExists()) if (api32.FileExists() || api32_o.FileExists() || api64.FileExists() || api64_o.FileExists() || config.FileExists() || log.FileExists())
items.Add(new ContextMenuItem($"Open EOS Directory #{++epic}", "File Explorer", _ = items.Add(new ContextMenuItem($"Open EOS Directory #{++epic}", "File Explorer",
(_, _) => Diagnostics.OpenDirectoryInFileExplorer(directory))); (_, _) => Diagnostics.OpenDirectoryInFileExplorer(directory)));
} }
if (selection.Platform is Platform.Ubisoft) if (selection.Platform is Platform.Ubisoft)
@ -928,13 +951,13 @@ internal sealed partial class SelectForm : CustomForm
directory.GetUplayR1Components(out string api32, out string api32_o, out string api64, out string api64_o, out string config, directory.GetUplayR1Components(out string api32, out string api32_o, out string api64, out string api64_o, out string config,
out string log); out string log);
if (api32.FileExists() || api32_o.FileExists() || api64.FileExists() || api64_o.FileExists() || config.FileExists() || log.FileExists()) if (api32.FileExists() || api32_o.FileExists() || api64.FileExists() || api64_o.FileExists() || config.FileExists() || log.FileExists())
items.Add(new ContextMenuItem($"Open Uplay R1 Directory #{++r1}", "File Explorer", _ = items.Add(new ContextMenuItem($"Open Uplay R1 Directory #{++r1}", "File Explorer",
(_, _) => Diagnostics.OpenDirectoryInFileExplorer(directory))); (_, _) => Diagnostics.OpenDirectoryInFileExplorer(directory)));
directory.GetUplayR2Components(out string old_api32, out string old_api64, out api32, out api32_o, out api64, out api64_o, out config, directory.GetUplayR2Components(out string old_api32, out string old_api64, out api32, out api32_o, out api64, out api64_o, out config,
out log); out log);
if (old_api32.FileExists() || old_api64.FileExists() || api32.FileExists() || api32_o.FileExists() || api64.FileExists() if (old_api32.FileExists() || old_api64.FileExists() || api32.FileExists() || api32_o.FileExists() || api64.FileExists()
|| api64_o.FileExists() || config.FileExists() || log.FileExists()) || api64_o.FileExists() || config.FileExists() || log.FileExists())
items.Add(new ContextMenuItem($"Open Uplay R2 Directory #{++r2}", "File Explorer", _ = items.Add(new ContextMenuItem($"Open Uplay R2 Directory #{++r2}", "File Explorer",
(_, _) => Diagnostics.OpenDirectoryInFileExplorer(directory))); (_, _) => Diagnostics.OpenDirectoryInFileExplorer(directory)));
} }
} }
@ -942,39 +965,39 @@ internal sealed partial class SelectForm : CustomForm
{ {
if (selection?.Platform is Platform.Steam || dlcParentSelection?.Platform is Platform.Steam) if (selection?.Platform is Platform.Steam || dlcParentSelection?.Platform is Platform.Steam)
{ {
items.Add(new ToolStripSeparator()); _ = items.Add(new ToolStripSeparator());
items.Add(new ContextMenuItem("Open SteamDB", "SteamDB", (_, _) => Diagnostics.OpenUrlInInternetBrowser("https://steamdb.info/app/" + id))); _ = items.Add(new ContextMenuItem("Open SteamDB", "SteamDB",
(_, _) => Diagnostics.OpenUrlInInternetBrowser("https://steamdb.info/app/" + id)));
} }
if (selection is not null) if (selection is not null)
switch (selection.Platform) switch (selection.Platform)
{ {
case Platform.Steam: case Platform.Steam:
items.Add(new ContextMenuItem("Open Steam Store", "Steam Store", _ = items.Add(new ContextMenuItem("Open Steam Store", "Steam Store",
(_, _) => Diagnostics.OpenUrlInInternetBrowser(selection.ProductUrl))); (_, _) => Diagnostics.OpenUrlInInternetBrowser(selection.Product)));
items.Add(new ContextMenuItem("Open Steam Community", ("Sub_" + id, selection.SubIconUrl), "Steam Community", _ = items.Add(new ContextMenuItem("Open Steam Community", ("Sub_" + id, selection.SubIcon), "Steam Community",
(_, _) => Diagnostics.OpenUrlInInternetBrowser("https://steamcommunity.com/app/" + id))); (_, _) => Diagnostics.OpenUrlInInternetBrowser("https://steamcommunity.com/app/" + id)));
break; break;
case Platform.Epic: case Platform.Epic:
items.Add(new ToolStripSeparator()); _ = items.Add(new ToolStripSeparator());
items.Add(new ContextMenuItem("Open ScreamDB", "ScreamDB", _ = items.Add(new ContextMenuItem("Open ScreamDB", "ScreamDB",
(_, _) => Diagnostics.OpenUrlInInternetBrowser("https://scream-db.web.app/offers/" + id))); (_, _) => Diagnostics.OpenUrlInInternetBrowser("https://scream-db.web.app/offers/" + id)));
items.Add(new ContextMenuItem("Open Epic Games Store", "Epic Games", _ = items.Add(new ContextMenuItem("Open Epic Games Store", "Epic Games",
(_, _) => Diagnostics.OpenUrlInInternetBrowser(selection.ProductUrl))); (_, _) => Diagnostics.OpenUrlInInternetBrowser(selection.Product)));
break; break;
case Platform.Ubisoft: case Platform.Ubisoft:
items.Add(new ToolStripSeparator()); _ = items.Add(new ToolStripSeparator());
items.Add(new ContextMenuItem("Open Ubisoft Store", "Ubisoft Store", _ = items.Add(new ContextMenuItem("Open Ubisoft Store", "Ubisoft Store",
(_, _) => Diagnostics.OpenUrlInInternetBrowser( (_, _) => Diagnostics.OpenUrlInInternetBrowser(
"https://store.ubi.com/us/" + selection.Name.Replace(" ", "-").ToLowerInvariant()))); "https://store.ubi.com/us/" + selection.Name.Replace(" ", "-").ToLowerInvariant())));
break; break;
} }
} }
if (selection?.WebsiteUrl != null) if (selection?.Website is not null)
items.Add(new ContextMenuItem("Open Official Website", ("Web_" + id, IconGrabber.GetDomainFaviconUrl(selection.WebsiteUrl)), _ = items.Add(new ContextMenuItem("Open Official Website", ("Web_" + id, IconGrabber.GetDomainFaviconUrl(selection.Website)),
(_, _) => Diagnostics.OpenUrlInInternetBrowser(selection.WebsiteUrl))); (_, _) => Diagnostics.OpenUrlInInternetBrowser(selection.Website)));
contextMenuStrip.Show(selectionTreeView, location); contextMenuStrip.Show(selectionTreeView, location);
contextMenuStrip.Refresh(); contextMenuStrip.Refresh();
ContextMenuStrip.Tag = null;
}); });
private void OnLoad(object sender, EventArgs _) private void OnLoad(object sender, EventArgs _)
@ -996,9 +1019,9 @@ internal sealed partial class SelectForm : CustomForm
private void OnAccept(bool uninstall = false) private void OnAccept(bool uninstall = false)
{ {
if (!ProgramSelection.All.Any()) if (Selection.All.Count < 1)
return; return;
if (ProgramSelection.AllEnabled.Any(selection => !Program.AreDllsLockedDialog(this, selection))) if (Selection.AllEnabled.Any(selection => !Program.AreDllsLockedDialog(this, selection)))
return; return;
if (!uninstall && ParadoxLauncher.DlcDialog(this)) if (!uninstall && ParadoxLauncher.DlcDialog(this))
return; return;
@ -1053,8 +1076,8 @@ internal sealed partial class SelectForm : CustomForm
private void OnKoaloaderAllCheckBoxChanged(object sender, EventArgs e) private void OnKoaloaderAllCheckBoxChanged(object sender, EventArgs e)
{ {
bool shouldCheck = ProgramSelection.AllSafe.Any(selection => !selection.Koaloader); bool shouldCheck = Selection.AllSafe.Any(selection => !selection.Koaloader);
foreach (ProgramSelection selection in ProgramSelection.AllSafe) foreach (Selection selection in Selection.AllSafe)
selection.Koaloader = shouldCheck; selection.Koaloader = shouldCheck;
selectionTreeView.Invalidate(); selectionTreeView.Invalidate();
koaloaderAllCheckBox.CheckedChanged -= OnKoaloaderAllCheckBoxChanged; koaloaderAllCheckBox.CheckedChanged -= OnKoaloaderAllCheckBoxChanged;
@ -1076,7 +1099,7 @@ internal sealed partial class SelectForm : CustomForm
if (node.Text == "Unknown" ? node.Checked : !node.Checked) if (node.Text == "Unknown" ? node.Checked : !node.Checked)
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(); choices = choices.Distinct().ToList();
ProgramData.WriteDlcChoices(choices); ProgramData.WriteDlcChoices(choices);
@ -1111,19 +1134,19 @@ internal sealed partial class SelectForm : CustomForm
resetButton.Enabled = CanResetDlc(); resetButton.Enabled = CanResetDlc();
} }
private static bool AreKoaloaderSelectionsDefault() => ProgramSelection.AllSafe.All(selection => selection.Koaloader && selection.KoaloaderProxy is null); private static bool AreKoaloaderSelectionsDefault() => Selection.AllSafe.All(selection => selection.Koaloader && selection.KoaloaderProxy is null);
private static bool CanSaveKoaloader() => ProgramData.ReadKoaloaderChoices().Any() || !AreKoaloaderSelectionsDefault(); private static bool CanSaveKoaloader() => ProgramData.ReadKoaloaderChoices().Any() || !AreKoaloaderSelectionsDefault();
private void OnSaveKoaloader(object sender, EventArgs e) private void OnSaveKoaloader(object sender, EventArgs e)
{ {
List<(Platform platform, string id, string proxy, bool enabled)> choices = ProgramData.ReadKoaloaderChoices().ToList(); List<(Platform platform, string id, string proxy, bool enabled)> choices = ProgramData.ReadKoaloaderChoices().ToList();
foreach (ProgramSelection selection in ProgramSelection.AllSafe) foreach (Selection selection in Selection.AllSafe)
{ {
_ = choices.RemoveAll(c => c.platform == selection.Platform && c.id == selection.Id); _ = choices.RemoveAll(c => c.platform == selection.Platform && c.id == selection.Id);
if (selection.KoaloaderProxy is not null and not ProgramSelection.DefaultKoaloaderProxy || !selection.Koaloader) if (selection.KoaloaderProxy is not null and not Selection.DefaultKoaloaderProxy || !selection.Koaloader)
choices.Add((selection.Platform, selection.Id, choices.Add((selection.Platform, selection.Id, selection.KoaloaderProxy == Selection.DefaultKoaloaderProxy ? null : selection.KoaloaderProxy,
selection.KoaloaderProxy == ProgramSelection.DefaultKoaloaderProxy ? null : selection.KoaloaderProxy, selection.Koaloader)); selection.Koaloader));
} }
ProgramData.WriteKoaloaderProxyChoices(choices); ProgramData.WriteKoaloaderProxyChoices(choices);
saveKoaloaderButton.Enabled = CanSaveKoaloader(); saveKoaloaderButton.Enabled = CanSaveKoaloader();
@ -1135,7 +1158,7 @@ internal sealed partial class SelectForm : CustomForm
private void OnLoadKoaloader(object sender, EventArgs e) private void OnLoadKoaloader(object sender, EventArgs e)
{ {
List<(Platform platform, string id, string proxy, bool enabled)> choices = ProgramData.ReadKoaloaderChoices().ToList(); List<(Platform platform, string id, string proxy, bool enabled)> choices = ProgramData.ReadKoaloaderChoices().ToList();
foreach (ProgramSelection selection in ProgramSelection.AllSafe) foreach (Selection selection in Selection.AllSafe)
if (choices.Any(c => c.platform == selection.Platform && c.id == selection.Id)) if (choices.Any(c => c.platform == selection.Platform && c.id == selection.Id))
{ {
(Platform platform, string id, string proxy, bool enabled) (Platform platform, string id, string proxy, bool enabled)
@ -1146,12 +1169,12 @@ internal sealed partial class SelectForm : CustomForm
proxy.GetProxyInfoFromIdentifier(out currentProxy, out _); proxy.GetProxyInfoFromIdentifier(out currentProxy, out _);
if (proxy != currentProxy && choices.Remove(choice)) // convert pre-v4.1.0.0 choices if (proxy != currentProxy && choices.Remove(choice)) // convert pre-v4.1.0.0 choices
choices.Add((platform, id, currentProxy, enabled)); choices.Add((platform, id, currentProxy, enabled));
if (currentProxy is null or ProgramSelection.DefaultKoaloaderProxy && enabled) if (currentProxy is null or Selection.DefaultKoaloaderProxy && enabled)
_ = choices.RemoveAll(c => c.platform == platform && c.id == id); _ = choices.RemoveAll(c => c.platform == platform && c.id == id);
else else
{ {
selection.Koaloader = enabled; selection.Koaloader = enabled;
selection.KoaloaderProxy = currentProxy == ProgramSelection.DefaultKoaloaderProxy ? currentProxy : proxy; selection.KoaloaderProxy = currentProxy == Selection.DefaultKoaloaderProxy ? currentProxy : proxy;
} }
} }
else else
@ -1168,7 +1191,7 @@ internal sealed partial class SelectForm : CustomForm
private void OnResetKoaloader(object sender, EventArgs e) private void OnResetKoaloader(object sender, EventArgs e)
{ {
foreach (ProgramSelection selection in ProgramSelection.AllSafe) foreach (Selection selection in Selection.AllSafe)
{ {
selection.Koaloader = true; selection.Koaloader = true;
selection.KoaloaderProxy = null; selection.KoaloaderProxy = null;
@ -1182,7 +1205,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 = ProgramSelection.AllSafe.TrueForAll(selection => selection.Koaloader); koaloaderAllCheckBox.Checked = Selection.AllSafe.TrueForAll(selection => selection.Koaloader);
koaloaderAllCheckBox.CheckedChanged += OnKoaloaderAllCheckBoxChanged; koaloaderAllCheckBox.CheckedChanged += OnKoaloaderAllCheckBoxChanged;
} }

View file

@ -27,13 +27,13 @@ internal static class EpicLibrary
} }
} }
internal static async Task<List<(string directory, BinaryType binaryType)>> GetExecutableDirectories(string gameDirectory) internal static async Task<HashSet<(string directory, BinaryType binaryType)>> GetExecutableDirectories(string gameDirectory)
=> await Task.Run(async () => await gameDirectory.GetExecutableDirectories(true)); => await Task.Run(async () => await gameDirectory.GetExecutableDirectories(true));
internal static async Task<List<Manifest>> GetGames() internal static async Task<HashSet<Manifest>> GetGames()
=> await Task.Run(() => => await Task.Run(() =>
{ {
List<Manifest> games = new(); HashSet<Manifest> games = new();
string manifests = EpicManifestsPath; string manifests = EpicManifestsPath;
if (!manifests.DirectoryExists()) if (!manifests.DirectoryExists())
return games; return games;
@ -47,7 +47,7 @@ internal static class EpicLibrary
Manifest manifest = JsonConvert.DeserializeObject<Manifest>(json); Manifest manifest = JsonConvert.DeserializeObject<Manifest>(json);
if (manifest is not null && manifest.CatalogItemId == manifest.MainGameCatalogItemId && !games.Any(g if (manifest is not null && manifest.CatalogItemId == manifest.MainGameCatalogItemId && !games.Any(g
=> g.CatalogItemId == manifest.CatalogItemId && g.InstallLocation == manifest.InstallLocation)) => g.CatalogItemId == manifest.CatalogItemId && g.InstallLocation == manifest.InstallLocation))
games.Add(manifest); _ = games.Add(manifest);
} }
catch catch
{ {

View file

@ -54,7 +54,7 @@ internal static class EpicStore
foreach (Element element in searchStore) foreach (Element element in searchStore)
{ {
string title = element.Title; string title = element.Title;
string product = element.CatalogNs is not null && element.CatalogNs.Mappings.Any() ? element.CatalogNs.Mappings.First().PageSlug : null; string product = element.CatalogNs is not null && element.CatalogNs.Mappings.Length > 0 ? element.CatalogNs.Mappings.First().PageSlug : null;
string icon = null; string icon = null;
for (int i = 0; i < element.KeyImages?.Length; i++) for (int i = 0; i < element.KeyImages?.Length; i++)
{ {
@ -71,7 +71,7 @@ internal static class EpicStore
foreach (Element element in catalogOffers) foreach (Element element in catalogOffers)
{ {
string title = element.Title; string title = element.Title;
string product = element.CatalogNs is not null && element.CatalogNs.Mappings.Any() ? element.CatalogNs.Mappings.First().PageSlug : null; string product = element.CatalogNs is not null && element.CatalogNs.Mappings.Length > 0 ? element.CatalogNs.Mappings.First().PageSlug : null;
string icon = null; string icon = null;
for (int i = 0; i < element.KeyImages?.Length; i++) for (int i = 0; i < element.KeyImages?.Length; i++)
{ {

View file

@ -34,49 +34,39 @@ internal static class ParadoxLauncher
} }
} }
internal static async Task<List<(string directory, BinaryType binaryType)>> GetExecutableDirectories(string gameDirectory) 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"))); => await Task.Run(async () => await gameDirectory.GetExecutableDirectories(validFunc: path => !Path.GetFileName(path).Contains("bootstrapper")));
private static void PopulateDlc(ProgramSelection paradoxLauncher = null) private static void PopulateDlc(Selection paradoxLauncher = null)
{ {
paradoxLauncher ??= ProgramSelection.FromPlatformId(Platform.Paradox, "PL"); paradoxLauncher ??= Selection.FromPlatformId(Platform.Paradox, "PL");
if (paradoxLauncher is not null) if (paradoxLauncher is null)
{ return;
paradoxLauncher.ExtraDlc.Clear(); paradoxLauncher.ExtraSelections.Clear();
paradoxLauncher.ExtraSelectedDlc.Clear(); foreach (Selection selection in Selection.AllEnabled.Where(s => s != paradoxLauncher && s.Publisher == "Paradox Interactive"))
foreach (ProgramSelection selection in ProgramSelection.AllEnabled.Where(s => s != paradoxLauncher && s.Publisher == "Paradox Interactive")) _ = paradoxLauncher.ExtraSelections.Add(selection);
{ if (paradoxLauncher.ExtraSelections.Count > 0)
paradoxLauncher.ExtraDlc.Add(selection.Id, (selection.Name, selection.AllDlc)); return;
paradoxLauncher.ExtraSelectedDlc.Add(selection.Id, (selection.Name, selection.SelectedDlc)); foreach (Selection selection in Selection.AllSafe.Where(s => s != paradoxLauncher && s.Publisher == "Paradox Interactive"))
} _ = paradoxLauncher.ExtraSelections.Add(selection);
if (!paradoxLauncher.ExtraDlc.Any())
foreach (ProgramSelection selection in ProgramSelection.AllSafe.Where(s => s != paradoxLauncher && s.Publisher == "Paradox Interactive"))
{
paradoxLauncher.ExtraDlc.Add(selection.Id, (selection.Name, selection.AllDlc));
paradoxLauncher.ExtraSelectedDlc.Add(selection.Id, (selection.Name, selection.AllDlc));
}
}
} }
internal static bool DlcDialog(Form form) internal static bool DlcDialog(Form form)
{ {
ProgramSelection paradoxLauncher = ProgramSelection.FromPlatformId(Platform.Paradox, "PL"); Selection paradoxLauncher = Selection.FromPlatformId(Platform.Paradox, "PL");
if (paradoxLauncher is not null && paradoxLauncher.Enabled) if (paradoxLauncher is null || !paradoxLauncher.Enabled)
{ return false;
PopulateDlc(paradoxLauncher); PopulateDlc(paradoxLauncher);
if (!paradoxLauncher.ExtraDlc.Any()) if (paradoxLauncher.ExtraSelections.Count > 0)
{ return false;
using DialogForm dialogForm = new(form); using DialogForm dialogForm = new(form);
return dialogForm.Show(SystemIcons.Warning, return dialogForm.Show(SystemIcons.Warning,
"WARNING: There are no scanned games with DLC that can be added to the Paradox Launcher!" "WARNING: There are no scanned games with DLC that can be added to the Paradox Launcher!"
+ "\n\nInstalling DLC unlockers for the Paradox Launcher alone can cause existing configurations to be deleted!", "Ignore", "Cancel", + "\n\nInstalling DLC unlockers for the Paradox Launcher alone can cause existing configurations to be deleted!", "Ignore", "Cancel",
"Paradox Launcher") != DialogResult.OK; "Paradox Launcher") != DialogResult.OK;
}
}
return false;
} }
internal static async Task<RepairResult> Repair(Form form, ProgramSelection selection) internal static async Task<RepairResult> Repair(Form form, Selection selection)
{ {
InstallForm installForm = form as InstallForm; InstallForm installForm = form as InstallForm;
if (!Program.AreDllsLockedDialog(form, selection)) if (!Program.AreDllsLockedDialog(form, selection))

View file

@ -22,14 +22,14 @@ internal static class SteamLibrary
} }
} }
internal static async Task<List<(string directory, BinaryType binaryType)>> GetExecutableDirectories(string gameDirectory) internal static async Task<HashSet<(string directory, BinaryType binaryType)>> GetExecutableDirectories(string gameDirectory)
=> await Task.Run(async () => await gameDirectory.GetExecutableDirectories(true)); => await Task.Run(async () => await gameDirectory.GetExecutableDirectories(true));
internal static async Task<List<(string appId, string name, string branch, int buildId, string gameDirectory)>> GetGames() internal static async Task<HashSet<(string appId, string name, string branch, int buildId, string gameDirectory)>> GetGames()
=> await Task.Run(async () => => await Task.Run(async () =>
{ {
List<(string appId, string name, string branch, int buildId, string gameDirectory)> games = new(); HashSet<(string appId, string name, string branch, int buildId, string gameDirectory)> games = new();
List<string> gameLibraryDirectories = await GetLibraryDirectories(); HashSet<string> gameLibraryDirectories = await GetLibraryDirectories();
foreach (string libraryDirectory in gameLibraryDirectories) foreach (string libraryDirectory in gameLibraryDirectories)
{ {
if (Program.Canceled) if (Program.Canceled)
@ -37,7 +37,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;
}); });
@ -85,10 +85,10 @@ internal static class SteamLibrary
return games; return games;
}); });
private static async Task<List<string>> GetLibraryDirectories() private static async Task<HashSet<string>> GetLibraryDirectories()
=> await Task.Run(() => => await Task.Run(() =>
{ {
List<string> gameDirectories = new(); HashSet<string> gameDirectories = new();
if (Program.Canceled) if (Program.Canceled)
return gameDirectories; return gameDirectories;
string steamInstallPath = InstallPath; string steamInstallPath = InstallPath;
@ -97,7 +97,7 @@ internal static class SteamLibrary
string libraryFolder = steamInstallPath + @"\steamapps"; string libraryFolder = steamInstallPath + @"\steamapps";
if (!libraryFolder.DirectoryExists()) if (!libraryFolder.DirectoryExists())
return gameDirectories; return gameDirectories;
gameDirectories.Add(libraryFolder); _ = gameDirectories.Add(libraryFolder);
string libraryFolders = libraryFolder + @"\libraryfolders.vdf"; string libraryFolders = libraryFolder + @"\libraryfolders.vdf";
if (!libraryFolders.FileExists() || !ValveDataFile.TryDeserialize(libraryFolders.ReadFile(), out VProperty result)) if (!libraryFolders.FileExists() || !ValveDataFile.TryDeserialize(libraryFolders.ReadFile(), out VProperty result))
return gameDirectories; return gameDirectories;
@ -108,8 +108,8 @@ internal static class SteamLibrary
if (string.IsNullOrWhiteSpace(path)) if (string.IsNullOrWhiteSpace(path))
continue; continue;
path += @"\steamapps"; path += @"\steamapps";
if (path.DirectoryExists() && !gameDirectories.Contains(path)) if (path.DirectoryExists())
gameDirectories.Add(path); _ = gameDirectories.Add(path);
} }
return gameDirectories; return gameDirectories;
}); });

View file

@ -12,7 +12,7 @@ internal static class UbisoftLibrary
{ {
private static RegistryKey installsKey; private static RegistryKey installsKey;
internal static RegistryKey InstallsKey private static RegistryKey InstallsKey
{ {
get get
{ {
@ -22,13 +22,13 @@ internal static class UbisoftLibrary
} }
} }
internal static async Task<List<(string directory, BinaryType binaryType)>> GetExecutableDirectories(string gameDirectory) internal static async Task<HashSet<(string directory, BinaryType binaryType)>> GetExecutableDirectories(string gameDirectory)
=> await Task.Run(async () => await gameDirectory.GetExecutableDirectories(true)); => await Task.Run(async () => await gameDirectory.GetExecutableDirectories(true));
internal static async Task<List<(string gameId, string name, string gameDirectory)>> GetGames() internal static async Task<HashSet<(string gameId, string name, string gameDirectory)>> GetGames()
=> await Task.Run(() => => await Task.Run(() =>
{ {
List<(string gameId, string name, string gameDirectory)> games = new(); HashSet<(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;
@ -37,7 +37,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

@ -48,7 +48,7 @@ internal static class Program
return ProtectedGameDirectories.Any(path => (directory + path).DirectoryExists()); return ProtectedGameDirectories.Any(path => (directory + path).DirectoryExists());
} }
internal static bool AreDllsLockedDialog(Form form, ProgramSelection selection) internal static bool AreDllsLockedDialog(Form form, Selection selection)
{ {
while (true) while (true)
{ {

View file

@ -75,7 +75,7 @@ internal static class Koaloader
} }
SortedList<string, string> targets = new(PlatformIdComparer.String); SortedList<string, string> targets = new(PlatformIdComparer.String);
SortedList<string, string> modules = new(PlatformIdComparer.String); SortedList<string, string> modules = new(PlatformIdComparer.String);
if (targets.Any() || modules.Any()) if (targets.Count > 0 || modules.Count > 0)
{ {
/*if (installForm is not null) /*if (installForm is not null)
installForm.UpdateUser("Generating Koaloader configuration for " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation);*/ installForm.UpdateUser("Generating Koaloader configuration for " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation);*/
@ -92,13 +92,13 @@ internal static class Koaloader
} }
} }
private static void WriteConfig(StreamWriter writer, SortedList<string, string> targets, SortedList<string, string> modules, InstallForm installForm = null) private static void WriteConfig(TextWriter writer, SortedList<string, string> targets, SortedList<string, string> modules, InstallForm installForm = null)
{ {
writer.WriteLine("{"); writer.WriteLine("{");
writer.WriteLine(" \"logging\": false,"); writer.WriteLine(" \"logging\": false,");
writer.WriteLine(" \"enabled\": true,"); writer.WriteLine(" \"enabled\": true,");
writer.WriteLine(" \"auto_load\": " + (modules.Any() ? "false" : "true") + ","); writer.WriteLine(" \"auto_load\": " + (modules.Count > 0 ? "false" : "true") + ",");
if (targets.Any()) if (targets.Count > 0)
{ {
writer.WriteLine(" \"targets\": ["); writer.WriteLine(" \"targets\": [");
KeyValuePair<string, string> lastTarget = targets.Last(); KeyValuePair<string, string> lastTarget = targets.Last();
@ -112,7 +112,7 @@ internal static class Koaloader
} }
else else
writer.WriteLine(" \"targets\": []"); writer.WriteLine(" \"targets\": []");
if (modules.Any()) if (modules.Count > 0)
{ {
writer.WriteLine(" \"modules\": ["); writer.WriteLine(" \"modules\": [");
KeyValuePair<string, string> lastModule = modules.Last(); KeyValuePair<string, string> lastModule = modules.Last();
@ -166,11 +166,11 @@ internal static class Koaloader
await Uninstall(rootDirectory, null, installForm, deleteConfig); await Uninstall(rootDirectory, null, installForm, deleteConfig);
}); });
internal static async Task Install(string directory, BinaryType binaryType, ProgramSelection selection, string rootDirectory = null, internal static async Task Install(string directory, BinaryType binaryType, Selection selection, string rootDirectory = null,
InstallForm installForm = null, bool generateConfig = true) InstallForm installForm = null, bool generateConfig = true)
=> await Task.Run(() => => await Task.Run(() =>
{ {
string proxy = selection.KoaloaderProxy ?? ProgramSelection.DefaultKoaloaderProxy; string proxy = selection.KoaloaderProxy ?? Selection.DefaultKoaloaderProxy;
string path = directory + @"\" + proxy + ".dll"; string path = directory + @"\" + proxy + ".dll";
foreach (string _path in directory.GetKoaloaderProxies().Where(p => p != path && p.FileExists() && p.IsResourceFile(ResourceIdentifier.Koaloader))) foreach (string _path in directory.GetKoaloaderProxies().Where(p => p != path && p.FileExists() && p.IsResourceFile(ResourceIdentifier.Koaloader)))
{ {

View file

@ -483,21 +483,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<List<(string directory, BinaryType binaryType)>> GetExecutableDirectories(this string rootDirectory, bool filterCommon = false, internal static async Task<HashSet<(string directory, BinaryType binaryType)>> GetExecutableDirectories(this string rootDirectory,
Func<string, bool> validFunc = null) bool filterCommon = false, 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).ToList()); }).DistinctBy(e => e.path).ToHashSet());
internal static async Task<List<(string path, BinaryType binaryType)>> GetExecutables(this string rootDirectory, bool filterCommon = false, internal static async Task<HashSet<(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(() =>
{ {
List<(string path, BinaryType binaryType)> executables = new(); HashSet<(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,7 +507,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); Thread.Sleep(1);
} }
foreach (string path in rootDirectory.EnumerateDirectory("*.exe", true)) foreach (string path in rootDirectory.EnumerateDirectory("*.exe", true))
@ -517,10 +517,10 @@ 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); Thread.Sleep(1);
} }
return !executables.Any() ? null : executables; return executables.Count > 0 ? executables : null;
}); });
private static bool IsCommonIncorrectExecutable(this string rootDirectory, string path) private static bool IsCommonIncorrectExecutable(this string rootDirectory, string path)
@ -533,10 +533,10 @@ internal static class Resources
|| subPath.Contains("ANTICHEAT"); || subPath.Contains("ANTICHEAT");
} }
internal static async Task<List<string>> GetDllDirectoriesFromGameDirectory(this string gameDirectory, Platform platform) internal static async Task<HashSet<string>> GetDllDirectoriesFromGameDirectory(this string gameDirectory, Platform platform)
=> await Task.Run(() => => await Task.Run(() =>
{ {
List<string> dllDirectories = new(); HashSet<string> dllDirectories = new();
if (Program.Canceled || !gameDirectory.DirectoryExists()) if (Program.Canceled || !gameDirectory.DirectoryExists())
return null; return null;
foreach (string directory in gameDirectory.EnumerateSubdirectories("*", true).Append(gameDirectory)) foreach (string directory in gameDirectory.EnumerateSubdirectories("*", true).Append(gameDirectory))
@ -555,7 +555,7 @@ internal static class Resources
if (api.FileExists() || api_o.FileExists() || api64.FileExists() || api64_o.FileExists() if (api.FileExists() || api_o.FileExists() || api64.FileExists() || api64_o.FileExists()
|| (old_config.FileExists() || config.FileExists() || old_log.FileExists() || log.FileExists() || cache.FileExists()) || (old_config.FileExists() || config.FileExists() || old_log.FileExists() || log.FileExists() || cache.FileExists())
&& !koaloaderInstalled) && !koaloaderInstalled)
dllDirectories.Add(subDirectory); _ = dllDirectories.Add(subDirectory);
} }
if (platform is Platform.Epic or Platform.Paradox) if (platform is Platform.Epic or Platform.Paradox)
{ {
@ -563,7 +563,7 @@ internal static class Resources
out string log); out string log);
if (api32.FileExists() || api32_o.FileExists() || api64.FileExists() || api64_o.FileExists() if (api32.FileExists() || api32_o.FileExists() || api64.FileExists() || api64_o.FileExists()
|| (config.FileExists() || log.FileExists()) && !koaloaderInstalled) || (config.FileExists() || log.FileExists()) && !koaloaderInstalled)
dllDirectories.Add(subDirectory); _ = dllDirectories.Add(subDirectory);
} }
if (platform is Platform.Ubisoft) if (platform is Platform.Ubisoft)
{ {
@ -571,15 +571,15 @@ internal static class Resources
out string log); out string log);
if (api32.FileExists() || api32_o.FileExists() || api64.FileExists() || api64_o.FileExists() if (api32.FileExists() || api32_o.FileExists() || api64.FileExists() || api64_o.FileExists()
|| (config.FileExists() || log.FileExists()) && !koaloaderInstalled) || (config.FileExists() || log.FileExists()) && !koaloaderInstalled)
dllDirectories.Add(subDirectory); _ = dllDirectories.Add(subDirectory);
subDirectory.GetUplayR2Components(out string old_api32, out string old_api64, out api32, out api32_o, out api64, out api64_o, out config, subDirectory.GetUplayR2Components(out string old_api32, out string old_api64, out api32, out api32_o, out api64, out api64_o, out config,
out log); out log);
if (old_api32.FileExists() || old_api64.FileExists() || api32.FileExists() || api32_o.FileExists() || api64.FileExists() if (old_api32.FileExists() || old_api64.FileExists() || api32.FileExists() || api32_o.FileExists() || api64.FileExists()
|| api64_o.FileExists() || (config.FileExists() || log.FileExists()) && !koaloaderInstalled) || api64_o.FileExists() || (config.FileExists() || log.FileExists()) && !koaloaderInstalled)
dllDirectories.Add(subDirectory); _ = dllDirectories.Add(subDirectory);
} }
} }
return !dllDirectories.Any() ? null : dllDirectories; return dllDirectories.Count > 0 ? dllDirectories : null;
}); });
internal static void GetCreamApiComponents(this string directory, out string api32, out string api32_o, out string api64, out string api64_o, internal static void GetCreamApiComponents(this string directory, out string api32, out string api32_o, out string api64, out string api64_o,

View file

@ -22,27 +22,24 @@ internal static class ScreamAPI
log = directory + @"\ScreamAPI.log"; log = directory + @"\ScreamAPI.log";
} }
internal static void CheckConfig(string directory, ProgramSelection 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 _);
IEnumerable<KeyValuePair<string, (DlcType type, string name, string icon)>> overrideCatalogItems List<SelectionDLC> overrideCatalogItems = selection.DLC.Where(dlc => dlc.Type is DLCType.EpicCatalogItem && !dlc.Enabled).ToList();
= selection.AllDlc.Where(pair => pair.Value.type is DlcType.EpicCatalogItem).Except(selection.SelectedDlc); List<SelectionDLC> overrideEntitlements = selection.DLC.Where(dlc => dlc.Type is DLCType.EpicEntitlement && !dlc.Enabled).ToList();
foreach (KeyValuePair<string, (string _, SortedList<string, (DlcType type, string name, string icon)> extraDlc)> pair in selection.ExtraSelectedDlc) foreach (Selection extraSelection in selection.ExtraSelections)
overrideCatalogItems = overrideCatalogItems.Except(pair.Value.extraDlc); {
IEnumerable<KeyValuePair<string, (DlcType type, string name, string icon)>> entitlements overrideCatalogItems.AddRange(extraSelection.DLC.Where(dlc => dlc.Type is DLCType.EpicCatalogItem && !dlc.Enabled));
= selection.SelectedDlc.Where(pair => pair.Value.type == DlcType.EpicEntitlement); overrideEntitlements.AddRange(extraSelection.DLC.Where(dlc => dlc.Type is DLCType.EpicEntitlement && !dlc.Enabled));
foreach (KeyValuePair<string, (string _, SortedList<string, (DlcType type, string name, string icon)> dlc)> pair in selection.ExtraSelectedDlc) }
entitlements = entitlements.Concat(pair.Value.dlc.Where(pair => pair.Value.type == DlcType.EpicEntitlement)); if (overrideCatalogItems.Count > 0 || overrideEntitlements.Count > 0)
overrideCatalogItems = overrideCatalogItems.ToList();
entitlements = entitlements.ToList();
if (overrideCatalogItems.Any() || entitlements.Any())
{ {
/*if (installForm is not null) /*if (installForm is not null)
installForm.UpdateUser("Generating ScreamAPI configuration for " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation);*/ installForm.UpdateUser("Generating ScreamAPI configuration for " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation);*/
config.CreateFile(true, installForm).Close(); config.CreateFile(true, installForm).Close();
StreamWriter writer = new(config, true, Encoding.UTF8); StreamWriter writer = new(config, true, Encoding.UTF8);
WriteConfig(writer, new(overrideCatalogItems.ToDictionary(pair => pair.Key, pair => pair.Value), PlatformIdComparer.String), WriteConfig(writer, new(overrideCatalogItems.ToDictionary(dlc => dlc.Id, dlc => dlc), PlatformIdComparer.String),
new(entitlements.ToDictionary(pair => pair.Key, pair => pair.Value), PlatformIdComparer.String), installForm); new(overrideEntitlements.ToDictionary(dlc => dlc.Id, dlc => dlc), PlatformIdComparer.String), installForm);
writer.Flush(); writer.Flush();
writer.Close(); writer.Close();
} }
@ -53,8 +50,8 @@ internal static class ScreamAPI
} }
} }
private static void WriteConfig(StreamWriter writer, SortedList<string, (DlcType type, string name, string icon)> overrideCatalogItems, private static void WriteConfig(TextWriter writer, SortedList<string, SelectionDLC> overrideCatalogItems, SortedList<string, SelectionDLC> entitlements,
SortedList<string, (DlcType type, string name, string icon)> entitlements, InstallForm installForm = null) InstallForm installForm = null)
{ {
writer.WriteLine("{"); writer.WriteLine("{");
writer.WriteLine(" \"version\": 2,"); writer.WriteLine(" \"version\": 2,");
@ -63,16 +60,16 @@ internal static class ScreamAPI
writer.WriteLine(" \"block_metrics\": false,"); writer.WriteLine(" \"block_metrics\": false,");
writer.WriteLine(" \"catalog_items\": {"); writer.WriteLine(" \"catalog_items\": {");
writer.WriteLine(" \"unlock_all\": true,"); writer.WriteLine(" \"unlock_all\": true,");
if (overrideCatalogItems.Any()) if (overrideCatalogItems.Count > 0)
{ {
writer.WriteLine(" \"override\": ["); writer.WriteLine(" \"override\": [");
KeyValuePair<string, (DlcType type, string name, string icon)> lastOverrideCatalogItem = overrideCatalogItems.Last(); KeyValuePair<string, SelectionDLC> lastOverrideCatalogItem = overrideCatalogItems.Last();
foreach (KeyValuePair<string, (DlcType type, string name, string icon)> pair in overrideCatalogItems) foreach (KeyValuePair<string, SelectionDLC> pair in overrideCatalogItems)
{ {
string id = pair.Key; SelectionDLC selectionDlc = pair.Value;
(_, string name, _) = pair.Value; writer.WriteLine($" \"{selectionDlc.Id}\"{(pair.Equals(lastOverrideCatalogItem) ? "" : ",")}");
writer.WriteLine($" \"{id}\"{(pair.Equals(lastOverrideCatalogItem) ? "" : ",")}"); installForm?.UpdateUser($"Added locked catalog item to ScreamAPI.json with id {selectionDlc.Id} ({selectionDlc.Name})", LogTextBox.Action,
installForm?.UpdateUser($"Added override catalog item to ScreamAPI.json with id {id} ({name})", LogTextBox.Action, false); false);
} }
writer.WriteLine(" ]"); writer.WriteLine(" ]");
} }
@ -82,16 +79,16 @@ internal static class ScreamAPI
writer.WriteLine(" \"entitlements\": {"); writer.WriteLine(" \"entitlements\": {");
writer.WriteLine(" \"unlock_all\": true,"); writer.WriteLine(" \"unlock_all\": true,");
writer.WriteLine(" \"auto_inject\": true,"); writer.WriteLine(" \"auto_inject\": true,");
if (entitlements.Any()) if (entitlements.Count > 0)
{ {
writer.WriteLine(" \"inject\": ["); writer.WriteLine(" \"inject\": [");
KeyValuePair<string, (DlcType type, string name, string icon)> lastEntitlement = entitlements.Last(); KeyValuePair<string, SelectionDLC> lastEntitlement = entitlements.Last();
foreach (KeyValuePair<string, (DlcType type, string name, string icon)> pair in entitlements) foreach (KeyValuePair<string, SelectionDLC> pair in entitlements)
{ {
string id = pair.Key; SelectionDLC selectionDlc = pair.Value;
(_, string name, _) = pair.Value; writer.WriteLine($" \"{selectionDlc.Id}\"{(pair.Equals(lastEntitlement) ? "" : ",")}");
writer.WriteLine($" \"{id}\"{(pair.Equals(lastEntitlement) ? "" : ",")}"); installForm?.UpdateUser($"Added locked entitlement to ScreamAPI.json with id {selectionDlc.Id} ({selectionDlc.Name})", LogTextBox.Action,
installForm?.UpdateUser($"Added entitlement to ScreamAPI.json with id {id} ({name})", LogTextBox.Action, false); false);
} }
writer.WriteLine(" ]"); writer.WriteLine(" ]");
} }
@ -139,7 +136,7 @@ internal static class ScreamAPI
} }
}); });
internal static async Task Install(string directory, ProgramSelection selection, InstallForm installForm = null, bool generateConfig = true) internal static async Task Install(string directory, Selection selection, InstallForm installForm = null, bool generateConfig = true)
=> await Task.Run(() => => await Task.Run(() =>
{ {
directory.GetScreamApiComponents(out string api32, out string api32_o, out string api64, out string api64_o, out _, out _); directory.GetScreamApiComponents(out string api32, out string api32_o, out string api64, out string api64_o, out _, out _);

View file

@ -25,46 +25,38 @@ internal static class SmokeAPI
cache = directory + @"\SmokeAPI.cache.json"; cache = directory + @"\SmokeAPI.cache.json";
} }
internal static void CheckConfig(string directory, ProgramSelection 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<KeyValuePair<string, (DlcType type, string name, string icon)>> overrideDlc = selection.AllDlc.Except(selection.SelectedDlc).ToList(); List<SelectionDLC> overrideDlc = selection.DLC.Where(dlc => !dlc.Enabled).ToList();
foreach (KeyValuePair<string, (string name, SortedList<string, (DlcType type, string name, string icon)> dlc)> pair in selection.ExtraDlc) foreach (Selection extraSelection in selection.ExtraSelections)
if (selection.ExtraSelectedDlc.TryGetValue(pair.Key, overrideDlc.AddRange(extraSelection.DLC.Where(dlc => !dlc.Enabled));
out (string name, SortedList<string, (DlcType type, string name, string icon)> dlc) selectedPair)) List<SelectionDLC> injectDlc = new();
overrideDlc.AddRange(pair.Value.dlc.Except(selectedPair.dlc)); if (selection.DLC.Count() > 64)
List<KeyValuePair<string, (DlcType type, string name, string icon)>> injectDlc = new(); injectDlc.AddRange(selection.DLC.Where(dlc => dlc.Enabled && dlc.Type is DLCType.SteamHidden));
if (selection.AllDlc.Count > 64) List<KeyValuePair<string, (string name, SortedList<string, SelectionDLC> injectDlc)>> extraApps = new();
injectDlc.AddRange(selection.SelectedDlc.Where(pair => pair.Value.type is DlcType.SteamHidden)); foreach (Selection extraSelection in selection.ExtraSelections.Where(extraSelection => extraSelection.DLC.Count() > 64))
List<KeyValuePair<string, (string name, SortedList<string, (DlcType type, string name, string icon)> injectDlc)>> extraApps = new(); {
if (selection.ExtraDlc.Any(e => e.Value.dlc.Count > 64)) SortedList<string, SelectionDLC> extraInjectDlc = new(PlatformIdComparer.String);
foreach (KeyValuePair<string, (string name, SortedList<string, (DlcType type, string name, string icon)> injectDlc)> pair in selection foreach (SelectionDLC extraDlc in extraSelection.DLC.Where(extraDlc => extraDlc.Enabled && extraDlc.Type is DLCType.SteamHidden))
.ExtraSelectedDlc) extraInjectDlc.Add(extraDlc.Id, extraDlc);
if (selection.ExtraDlc.First(e => e.Key == pair.Key).Value.dlc.Count > 64) if (extraInjectDlc.Count > 0)
{ extraApps.Add(new(extraSelection.Id, (extraSelection.Name, extraInjectDlc)));
SortedList<string, (DlcType type, string name, string icon)> extraInjectDlc = new(PlatformIdComparer.String); }
foreach (KeyValuePair<string, (DlcType type, string name, string icon)> extraPair in pair.Value.injectDlc.Where(extraPair
=> extraPair.Value.type is DlcType.SteamHidden))
extraInjectDlc.Add(extraPair.Key, extraPair.Value);
KeyValuePair<string, (string name, SortedList<string, (DlcType type, string name, string icon)> injectDlc)> newExtraPair = new(pair.Key,
(pair.Value.name, extraInjectDlc));
extraApps.Add(newExtraPair);
}
injectDlc = injectDlc.ToList();
if (old_config.FileExists()) if (old_config.FileExists())
{ {
old_config.DeleteFile(); old_config.DeleteFile();
installForm?.UpdateUser($"Deleted old configuration: {Path.GetFileName(old_config)}", LogTextBox.Action, false); installForm?.UpdateUser($"Deleted old configuration: {Path.GetFileName(old_config)}", LogTextBox.Action, false);
} }
if (selection.ExtraSelectedDlc.Any(p => p.Value.dlc.Any()) || overrideDlc.Any() || injectDlc.Any()) if (selection.ExtraSelections.Any(extraSelection => extraSelection.DLC.Any()) || overrideDlc.Count > 0 || injectDlc.Count > 0)
{ {
/*if (installForm is not null) /*if (installForm is not null)
installForm.UpdateUser("Generating SmokeAPI configuration for " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation);*/ installForm.UpdateUser("Generating SmokeAPI configuration for " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation);*/
config.CreateFile(true, installForm).Close(); config.CreateFile(true, installForm).Close();
StreamWriter writer = new(config, true, Encoding.UTF8); StreamWriter writer = new(config, true, Encoding.UTF8);
WriteConfig(writer, selection.Id, new(extraApps.ToDictionary(pair => pair.Key, pair => pair.Value), PlatformIdComparer.String), WriteConfig(writer, selection.Id, new(extraApps.ToDictionary(extraApp => extraApp.Key, extraApp => extraApp.Value), PlatformIdComparer.String),
new(overrideDlc.ToDictionary(pair => pair.Key, pair => pair.Value), PlatformIdComparer.String), new(overrideDlc.ToDictionary(dlc => dlc.Id, dlc => dlc), PlatformIdComparer.String),
new(injectDlc.ToDictionary(pair => pair.Key, pair => pair.Value), PlatformIdComparer.String), installForm); new(injectDlc.ToDictionary(dlc => dlc.Id, dlc => dlc), PlatformIdComparer.String), installForm);
writer.Flush(); writer.Flush();
writer.Close(); writer.Close();
} }
@ -75,10 +67,8 @@ internal static class SmokeAPI
} }
} }
private static void WriteConfig(StreamWriter writer, string appId, private static void WriteConfig(TextWriter writer, string appId, SortedList<string, (string name, SortedList<string, SelectionDLC> injectDlc)> extraApps,
SortedList<string, (string name, SortedList<string, (DlcType type, string name, string icon)> injectDlc)> extraApps, SortedList<string, SelectionDLC> overrideDlc, SortedList<string, SelectionDLC> injectDlc, InstallForm installForm = null)
SortedList<string, (DlcType type, string name, string icon)> overrideDlc, SortedList<string, (DlcType type, string name, string icon)> injectDlc,
InstallForm installForm = null)
{ {
writer.WriteLine("{"); writer.WriteLine("{");
writer.WriteLine(" \"$version\": 2,"); writer.WriteLine(" \"$version\": 2,");
@ -89,13 +79,13 @@ internal static class SmokeAPI
if (overrideDlc.Count > 0) if (overrideDlc.Count > 0)
{ {
writer.WriteLine(" \"override_dlc_status\": {"); writer.WriteLine(" \"override_dlc_status\": {");
KeyValuePair<string, (DlcType type, string name, string icon)> lastOverrideDlc = overrideDlc.Last(); KeyValuePair<string, SelectionDLC> lastOverrideDlc = overrideDlc.Last();
foreach (KeyValuePair<string, (DlcType type, string name, string icon)> pair in overrideDlc) foreach (KeyValuePair<string, SelectionDLC> pair in overrideDlc)
{ {
string dlcId = pair.Key; SelectionDLC selectionDlc = pair.Value;
(_, string dlcName, _) = pair.Value; writer.WriteLine($" \"{selectionDlc.Id}\": \"locked\"{(pair.Equals(lastOverrideDlc) ? "" : ",")}");
writer.WriteLine($" \"{dlcId}\": \"locked\"{(pair.Equals(lastOverrideDlc) ? "" : ",")}"); installForm?.UpdateUser($"Added locked DLC to SmokeAPI.config.json with appid {selectionDlc.Id} ({selectionDlc.Name})", LogTextBox.Action,
installForm?.UpdateUser($"Added locked DLC to SmokeAPI.config.json with appid {dlcId} ({dlcName})", LogTextBox.Action, false); false);
} }
writer.WriteLine(" },"); writer.WriteLine(" },");
} }
@ -110,34 +100,34 @@ internal static class SmokeAPI
{ {
writer.WriteLine(" \"" + appId + "\": {"); writer.WriteLine(" \"" + appId + "\": {");
writer.WriteLine(" \"dlcs\": {"); writer.WriteLine(" \"dlcs\": {");
KeyValuePair<string, (DlcType type, string name, string icon)> lastInjectDlc = injectDlc.Last(); KeyValuePair<string, SelectionDLC> lastInjectDlc = injectDlc.Last();
foreach (KeyValuePair<string, (DlcType type, string name, string icon)> pair in injectDlc) foreach (KeyValuePair<string, SelectionDLC> pair in injectDlc)
{ {
string dlcId = pair.Key; SelectionDLC selectionDlc = pair.Value;
(_, string dlcName, _) = pair.Value; writer.WriteLine($" \"{selectionDlc.Id}\": \"{selectionDlc.Name}\"{(pair.Equals(lastInjectDlc) ? "" : ",")}");
writer.WriteLine($" \"{dlcId}\": \"{dlcName}\"{(pair.Equals(lastInjectDlc) ? "" : ",")}"); installForm?.UpdateUser($"Added extra DLC to SmokeAPI.config.json with appid {selectionDlc.Id} ({selectionDlc.Name})", LogTextBox.Action,
installForm?.UpdateUser($"Added extra DLC to SmokeAPI.config.json with appid {dlcId} ({dlcName})", LogTextBox.Action, false); false);
} }
writer.WriteLine(" }"); writer.WriteLine(" }");
writer.WriteLine(extraApps.Count > 0 ? " }," : " }"); writer.WriteLine(extraApps.Count > 0 ? " }," : " }");
} }
if (extraApps.Count > 0) if (extraApps.Count > 0)
{ {
KeyValuePair<string, (string name, SortedList<string, (DlcType type, string name, string icon)> injectDlc)> lastExtraApp = extraApps.Last(); KeyValuePair<string, (string name, SortedList<string, SelectionDLC> injectDlc)> lastExtraApp = extraApps.Last();
foreach (KeyValuePair<string, (string name, SortedList<string, (DlcType type, string name, string icon)> injectDlc)> pair in extraApps) foreach (KeyValuePair<string, (string name, SortedList<string, SelectionDLC> injectDlc)> pair in extraApps)
{ {
string extraAppId = pair.Key; string extraAppId = pair.Key;
(string _ /*extraAppName*/, SortedList<string, (DlcType type, string name, string icon)> extraInjectDlc) = pair.Value; (string _ /*extraAppName*/, SortedList<string, SelectionDLC> extraInjectDlc) = pair.Value;
writer.WriteLine(" \"" + extraAppId + "\": {"); writer.WriteLine(" \"" + extraAppId + "\": {");
writer.WriteLine(" \"dlcs\": {"); writer.WriteLine(" \"dlcs\": {");
//installForm?.UpdateUser($"Added extra app to SmokeAPI.config.json with appid {extraAppId} ({extraAppName})", LogTextBox.Action, false); //installForm?.UpdateUser($"Added extra app to SmokeAPI.config.json with appid {extraAppId} ({extraAppName})", LogTextBox.Action, false);
KeyValuePair<string, (DlcType type, string name, string icon)> lastExtraAppDlc = extraInjectDlc.Last(); KeyValuePair<string, SelectionDLC> lastExtraAppDlc = extraInjectDlc.Last();
foreach (KeyValuePair<string, (DlcType type, string name, string icon)> extraPair in extraInjectDlc) foreach (KeyValuePair<string, SelectionDLC> extraPair in extraInjectDlc)
{ {
string dlcId = extraPair.Key; SelectionDLC selectionDlc = extraPair.Value;
(_, string dlcName, _) = extraPair.Value; writer.WriteLine($" \"{selectionDlc.Id}\": \"{selectionDlc.Name}\"{(extraPair.Equals(lastExtraAppDlc) ? "" : ",")}");
writer.WriteLine($" \"{dlcId}\": \"{dlcName}\"{(extraPair.Equals(lastExtraAppDlc) ? "" : ",")}"); installForm?.UpdateUser($"Added extra DLC to SmokeAPI.config.json with appid {selectionDlc.Id} ({selectionDlc.Name})",
installForm?.UpdateUser($"Added extra DLC to SmokeAPI.config.json with appid {dlcId} ({dlcName})", LogTextBox.Action, false); LogTextBox.Action, false);
} }
writer.WriteLine(" }"); writer.WriteLine(" }");
writer.WriteLine(pair.Equals(lastExtraApp) ? " }" : " },"); writer.WriteLine(pair.Equals(lastExtraApp) ? " }" : " },");
@ -211,7 +201,7 @@ internal static class SmokeAPI
} }
}); });
internal static async Task Install(string directory, ProgramSelection selection, InstallForm installForm = null, bool generateConfig = true) internal static async Task Install(string directory, Selection selection, InstallForm installForm = null, bool generateConfig = true)
=> await Task.Run(() => => await Task.Run(() =>
{ {
directory.GetCreamApiComponents(out _, out _, out _, out _, out string oldConfig); directory.GetCreamApiComponents(out _, out _, out _, out _, out string oldConfig);

View file

@ -22,20 +22,19 @@ internal static class UplayR1
log = directory + @"\UplayR1Unlocker.log"; log = directory + @"\UplayR1Unlocker.log";
} }
internal static void CheckConfig(string directory, ProgramSelection 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 _);
IEnumerable<KeyValuePair<string, (DlcType type, string name, string icon)>> blacklistDlc = selection.AllDlc.Except(selection.SelectedDlc); List<SelectionDLC> blacklistDlc = selection.DLC.Where(dlc => !dlc.Enabled).ToList();
foreach (KeyValuePair<string, (string _, SortedList<string, (DlcType type, string name, string icon)> extraDlc)> pair in selection.ExtraSelectedDlc) foreach (Selection extraSelection in selection.ExtraSelections)
blacklistDlc = blacklistDlc.Except(pair.Value.extraDlc); blacklistDlc.AddRange(extraSelection.DLC.Where(dlc => !dlc.Enabled));
blacklistDlc = blacklistDlc.ToList(); if (blacklistDlc.Count > 0)
if (blacklistDlc.Any())
{ {
/*if (installForm is not null) /*if (installForm is not null)
installForm.UpdateUser("Generating Uplay R1 Unlocker configuration for " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation);*/ installForm.UpdateUser("Generating Uplay R1 Unlocker configuration for " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation);*/
config.CreateFile(true, installForm).Close(); config.CreateFile(true, installForm).Close();
StreamWriter writer = new(config, true, Encoding.UTF8); StreamWriter writer = new(config, true, Encoding.UTF8);
WriteConfig(writer, new(blacklistDlc.ToDictionary(pair => pair.Key, pair => pair.Value), PlatformIdComparer.String), installForm); WriteConfig(writer, new(blacklistDlc.ToDictionary(dlc => dlc.Id, dlc => dlc), PlatformIdComparer.String), installForm);
writer.Flush(); writer.Flush();
writer.Close(); writer.Close();
} }
@ -46,8 +45,7 @@ internal static class UplayR1
} }
} }
private static void WriteConfig(StreamWriter writer, SortedList<string, (DlcType type, string name, string icon)> blacklistDlc, private static void WriteConfig(TextWriter writer, SortedList<string, SelectionDLC> blacklistDlc, InstallForm installForm = null)
InstallForm installForm = null)
{ {
writer.WriteLine("{"); writer.WriteLine("{");
writer.WriteLine(" \"logging\": false,"); writer.WriteLine(" \"logging\": false,");
@ -56,13 +54,13 @@ internal static class UplayR1
if (blacklistDlc.Count > 0) if (blacklistDlc.Count > 0)
{ {
writer.WriteLine(" \"blacklist\": ["); writer.WriteLine(" \"blacklist\": [");
KeyValuePair<string, (DlcType type, string name, string icon)> lastBlacklistDlc = blacklistDlc.Last(); KeyValuePair<string, SelectionDLC> lastBlacklistDlc = blacklistDlc.Last();
foreach (KeyValuePair<string, (DlcType type, string name, string icon)> pair in blacklistDlc) foreach (KeyValuePair<string, SelectionDLC> pair in blacklistDlc)
{ {
string dlcId = pair.Key; SelectionDLC selectionDlc = pair.Value;
(_, string dlcName, _) = pair.Value; writer.WriteLine($" {selectionDlc.Id}{(pair.Equals(lastBlacklistDlc) ? "" : ",")}");
writer.WriteLine($" {dlcId}{(pair.Equals(lastBlacklistDlc) ? "" : ",")}"); installForm?.UpdateUser($"Added blacklist DLC to UplayR1Unlocker.jsonc with appid {selectionDlc.Id} ({selectionDlc.Name})", LogTextBox.Action,
installForm?.UpdateUser($"Added blacklist DLC to UplayR1Unlocker.jsonc with appid {dlcId} ({dlcName})", LogTextBox.Action, false); false);
} }
writer.WriteLine(" ],"); writer.WriteLine(" ],");
} }
@ -109,7 +107,7 @@ internal static class UplayR1
} }
}); });
internal static async Task Install(string directory, ProgramSelection selection, InstallForm installForm = null, bool generateConfig = true) internal static async Task Install(string directory, Selection selection, InstallForm installForm = null, bool generateConfig = true)
=> await Task.Run(() => => await Task.Run(() =>
{ {
directory.GetUplayR1Components(out string api32, out string api32_o, out string api64, out string api64_o, out _, out _); directory.GetUplayR1Components(out string api32, out string api32_o, out string api64, out string api64_o, out _, out _);

View file

@ -24,20 +24,19 @@ internal static class UplayR2
log = directory + @"\UplayR2Unlocker.log"; log = directory + @"\UplayR2Unlocker.log";
} }
internal static void CheckConfig(string directory, ProgramSelection 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 _);
IEnumerable<KeyValuePair<string, (DlcType type, string name, string icon)>> blacklistDlc = selection.AllDlc.Except(selection.SelectedDlc); List<SelectionDLC> blacklistDlc = selection.DLC.Where(dlc => !dlc.Enabled).ToList();
foreach (KeyValuePair<string, (string _, SortedList<string, (DlcType type, string name, string icon)> extraDlc)> pair in selection.ExtraSelectedDlc) foreach (Selection extraSelection in selection.ExtraSelections)
blacklistDlc = blacklistDlc.Except(pair.Value.extraDlc); blacklistDlc.AddRange(extraSelection.DLC.Where(dlc => !dlc.Enabled));
blacklistDlc = blacklistDlc.ToList(); if (blacklistDlc.Count > 0)
if (blacklistDlc.Any())
{ {
/*if (installForm is not null) /*if (installForm is not null)
installForm.UpdateUser("Generating Uplay R2 Unlocker configuration for " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation);*/ installForm.UpdateUser("Generating Uplay R2 Unlocker configuration for " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation);*/
config.CreateFile(true, installForm).Close(); config.CreateFile(true, installForm).Close();
StreamWriter writer = new(config, true, Encoding.UTF8); StreamWriter writer = new(config, true, Encoding.UTF8);
WriteConfig(writer, new(blacklistDlc.ToDictionary(pair => pair.Key, pair => pair.Value), PlatformIdComparer.String), installForm); WriteConfig(writer, new(blacklistDlc.ToDictionary(dlc => dlc.Id, dlc => dlc), PlatformIdComparer.String), installForm);
writer.Flush(); writer.Flush();
writer.Close(); writer.Close();
} }
@ -48,8 +47,7 @@ internal static class UplayR2
} }
} }
private static void WriteConfig(StreamWriter writer, SortedList<string, (DlcType type, string name, string icon)> blacklistDlc, private static void WriteConfig(TextWriter writer, SortedList<string, SelectionDLC> blacklistDlc, InstallForm installForm = null)
InstallForm installForm = null)
{ {
writer.WriteLine("{"); writer.WriteLine("{");
writer.WriteLine(" \"logging\": false,"); writer.WriteLine(" \"logging\": false,");
@ -60,13 +58,13 @@ internal static class UplayR2
if (blacklistDlc.Count > 0) if (blacklistDlc.Count > 0)
{ {
writer.WriteLine(" \"blacklist\": ["); writer.WriteLine(" \"blacklist\": [");
KeyValuePair<string, (DlcType type, string name, string icon)> lastBlacklistDlc = blacklistDlc.Last(); KeyValuePair<string, SelectionDLC> lastBlacklistDlc = blacklistDlc.Last();
foreach (KeyValuePair<string, (DlcType type, string name, string icon)> pair in blacklistDlc) foreach (KeyValuePair<string, SelectionDLC> pair in blacklistDlc)
{ {
string dlcId = pair.Key; SelectionDLC selectionDlc = pair.Value;
(_, string dlcName, _) = pair.Value; writer.WriteLine($" {selectionDlc.Id}{(pair.Equals(lastBlacklistDlc) ? "" : ",")}");
writer.WriteLine($" {dlcId}{(pair.Equals(lastBlacklistDlc) ? "" : ",")}"); installForm?.UpdateUser($"Added blacklist DLC to UplayR2Unlocker.jsonc with appid {selectionDlc.Id} ({selectionDlc.Name})", LogTextBox.Action,
installForm?.UpdateUser($"Added blacklist DLC to UplayR2Unlocker.jsonc with appid {dlcId} ({dlcName})", LogTextBox.Action, false); false);
} }
writer.WriteLine(" ],"); writer.WriteLine(" ],");
} }
@ -109,14 +107,13 @@ internal static class UplayR2
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, ProgramSelection selection, InstallForm installForm = null, bool generateConfig = true) internal static async Task Install(string directory, Selection selection, InstallForm installForm = null, bool generateConfig = true)
=> await Task.Run(() => => await Task.Run(() =>
{ {
directory.GetUplayR2Components(out string old_api32, out string old_api64, out string api32, out string api32_o, out string api64, directory.GetUplayR2Components(out string old_api32, out string old_api64, out string api32, out string api32_o, out string api64,

View file

@ -1,6 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using CreamInstaller.Components;
using CreamInstaller.Resources; using CreamInstaller.Resources;
using CreamInstaller.Utility; using CreamInstaller.Utility;
using static CreamInstaller.Resources.Resources; using static CreamInstaller.Resources.Resources;
@ -13,47 +12,31 @@ public enum Platform
Epic, Ubisoft Epic, Ubisoft
} }
public enum DlcType internal sealed class Selection
{
Steam, SteamHidden, EpicCatalogItem,
EpicEntitlement
}
internal sealed class ProgramSelection
{ {
internal const string DefaultKoaloaderProxy = "version"; internal const string DefaultKoaloaderProxy = "version";
internal static readonly List<ProgramSelection> All = new(); internal static readonly HashSet<Selection> All = new();
internal readonly HashSet<Selection> ExtraSelections = new();
internal readonly SortedList<string, (DlcType type, string name, string icon)> AllDlc = new(PlatformIdComparer.String); internal HashSet<string> DllDirectories;
internal readonly SortedList<string, (string name, SortedList<string, (DlcType type, string name, string icon)> dlc)> ExtraDlc = new();
internal readonly SortedList<string, (string name, SortedList<string, (DlcType type, string name, string icon)> dlc)> ExtraSelectedDlc = new();
internal readonly SortedList<string, (DlcType type, string name, string icon)> SelectedDlc = new(PlatformIdComparer.String);
internal List<string> DllDirectories;
internal bool Enabled; internal bool Enabled;
internal List<(string directory, BinaryType binaryType)> ExecutableDirectories; internal HashSet<(string directory, BinaryType binaryType)> ExecutableDirectories;
internal string IconUrl; internal string Icon;
internal string Id = "0"; internal string Id = "0";
internal bool Koaloader; internal bool Koaloader;
internal string KoaloaderProxy; internal string KoaloaderProxy;
internal string Name = "Program"; internal string Name = "Program";
internal Platform Platform; internal Platform Platform;
internal string Product;
internal string ProductUrl;
internal string Publisher; internal string Publisher;
internal string RootDirectory; internal string RootDirectory;
internal string SubIconUrl; internal string SubIcon;
internal string Website;
internal string WebsiteUrl; internal Selection() => All.Add(this);
internal ProgramSelection() => All.Add(this); internal IEnumerable<SelectionDLC> DLC => SelectionDLC.AllSafe.Where(dlc => dlc.Selection == this);
internal bool AreDllsLocked internal bool AreDllsLocked
{ {
@ -96,69 +79,41 @@ internal sealed class ProgramSelection
} }
} }
internal static List<ProgramSelection> AllSafe => All.ToList(); internal static List<Selection> AllSafe => All.ToList();
internal static List<ProgramSelection> AllEnabled => AllSafe.FindAll(s => s.Enabled); internal static List<Selection> AllEnabled => AllSafe.FindAll(s => s.Enabled);
private void Toggle(string dlcAppId, (DlcType type, string name, string icon) dlcApp, bool enabled) private void Remove()
{ {
if (enabled) _ = All.Remove(this);
SelectedDlc[dlcAppId] = dlcApp; foreach (SelectionDLC dlc in DLC)
else dlc.Selection = null;
_ = SelectedDlc.Remove(dlcAppId);
}
internal void ToggleDlc(string dlcId, bool enabled)
{
foreach ((string appId, (DlcType type, string name, string icon) dlcApp) in AllDlc)
{
if (appId != dlcId)
continue;
Toggle(appId, dlcApp, enabled);
break;
}
Enabled = SelectedDlc.Any() || ExtraSelectedDlc.Any();
}
private void Validate()
{
if (Program.IsGameBlocked(Name, RootDirectory))
{
_ = All.Remove(this);
return;
}
if (!RootDirectory.DirectoryExists())
{
_ = All.Remove(this);
return;
}
_ = DllDirectories.RemoveAll(directory => !directory.DirectoryExists());
if (!DllDirectories.Any())
_ = All.Remove(this);
} }
private void Validate(List<(Platform platform, string id, string name)> programsToScan) private void Validate(List<(Platform platform, string id, string name)> programsToScan)
{ {
if (programsToScan is null || !programsToScan.Any(p => p.platform == Platform && p.id == Id)) if (programsToScan is null || !programsToScan.Any(p => p.platform == Platform && p.id == Id))
{ {
_ = All.Remove(this); Remove();
return; return;
} }
Validate(); if (Program.IsGameBlocked(Name, RootDirectory))
{
Remove();
return;
}
if (!RootDirectory.DirectoryExists())
{
Remove();
return;
}
_ = DllDirectories.RemoveWhere(directory => !directory.DirectoryExists());
if (DllDirectories.Count < 1)
Remove();
} }
internal static void ValidateAll() => AllSafe.ForEach(selection => selection.Validate());
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)); => AllSafe.ForEach(selection => selection.Validate(programsToScan));
internal static ProgramSelection FromPlatformId(Platform platform, string gameId) => AllSafe.Find(s => s.Platform == platform && s.Id == gameId); internal static Selection FromPlatformId(Platform platform, string gameId) => AllSafe.Find(s => s.Platform == platform && s.Id == gameId);
internal static (string gameId, (DlcType type, string name, string icon) app)? GetDlcFromPlatformId(Platform platform, string dlcId)
{
foreach (ProgramSelection selection in AllSafe.Where(s => s.Platform == platform))
foreach (KeyValuePair<string, (DlcType type, string name, string icon)> pair in selection.AllDlc.Where(p => p.Key == dlcId))
return (selection.Id, pair.Value);
return null;
}
} }

View file

@ -0,0 +1,38 @@
using System.Collections.Generic;
using System.Linq;
namespace CreamInstaller;
public enum DLCType
{
Steam, SteamHidden, EpicCatalogItem,
EpicEntitlement
}
internal sealed class SelectionDLC
{
private static readonly HashSet<SelectionDLC> All = new();
internal bool Enabled;
internal string Icon;
internal string Id;
internal string Name;
internal string Product;
internal string Publisher;
private Selection selection;
internal DLCType Type;
internal Selection Selection
{
get => selection;
set
{
selection = value;
_ = value is null ? All.Remove(this) : All.Add(this);
}
}
internal static List<SelectionDLC> AllSafe => All.ToList();
internal static SelectionDLC FromPlatformId(Platform platform, string dlcId) => AllSafe.Find(dlc => dlc.Selection.Platform == platform && dlc.Id == dlcId);
}

View file

@ -1,5 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Concurrent;
using System.Drawing; using System.Drawing;
using System.Globalization; using System.Globalization;
using System.Net; using System.Net;
@ -15,7 +15,7 @@ internal static class HttpClientManager
{ {
internal static HttpClient HttpClient; internal static HttpClient HttpClient;
private static readonly Dictionary<string, string> HttpContentCache = new(); private static readonly ConcurrentDictionary<string, string> HttpContentCache = new();
internal static void Setup() internal static void Setup()
{ {

View file

@ -36,7 +36,7 @@ internal static class SafeIO
while (!Program.Canceled) while (!Program.Canceled)
try try
{ {
Directory.CreateDirectory(directoryPath); _ = Directory.CreateDirectory(directoryPath);
break; break;
} }
catch (Exception e) catch (Exception e)
@ -251,6 +251,11 @@ internal static class SafeIO
private static DialogResult IOWarnInternal(this string filePath, string message, Exception e, Form form = null) private static DialogResult IOWarnInternal(this string filePath, string message, Exception e, Form form = null)
{ {
using DialogForm dialogForm = new(form); using DialogForm dialogForm = new(form);
return dialogForm.Show(SystemIcons.Warning, message + ": " + filePath.BeautifyPath() + "\n\n" + e.FormatException(), "Retry", "OK"); string description = message + ": " + filePath.BeautifyPath() + "\n\n";
if (e is IOException && (e.HResult & 0x0000FFFF) == 225) // virus or potentially unwanted software
description += "Please resolve your anti-virus and press retry to continue . . . ";
else
description += e.FormatException();
return dialogForm.Show(SystemIcons.Warning, description, "Retry", "OK");
} }
} }