From 93175e7089af8f2a4a5fc7a4624656996d34d53e Mon Sep 17 00:00:00 2001 From: pointfeev Date: Mon, 14 Feb 2022 18:41:10 -0500 Subject: [PATCH] v2.4.1.0 - Overhauled right-click context menu icon handling - The program will no longer get all game icons on start, and instead will load them when the game is right clicked - Added DLC icons to the right click menu - DLC icons in the right click menu will now display the game's icon if an icon doesn't exist for it --- CreamInstaller/Classes/ProgramSelection.cs | 70 +++++-------- CreamInstaller/CreamInstaller.csproj | 2 +- CreamInstaller/Forms/InstallForm.cs | 14 +-- CreamInstaller/Forms/SelectForm.cs | 109 ++++++++++++++------- CreamInstaller/Program.cs | 24 +++-- 5 files changed, 123 insertions(+), 96 deletions(-) diff --git a/CreamInstaller/Classes/ProgramSelection.cs b/CreamInstaller/Classes/ProgramSelection.cs index c327c69..0e27da5 100644 --- a/CreamInstaller/Classes/ProgramSelection.cs +++ b/CreamInstaller/Classes/ProgramSelection.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; -using System.Drawing; using System.IO; using System.Linq; -using System.Threading.Tasks; using Gameloop.Vdf.Linq; @@ -17,46 +15,17 @@ internal class ProgramSelection internal int SteamAppId = 0; internal string Name = "Program"; - internal Image Icon; - private string iconPath; - internal string IconPath - { - get => iconPath; - set - { - iconPath = value; - Task.Run(async () => Icon = await Program.GetImageFromUrl(iconPath)); - } - } - internal string IconStaticID - { - set => IconPath = $"https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/apps/{SteamAppId}/{value}.jpg"; - } - - internal Image ClientIcon; - private string clientIconPath; - internal string ClientIconPath - { - get => clientIconPath; - set - { - clientIconPath = value; - Task.Run(async () => ClientIcon = await Program.GetImageFromUrl(clientIconPath)); - } - } - internal string ClientIconStaticID - { - set => ClientIconPath = $"https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/apps/{SteamAppId}/{value}.ico"; - } + internal string IconStaticID = null; + internal string ClientIconStaticID = null; internal string RootDirectory; internal List SteamApiDllDirectories; internal VProperty AppInfo = null; - internal readonly SortedList AllSteamDlc = new(); - internal readonly SortedList SelectedSteamDlc = new(); - internal readonly List>> ExtraSteamAppIdDlc = new(); + internal readonly SortedList AllSteamDlc = new(); + internal readonly SortedList SelectedSteamDlc = new(); + internal readonly List>> ExtraSteamAppIdDlc = new(); internal bool AreSteamApiDllsLocked { @@ -72,27 +41,36 @@ internal class ProgramSelection } } - private void Toggle(KeyValuePair dlcApp, bool enabled) + private void Toggle(int dlcAppId, (string name, string iconStaticId) dlcApp, bool enabled) { - if (enabled) SelectedSteamDlc[dlcApp.Key] = dlcApp.Value; - else SelectedSteamDlc.Remove(dlcApp.Key); + if (enabled) SelectedSteamDlc[dlcAppId] = dlcApp; + else SelectedSteamDlc.Remove(dlcAppId); } internal void ToggleDlc(int dlcAppId, bool enabled) { - foreach (KeyValuePair dlcApp in AllSteamDlc) - if (dlcApp.Key == dlcAppId) + foreach (KeyValuePair pair in AllSteamDlc) + { + int appId = pair.Key; + (string name, string iconStaticId) dlcApp = pair.Value; + if (appId == dlcAppId) { - Toggle(dlcApp, enabled); + Toggle(appId, dlcApp, enabled); break; } + } Enabled = SelectedSteamDlc.Any(); } internal void ToggleAllDlc(bool enabled) { if (!enabled) SelectedSteamDlc.Clear(); - else foreach (KeyValuePair dlcApp in AllSteamDlc) Toggle(dlcApp, enabled); + else foreach (KeyValuePair pair in AllSteamDlc) + { + int appId = pair.Key; + (string name, string iconStaticId) dlcApp = pair.Value; + Toggle(appId, dlcApp, enabled); + } Enabled = SelectedSteamDlc.Any(); } @@ -126,11 +104,11 @@ internal class ProgramSelection internal static ProgramSelection FromAppId(int appId) => AllUsable.Find(s => s.SteamAppId == appId); - internal static KeyValuePair? GetDlcFromAppId(int appId) + internal static (int gameAppId, (string name, string iconStaticId) app)? GetDlcFromAppId(int appId) { foreach (ProgramSelection selection in AllUsable) - foreach (KeyValuePair app in selection.AllSteamDlc) - if (app.Key == appId) return app; + foreach (KeyValuePair pair in selection.AllSteamDlc) + if (pair.Key == appId) return (selection.SteamAppId, pair.Value); return null; } } diff --git a/CreamInstaller/CreamInstaller.csproj b/CreamInstaller/CreamInstaller.csproj index e5ed0b7..01a9992 100644 --- a/CreamInstaller/CreamInstaller.csproj +++ b/CreamInstaller/CreamInstaller.csproj @@ -5,7 +5,7 @@ True Resources\ini.ico true - 2.4.0.0 + 2.4.1.0 Resources\ini.ico Automatically generates and installs CreamAPI files for Steam games on the user's computer. It can also generate and install CreamAPI for the Paradox Launcher should the user select a Paradox Interactive game. diff --git a/CreamInstaller/Forms/InstallForm.cs b/CreamInstaller/Forms/InstallForm.cs index c402071..f56b29a 100644 --- a/CreamInstaller/Forms/InstallForm.cs +++ b/CreamInstaller/Forms/InstallForm.cs @@ -33,7 +33,7 @@ internal partial class InstallForm : CustomForm internal void UpdateProgress(int progress) { - int value = (int)((float)(CompleteOperationsCount / (float)OperationsCount) * 100) + (progress / OperationsCount); + int value = (int)((float)(CompleteOperationsCount / (float)OperationsCount) * 100) + progress / OperationsCount; if (value < userProgressBar.Value) return; userProgressBar.Value = value; } @@ -49,7 +49,7 @@ internal partial class InstallForm : CustomForm await Task.Run(() => Thread.Sleep(0)); // to keep the text box control from glitching } - internal async Task WriteConfiguration(StreamWriter writer, int steamAppId, string name, SortedList steamDlcApps) + internal async Task WriteConfiguration(StreamWriter writer, int steamAppId, string name, SortedList steamDlcApps) { writer.WriteLine(); writer.WriteLine($"; {name}"); @@ -58,10 +58,12 @@ internal partial class InstallForm : CustomForm writer.WriteLine(); writer.WriteLine("[dlc]"); await UpdateUser($"Added game to cream_api.ini with appid {steamAppId} ({name})", InstallationLog.Resource, info: false); - foreach (KeyValuePair dlcApp in steamDlcApps) + foreach (KeyValuePair pair in steamDlcApps) { - writer.WriteLine($"{dlcApp.Key} = {dlcApp.Value}"); - await UpdateUser($"Added DLC to cream_api.ini with appid {dlcApp.Key} ({dlcApp.Value})", InstallationLog.Resource, info: false); + int appId = pair.Key; + (string name, string iconStaticId) dlcApp = pair.Value; + writer.WriteLine($"{appId} = {dlcApp.name}"); + await UpdateUser($"Added DLC to cream_api.ini with appid {appId} ({dlcApp.name})", InstallationLog.Resource, info: false); } } @@ -134,7 +136,7 @@ internal partial class InstallForm : CustomForm StreamWriter writer = new(cApi, true, Encoding.UTF8); writer.WriteLine("; " + Application.CompanyName + " v" + Application.ProductVersion); if (selection.SteamAppId > 0) await WriteConfiguration(writer, selection.SteamAppId, selection.Name, selection.SelectedSteamDlc); - foreach (Tuple> extraAppDlc in selection.ExtraSteamAppIdDlc) + foreach (Tuple> extraAppDlc in selection.ExtraSteamAppIdDlc) await WriteConfiguration(writer, extraAppDlc.Item1, extraAppDlc.Item2, extraAppDlc.Item3); writer.Flush(); writer.Close(); diff --git a/CreamInstaller/Forms/SelectForm.cs b/CreamInstaller/Forms/SelectForm.cs index dfd9702..c34476a 100644 --- a/CreamInstaller/Forms/SelectForm.cs +++ b/CreamInstaller/Forms/SelectForm.cs @@ -178,9 +178,8 @@ internal partial class SelectForm : CustomForm { if (Program.Canceled) return; List> applicablePrograms = new(); - string launcherRootDirectory = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\Programs\\Paradox Interactive"; - if (Directory.Exists(launcherRootDirectory)) - applicablePrograms.Add(new(0, "Paradox Launcher", "", 0, launcherRootDirectory)); + if (Directory.Exists(Program.ParadoxLauncherDirectory)) + applicablePrograms.Add(new(0, "Paradox Launcher", "", 0, Program.ParadoxLauncherDirectory)); List gameLibraryDirectories = await GameLibraryDirectories(); foreach (string libraryDirectory in gameLibraryDirectories) { @@ -224,7 +223,7 @@ internal partial class SelectForm : CustomForm return; } if (Program.Canceled) return; - ConcurrentDictionary dlc = new(); + ConcurrentDictionary dlc = new(); List dlcTasks = new(); List dlcIds = await SteamCMD.ParseDlcAppIds(appInfo); if (dlcIds.Count > 0) @@ -237,15 +236,22 @@ internal partial class SelectForm : CustomForm { if (Program.Canceled) return; string dlcName = null; + string dlcIconStaticId = null; VProperty dlcAppInfo = await SteamCMD.GetAppInfo(id); - if (dlcAppInfo is not null) dlcName = dlcAppInfo.Value?.GetChild("common")?.GetChild("name")?.ToString(); + if (dlcAppInfo is not null) + { + dlcName = dlcAppInfo.Value?.GetChild("common")?.GetChild("name")?.ToString(); + dlcIconStaticId = dlcAppInfo.Value?.GetChild("common")?.GetChild("icon")?.ToString(); + dlcIconStaticId ??= dlcAppInfo.Value?.GetChild("common")?.GetChild("logo_small")?.ToString(); + dlcIconStaticId ??= dlcAppInfo.Value?.GetChild("common")?.GetChild("logo")?.ToString(); + } if (Program.Canceled) return; if (string.IsNullOrWhiteSpace(dlcName)) { RemoveFromRemainingDLCs(id.ToString()); return; } - dlc[id] = /*$"[{id}] " +*/ dlcName; + dlc[id] = /*$"[{id}] " +*/ (dlcName, dlcIconStaticId); RemoveFromRemainingDLCs(id.ToString()); progress.Report(++CompleteTasks); }); @@ -279,15 +285,8 @@ internal partial class SelectForm : CustomForm selection.RootDirectory = directory; selection.SteamApiDllDirectories = dllDirectories; selection.AppInfo = appInfo; - if (selection.Icon is null) - { - if (appId == 0) selection.Icon = Program.GetFileIconImage(directory + @"\launcher\bootstrapper-v2.exe"); - else - { - selection.IconStaticID = appInfo?.Value?.GetChild("common")?.GetChild("icon")?.ToString(); - selection.ClientIconStaticID = appInfo?.Value?.GetChild("common")?.GetChild("clienticon")?.ToString(); - } - } + selection.IconStaticID = appInfo?.Value?.GetChild("common")?.GetChild("icon")?.ToString(); + selection.ClientIconStaticID = appInfo?.Value?.GetChild("common")?.GetChild("clienticon")?.ToString(); if (allCheckBox.Checked) selection.Enabled = true; if (Program.Canceled) return; @@ -306,15 +305,17 @@ internal partial class SelectForm : CustomForm } else { - foreach (KeyValuePair dlcApp in dlc) + foreach (KeyValuePair pair in dlc) { if (Program.Canceled || programNode is null) return; - selection.AllSteamDlc[dlcApp.Key] = dlcApp.Value; - if (allCheckBox.Checked) selection.SelectedSteamDlc[dlcApp.Key] = dlcApp.Value; - TreeNode dlcNode = TreeNodes.Find(s => s.Name == "" + dlcApp.Key) ?? new(); - dlcNode.Name = "" + dlcApp.Key; - dlcNode.Text = dlcApp.Value; - dlcNode.Checked = selection.SelectedSteamDlc.Contains(dlcApp); + int appId = pair.Key; + (string name, string iconStaticId) dlcApp = pair.Value; + selection.AllSteamDlc[appId] = dlcApp; + if (allCheckBox.Checked) selection.SelectedSteamDlc[appId] = dlcApp; + TreeNode dlcNode = TreeNodes.Find(s => s.Name == "" + appId) ?? new(); + dlcNode.Name = appId.ToString(); + dlcNode.Text = dlcApp.name; + dlcNode.Checked = selection.SelectedSteamDlc.ContainsKey(appId); dlcNode.Remove(); programNode.Nodes.Add(dlcNode); } @@ -485,6 +486,14 @@ internal partial class SelectForm : CustomForm Dictionary images = new(); Task.Run(async () => { + if (Directory.Exists(Program.ParadoxLauncherDirectory)) + { + foreach (string file in Directory.GetFiles(Program.ParadoxLauncherDirectory, "*.exe")) + { + images["Paradox Launcher"] = Program.GetFileIconImage(file); + break; + } + } images["Notepad"] = Program.GetNotepadImage(); images["File Explorer"] = Program.GetFileExplorerImage(); images["SteamDB"] = await Program.GetImageFromUrl("https://steamdb.info/favicon.ico"); @@ -492,24 +501,51 @@ internal partial class SelectForm : CustomForm images["Steam Community"] = await Program.GetImageFromUrl("https://steamcommunity.com/favicon.ico"); }); Image Image(string identifier) => images.GetValueOrDefault(identifier, null); + void TrySetImageAsync(ToolStripMenuItem menuItem, int appId, string iconStaticId, bool client = false) => + Task.Run(async () => + { + menuItem.Image = client ? await Program.GetSteamClientIcon(appId, iconStaticId) : await Program.GetSteamIcon(appId, iconStaticId); + images[client ? "ClientIcon_" + appId : "Icon_" + appId] = menuItem.Image; + }); selectionTreeView.NodeMouseClick += (sender, e) => { TreeNode node = e.Node; TreeNode parentNode = node.Parent; if (!int.TryParse(node.Name, out int appId)) return; ProgramSelection selection = ProgramSelection.FromAppId(appId); + (int gameAppId, (string name, string iconStaticId) app)? dlc = null; + if (selection is null) dlc = ProgramSelection.GetDlcFromAppId(appId); if (e.Button == MouseButtons.Right && node.Bounds.Contains(e.Location)) { selectionTreeView.SelectedNode = node; nodeContextMenu.Items.Clear(); + ToolStripMenuItem header = new(selection?.Name ?? node.Text, Image(appId == 0 ? "Paradox Launcher" : "Icon_" + node.Name)); + if (header.Image is null) + { + string iconStaticId = dlc?.app.iconStaticId ?? selection?.IconStaticID; + if (iconStaticId is not null) + TrySetImageAsync(header, appId, iconStaticId); + else if (dlc is not null) + { + int gameAppId = dlc.Value.gameAppId; + header.Image = Image("Icon_" + gameAppId); + ProgramSelection gameSelection = ProgramSelection.FromAppId(gameAppId); + iconStaticId = gameSelection?.IconStaticID; + if (header.Image is null && iconStaticId is not null) + TrySetImageAsync(header, gameAppId, iconStaticId); + } + } + nodeContextMenu.Items.Add(header); + string appInfo = $@"{SteamCMD.AppInfoPath}\{appId}.vdf"; + if (appId != 0 && Directory.Exists(Directory.GetDirectoryRoot(appInfo)) && File.Exists(appInfo)) + { + nodeContextMenu.Items.Add(new ToolStripSeparator()); + nodeContextMenu.Items.Add(new ToolStripMenuItem("Open AppInfo", Image("Notepad"), + new EventHandler((sender, e) => Program.OpenFileInNotepad(appInfo)))); + } if (selection is not null) { - nodeContextMenu.Items.Add(new ToolStripMenuItem(selection.Name, selection.Icon)); nodeContextMenu.Items.Add(new ToolStripSeparator()); - string appInfo = $@"{SteamCMD.AppInfoPath}\{appId}.vdf"; - if (Directory.Exists(Directory.GetDirectoryRoot(appInfo)) && File.Exists(appInfo)) - nodeContextMenu.Items.Add(new ToolStripMenuItem("Open AppInfo", Image("Notepad"), - new EventHandler((sender, e) => Program.OpenFileInNotepad(appInfo)))); nodeContextMenu.Items.Add(new ToolStripMenuItem("Open Root Directory", Image("File Explorer"), new EventHandler((sender, e) => Program.OpenDirectoryInFileExplorer(selection.RootDirectory)))); for (int i = 0; i < selection.SteamApiDllDirectories.Count; i++) @@ -519,19 +555,24 @@ internal partial class SelectForm : CustomForm new EventHandler((sender, e) => Program.OpenDirectoryInFileExplorer(directory)))); } } - else - { - nodeContextMenu.Items.Add(new ToolStripMenuItem(node.Text)); - nodeContextMenu.Items.Add(new ToolStripSeparator()); - } if (appId != 0) { + nodeContextMenu.Items.Add(new ToolStripSeparator()); nodeContextMenu.Items.Add(new ToolStripMenuItem("Open SteamDB", Image("SteamDB"), new EventHandler((sender, e) => Program.OpenUrlInInternetBrowser("https://steamdb.info/app/" + appId)))); nodeContextMenu.Items.Add(new ToolStripMenuItem("Open Steam Store", Image("Steam Store"), new EventHandler((sender, e) => Program.OpenUrlInInternetBrowser("https://store.steampowered.com/app/" + appId)))); - if (selection is not null) nodeContextMenu.Items.Add(new ToolStripMenuItem("Open Steam Community", selection.ClientIcon ?? Image("Steam Community"), - new EventHandler((sender, e) => Program.OpenUrlInInternetBrowser("https://steamcommunity.com/app/" + appId)))); + if (selection is not null) + { + ToolStripMenuItem steamCommunity = new("Open Steam Community", Image("ClientIcon_" + node.Name), + new EventHandler((sender, e) => Program.OpenUrlInInternetBrowser("https://steamcommunity.com/app/" + appId))); + nodeContextMenu.Items.Add(steamCommunity); + if (steamCommunity.Image is null) + { + steamCommunity.Image = Image("Steam Community"); + TrySetImageAsync(steamCommunity, appId, selection.ClientIconStaticID, true); + } + } } nodeContextMenu.Show(selectionTreeView, e.Location); } diff --git a/CreamInstaller/Program.cs b/CreamInstaller/Program.cs index a437066..5532853 100644 --- a/CreamInstaller/Program.cs +++ b/CreamInstaller/Program.cs @@ -28,6 +28,8 @@ internal static class Program internal static readonly string[] ProtectedGameDirectories = { @"\EasyAntiCheat", @"\BattlEye" }; // DLL detections internal static readonly string[] ProtectedGameDirectoryExceptions = { "Arma 3" }; // Arma 3's BattlEye doesn't detect DLL changes? + internal static readonly string ParadoxLauncherDirectory = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"\Programs\Paradox Interactive\launcher"; + internal static bool IsGameBlocked(string name, string directory) { if (!BlockProtectedGames) return false; @@ -63,6 +65,10 @@ internal static class Program mutex.Close(); } + private static readonly string SteamAppImagesPath = "https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/apps/"; + internal static async Task GetSteamIcon(int steamAppId, string iconStaticId) => await GetImageFromUrl(SteamAppImagesPath + $"/{steamAppId}/{iconStaticId}.jpg"); + internal static async Task GetSteamClientIcon(int steamAppId, string clientIconStaticId) => await GetImageFromUrl(SteamAppImagesPath + $"/{steamAppId}/{clientIconStaticId}.ico"); + internal static async Task GetImageFromUrl(string url) { try @@ -121,9 +127,18 @@ internal static class Program return false; } + internal static void Invoke(this Control control, MethodInvoker methodInvoker) => control.Invoke(methodInvoker); + internal static SelectForm SelectForm; internal static InstallForm InstallForm; + internal static void InheritLocation(this Form form, Form fromForm) + { + int X = fromForm.Location.X + fromForm.Size.Width / 2 - form.Size.Width / 2; + int Y = fromForm.Location.Y + fromForm.Size.Height / 2 - form.Size.Height / 2; + form.Location = new(X, Y); + } + internal static List ProgramSelections = new(); internal static bool Canceled = false; @@ -133,14 +148,5 @@ internal static class Program await SteamCMD.Cleanup(); } - internal static void Invoke(this Control control, MethodInvoker methodInvoker) => control.Invoke(methodInvoker); - private static void OnApplicationExit(object s, EventArgs e) => Cleanup(); - - internal static void InheritLocation(this Form form, Form fromForm) - { - int X = fromForm.Location.X + fromForm.Size.Width / 2 - form.Size.Width / 2; - int Y = fromForm.Location.Y + fromForm.Size.Height / 2 - form.Size.Height / 2; - form.Location = new(X, Y); - } }