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 { }
}));