diff --git a/CreamInstaller/Classes/ProgramSelection.cs b/CreamInstaller/Classes/ProgramSelection.cs index 0415de9..a8de90b 100644 --- a/CreamInstaller/Classes/ProgramSelection.cs +++ b/CreamInstaller/Classes/ProgramSelection.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.IO; using System.Linq; +using System.Threading.Tasks; using Gameloop.Vdf.Linq; @@ -12,9 +14,42 @@ namespace CreamInstaller internal bool Enabled = false; internal bool Usable = true; - internal string Name; + 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 RootDirectory; - internal int SteamAppId; internal List SteamApiDllDirectories; internal VProperty AppInfo = null; @@ -31,10 +66,7 @@ namespace CreamInstaller { string api = directory + @"\steam_api.dll"; string api64 = directory + @"\steam_api64.dll"; - if (api.IsFilePathLocked() || api64.IsFilePathLocked()) - { - return true; - } + if (api.IsFilePathLocked() || api64.IsFilePathLocked()) return true; } return false; } @@ -42,43 +74,25 @@ namespace CreamInstaller private void Toggle(KeyValuePair dlcApp, bool enabled) { - if (enabled) - { - SelectedSteamDlc[dlcApp.Key] = dlcApp.Value; - } - else - { - SelectedSteamDlc.Remove(dlcApp.Key); - } + if (enabled) SelectedSteamDlc[dlcApp.Key] = dlcApp.Value; + else SelectedSteamDlc.Remove(dlcApp.Key); } internal void ToggleDlc(int dlcAppId, bool enabled) { foreach (KeyValuePair dlcApp in AllSteamDlc) - { if (dlcApp.Key == dlcAppId) { Toggle(dlcApp, enabled); break; } - } Enabled = SelectedSteamDlc.Any(); } internal void ToggleAllDlc(bool enabled) { - if (!enabled) - { - SelectedSteamDlc.Clear(); - } - else - { - foreach (KeyValuePair dlcApp in AllSteamDlc) - { - Toggle(dlcApp, enabled); - } - } - + if (!enabled) SelectedSteamDlc.Clear(); + else foreach (KeyValuePair dlcApp in AllSteamDlc) Toggle(dlcApp, enabled); Enabled = SelectedSteamDlc.Any(); } @@ -86,36 +100,37 @@ namespace CreamInstaller internal void Validate() { - SteamApiDllDirectories.RemoveAll(directory => !Directory.Exists(directory)); - if (!Directory.Exists(RootDirectory) || !SteamApiDllDirectories.Any()) + if (Program.BlockProtectedGames && Program.IsGameBlocked(Name, RootDirectory)) { All.Remove(this); + return; } + if (!Directory.Exists(RootDirectory)) + { + All.Remove(this); + return; + } + SteamApiDllDirectories.RemoveAll(directory => !Directory.Exists(directory)); + if (!SteamApiDllDirectories.Any()) All.Remove(this); } - internal static void ValidateAll() => All.ForEach(selection => selection.Validate()); + internal static void ValidateAll() => AllSafe.ForEach(selection => selection.Validate()); internal static List All => Program.ProgramSelections; - internal static List AllSafe => All.FindAll(s => s.Usable); + internal static List AllSafe => All.ToList(); - internal static List AllSafeEnabled => AllSafe.FindAll(s => s.Enabled); + internal static List AllUsable => All.FindAll(s => s.Usable); - internal static ProgramSelection FromAppId(int appId) => AllSafe.Find(s => s.SteamAppId == appId); + internal static List AllUsableEnabled => AllUsable.FindAll(s => s.Enabled); + + internal static ProgramSelection FromAppId(int appId) => AllUsable.Find(s => s.SteamAppId == appId); internal static KeyValuePair? GetDlcFromAppId(int appId) { - foreach (ProgramSelection selection in AllSafe) - { + foreach (ProgramSelection selection in AllUsable) foreach (KeyValuePair app in selection.AllSteamDlc) - { - if (app.Key == appId) - { - return app; - } - } - } - + if (app.Key == appId) return app; return null; } } diff --git a/CreamInstaller/CreamInstaller.csproj b/CreamInstaller/CreamInstaller.csproj index 8b379fb..cae69ba 100644 --- a/CreamInstaller/CreamInstaller.csproj +++ b/CreamInstaller/CreamInstaller.csproj @@ -5,7 +5,7 @@ true Resources\ini.ico true - 2.2.2.0 + 2.2.3.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. @@ -42,7 +42,7 @@ - + diff --git a/CreamInstaller/Forms/InstallForm.cs b/CreamInstaller/Forms/InstallForm.cs index 35579a7..ad7d64a 100644 --- a/CreamInstaller/Forms/InstallForm.cs +++ b/CreamInstaller/Forms/InstallForm.cs @@ -150,9 +150,10 @@ namespace CreamInstaller private async Task Operate() { - List programSelections = ProgramSelection.AllSafeEnabled; + List programSelections = ProgramSelection.AllUsableEnabled; OperationsCount = programSelections.Count; CompleteOperationsCount = 0; + List disabledSelections = new(); foreach (ProgramSelection selection in programSelections) { if (!Program.IsProgramRunningDialog(this, selection)) throw new OperationCanceledException(); @@ -161,6 +162,7 @@ namespace CreamInstaller await OperateFor(selection); await UpdateUser($"Operation succeeded for {selection.Name}.", InstallationLog.Success); selection.Enabled = false; + disabledSelections.Add(selection); } catch (Exception exception) { @@ -169,13 +171,14 @@ namespace CreamInstaller ++CompleteOperationsCount; } await Program.Cleanup(); - List FailedSelections = ProgramSelection.AllSafeEnabled; + List FailedSelections = ProgramSelection.AllUsableEnabled; if (FailedSelections.Any()) if (FailedSelections.Count == 1) throw new CustomMessageException($"Operation failed for {FailedSelections.First().Name}."); else throw new CustomMessageException($"Operation failed for {FailedSelections.Count} programs."); + foreach (ProgramSelection selection in disabledSelections) selection.Enabled = true; } - private readonly int ProgramCount = ProgramSelection.AllSafeEnabled.Count; + private readonly int ProgramCount = ProgramSelection.AllUsableEnabled.Count; private async void Start() { diff --git a/CreamInstaller/Forms/SelectForm.Designer.cs b/CreamInstaller/Forms/SelectForm.Designer.cs index f5eb2ef..e655751 100644 --- a/CreamInstaller/Forms/SelectForm.Designer.cs +++ b/CreamInstaller/Forms/SelectForm.Designer.cs @@ -31,6 +31,7 @@ namespace CreamInstaller /// private void InitializeComponent() { + this.components = new System.ComponentModel.Container(); this.installButton = new System.Windows.Forms.Button(); this.cancelButton = new System.Windows.Forms.Button(); this.label1 = new System.Windows.Forms.Label(); @@ -46,6 +47,7 @@ namespace CreamInstaller this.progressLabel = new System.Windows.Forms.Label(); this.scanButton = new System.Windows.Forms.Button(); this.uninstallButton = new System.Windows.Forms.Button(); + this.nodeContextMenu = new System.Windows.Forms.ContextMenuStrip(this.components); this.groupBox1.SuspendLayout(); this.flowLayoutPanel1.SuspendLayout(); this.flowLayoutPanel2.SuspendLayout(); @@ -166,7 +168,6 @@ namespace CreamInstaller this.selectionTreeView.CheckBoxes = true; this.selectionTreeView.Enabled = false; this.selectionTreeView.FullRowSelect = true; - this.selectionTreeView.HotTracking = true; this.selectionTreeView.Location = new System.Drawing.Point(6, 22); this.selectionTreeView.Name = "selectionTreeView"; this.selectionTreeView.Size = new System.Drawing.Size(548, 280); @@ -206,12 +207,12 @@ namespace CreamInstaller this.progressBar1.Size = new System.Drawing.Size(560, 23); this.progressBar1.TabIndex = 9; // - // label2 + // progressLabel // this.progressLabel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); this.progressLabel.Location = new System.Drawing.Point(12, 279); - this.progressLabel.Name = "label2"; + this.progressLabel.Name = "progressLabel"; this.progressLabel.Size = new System.Drawing.Size(760, 15); this.progressLabel.TabIndex = 10; this.progressLabel.Text = "Loading . . ."; @@ -242,6 +243,11 @@ namespace CreamInstaller this.uninstallButton.UseVisualStyleBackColor = true; this.uninstallButton.Click += new System.EventHandler(this.OnUninstall); // + // nodeContextMenu + // + this.nodeContextMenu.Name = "nodeContextMenu"; + this.nodeContextMenu.Size = new System.Drawing.Size(61, 4); + // // SelectForm // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); @@ -290,6 +296,7 @@ namespace CreamInstaller private FlowLayoutPanel flowLayoutPanel1; private FlowLayoutPanel flowLayoutPanel2; private Button uninstallButton; + private ContextMenuStrip nodeContextMenu; } } diff --git a/CreamInstaller/Forms/SelectForm.cs b/CreamInstaller/Forms/SelectForm.cs index 36f4c72..0f290f6 100644 --- a/CreamInstaller/Forms/SelectForm.cs +++ b/CreamInstaller/Forms/SelectForm.cs @@ -2,7 +2,6 @@ using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Diagnostics; using System.Drawing; using System.IO; using System.Linq; @@ -157,24 +156,9 @@ namespace CreamInstaller int buildId = program.Item4; string directory = program.Item5; ProgramSelection selection = ProgramSelection.FromAppId(appId); + if (selection is not null) selection.Validate(); if (Program.Canceled) return; - if (Program.BlockProtectedGames) - { - bool blockedGame = Program.ProtectedGameNames.Contains(name); - if (!Program.ProtectedGameDirectoryExceptions.Contains(name)) - foreach (string path in Program.ProtectedGameDirectories) - if (Directory.Exists(directory + path)) - blockedGame = true; - if (blockedGame) - { - if (selection is not null) - { - selection.Enabled = false; - selection.Usable = false; - } - continue; - } - } + if (Program.BlockProtectedGames && Program.IsGameBlocked(name, directory)) continue; RunningTasks.Add(Task.Run(async () => { if (Program.Canceled) return; @@ -215,11 +199,20 @@ namespace CreamInstaller selection ??= new(); selection.Usable = true; + selection.SteamAppId = appId; selection.Name = name; selection.RootDirectory = directory; - selection.SteamAppId = appId; 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?["common"]?["icon"]?.ToString(); + selection.ClientIconStaticID = appInfo?.Value?["common"]?["clienticon"]?.ToString(); + } + } if (allCheckBox.Checked) selection.Enabled = true; foreach (Task task in dlcTasks) @@ -270,7 +263,7 @@ namespace CreamInstaller progress.Report(RunningTasks.Count); } - private async void OnLoad(bool validating = false) + private async void OnLoad() { retry: try @@ -300,16 +293,15 @@ namespace CreamInstaller if (_progress < 0) maxProgress = -_progress; else curProgress = _progress; int p = Math.Max(Math.Min((int)((float)(curProgress / (float)maxProgress) * 100), 100), 0); - if (validating) progressLabel.Text = $"Validating . . . {p}% ({curProgress}/{maxProgress})"; - else if (setup) progressLabel.Text = $"Setting up SteamCMD . . . {p}% ({curProgress}/{maxProgress})"; - else progressLabel.Text = $"Gathering and caching your applicable games and their DLCs . . . {p}% ({curProgress}/{maxProgress})"; + progressLabel.Text = setup ? $"Setting up SteamCMD . . . {p}% ({curProgress}/{maxProgress})" + : $"Gathering and caching your applicable games and their DLCs . . . {p}% ({curProgress}/{maxProgress})"; progressBar1.Value = p; }; iProgress.Report(-1660); // not exact, number varies int cur = 0; iProgress.Report(cur); - if (!validating) progressLabel.Text = "Setting up SteamCMD . . . "; + progressLabel.Text = "Setting up SteamCMD . . . "; if (!Directory.Exists(SteamCMD.DirectoryPath)) Directory.CreateDirectory(SteamCMD.DirectoryPath); FileSystemWatcher watcher = new(SteamCMD.DirectoryPath); @@ -321,14 +313,13 @@ namespace CreamInstaller watcher.Dispose(); setup = false; - if (!validating) progressLabel.Text = "Gathering and caching your applicable games and their DLCs . . . "; - - await GetCreamApiApplicablePrograms(iProgress); + progressLabel.Text = "Gathering and caching your applicable games and their DLCs . . . "; ProgramSelection.ValidateAll(); TreeNodes.ForEach(node => { - if (node.Parent is null && ProgramSelection.FromAppId(int.Parse(node.Name)) is null) node.Remove(); + if (!int.TryParse(node.Name, out int appId) || node.Parent is null && ProgramSelection.FromAppId(appId) is null) node.Remove(); }); + await GetCreamApiApplicablePrograms(iProgress); progressBar1.Value = 100; groupBox1.Size = new(groupBox1.Size.Width, groupBox1.Size.Height + 44); @@ -337,15 +328,12 @@ namespace CreamInstaller selectionTreeView.Enabled = ProgramSelection.All.Any(); allCheckBox.Enabled = selectionTreeView.Enabled; noneFoundLabel.Visible = !selectionTreeView.Enabled; - installButton.Enabled = ProgramSelection.AllSafeEnabled.Any(); + installButton.Enabled = ProgramSelection.AllUsableEnabled.Any(); uninstallButton.Enabled = installButton.Enabled; cancelButton.Enabled = false; scanButton.Enabled = true; blockedGamesCheckBox.Enabled = true; blockProtectedHelpButton.Enabled = true; - - progressLabel.Text = "Validating . . . "; - //if (!validating && !Program.Canceled) OnLoad(true); } catch (Exception e) { @@ -383,7 +371,7 @@ namespace CreamInstaller allCheckBox.CheckedChanged += OnAllCheckBoxChanged; } } - installButton.Enabled = ProgramSelection.AllSafeEnabled.Any(); + installButton.Enabled = ProgramSelection.AllUsableEnabled.Any(); uninstallButton.Enabled = installButton.Enabled; } @@ -391,9 +379,9 @@ namespace CreamInstaller { public int Compare(object a, object b) { - TreeNode A = a as TreeNode; - TreeNode B = b as TreeNode; - return int.Parse(A.Name) > int.Parse(B.Name) ? 1 : 0; + if (!int.TryParse((a as TreeNode).Name, out int A)) return 1; + if (!int.TryParse((b as TreeNode).Name, out int B)) return 0; + return A > B ? 1 : 0; } } @@ -401,16 +389,54 @@ namespace CreamInstaller { selectionTreeView.TreeViewNodeSorter = new TreeNodeSorter(); selectionTreeView.AfterCheck += OnTreeViewNodeCheckedChanged; + Dictionary images = new(); + Task.Run(async () => + { + images["File Explorer"] = Program.GetFileExplorerImage(); + images["SteamDB"] = await Program.GetImageFromUrl("https://steamdb.info/favicon.ico"); + images["Steam Store"] = await Program.GetImageFromUrl("https://store.steampowered.com/favicon.ico"); + images["Steam Community"] = await Program.GetImageFromUrl("https://steamcommunity.com/favicon.ico"); + }); + Image Image(string identifier) => images.GetValueOrDefault(identifier, null); selectionTreeView.NodeMouseClick += (sender, e) => { TreeNode node = e.Node; - string appId = node.Name; - if (e.Button == MouseButtons.Right && node.Bounds.Contains(e.Location) && appId != "0") - Process.Start(new ProcessStartInfo + TreeNode parentNode = node.Parent; + if (!int.TryParse(node.Name, out int appId)) return; + ProgramSelection selection = ProgramSelection.FromAppId(appId); + if (e.Button == MouseButtons.Right && node.Bounds.Contains(e.Location)) + { + selectionTreeView.SelectedNode = node; + nodeContextMenu.Items.Clear(); + if (selection is not null) { - FileName = "https://steamdb.info/app/" + appId, - UseShellExecute = true - }); + nodeContextMenu.Items.Add(new ToolStripMenuItem(selection.Name, selection.Icon)); + nodeContextMenu.Items.Add(new ToolStripSeparator()); + 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++) + { + string directory = selection.SteamApiDllDirectories[i]; + nodeContextMenu.Items.Add(new ToolStripMenuItem($"Open Steamworks Directory ({i + 1})", Image("File Explorer"), + 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 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)))); + } + nodeContextMenu.Show(selectionTreeView, e.Location); + } }; OnLoad(); } @@ -421,14 +447,14 @@ namespace CreamInstaller if (paradoxLauncher is not null) { paradoxLauncher.ExtraSteamAppIdDlc.Clear(); - foreach (ProgramSelection selection in ProgramSelection.AllSafeEnabled) + foreach (ProgramSelection selection in ProgramSelection.AllUsableEnabled) { if (selection.Name == paradoxLauncher.Name) continue; if (selection.AppInfo.Value["extended"]["publisher"].ToString() != "Paradox Interactive") continue; paradoxLauncher.ExtraSteamAppIdDlc.Add(new(selection.SteamAppId, selection.Name, selection.SelectedSteamDlc)); } if (!paradoxLauncher.ExtraSteamAppIdDlc.Any()) - foreach (ProgramSelection selection in ProgramSelection.AllSafe) + foreach (ProgramSelection selection in ProgramSelection.AllUsable) { if (selection.Name == paradoxLauncher.Name) continue; if (selection.AppInfo.Value["extended"]["publisher"].ToString() != "Paradox Interactive") continue; @@ -445,12 +471,10 @@ namespace CreamInstaller PopulateParadoxLauncherDlc(paradoxLauncher); if (!paradoxLauncher.ExtraSteamAppIdDlc.Any()) { - if (new DialogForm(form).Show(Program.ApplicationName, SystemIcons.Warning, - $"WARNING: There are no installed games with DLC that can be added to the Paradox Launcher!" + - "\n\nInstalling CreamAPI for the Paradox Launcher is pointless, since no DLC will be added to the configuration!", - "Ignore", "Cancel") == DialogResult.OK) - return false; - return true; + return new DialogForm(form).Show(Program.ApplicationName, SystemIcons.Warning, + $"WARNING: There are no installed games with DLC that can be added to the Paradox Launcher!" + + "\n\nInstalling CreamAPI for the Paradox Launcher is pointless, since no DLC will be added to the configuration!", + "Ignore", "Cancel") != DialogResult.OK; } } return false; @@ -460,7 +484,7 @@ namespace CreamInstaller { if (ProgramSelection.All.Any()) { - foreach (ProgramSelection selection in ProgramSelection.AllSafeEnabled) + foreach (ProgramSelection selection in ProgramSelection.AllUsableEnabled) if (!Program.IsProgramRunningDialog(this, selection)) return; if (ParadoxLauncherDlcDialog(this)) return; Hide(); @@ -483,10 +507,7 @@ namespace CreamInstaller private void OnCancel(object sender, EventArgs e) { progressLabel.Text = "Cancelling . . . "; - Task.Run(async () => - { - await Program.Cleanup(); - }); + Task.Run(async () => await Program.Cleanup()); } private void OnAllCheckBoxChanged(object sender, EventArgs e) diff --git a/CreamInstaller/Forms/SelectForm.resx b/CreamInstaller/Forms/SelectForm.resx index f298a7b..97370c1 100644 --- a/CreamInstaller/Forms/SelectForm.resx +++ b/CreamInstaller/Forms/SelectForm.resx @@ -57,4 +57,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + 17, 17 + \ No newline at end of file diff --git a/CreamInstaller/Program.cs b/CreamInstaller/Program.cs index b4b9663..c00d975 100644 --- a/CreamInstaller/Program.cs +++ b/CreamInstaller/Program.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.IO; +using System.Linq; +using System.Net.Http; using System.Reflection; using System.Threading; using System.Threading.Tasks; @@ -24,6 +26,15 @@ namespace CreamInstaller 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 bool IsGameBlocked(string name, string directory) + { + if (ProtectedGameNames.Contains(name)) return true; + if (!ProtectedGameDirectoryExceptions.Contains(name)) + foreach (string path in ProtectedGameDirectories) + if (Directory.Exists(directory + path)) return true; + return false; + } + [STAThread] private static void Main() { @@ -49,6 +60,34 @@ namespace CreamInstaller mutex.Close(); } + internal static async Task GetImageFromUrl(string url) + { + try + { + HttpClient httpClient = new(); + httpClient.DefaultRequestHeaders.Add("user-agent", "CreamInstaller"); + return new Bitmap(await httpClient.GetStreamAsync(url)); + } + catch { } + return null; + } + + internal static void OpenDirectoryInFileExplorer(string path) => Process.Start(new ProcessStartInfo + { + FileName = "explorer.exe", + Arguments = path + }); + + internal static void OpenUrlInInternetBrowser(string url) => Process.Start(new ProcessStartInfo + { + FileName = url, + UseShellExecute = true + }); + + internal static Image GetFileIconImage(string path) => File.Exists(path) ? Icon.ExtractAssociatedIcon(path).ToBitmap() : null; + + internal static Image GetFileExplorerImage() => GetFileIconImage(Environment.GetFolderPath(Environment.SpecialFolder.Windows) + @"\explorer.exe"); + internal static bool IsProgramRunningDialog(Form form, ProgramSelection selection) { if (selection.AreSteamApiDllsLocked)