CreamInstaller/CreamInstaller/Forms/SelectForm.cs

1209 lines
No EOL
54 KiB
C#

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using CreamInstaller.Components;
using CreamInstaller.Platforms.Epic;
using CreamInstaller.Platforms.Epic.Heroic;
using CreamInstaller.Platforms.Paradox;
using CreamInstaller.Platforms.Steam;
using CreamInstaller.Platforms.Ubisoft;
using CreamInstaller.Resources;
using CreamInstaller.Utility;
using static CreamInstaller.Resources.Resources;
namespace CreamInstaller.Forms;
internal sealed partial class SelectForm : CustomForm
{
// TODO: fix the form display reset save load buttons for proxy
// consolidate all reset save load functionality into only 3 buttons instead of 6?
private const string HelpButtonListPrefix = "\n • ";
private static SelectForm current;
private readonly ConcurrentDictionary<string, string> remainingDLCs = new();
private readonly ConcurrentDictionary<string, string> remainingGames = new();
private List<(Platform platform, string id, string name)> programsToScan;
private SelectForm()
{
InitializeComponent();
selectionTreeView.TreeViewNodeSorter = PlatformIdComparer.NodeName;
Text = Program.ApplicationName;
}
internal static SelectForm Current
{
get
{
if (current is not null && (current.Disposing || current.IsDisposed))
current = null;
return current ??= new();
}
}
private static void UpdateRemaining(Label label, ConcurrentDictionary<string, string> list, string descriptor)
=> label.Text = list.IsEmpty
? ""
: $"Remaining {descriptor} ({list.Count}): " + string.Join(", ", list.Values).Replace("&", "&&");
private void UpdateRemainingGames() => UpdateRemaining(progressLabelGames, remainingGames, "games");
private void AddToRemainingGames(string gameName)
{
if (Program.Canceled)
return;
Invoke(delegate
{
if (Program.Canceled)
return;
remainingGames[gameName] = gameName;
UpdateRemainingGames();
});
}
private void RemoveFromRemainingGames(string gameName)
{
if (Program.Canceled)
return;
Invoke(delegate
{
if (Program.Canceled)
return;
_ = remainingGames.Remove(gameName, out _);
UpdateRemainingGames();
});
}
private void UpdateRemainingDLCs() => UpdateRemaining(progressLabelDLCs, remainingDLCs, "DLCs");
private void AddToRemainingDLCs(string dlcId)
{
if (Program.Canceled)
return;
Invoke(delegate
{
if (Program.Canceled)
return;
remainingDLCs[dlcId] = dlcId;
UpdateRemainingDLCs();
});
}
private void RemoveFromRemainingDLCs(string dlcId)
{
if (Program.Canceled)
return;
Invoke(delegate
{
if (Program.Canceled)
return;
_ = remainingDLCs.Remove(dlcId, out _);
UpdateRemainingDLCs();
});
}
private async Task GetApplicablePrograms(IProgress<int> progress, bool uninstallAll = false)
{
if (!uninstallAll && (programsToScan is null || programsToScan.Count < 1))
return;
int totalGameCount = 0;
int completeGameCount = 0;
void AddToRemainingGames(string gameName)
{
this.AddToRemainingGames(gameName);
progress.Report(-Interlocked.Increment(ref totalGameCount));
progress.Report(completeGameCount);
}
void RemoveFromRemainingGames(string gameName)
{
this.RemoveFromRemainingGames(gameName);
progress.Report(Interlocked.Increment(ref completeGameCount));
}
if (Program.Canceled)
return;
remainingGames.Clear(); // for display purposes only, otherwise ignorable
remainingDLCs.Clear(); // for display purposes only, otherwise ignorable
List<Task> appTasks = new();
if (uninstallAll || programsToScan.Any(c => c.platform is Platform.Paradox))
{
AddToRemainingGames("Paradox Launcher");
HashSet<string> dllDirectories =
await ParadoxLauncher.InstallPath.GetDllDirectoriesFromGameDirectory(Platform.Paradox);
if (dllDirectories is not null)
{
Selection selection = Selection.GetOrCreate(Platform.Paradox, "PL", "Paradox Launcher",
ParadoxLauncher.InstallPath, dllDirectories,
await ParadoxLauncher.InstallPath.GetExecutableDirectories(validFunc: path =>
!Path.GetFileName(path).Contains("bootstrapper")));
if (uninstallAll)
selection.Enabled = true;
else if (selection.TreeNode.TreeView is null)
_ = selectionTreeView.Nodes.Add(selection.TreeNode);
RemoveFromRemainingGames("Paradox Launcher");
}
}
int steamGamesToCheck;
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();
steamGamesToCheck = steamGames.Count;
foreach ((string appId, string name, string branch, int buildId, string gameDirectory) in steamGames)
{
if (Program.Canceled)
return;
if (!uninstallAll && (Program.IsGameBlocked(name, gameDirectory) ||
!programsToScan.Any(c => c.platform is Platform.Steam && c.id == appId)))
{
_ = Interlocked.Decrement(ref steamGamesToCheck);
continue;
}
AddToRemainingGames(name);
Task task = Task.Run(async () =>
{
if (Program.Canceled)
return;
HashSet<string> dllDirectories =
await gameDirectory.GetDllDirectoriesFromGameDirectory(Platform.Steam);
if (dllDirectories is null)
{
_ = Interlocked.Decrement(ref steamGamesToCheck);
RemoveFromRemainingGames(name);
return;
}
if (uninstallAll)
{
Selection bareSelection = Selection.GetOrCreate(Platform.Steam, appId, name, gameDirectory,
dllDirectories,
await gameDirectory.GetExecutableDirectories(true));
bareSelection.Enabled = true;
RemoveFromRemainingGames(name);
return;
}
if (Program.Canceled)
return;
StoreAppData storeAppData = await SteamStore.QueryStoreAPI(appId);
_ = Interlocked.Decrement(ref steamGamesToCheck);
CmdAppData cmdAppData = await SteamCMD.GetAppInfo(appId, branch, buildId);
if (storeAppData is null && cmdAppData is null)
{
RemoveFromRemainingGames(name);
return;
}
if (Program.Canceled)
return;
ConcurrentDictionary<SelectionDLC, byte> dlc = new();
List<Task> dlcTasks = [];
HashSet<string> dlcIds = [];
if (storeAppData is not null)
foreach (string dlcId in await SteamStore.ParseDlcAppIds(storeAppData))
_ = dlcIds.Add(dlcId);
if (cmdAppData is not null)
foreach (string dlcId in await SteamCMD.ParseDlcAppIds(cmdAppData))
_ = dlcIds.Add(dlcId);
if (dlcIds.Count > 0)
foreach (string dlcAppId in dlcIds)
{
if (Program.Canceled)
return;
AddToRemainingDLCs(dlcAppId);
Task task = Task.Run(async () =>
{
if (Program.Canceled)
return;
do // give games steam store api limit priority
Thread.Sleep(200);
while (!Program.Canceled && steamGamesToCheck > 0);
if (Program.Canceled)
return;
string fullGameAppId = null;
string dlcName = null;
string dlcIcon = null;
bool onSteamStore = false;
StoreAppData dlcStoreAppData = await SteamStore.QueryStoreAPI(dlcAppId, true);
if (dlcStoreAppData is not null)
{
dlcName = dlcStoreAppData.Name;
dlcIcon = dlcStoreAppData.HeaderImage;
onSteamStore = true;
fullGameAppId = dlcStoreAppData.FullGame?.AppId;
}
else
{
CmdAppData dlcCmdAppData = await SteamCMD.GetAppInfo(dlcAppId);
if (dlcCmdAppData is not null)
{
dlcName = dlcCmdAppData.Common?.Name;
string dlcIconStaticId = dlcCmdAppData.Common?.Icon;
dlcIconStaticId ??= dlcCmdAppData.Common?.LogoSmall;
dlcIconStaticId ??= dlcCmdAppData.Common?.Logo;
if (dlcIconStaticId is not null)
dlcIcon = IconGrabber.SteamAppImagesPath +
@$"\{dlcAppId}\{dlcIconStaticId}.jpg";
fullGameAppId = dlcCmdAppData.Common?.Parent;
}
}
if (fullGameAppId != null && fullGameAppId != appId)
{
string fullGameName = null;
string fullGameIcon = null;
bool fullGameOnSteamStore = false;
StoreAppData fullGameStoreAppData =
await SteamStore.QueryStoreAPI(fullGameAppId, true);
if (fullGameStoreAppData is not null)
{
fullGameName = fullGameStoreAppData.Name;
fullGameIcon = fullGameStoreAppData.HeaderImage;
fullGameOnSteamStore = true;
}
else
{
CmdAppData fullGameAppInfo = await SteamCMD.GetAppInfo(fullGameAppId);
if (fullGameAppInfo is not null)
{
fullGameName = fullGameAppInfo.Common?.Name;
string fullGameIconStaticId = fullGameAppInfo.Common?.Icon;
fullGameIconStaticId ??= fullGameAppInfo.Common?.LogoSmall;
fullGameIconStaticId ??= fullGameAppInfo.Common?.Logo;
if (fullGameIconStaticId is not null)
dlcIcon = IconGrabber.SteamAppImagesPath +
@$"\{fullGameAppId}\{fullGameIconStaticId}.jpg";
}
}
if (Program.Canceled)
return;
if (!string.IsNullOrWhiteSpace(fullGameName))
{
SelectionDLC fullGameDlc = SelectionDLC.GetOrCreate(
fullGameOnSteamStore ? DLCType.Steam : DLCType.SteamHidden, appId,
fullGameAppId, fullGameName);
fullGameDlc.Icon = fullGameIcon;
_ = dlc.TryAdd(fullGameDlc, default);
}
}
if (Program.Canceled)
return;
if (string.IsNullOrWhiteSpace(dlcName))
dlcName = "Unknown";
SelectionDLC _dlc = SelectionDLC.GetOrCreate(
onSteamStore ? DLCType.Steam : DLCType.SteamHidden, appId, dlcAppId, dlcName);
_dlc.Icon = dlcIcon;
_ = dlc.TryAdd(_dlc, default);
RemoveFromRemainingDLCs(dlcAppId);
});
dlcTasks.Add(task);
}
else
{
RemoveFromRemainingGames(name);
return;
}
if (Program.Canceled)
return;
foreach (Task task in dlcTasks)
{
if (Program.Canceled)
return;
await task;
}
steamGamesToCheck = 0;
if (dlc.IsEmpty)
{
RemoveFromRemainingGames(name);
return;
}
Selection selection = Selection.GetOrCreate(Platform.Steam, appId, storeAppData?.Name ?? name,
gameDirectory, dllDirectories,
await gameDirectory.GetExecutableDirectories(true));
selection.Product = "https://store.steampowered.com/app/" + appId;
selection.Icon = IconGrabber.SteamAppImagesPath + @$"\{appId}\{cmdAppData?.Common?.Icon}.jpg";
selection.SubIcon = storeAppData?.HeaderImage ?? IconGrabber.SteamAppImagesPath
+ @$"\{appId}\{cmdAppData?.Common?.ClientIcon}.ico";
selection.Publisher = storeAppData?.Publishers[0] ?? cmdAppData?.Extended?.Publisher;
selection.Website = storeAppData?.Website;
if (Program.Canceled)
return;
Invoke(delegate
{
if (Program.Canceled)
return;
if (selection.TreeNode.TreeView is null)
_ = selectionTreeView.Nodes.Add(selection.TreeNode);
foreach ((SelectionDLC dlc, _) in dlc)
{
if (Program.Canceled)
return;
dlc.Selection = selection;
}
});
if (Program.Canceled)
return;
RemoveFromRemainingGames(name);
});
appTasks.Add(task);
}
}
if (uninstallAll || programsToScan.Any(c => c.platform is Platform.Epic))
{
List<Manifest> epicGames = await EpicLibrary.GetGames();
foreach (Manifest manifest in epicGames)
{
string @namespace = manifest.CatalogNamespace;
string name = manifest.DisplayName;
string directory = manifest.InstallLocation;
if (Program.Canceled)
return;
if (!uninstallAll && (Program.IsGameBlocked(name, directory) ||
!programsToScan.Any(c => c.platform is Platform.Epic && c.id == @namespace)))
continue;
AddToRemainingGames(name);
Task task = Task.Run(async () =>
{
if (Program.Canceled)
return;
HashSet<string> dllDirectories = await directory.GetDllDirectoriesFromGameDirectory(Platform.Epic);
if (dllDirectories is null)
{
RemoveFromRemainingGames(name);
return;
}
if (uninstallAll)
{
Selection bareSelection = Selection.GetOrCreate(Platform.Epic, @namespace, name, directory,
dllDirectories,
await directory.GetExecutableDirectories(true));
bareSelection.Enabled = true;
RemoveFromRemainingGames(name);
return;
}
if (Program.Canceled)
return;
List<Task> dlcTasks = new();
ConcurrentDictionary<SelectionDLC, byte> catalogItems = new();
List<(string id, string name, string product, string icon, string developer)> catalogIds =
await EpicStore.QueryCatalog(@namespace);
if (catalogIds.Count > 0)
foreach ((string id, string name, string product, string icon, string developer) in catalogIds)
{
if (Program.Canceled)
return;
AddToRemainingDLCs(id);
Task task = Task.Run(() =>
{
if (Program.Canceled)
return;
SelectionDLC catalogItem = SelectionDLC.GetOrCreate(DLCType.Epic, @namespace, id, name);
catalogItem.Icon = icon;
catalogItem.Product = product;
catalogItem.Publisher = developer;
_ = catalogItems.TryAdd(catalogItem, default);
RemoveFromRemainingDLCs(id);
});
dlcTasks.Add(task);
}
if (Program.Canceled)
return;
foreach (Task task in dlcTasks)
{
if (Program.Canceled)
return;
await task;
}
if (catalogItems.IsEmpty)
{
RemoveFromRemainingGames(name);
return;
}
Selection selection = Selection.GetOrCreate(Platform.Epic, @namespace, name, directory,
dllDirectories,
await directory.GetExecutableDirectories(true));
foreach ((SelectionDLC dlc, _) in catalogItems.Where(dlc => dlc.Key.Name == selection.Name))
{
if (Program.Canceled)
return;
selection.Product = "https://www.epicgames.com/store/product/" + dlc.Product;
selection.Icon = dlc.Icon;
selection.Publisher = dlc.Publisher;
}
if (Program.Canceled)
return;
Invoke(delegate
{
if (Program.Canceled)
return;
if (selection.TreeNode.TreeView is null)
_ = selectionTreeView.Nodes.Add(selection.TreeNode);
if (catalogItems.IsEmpty)
return;
foreach ((SelectionDLC dlc, _) in catalogItems)
{
if (Program.Canceled)
return;
dlc.Selection = selection;
}
});
if (Program.Canceled)
return;
RemoveFromRemainingGames(name);
});
appTasks.Add(task);
}
}
if (uninstallAll || programsToScan.Any(c => c.platform is Platform.Ubisoft))
{
List<(string gameId, string name, string gameDirectory)> ubisoftGames = await UbisoftLibrary.GetGames();
foreach ((string gameId, string name, string gameDirectory) in ubisoftGames)
{
if (Program.Canceled)
return;
if (!uninstallAll && (Program.IsGameBlocked(name, gameDirectory) ||
!programsToScan.Any(c => c.platform is Platform.Ubisoft && c.id == gameId)))
continue;
AddToRemainingGames(name);
Task task = Task.Run(async () =>
{
if (Program.Canceled)
return;
HashSet<string> dllDirectories =
await gameDirectory.GetDllDirectoriesFromGameDirectory(Platform.Ubisoft);
if (dllDirectories is null)
{
RemoveFromRemainingGames(name);
return;
}
if (uninstallAll)
{
Selection bareSelection = Selection.GetOrCreate(Platform.Ubisoft, gameId, name, gameDirectory,
dllDirectories,
await gameDirectory.GetExecutableDirectories(true));
bareSelection.Enabled = true;
RemoveFromRemainingGames(name);
return;
}
if (Program.Canceled)
return;
Selection selection = Selection.GetOrCreate(Platform.Ubisoft, gameId, name, gameDirectory,
dllDirectories,
await gameDirectory.GetExecutableDirectories(true));
selection.Icon = IconGrabber.GetDomainFaviconUrl("store.ubi.com");
Invoke(delegate
{
if (Program.Canceled)
return;
if (selection.TreeNode.TreeView is null)
_ = selectionTreeView.Nodes.Add(selection.TreeNode);
});
if (Program.Canceled)
return;
RemoveFromRemainingGames(name);
});
appTasks.Add(task);
}
}
foreach (Task task in appTasks)
{
if (Program.Canceled)
return;
await task;
}
steamGamesToCheck = 0;
}
private async void OnLoad(bool forceScan = false, bool forceProvideChoices = false)
{
Program.Canceled = false;
blockedGamesCheckBox.Enabled = false;
blockProtectedHelpButton.Enabled = false;
cancelButton.Enabled = true;
scanButton.Enabled = false;
noneFoundLabel.Visible = false;
allCheckBox.Enabled = false;
proxyAllCheckBox.Enabled = false;
installButton.Enabled = false;
uninstallButton.Enabled = installButton.Enabled;
selectionTreeView.Enabled = false;
saveButton.Enabled = false;
loadButton.Enabled = false;
resetButton.Enabled = false;
progressLabel.Text = "Waiting for user to select which programs/games to scan . . .";
ShowProgressBar();
await ProgramData.Setup(this);
bool scan = forceScan;
if (!scan && (programsToScan is null || programsToScan.Count < 1 || forceProvideChoices))
{
List<(Platform platform, string id, string name, bool alreadySelected)> gameChoices = new();
if (ParadoxLauncher.InstallPath.DirectoryExists())
gameChoices.Add((Platform.Paradox, "PL", "Paradox Launcher",
programsToScan is not null &&
programsToScan.Any(p => p.platform is Platform.Paradox && p.id == "PL")));
if (SteamLibrary.InstallPath.DirectoryExists())
foreach ((string appId, string name, string _, int _, string _) in
(await SteamLibrary.GetGames()).Where(g
=> !Program.IsGameBlocked(g.name, g.gameDirectory)))
gameChoices.Add((Platform.Steam, appId, name,
programsToScan is not null &&
programsToScan.Any(p => p.platform is Platform.Steam && p.id == appId)));
if (EpicLibrary.EpicManifestsPath.DirectoryExists() || HeroicLibrary.HeroicLibraryPath.DirectoryExists())
gameChoices.AddRange((await EpicLibrary.GetGames())
.Where(m => !Program.IsGameBlocked(m.DisplayName, m.InstallLocation)).Select(manifest
=> (Platform.Epic, manifest.CatalogNamespace, manifest.DisplayName,
programsToScan is not null && programsToScan.Any(p =>
p.platform is Platform.Epic && p.id == manifest.CatalogNamespace))));
foreach ((string gameId, string name, string _) in (await UbisoftLibrary.GetGames()).Where(g =>
!Program.IsGameBlocked(g.name, g.gameDirectory)))
gameChoices.Add((Platform.Ubisoft, gameId, name,
programsToScan is not null &&
programsToScan.Any(p => p.platform is Platform.Ubisoft && p.id == gameId)));
if (gameChoices.Count > 0)
{
using SelectDialogForm form = new(this);
DialogResult selectResult = form.QueryUser("Choose which programs and/or games to scan:", gameChoices,
out List<(Platform platform, string id, string name)> choices);
if (selectResult == DialogResult.Abort)
{
int maxProgress = 0;
int curProgress = 0;
Progress<int> progress = new();
IProgress<int> iProgress = progress;
progress.ProgressChanged += (_, _progress) =>
{
if (Program.Canceled)
return;
if (_progress < 0 || _progress > maxProgress)
maxProgress = -_progress;
else
curProgress = _progress;
int p = Math.Max(Math.Min((int)((float)curProgress / maxProgress * 100), 100), 0);
progressLabel.Text = $"Quickly gathering games for uninstallation . . . {p}%";
progressBar.Value = p;
};
progressLabel.Text = "Quickly gathering games for uninstallation . . . ";
foreach (Selection selection in Selection.All.Keys)
selection.TreeNode.Remove();
await GetApplicablePrograms(iProgress, true);
if (!Program.Canceled)
OnUninstall(null, null);
Selection.All.Clear();
programsToScan = null;
}
else
scan = selectResult == DialogResult.OK && choices is not null && choices.Count > 0;
const string retry = "\n\nPress the \"Rescan\" button to re-choose.";
if (scan)
{
programsToScan = choices;
noneFoundLabel.Text = "None of the chosen programs nor games were applicable!" + retry;
}
else
noneFoundLabel.Text = "You didn't choose any programs nor games!" + retry;
}
else
noneFoundLabel.Text = "No applicable programs nor games were found on your computer!";
}
if (scan)
{
bool setup = true;
int maxProgress = 0;
int curProgress = 0;
Progress<int> progress = new();
IProgress<int> iProgress = progress;
progress.ProgressChanged += (_, _progress) =>
{
if (Program.Canceled)
return;
if (_progress < 0 || _progress > maxProgress)
maxProgress = -_progress;
else
curProgress = _progress;
int p = Math.Max(Math.Min((int)((float)curProgress / maxProgress * 100), 100), 0);
progressLabel.Text =
setup
? $"Setting up SteamCMD . . . {p}%"
: $"Gathering and caching your applicable games and their DLCs . . . {p}%";
progressBar.Value = p;
};
if (SteamLibrary.InstallPath.DirectoryExists() && programsToScan is not null &&
programsToScan.Any(c => c.platform is Platform.Steam))
{
progressLabel.Text = "Setting up SteamCMD . . . ";
if (!await SteamCMD.Setup(iProgress))
{
HideProgressBar();
OnLoad(forceScan, true);
return;
}
}
setup = false;
progressLabel.Text = "Gathering and caching your applicable games and their DLCs . . . ";
Selection.ValidateAll(programsToScan);
foreach (Selection selection in Selection.All.Keys)
selection.TreeNode.Remove();
await GetApplicablePrograms(iProgress);
await SteamCMD.Cleanup();
}
OnLoadSelections(null, null);
HideProgressBar();
selectionTreeView.Enabled = !Selection.All.IsEmpty;
allCheckBox.Enabled = selectionTreeView.Enabled;
proxyAllCheckBox.Enabled = selectionTreeView.Enabled;
noneFoundLabel.Visible = !selectionTreeView.Enabled;
installButton.Enabled = Selection.AllEnabled.Any();
uninstallButton.Enabled = installButton.Enabled;
saveButton.Enabled = CanSaveSelections();
loadButton.Enabled = CanLoadSelections();
resetButton.Enabled = CanResetSelections();
cancelButton.Enabled = false;
scanButton.Enabled = true;
blockedGamesCheckBox.Enabled = true;
blockProtectedHelpButton.Enabled = true;
}
private void OnTreeViewNodeCheckedChanged(object sender, TreeViewEventArgs e)
{
if (e.Action == TreeViewAction.Unknown)
return;
TreeNode node = e.Node;
if (node is null)
return;
SyncNodeAncestors(node);
SyncNodeDescendants(node);
allCheckBox.CheckedChanged -= OnAllCheckBoxChanged;
allCheckBox.Checked = EnumerateTreeNodes(selectionTreeView.Nodes)
.All(node => node.Text == "Unknown" || node.Checked);
allCheckBox.CheckedChanged += OnAllCheckBoxChanged;
installButton.Enabled = Selection.AllEnabled.Any();
uninstallButton.Enabled = installButton.Enabled;
if (sender is "OnLoadSelections" or "OnResetSelections")
return;
saveButton.Enabled = CanSaveSelections();
resetButton.Enabled = CanResetSelections();
}
private static void SyncNodeAncestors(TreeNode node)
{
TreeNode parentNode = node.Parent;
if (parentNode is null)
return;
parentNode.Checked = parentNode.Nodes.Cast<TreeNode>().Any(childNode => childNode.Checked);
SyncNodeAncestors(parentNode);
}
private static void SyncNodeDescendants(TreeNode node)
{
foreach (TreeNode childNode in node.Nodes)
{
if (childNode.Text == "Unknown")
continue;
childNode.Checked = node.Checked;
SyncNodeDescendants(childNode);
}
}
private static IEnumerable<TreeNode> EnumerateTreeNodes(TreeNodeCollection nodeCollection)
{
foreach (TreeNode rootNode in nodeCollection)
{
yield return rootNode;
foreach (TreeNode childNode in EnumerateTreeNodes(rootNode.Nodes))
yield return childNode;
}
}
private void ShowProgressBar()
{
progressBar.Value = 0;
progressLabelGames.Text = "Loading . . . ";
progressLabel.Visible = true;
progressLabelGames.Text = "";
progressLabelGames.Visible = true;
progressLabelDLCs.Text = "";
progressLabelDLCs.Visible = true;
progressBar.Visible = true;
programsGroupBox.Size = programsGroupBox.Size with
{
Height = programsGroupBox.Size.Height - progressLabel.Size.Height - progressLabelGames.Size.Height -
progressLabelDLCs.Size.Height - progressBar.Size.Height - 6
};
}
private void HideProgressBar()
{
progressBar.Value = 100;
progressLabel.Visible = false;
progressLabelGames.Visible = false;
progressLabelDLCs.Visible = false;
progressBar.Visible = false;
programsGroupBox.Size = programsGroupBox.Size with
{
Height = programsGroupBox.Size.Height + progressLabel.Size.Height + progressLabelGames.Size.Height +
progressLabelDLCs.Size.Height + progressBar.Size.Height + 6
};
}
internal void OnNodeRightClick(TreeNode node, Point location)
=> Invoke(() =>
{
ContextMenuStrip contextMenuStrip = new();
ToolStripItemCollection items = contextMenuStrip.Items;
string id = node.Name;
Platform platform = (Platform)node.Tag;
Selection selection = Selection.FromId(platform, id);
SelectionDLC dlc = null;
if (selection is null)
dlc = SelectionDLC.FromId((DLCType)node.Tag, node.Parent?.Name, id);
Selection dlcParentSelection = null;
if (dlc is not null)
dlcParentSelection = dlc.Selection;
if (selection is null && dlcParentSelection is null)
return;
ContextMenuItem header = id == "PL"
? new(node.Text, "Paradox Launcher")
: selection is not null
? new(node.Text, (id, selection.Icon))
: new(node.Text, (id, dlc.Icon), (id, dlcParentSelection.Icon));
_ = items.Add(header);
string appInfoVDF = $@"{SteamCMD.AppInfoPath}\{id}.vdf";
string appInfoJSON = $@"{SteamCMD.AppInfoPath}\{id}.json";
string cooldown = $@"{ProgramData.CooldownPath}\{id}.txt";
if (appInfoVDF.FileExists() || appInfoJSON.FileExists())
{
List<ContextMenuItem> queries = new();
if (appInfoJSON.FileExists())
{
string platformString = selection is null || selection.Platform is Platform.Steam
? "Steam Store "
: selection.Platform is Platform.Epic
? "Epic GraphQL "
: "";
queries.Add(new($"Open {platformString}Query", "Notepad",
(_, _) => Diagnostics.OpenFileInNotepad(appInfoJSON)));
}
if (appInfoVDF.FileExists())
queries.Add(new("Open SteamCMD Query", "Notepad",
(_, _) => Diagnostics.OpenFileInNotepad(appInfoVDF)));
if (queries.Count > 0)
{
_ = items.Add(new ToolStripSeparator());
foreach (ContextMenuItem query in queries)
_ = items.Add(query);
_ = items.Add(new ContextMenuItem("Refresh Queries", "Command Prompt", (_, _) =>
{
appInfoVDF.DeleteFile();
appInfoJSON.DeleteFile();
cooldown.DeleteFile();
selection?.Remove();
if (dlc is not null)
dlc.Selection = null;
OnLoad(true);
}));
}
}
if (selection is not null)
{
if (id == "PL")
{
_ = items.Add(new ToolStripSeparator());
async void EventHandler(object sender, EventArgs e)
{
_ = await ParadoxLauncher.Repair(this, selection);
Program.Canceled = false;
}
_ = items.Add(new ContextMenuItem("Repair", "Command Prompt", EventHandler));
}
_ = items.Add(new ToolStripSeparator());
_ = items.Add(new ContextMenuItem("Open Root Directory", "File Explorer",
(_, _) => Diagnostics.OpenDirectoryInFileExplorer(selection.RootDirectory)));
int executables = 0;
foreach ((string directory, BinaryType binaryType) in selection.ExecutableDirectories)
_ = items.Add(new ContextMenuItem(
$"Open Executable Directory #{++executables} ({(binaryType == BinaryType.BIT32 ? "32" : "64")}-bit)",
"File Explorer", (_, _) => Diagnostics.OpenDirectoryInFileExplorer(directory)));
HashSet<string> directories = selection.DllDirectories;
int steam = 0, epic = 0, r1 = 0, r2 = 0;
if (selection.Platform is Platform.Steam or Platform.Paradox)
foreach (string directory in directories)
{
directory.GetSmokeApiComponents(out string api32, out string api32_o, out string api64,
out string api64_o, out string old_config,
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()
|| config.FileExists() || old_log.FileExists() || log.FileExists() || cache.FileExists())
_ = items.Add(new ContextMenuItem($"Open Steamworks Directory #{++steam}", "File Explorer",
(_, _) => Diagnostics.OpenDirectoryInFileExplorer(directory)));
}
if (selection.Platform is Platform.Epic or Platform.Paradox)
foreach (string directory in directories)
{
directory.GetScreamApiComponents(out string api32, out string api32_o, out string api64,
out string api64_o, out string config,
out string log);
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",
(_, _) => Diagnostics.OpenDirectoryInFileExplorer(directory)));
}
if (selection.Platform is Platform.Ubisoft)
foreach (string directory in directories)
{
directory.GetUplayR1Components(out string api32, out string api32_o, out string api64,
out string api64_o, out string config,
out string log);
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",
(_, _) => 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,
out log);
if (old_api32.FileExists() || old_api64.FileExists() || api32.FileExists() ||
api32_o.FileExists() || api64.FileExists()
|| api64_o.FileExists() || config.FileExists() || log.FileExists())
_ = items.Add(new ContextMenuItem($"Open Uplay R2 Directory #{++r2}", "File Explorer",
(_, _) => Diagnostics.OpenDirectoryInFileExplorer(directory)));
}
}
if (id != "PL")
{
if (selection?.Platform is Platform.Steam || dlcParentSelection?.Platform is Platform.Steam)
{
_ = items.Add(new ToolStripSeparator());
_ = items.Add(new ContextMenuItem("Open SteamDB", "SteamDB",
(_, _) => Diagnostics.OpenUrlInInternetBrowser("https://steamdb.info/app/" + id)));
}
if (selection is not null)
switch (selection.Platform)
{
case Platform.Steam:
_ = items.Add(new ContextMenuItem("Open Steam Store", "Steam Store",
(_, _) => Diagnostics.OpenUrlInInternetBrowser(selection.Product)));
_ = items.Add(new ContextMenuItem("Open Steam Community", ("Sub_" + id, selection.SubIcon),
"Steam Community",
(_, _) => Diagnostics.OpenUrlInInternetBrowser("https://steamcommunity.com/app/" +
id)));
break;
case Platform.Epic:
_ = items.Add(new ToolStripSeparator());
_ = items.Add(new ContextMenuItem("Open ScreamDB", "ScreamDB",
(_, _) => Diagnostics.OpenUrlInInternetBrowser("https://scream-db.web.app/offers/" +
id)));
_ = items.Add(new ContextMenuItem("Open Epic Games Store", "Epic Games",
(_, _) => Diagnostics.OpenUrlInInternetBrowser(selection.Product)));
break;
case Platform.Ubisoft:
_ = items.Add(new ToolStripSeparator());
_ = items.Add(new ContextMenuItem("Open Ubisoft Store", "Ubisoft Store",
(_, _) => Diagnostics.OpenUrlInInternetBrowser(
"https://store.ubi.com/us/" +
selection.Name.Replace(" ", "-").ToLowerInvariant())));
break;
}
}
if (selection?.Website is not null)
_ = items.Add(new ContextMenuItem("Open Official Website",
("Web_" + id, IconGrabber.GetDomainFaviconUrl(selection.Website)),
(_, _) => Diagnostics.OpenUrlInInternetBrowser(selection.Website)));
contextMenuStrip.Show(selectionTreeView, location);
contextMenuStrip.Refresh();
});
private void OnLoad(object sender, EventArgs _)
{
retry:
try
{
HideProgressBar();
selectionTreeView.AfterCheck += OnTreeViewNodeCheckedChanged;
OnLoad(forceProvideChoices: true);
}
catch (Exception e)
{
if (e.HandleException(this))
goto retry;
Close();
}
}
private void OnAccept(bool uninstall = false)
{
if (Selection.All.IsEmpty || !uninstall && ParadoxLauncher.DlcDialog(this))
return;
Hide();
InstallForm form = new(uninstall);
form.InheritLocation(this);
form.FormClosing += (_, _) =>
{
if (form.Reselecting)
{
InheritLocation(form);
Show();
#if DEBUG
DebugForm.Current.Attach(this);
#endif
OnLoad();
}
else
Close();
};
form.Show();
Hide();
#if DEBUG
DebugForm.Current.Attach(form);
#endif
}
private void OnInstall(object sender, EventArgs e) => OnAccept();
private void OnUninstall(object sender, EventArgs e) => OnAccept(true);
private void OnScan(object sender, EventArgs e) => OnLoad(forceProvideChoices: true);
private void OnCancel(object sender, EventArgs e)
{
progressLabel.Text = "Cancelling . . . ";
Program.Cleanup();
}
private void OnAllCheckBoxChanged(object sender, EventArgs e)
{
bool shouldEnable = Selection.All.Keys.Any(s => !s.Enabled);
foreach (Selection selection in Selection.All.Keys.Where(s => s.Enabled != shouldEnable))
{
selection.Enabled = shouldEnable;
OnTreeViewNodeCheckedChanged("OnAllCheckBoxChanged", new(selection.TreeNode, TreeViewAction.ByMouse));
}
allCheckBox.CheckedChanged -= OnAllCheckBoxChanged;
allCheckBox.Checked = shouldEnable;
allCheckBox.CheckedChanged += OnAllCheckBoxChanged;
}
private void OnProxyAllCheckBoxChanged(object sender, EventArgs e)
{
bool shouldEnable = Selection.All.Keys.Any(selection => !selection.UseProxy);
foreach (Selection selection in Selection.All.Keys)
selection.UseProxy = shouldEnable;
selectionTreeView.Invalidate();
proxyAllCheckBox.CheckedChanged -= OnProxyAllCheckBoxChanged;
proxyAllCheckBox.Checked = shouldEnable;
proxyAllCheckBox.CheckedChanged += OnProxyAllCheckBoxChanged;
resetButton.Enabled = CanResetSelections();
saveButton.Enabled = CanSaveSelections();
}
private bool AreSelectionsDefault()
=> EnumerateTreeNodes(selectionTreeView.Nodes).All(node
=> node.Parent is null || node.Tag is not Platform and not DLCType ||
(node.Text == "Unknown" ? !node.Checked : node.Checked));
private static bool AreProxySelectionsDefault() => Selection.All.Keys.All(selection => !selection.UseProxy);
private bool CanSaveDlc() =>
installButton.Enabled && (ProgramData.ReadDlcChoices().Any() || !AreSelectionsDefault());
private static bool CanSaveProxy() =>
ProgramData.ReadProxyChoices().Any() || !AreProxySelectionsDefault();
private bool CanSaveSelections() => CanSaveDlc() || CanSaveProxy();
private void OnSaveSelections(object sender, EventArgs e)
{
List<(Platform platform, string gameId, string dlcId)> dlcChoices = ProgramData.ReadDlcChoices().ToList();
foreach (SelectionDLC dlc in SelectionDLC.All.Keys)
{
_ = dlcChoices.RemoveAll(n =>
n.platform == dlc.Selection.Platform && n.gameId == dlc.Selection.Id && n.dlcId == dlc.Id);
if (dlc.Name == "Unknown" ? dlc.Enabled : !dlc.Enabled)
dlcChoices.Add((dlc.Selection.Platform, dlc.Selection.Id, dlc.Id));
}
ProgramData.WriteDlcChoices(dlcChoices);
List<(Platform platform, string id, string proxy, bool enabled)> proxyChoices =
ProgramData.ReadProxyChoices().ToList();
foreach (Selection selection in Selection.All.Keys)
{
_ = proxyChoices.RemoveAll(c => c.platform == selection.Platform && c.id == selection.Id);
if (selection.UseProxy)
proxyChoices.Add((selection.Platform, selection.Id,
selection.Proxy == Selection.DefaultProxy ? null : selection.Proxy,
selection.UseProxy));
}
ProgramData.WriteProxyChoices(proxyChoices);
loadButton.Enabled = CanLoadSelections();
saveButton.Enabled = CanSaveSelections();
}
private static bool CanLoadDlc() => ProgramData.ReadDlcChoices().Any();
private static bool CanLoadProxy() => ProgramData.ReadProxyChoices().Any();
private bool CanLoadSelections() => CanLoadDlc() || CanLoadProxy();
private void OnLoadSelections(object sender, EventArgs e)
{
List<(Platform platform, string gameId, string dlcId)> dlcChoices = ProgramData.ReadDlcChoices().ToList();
foreach (SelectionDLC dlc in SelectionDLC.All.Keys)
{
dlc.Enabled = dlcChoices.Any(c =>
c.platform == dlc.Selection?.Platform && c.gameId == dlc.Selection?.Id && c.dlcId == dlc.Id)
? dlc.Name == "Unknown"
: dlc.Name != "Unknown";
OnTreeViewNodeCheckedChanged("OnLoadSelections", new(dlc.TreeNode, TreeViewAction.ByMouse));
}
List<(Platform platform, string id, string proxy, bool enabled)> proxyChoices =
ProgramData.ReadProxyChoices().ToList();
foreach (Selection selection in Selection.All.Keys)
if (proxyChoices.Any(c => c.platform == selection.Platform && c.id == selection.Id))
{
(Platform platform, string id, string proxy, bool enabled)
choice = proxyChoices.First(c => c.platform == selection.Platform && c.id == selection.Id);
(Platform platform, string id, string proxy, bool enabled) = choice;
string currentProxy = proxy;
if (proxy is not null && proxy.Contains('.')) // convert pre-v4.1.0.0 choices
proxy.GetProxyInfoFromIdentifier(out currentProxy, out _);
if (proxy != currentProxy && proxyChoices.Remove(choice)) // convert pre-v4.1.0.0 choices
proxyChoices.Add((platform, id, currentProxy, enabled));
if (currentProxy is null or Selection.DefaultProxy && !enabled)
_ = proxyChoices.RemoveAll(c => c.platform == platform && c.id == id);
else
{
selection.UseProxy = enabled;
selection.Proxy = currentProxy == Selection.DefaultProxy ? currentProxy : proxy;
}
}
else
{
selection.UseProxy = false;
selection.Proxy = null;
}
ProgramData.WriteProxyChoices(proxyChoices);
loadButton.Enabled = CanLoadSelections();
OnProxyChanged();
}
private bool CanResetDlc() => !AreSelectionsDefault();
private static bool CanResetProxy() => !AreProxySelectionsDefault();
private bool CanResetSelections() => CanResetDlc() || CanResetProxy();
private void OnResetSelections(object sender, EventArgs e)
{
foreach (SelectionDLC dlc in SelectionDLC.All.Keys)
{
dlc.Enabled = dlc.Name != "Unknown";
OnTreeViewNodeCheckedChanged("OnResetSelections", new(dlc.TreeNode, TreeViewAction.ByMouse));
}
foreach (Selection selection in Selection.All.Keys)
{
selection.UseProxy = false;
selection.Proxy = null;
}
OnProxyChanged();
}
internal void OnProxyChanged()
{
selectionTreeView.Invalidate();
saveButton.Enabled = CanSaveSelections();
resetButton.Enabled = CanResetSelections();
proxyAllCheckBox.CheckedChanged -= OnProxyAllCheckBoxChanged;
proxyAllCheckBox.Checked = Selection.All.Keys.All(selection => !selection.CanUseProxy || selection.UseProxy);
proxyAllCheckBox.CheckedChanged += OnProxyAllCheckBoxChanged;
}
private void OnBlockProtectedGamesCheckBoxChanged(object sender, EventArgs e)
{
Program.BlockProtectedGames = blockedGamesCheckBox.Checked;
OnLoad(forceProvideChoices: true);
}
private void OnBlockProtectedGamesHelpButtonClicked(object sender, EventArgs e)
{
StringBuilder blockedGames = new();
foreach (string name in Program.ProtectedGames)
_ = blockedGames.Append(HelpButtonListPrefix + name);
StringBuilder blockedDirectories = new();
foreach (string path in Program.ProtectedGameDirectories)
_ = blockedDirectories.Append(HelpButtonListPrefix + path);
StringBuilder blockedDirectoryExceptions = new();
foreach (string name in Program.ProtectedGameDirectoryExceptions)
_ = blockedDirectoryExceptions.Append(HelpButtonListPrefix + name);
using DialogForm form = new(this);
_ = form.Show(SystemIcons.Information,
"Blocks the program from caching and displaying games protected by anti-cheats."
+ "\nYou disable this option and install DLC unlockers to protected games at your own risk!" +
"\n\nBlocked games: "
+ (string.IsNullOrWhiteSpace(blockedGames.ToString()) ? "(none)" : blockedGames) +
"\n\nBlocked game sub-directories: "
+ (string.IsNullOrWhiteSpace(blockedDirectories.ToString()) ? "(none)" : blockedDirectories) +
"\n\nBlocked game sub-directory exceptions: "
+ (string.IsNullOrWhiteSpace(blockedDirectoryExceptions.ToString())
? "(none)"
: blockedDirectoryExceptions),
customFormText: "Block Protected Games");
}
private void OnSortCheckBoxChanged(object sender, EventArgs e)
=> selectionTreeView.TreeViewNodeSorter =
sortCheckBox.Checked ? PlatformIdComparer.NodeText : PlatformIdComparer.NodeName;
}