diff --git a/CreamInstaller/CreamInstaller.csproj b/CreamInstaller/CreamInstaller.csproj
index 6275e9f..6bdcedc 100644
--- a/CreamInstaller/CreamInstaller.csproj
+++ b/CreamInstaller/CreamInstaller.csproj
@@ -154,6 +154,7 @@
+
diff --git a/CreamInstaller/Forms/SelectForm.cs b/CreamInstaller/Forms/SelectForm.cs
index a0a03c6..a4b370e 100644
--- a/CreamInstaller/Forms/SelectForm.cs
+++ b/CreamInstaller/Forms/SelectForm.cs
@@ -201,10 +201,10 @@ internal sealed partial class SelectForm : CustomForm
if (Program.Canceled)
return;
- AppData appData = await SteamStore.QueryStoreAPI(appId);
+ StoreAppData storeAppData = await SteamStore.QueryStoreAPI(appId);
_ = Interlocked.Decrement(ref steamGamesToCheck);
- VProperty appInfo = await SteamCMD.GetAppInfo(appId, branch, buildId);
- if (appData is null && appInfo is null)
+ CmdAppData cmdAppData = await SteamCMD.GetAppInfo(appId, branch, buildId);
+ if (storeAppData is null && cmdAppData is null)
{
RemoveFromRemainingGames(name);
return;
@@ -213,13 +213,13 @@ internal sealed partial class SelectForm : CustomForm
if (Program.Canceled)
return;
ConcurrentDictionary dlc = new();
- List dlcTasks = new();
- HashSet dlcIds = new();
- if (appData is not null)
- foreach (string dlcId in await SteamStore.ParseDlcAppIds(appData))
+ List dlcTasks = [];
+ HashSet dlcIds = [];
+ if (storeAppData is not null)
+ foreach (string dlcId in await SteamStore.ParseDlcAppIds(storeAppData))
_ = dlcIds.Add(dlcId);
- if (appInfo is not null)
- foreach (string dlcId in await SteamCMD.ParseDlcAppIds(appInfo))
+ 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)
@@ -240,31 +240,27 @@ internal sealed partial class SelectForm : CustomForm
string dlcName = null;
string dlcIcon = null;
bool onSteamStore = false;
- AppData dlcAppData = await SteamStore.QueryStoreAPI(dlcAppId, true);
- if (dlcAppData is not null)
+ StoreAppData dlcStoreAppData = await SteamStore.QueryStoreAPI(dlcAppId, true);
+ if (dlcStoreAppData is not null)
{
- dlcName = dlcAppData.Name;
- dlcIcon = dlcAppData.HeaderImage;
+ dlcName = dlcStoreAppData.Name;
+ dlcIcon = dlcStoreAppData.HeaderImage;
onSteamStore = true;
- fullGameAppId = dlcAppData.FullGame?.AppId;
+ fullGameAppId = dlcStoreAppData.FullGame?.AppId;
}
else
{
- VProperty dlcAppInfo = await SteamCMD.GetAppInfo(dlcAppId);
- if (dlcAppInfo is not null)
+ CmdAppData dlcCmdAppData = await SteamCMD.GetAppInfo(dlcAppId);
+ if (dlcCmdAppData is not null)
{
- dlcName = dlcAppInfo.Value.GetChild("common")?.GetChild("name")?.ToString();
- string 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();
+ 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 = dlcAppInfo.Value.GetChild("common")?.GetChild("parent")
- ?.ToString();
+ fullGameAppId = dlcCmdAppData.Common?.Parent;
}
}
@@ -273,26 +269,23 @@ internal sealed partial class SelectForm : CustomForm
string fullGameName = null;
string fullGameIcon = null;
bool fullGameOnSteamStore = false;
- AppData fullGameAppData = await SteamStore.QueryStoreAPI(fullGameAppId, true);
- if (fullGameAppData is not null)
+ StoreAppData fullGameStoreAppData =
+ await SteamStore.QueryStoreAPI(fullGameAppId, true);
+ if (fullGameStoreAppData is not null)
{
- fullGameName = fullGameAppData.Name;
- fullGameIcon = fullGameAppData.HeaderImage;
+ fullGameName = fullGameStoreAppData.Name;
+ fullGameIcon = fullGameStoreAppData.HeaderImage;
fullGameOnSteamStore = true;
}
else
{
- VProperty fullGameAppInfo = await SteamCMD.GetAppInfo(fullGameAppId);
+ CmdAppData fullGameAppInfo = await SteamCMD.GetAppInfo(fullGameAppId);
if (fullGameAppInfo is not null)
{
- fullGameName = fullGameAppInfo.Value.GetChild("common")?.GetChild("name")
- ?.ToString();
- string fullGameIconStaticId = fullGameAppInfo.Value.GetChild("common")
- ?.GetChild("icon")?.ToString();
- fullGameIconStaticId ??= fullGameAppInfo.Value.GetChild("common")
- ?.GetChild("logo_small")?.ToString();
- fullGameIconStaticId ??= fullGameAppInfo.Value.GetChild("common")
- ?.GetChild("logo")?.ToString();
+ 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";
@@ -345,17 +338,15 @@ internal sealed partial class SelectForm : CustomForm
return;
}
- Selection selection = Selection.GetOrCreate(Platform.Steam, appId, appData?.Name ?? name,
+ 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}\{appInfo?.Value.GetChild("common")?.GetChild("icon")}.jpg";
- selection.SubIcon = appData?.HeaderImage ?? IconGrabber.SteamAppImagesPath
- + @$"\{appId}\{appInfo?.Value.GetChild("common")?.GetChild("clienticon")}.ico";
- selection.Publisher = appData?.Publishers[0] ??
- appInfo?.Value.GetChild("extended")?.GetChild("publisher")?.ToString();
- selection.Website = appData?.Website;
+ 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
diff --git a/CreamInstaller/Platforms/Steam/CmdAppDetails.cs b/CreamInstaller/Platforms/Steam/CmdAppDetails.cs
new file mode 100644
index 0000000..a9b9464
--- /dev/null
+++ b/CreamInstaller/Platforms/Steam/CmdAppDetails.cs
@@ -0,0 +1,53 @@
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace CreamInstaller.Platforms.Steam;
+
+public class CmdAppCommon
+{
+ [JsonProperty(PropertyName = "type")] public string Type { get; set; }
+
+ [JsonProperty(PropertyName = "name")] public string Name { get; set; }
+
+ [JsonProperty(PropertyName = "icon")] public string Icon { get; set; }
+
+ [JsonProperty(PropertyName = "clienticon")]
+ public string ClientIcon { get; set; }
+
+ [JsonProperty(PropertyName = "logo_small")]
+ public string LogoSmall { get; set; }
+
+ [JsonProperty(PropertyName = "logo")] public string Logo { set; get; }
+
+ [JsonProperty(PropertyName = "parent")]
+ public string Parent { set; get; }
+}
+
+public class CmdAppExtended
+{
+ [JsonProperty(PropertyName = "listofdlc")]
+ public string Dlc { get; set; }
+
+ [JsonProperty(PropertyName = "publisher")]
+ public string Publisher { get; set; }
+}
+
+public class CmdAppData
+{
+ [JsonProperty(PropertyName = "common")]
+ public CmdAppCommon Common { get; set; }
+
+ [JsonProperty(PropertyName = "depots")]
+ public Dictionary Depots { get; set; }
+
+ [JsonProperty(PropertyName = "extended")]
+ public CmdAppExtended Extended { get; set; }
+}
+
+public class CmdAppDetails
+{
+ [JsonProperty(PropertyName = "status")]
+ public string Status { get; set; }
+
+ [JsonProperty(PropertyName = "data")] public Dictionary Data { get; set; }
+}
\ No newline at end of file
diff --git a/CreamInstaller/Platforms/Steam/SteamCMD.cs b/CreamInstaller/Platforms/Steam/SteamCMD.cs
index c5559b2..7290d6c 100644
--- a/CreamInstaller/Platforms/Steam/SteamCMD.cs
+++ b/CreamInstaller/Platforms/Steam/SteamCMD.cs
@@ -10,6 +10,7 @@ using System.Threading;
using System.Threading.Tasks;
using CreamInstaller.Resources;
using CreamInstaller.Utility;
+using Gameloop.Vdf.JsonConverter;
using Gameloop.Vdf.Linq;
#if DEBUG
using CreamInstaller.Forms;
@@ -17,7 +18,7 @@ using CreamInstaller.Forms;
namespace CreamInstaller.Platforms.Steam;
-internal static class SteamCMD
+internal static partial class SteamCMD
{
private const int ProcessLimit = 20;
@@ -32,10 +33,6 @@ internal static class SteamCMD
private static readonly string DllPath = DirectoryPath + @"\steamclient.dll";
private static readonly string AppCachePath = DirectoryPath + @"\appcache";
- private static readonly string ConfigPath = DirectoryPath + @"\config";
- private static readonly string DumpsPath = DirectoryPath + @"\dumps";
- private static readonly string LogsPath = DirectoryPath + @"\logs";
- private static readonly string SteamAppsPath = DirectoryPath + @"\steamapps";
private static string DirectoryPath => ProgramData.DirectoryPath;
internal static string AppInfoPath => ProgramData.AppInfoPath;
@@ -179,22 +176,7 @@ internal static class SteamCMD
await Kill();
try
{
- if (ConfigPath.DirectoryExists())
- foreach (string file in ConfigPath.EnumerateDirectory("*.tmp"))
- file.DeleteFile();
- foreach (string file in DirectoryPath.EnumerateDirectory("*.old"))
- file.DeleteFile();
- foreach (string file in DirectoryPath.EnumerateDirectory("*.delete"))
- file.DeleteFile();
- foreach (string file in DirectoryPath.EnumerateDirectory("*.crash"))
- file.DeleteFile();
- foreach (string file in DirectoryPath.EnumerateDirectory("*.ntfs_transaction_failed"))
- file.DeleteFile();
- AppCachePath
- .DeleteDirectory(); // this is definitely needed, so SteamCMD gets the latest information for us
- DumpsPath.DeleteDirectory();
- LogsPath.DeleteDirectory();
- SteamAppsPath.DeleteDirectory(); // this is just a useless folder created from +app_update 4
+ AppCachePath.DeleteDirectory();
}
catch
{
@@ -202,7 +184,7 @@ internal static class SteamCMD
}
});
- internal static async Task GetAppInfo(string appId, string branch = "public", int buildId = 0)
+ internal static async Task GetAppInfo(string appId, string branch = "public", int buildId = 0)
{
int attempts = 0;
while (!Program.Canceled)
@@ -254,18 +236,49 @@ internal static class SteamCMD
continue;
}
- if (!appInfo.Value.Children().Any())
- return appInfo;
- VToken type = appInfo.Value.GetChild("common")?.GetChild("type");
- if (type is not null && type.ToString() != "Game")
- return appInfo;
- string buildid = appInfo.Value.GetChild("depots")?.GetChild("branches")?.GetChild(branch)
- ?.GetChild("buildid")?.ToString();
+ CmdAppData appData;
+ try
+ {
+ if (appInfo.ToJson().Value.ToObject() is not { } cmdAppData)
+ {
+ appUpdateFile.DeleteFile();
+#if DEBUG
+ DebugForm.Current.Log(
+ "SteamCMD query failed on attempt #" + attempts + " for " + appId + " (" + branch +
+ "): VDF-JSON conversion failed",
+ LogTextBox.Warning);
+#endif
+ continue;
+ }
+
+ appData = cmdAppData;
+ }
+ catch
+#if DEBUG
+ (Exception e)
+#endif
+ {
+ appUpdateFile.DeleteFile();
+#if DEBUG
+ DebugForm.Current.Log(
+ "SteamCMD query failed on attempt #" + attempts + " for " + appId + " (" + branch +
+ "): VDF-JSON conversion failed (" + e.Message + ")",
+ LogTextBox.Warning);
+#endif
+ continue;
+ }
+
+ string type = appData.Common?.Type;
+ if (type is not null && type != "Game")
+ return appData;
+ if (appData.Depots is null || !appData.Depots.TryGetValue("branches", out dynamic appBranch))
+ return appData;
+ string buildid = appBranch?[branch]?.buildid;
if (buildid is null && type is not null)
- return appInfo;
+ return appData;
if (type is not null && (!int.TryParse(buildid, out int gamebuildId) || gamebuildId >= buildId))
- return appInfo;
- HashSet dlcAppIds = await ParseDlcAppIds(appInfo);
+ return appData;
+ HashSet dlcAppIds = await ParseDlcAppIds(appData);
foreach (string dlcAppUpdateFile in dlcAppIds.Select(id => $@"{AppInfoPath}\{id}.vdf"))
dlcAppUpdateFile.DeleteFile();
appUpdateFile.DeleteFile();
@@ -279,30 +292,27 @@ internal static class SteamCMD
return null;
}
- internal static async Task> ParseDlcAppIds(VProperty appInfo)
+ internal static async Task> ParseDlcAppIds(CmdAppData appData)
=> await Task.Run(() =>
{
HashSet dlcIds = [];
- if (Program.Canceled || appInfo is null)
+ if (Program.Canceled || appData is null)
return dlcIds;
- VToken extended = appInfo.Value.GetChild("extended");
- if (extended is not null)
- foreach (VToken vToken in extended.Where(p => p is VProperty { Key: "listofdlc" }))
- {
- VProperty property = (VProperty)vToken;
- foreach (string id in property.Value.ToString().Split(","))
- if (int.TryParse(id, out int appId) && appId > 0)
- _ = dlcIds.Add("" + appId);
- }
- VToken depots = appInfo.Value.GetChild("depots");
+ CmdAppExtended extended = appData.Extended;
+ if (extended?.Dlc != null)
+ foreach (string id in extended.Dlc.Split(","))
+ if (int.TryParse(id, out int appId) && appId > 0)
+ _ = dlcIds.Add("" + appId);
+
+ Dictionary depots = appData.Depots;
if (depots is null)
return dlcIds;
- foreach (VToken vToken in depots.Where(
- p => p is VProperty property && int.TryParse(property.Key, out int _)))
+
+ foreach ((_, dynamic depot) in depots.Where(p => int.TryParse(p.Key, out _)))
{
- VProperty property = (VProperty)vToken;
- if (int.TryParse(property.Value.GetChild("dlcappid")?.ToString(), out int appId) && appId > 0)
+ string dlcAppId = depot.dlcappid;
+ if (dlcAppId is not null && int.TryParse(dlcAppId, out int appId) && appId > 0)
_ = dlcIds.Add("" + appId);
}
diff --git a/CreamInstaller/Platforms/Steam/SteamStore.cs b/CreamInstaller/Platforms/Steam/SteamStore.cs
index 2f00978..34914b5 100644
--- a/CreamInstaller/Platforms/Steam/SteamStore.cs
+++ b/CreamInstaller/Platforms/Steam/SteamStore.cs
@@ -18,20 +18,20 @@ internal static class SteamStore
private const int CooldownGame = 600;
private const int CooldownDlc = 1200;
- internal static async Task> ParseDlcAppIds(AppData appData)
+ internal static async Task> ParseDlcAppIds(StoreAppData storeAppData)
=> await Task.Run(() =>
{
HashSet dlcIds = new();
- if (appData.DLC is null)
+ if (storeAppData.DLC is null)
return dlcIds;
- foreach (string dlcId in from appId in appData.DLC
+ foreach (string dlcId in from appId in storeAppData.DLC
where appId > 0
select appId.ToString(CultureInfo.InvariantCulture))
_ = dlcIds.Add(dlcId);
return dlcIds;
});
- internal static async Task QueryStoreAPI(string appId, bool isDlc = false, int attempts = 0)
+ internal static async Task QueryStoreAPI(string appId, bool isDlc = false, int attempts = 0)
{
while (!Program.Canceled)
{
@@ -50,11 +50,12 @@ internal static class SteamStore
foreach (KeyValuePair app in apps)
try
{
- AppDetails appDetails = JsonConvert.DeserializeObject(app.Value.ToString());
- if (appDetails is not null)
+ StoreAppDetails storeAppDetails =
+ JsonConvert.DeserializeObject(app.Value.ToString());
+ if (storeAppDetails is not null)
{
- AppData data = appDetails.Data;
- if (!appDetails.Success)
+ StoreAppData data = storeAppDetails.Data;
+ if (!storeAppDetails.Success)
{
#if DEBUG
DebugForm.Current.Log(
@@ -123,21 +124,19 @@ internal static class SteamStore
+ ": Response deserialization null");
#endif
}
- else
- {
#if DEBUG
+ else
DebugForm.Current.Log(
"Steam store query failed on attempt #" + attempts + " for " + appId + (isDlc ? " (DLC)" : "") +
": Response null",
LogTextBox.Warning);
#endif
- }
}
if (cachedExists)
try
{
- return JsonConvert.DeserializeObject(cacheFile.ReadFile());
+ return JsonConvert.DeserializeObject(cacheFile.ReadFile());
}
catch
{
diff --git a/CreamInstaller/Platforms/Steam/AppDetails.cs b/CreamInstaller/Platforms/Steam/StoreAppDetails.cs
similarity index 84%
rename from CreamInstaller/Platforms/Steam/AppDetails.cs
rename to CreamInstaller/Platforms/Steam/StoreAppDetails.cs
index 114482f..903965a 100644
--- a/CreamInstaller/Platforms/Steam/AppDetails.cs
+++ b/CreamInstaller/Platforms/Steam/StoreAppDetails.cs
@@ -3,14 +3,14 @@ using Newtonsoft.Json;
namespace CreamInstaller.Platforms.Steam;
-public class AppFullGame
+public class StoreAppFullGame
{
[JsonProperty(PropertyName = "appid")] public string AppId { get; set; }
[JsonProperty(PropertyName = "name")] public string Name { get; set; }
}
-public class AppData
+public class StoreAppData
{
[JsonProperty(PropertyName = "type")] public string Type { get; set; }
@@ -20,7 +20,7 @@ public class AppData
public int SteamAppId { get; set; }
[JsonProperty(PropertyName = "fullgame")]
- public AppFullGame FullGame { get; set; }
+ public StoreAppFullGame FullGame { get; set; }
[JsonProperty(PropertyName = "dlc")] public List DLC { get; set; }
@@ -40,10 +40,10 @@ public class AppData
public List Packages { get; set; }
}
-public class AppDetails
+public class StoreAppDetails
{
[JsonProperty(PropertyName = "success")]
public bool Success { get; set; }
- [JsonProperty(PropertyName = "data")] public AppData Data { get; set; }
+ [JsonProperty(PropertyName = "data")] public StoreAppData Data { get; set; }
}
\ No newline at end of file
diff --git a/CreamInstaller/Utility/ProgramData.cs b/CreamInstaller/Utility/ProgramData.cs
index 496bddc..1e80c9a 100644
--- a/CreamInstaller/Utility/ProgramData.cs
+++ b/CreamInstaller/Utility/ProgramData.cs
@@ -111,7 +111,7 @@ internal static class ProgramData
// ignored
}
- return Enumerable.Empty<(Platform platform, string id)>();
+ return [];
}
internal static void WriteProgramChoices(IEnumerable<(Platform platform, string id)> choices)
@@ -144,7 +144,7 @@ internal static class ProgramData
// ignored
}
- return Enumerable.Empty<(Platform platform, string gameId, string dlcId)>();
+ return [];
}
internal static void WriteDlcChoices(List<(Platform platform, string gameId, string dlcId)> choices)
@@ -177,7 +177,7 @@ internal static class ProgramData
// ignored
}
- return Enumerable.Empty<(Platform platform, string id, string proxy, bool enabled)>();
+ return [];
}
internal static void WriteProxyChoices(
diff --git a/CreamInstaller/Utility/SafeIO.cs b/CreamInstaller/Utility/SafeIO.cs
index 2334b80..9b71845 100644
--- a/CreamInstaller/Utility/SafeIO.cs
+++ b/CreamInstaller/Utility/SafeIO.cs
@@ -75,7 +75,7 @@ internal static class SafeIO
Form form = null)
{
if (!directoryPath.DirectoryExists())
- return Enumerable.Empty();
+ return [];
while (!Program.Canceled)
try
{
@@ -92,7 +92,7 @@ internal static class SafeIO
break;
}
- return Enumerable.Empty();
+ return [];
}
internal static IEnumerable EnumerateSubdirectories(this string directoryPath, string directoryPattern,
@@ -100,7 +100,7 @@ internal static class SafeIO
bool crucial = false, Form form = null)
{
if (!directoryPath.DirectoryExists())
- return Enumerable.Empty();
+ return [];
while (!Program.Canceled)
try
{
@@ -117,7 +117,7 @@ internal static class SafeIO
break;
}
- return Enumerable.Empty();
+ return [];
}
internal static bool FileExists(this string filePath) => File.Exists(filePath);