From 7cae7527876090375eb701d3e01bcc32b409ce4e Mon Sep 17 00:00:00 2001 From: pointfeev Date: Sat, 5 Mar 2022 20:36:43 -0500 Subject: [PATCH] v3.1.0.0 - Overhauled SteamCMD appinfo gathering, with substantially increased speed - Increased maximum SteamCMD processes from 20 to 30 - The gathering progress is now based on only games, so no more progress decreasing --- CreamInstaller/CreamInstaller.csproj | 2 +- CreamInstaller/SelectForm.cs | 28 +++++----- CreamInstaller/Steam/SteamCMD.cs | 78 +++++++++++++++++++++------- 3 files changed, 73 insertions(+), 35 deletions(-) diff --git a/CreamInstaller/CreamInstaller.csproj b/CreamInstaller/CreamInstaller.csproj index bd8773f..7281996 100644 --- a/CreamInstaller/CreamInstaller.csproj +++ b/CreamInstaller/CreamInstaller.csproj @@ -5,7 +5,7 @@ True Resources\ini.ico true - 3.0.2.1 + 3.1.0.0 Resources\ini.ico diff --git a/CreamInstaller/SelectForm.cs b/CreamInstaller/SelectForm.cs index 5685267..48812d2 100644 --- a/CreamInstaller/SelectForm.cs +++ b/CreamInstaller/SelectForm.cs @@ -81,12 +81,21 @@ internal partial class SelectForm : CustomForm }); } - internal readonly List RunningTasks = new(); private async Task GetApplicablePrograms(IProgress progress) { + int TotalGameCount = 0; + int CompleteGameCount = 0; + void AddToRemainingGames(string gameName) + { + this.AddToRemainingGames(gameName); + progress.Report(-++TotalGameCount); + } + void RemoveFromRemainingGames(string gameName) + { + this.RemoveFromRemainingGames(gameName); + progress.Report(++CompleteGameCount); + } if (Program.Canceled) return; - int CompleteTasks = 0; - RunningTasks.Clear(); // contains all running tasks including games AND their dlc RemainingGames.Clear(); // for display purposes only, otherwise ignorable RemainingDLCs.Clear(); // for display purposes only, otherwise ignorable List appTasks = new(); @@ -169,11 +178,8 @@ internal partial class SelectForm : CustomForm if (!string.IsNullOrWhiteSpace(dlcName)) dlc[dlcAppId] = (DlcType.Default, dlcName, dlcIconStaticId); RemoveFromRemainingDLCs(dlcAppId); - progress.Report(++CompleteTasks); }); dlcTasks.Add(task); - RunningTasks.Add(task); - progress.Report(-RunningTasks.Count); Thread.Sleep(10); // to reduce control & window freezing } } @@ -230,11 +236,8 @@ internal partial class SelectForm : CustomForm }); if (Program.Canceled) return; RemoveFromRemainingGames(name); - progress.Report(++CompleteTasks); }); appTasks.Add(task); - RunningTasks.Add(task); - progress.Report(-RunningTasks.Count); } } if (Directory.Exists(EpicLibrary.EpicAppDataPath)) @@ -273,11 +276,8 @@ internal partial class SelectForm : CustomForm if (Program.Canceled) return; entitlements[id] = (name, product, icon, developer); RemoveFromRemainingDLCs(id); - progress.Report(++CompleteTasks); }); dlcTasks.Add(task); - RunningTasks.Add(task); - progress.Report(-RunningTasks.Count); Thread.Sleep(10); // to reduce control & window freezing } } @@ -350,11 +350,8 @@ internal partial class SelectForm : CustomForm }); if (Program.Canceled) return; RemoveFromRemainingGames(name); - progress.Report(++CompleteTasks); }); appTasks.Add(task); - RunningTasks.Add(task); - progress.Report(-RunningTasks.Count); } } foreach (Task task in appTasks) @@ -362,7 +359,6 @@ internal partial class SelectForm : CustomForm if (Program.Canceled) return; await task; } - progress.Report(RunningTasks.Count); } private async void OnLoad() diff --git a/CreamInstaller/Steam/SteamCMD.cs b/CreamInstaller/Steam/SteamCMD.cs index d4d20aa..86694f9 100644 --- a/CreamInstaller/Steam/SteamCMD.cs +++ b/CreamInstaller/Steam/SteamCMD.cs @@ -18,15 +18,18 @@ namespace CreamInstaller.Steam; internal static class SteamCMD { - internal static readonly int ProcessLimit = 20; + internal static readonly int ProcessLimit = 30; internal static string DirectoryPath => ProgramData.DirectoryPath; internal static string AppInfoPath => ProgramData.AppInfoPath; internal static readonly string FilePath = DirectoryPath + @"\steamcmd.exe"; + private static readonly Dictionary AttemptCount = new(); // the more app_updates, the longer SteamCMD should wait for app_info_print + private static string GetArguments(string appId) => $@"@ShutdownOnFailedCommand 0 +force_install_dir {DirectoryPath} +login anonymous +app_info_print {appId} " + string.Concat(Enumerable.Repeat("+app_update 4 ", AttemptCount[appId])) + "+quit"; + private static readonly int[] locks = new int[ProcessLimit]; - internal static async Task Run(string command) => await Task.Run(() => + internal static async Task Run(string appId) => await Task.Run(() => { wait_for_lock: if (Program.Canceled) return ""; @@ -35,6 +38,13 @@ internal static class SteamCMD if (Program.Canceled) return ""; if (Interlocked.CompareExchange(ref locks[i], 1, 0) == 0) { + if (appId is not null) + { + if (AttemptCount.ContainsKey(appId)) + AttemptCount[appId]++; + else + AttemptCount[appId] = 0; + } if (Program.Canceled) return ""; List logs = new(); ProcessStartInfo processStartInfo = new() @@ -44,20 +54,55 @@ internal static class SteamCMD RedirectStandardInput = true, RedirectStandardError = true, UseShellExecute = false, - Arguments = command, + Arguments = appId is null ? "+quit" : GetArguments(appId), CreateNoWindow = true, StandardInputEncoding = Encoding.UTF8, StandardOutputEncoding = Encoding.UTF8, StandardErrorEncoding = Encoding.UTF8 }; - using Process process = Process.Start(processStartInfo); - process.OutputDataReceived += (object sender, DataReceivedEventArgs e) => logs.Add(e.Data); - process.BeginOutputReadLine(); - process.ErrorDataReceived += (object sender, DataReceivedEventArgs e) => logs.Add(e.Data); - process.BeginErrorReadLine(); - process.WaitForExit(); + Process process = Process.Start(processStartInfo); + string output = ""; + string appInfo = ""; + bool appInfoStarted = false; + DateTime lastOutput = DateTime.UtcNow; + while (true) + { + if (Program.Canceled) + { + process.Kill(true); + process.Close(); + break; + } + Thread.Sleep(0); + int c = process.StandardOutput.Read(); + if (c != -1) + { + lastOutput = DateTime.UtcNow; + char ch = (char)c; + if (ch == '{') appInfoStarted = true; + if (appInfoStarted) appInfo += ch; + else output += ch; + } + DateTime now = DateTime.UtcNow; + TimeSpan timeDiff = now - lastOutput; + if (timeDiff.TotalSeconds > 0.1) + { + process.Kill(true); + process.Close(); + if (output.Contains($"No app info for AppID {appId} found, requesting...")) + { + AttemptCount[appId]++; + processStartInfo.Arguments = GetArguments(appId); + process = Process.Start(processStartInfo); + appInfoStarted = false; + output = ""; + appInfo = ""; + } + else break; + } + } Interlocked.Decrement(ref locks[i]); - return string.Join("\r\n", logs); + return appInfo; } Thread.Sleep(0); } @@ -92,7 +137,7 @@ internal static class SteamCMD int cur = 0; progress.Report(cur); watcher.Changed += (sender, e) => progress.Report(++cur); - await Run($@"+quit"); + await Run(null); watcher.Dispose(); } } @@ -168,8 +213,7 @@ internal static class SteamCMD if (File.Exists(appUpdateFile)) output = File.ReadAllText(appUpdateFile, Encoding.UTF8); else { - // we add app_update 4 to allow the app_info_print to finish - output = await Run($@"@ShutdownOnFailedCommand 0 +force_install_dir {DirectoryPath} +login anonymous +app_info_print {appId} +app_update 4 +quit"); + output = await Run(appId); int openBracket = output.IndexOf("{"); int closeBracket = output.LastIndexOf("}"); if (openBracket != -1 && closeBracket != -1) @@ -178,17 +222,14 @@ internal static class SteamCMD output = output.Replace("ERROR! Failed to install app '4' (Invalid platform)", ""); File.WriteAllText(appUpdateFile, output, Encoding.UTF8); } + else goto restart; } if (Program.Canceled || output is null) return null; - if (!ValveDataFile.TryDeserialize(output, out VProperty appInfo)) + if (!ValveDataFile.TryDeserialize(output, out VProperty appInfo) || appInfo.Value is VValue) { File.Delete(appUpdateFile); - //new DialogForm(null).Show("GetAppInfo", SystemIcons.Information, "Deserialize exception:\n\n" + output, "OK"); goto restart; } - if (appInfo.Value is VValue) - //new DialogForm(null).Show("GetAppInfo", SystemIcons.Information, "VValue exception:\n\n" + output, "OK"); - goto restart; if (appInfo is null || appInfo.Value?.Children()?.ToList()?.Count == 0) return appInfo; VToken type = appInfo.Value?.GetChild("common")?.GetChild("type"); if (type is null || type.ToString() == "Game") @@ -243,6 +284,7 @@ internal static class SteamCMD { process.Kill(true); process.WaitForExit(); + process.Close(); } catch { } }));