diff --git a/CreamInstaller/Epic/EpicStore.cs b/CreamInstaller/Epic/EpicStore.cs index d1c41b0..47b9169 100644 --- a/CreamInstaller/Epic/EpicStore.cs +++ b/CreamInstaller/Epic/EpicStore.cs @@ -13,24 +13,28 @@ namespace CreamInstaller.Epic; internal static class EpicStore { - internal static async Task> ParseDlcAppIds(string categoryNamespace) + internal static async Task> ParseDlcAppIds(string categoryNamespace) { - List<(string id, string name)> dlcIds = new(); - Response response = await QueryEpicGraphQL(categoryNamespace); + List<(string id, string name, string product, string icon)> dlcIds = new(); + Response response = await QueryGraphQL(categoryNamespace); if (response is null) return dlcIds; List elements = new(response.Data.Catalog.CatalogOffers.Elements); elements.AddRange(response.Data.Catalog.SearchStore.Elements); foreach (Element element in elements) { - (string id, string name) app = (element.Items[0].Id, element.Title); + string product = null; + try { product = element.CatalogNs.Mappings[0].PageSlug; } catch { } + string icon = null; + try { icon = element.KeyImages[0].Url; } catch { } + (string id, string name, string product, string icon) app = (element.Items[0].Id, element.Title, product, icon); if (!dlcIds.Contains(app)) dlcIds.Add(app); } return dlcIds; } - internal static async Task QueryEpicGraphQL(string categoryNamespace) + internal static async Task QueryGraphQL(string categoryNamespace) { string encoded = HttpUtility.UrlEncode(categoryNamespace); Request request = new(encoded); diff --git a/CreamInstaller/Epic/GraphQL/Response.cs b/CreamInstaller/Epic/GraphQL/Response.cs index aba32d9..4bcae56 100644 --- a/CreamInstaller/Epic/GraphQL/Response.cs +++ b/CreamInstaller/Epic/GraphQL/Response.cs @@ -1,5 +1,4 @@ -using System; - + using Newtonsoft.Json; namespace CreamInstaller.Epic.GraphQL; diff --git a/CreamInstaller/Forms/InstallForm.cs b/CreamInstaller/Forms/InstallForm.cs index 8545abd..7fe3b82 100644 --- a/CreamInstaller/Forms/InstallForm.cs +++ b/CreamInstaller/Forms/InstallForm.cs @@ -53,7 +53,7 @@ internal partial class InstallForm : CustomForm } } - internal static void WriteCreamConfiguration(StreamWriter writer, string steamAppId, string name, SortedList steamDlcApps, InstallForm installForm = null) + internal static void WriteCreamConfiguration(StreamWriter writer, string steamAppId, string name, SortedList steamDlcApps, InstallForm installForm = null) { writer.WriteLine($"; {name}"); writer.WriteLine("[steam]"); @@ -62,7 +62,7 @@ internal partial class InstallForm : CustomForm writer.WriteLine("[dlc]"); if (installForm is not null) installForm.UpdateUser($"Added game to cream_api.ini with appid {steamAppId} ({name})", InstallationLog.Resource, info: false); - foreach (KeyValuePair pair in steamDlcApps) + foreach (KeyValuePair pair in steamDlcApps) { string appId = pair.Key; (string dlcName, _) = pair.Value; @@ -140,13 +140,13 @@ internal partial class InstallForm : CustomForm StreamWriter writer = new(cApi, true, Encoding.UTF8); if (selection.Id != "ParadoxLauncher") WriteCreamConfiguration(writer, selection.Id, selection.Name, selection.SelectedDlc, installForm); - foreach (Tuple> extraAppDlc in selection.ExtraDlc) + foreach (Tuple> extraAppDlc in selection.ExtraDlc) WriteCreamConfiguration(writer, extraAppDlc.Item1, extraAppDlc.Item2, extraAppDlc.Item3, installForm); writer.Flush(); writer.Close(); }); - internal static void WriteScreamConfiguration(StreamWriter writer, SortedList steamDlcApps, InstallForm installForm = null) + internal static void WriteScreamConfiguration(StreamWriter writer, SortedList dlcApps, InstallForm installForm = null) { writer.WriteLine("{"); writer.WriteLine(" \"version\": 2,"); @@ -156,8 +156,8 @@ internal partial class InstallForm : CustomForm writer.WriteLine(" \"catalog_items\": {"); writer.WriteLine(" \"unlock_all\": false,"); writer.WriteLine(" \"override\": ["); - KeyValuePair last = steamDlcApps.Last(); - foreach (KeyValuePair pair in steamDlcApps) + KeyValuePair last = dlcApps.Last(); + foreach (KeyValuePair pair in dlcApps) { string id = pair.Key; (string name, _) = pair.Value; @@ -171,7 +171,7 @@ internal partial class InstallForm : CustomForm writer.WriteLine(" \"unlock_all\": false,"); writer.WriteLine(" \"auto_inject\": false,"); writer.WriteLine(" \"inject\": ["); - foreach (KeyValuePair pair in steamDlcApps) + foreach (KeyValuePair pair in dlcApps) { string id = pair.Key; (string name, _) = pair.Value; @@ -252,7 +252,7 @@ internal partial class InstallForm : CustomForm StreamWriter writer = new(sApi, true, Encoding.UTF8); if (selection.Id != "ParadoxLauncher") WriteScreamConfiguration(writer, selection.SelectedDlc, installForm); - foreach (Tuple> extraAppDlc in selection.ExtraDlc) + foreach (Tuple> extraAppDlc in selection.ExtraDlc) WriteScreamConfiguration(writer, extraAppDlc.Item3, installForm); writer.Flush(); writer.Close(); diff --git a/CreamInstaller/Forms/SelectForm.cs b/CreamInstaller/Forms/SelectForm.cs index 15e87c3..7651877 100644 --- a/CreamInstaller/Forms/SelectForm.cs +++ b/CreamInstaller/Forms/SelectForm.cs @@ -140,7 +140,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); await SteamStore.ParseDlcAppIds(appId, dlcIds); @@ -162,6 +162,8 @@ internal partial class SelectForm : CustomForm 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 (dlcIconStaticId is not null) + dlcIconStaticId = IconGrabber.SteamAppImagesPath + @$"\{dlcAppId}\{dlcIconStaticId}.jpg"; } if (Program.Canceled) return; if (!string.IsNullOrWhiteSpace(dlcName)) @@ -196,8 +198,9 @@ internal partial class SelectForm : CustomForm selection.DllDirectories = dllDirectories; selection.IsSteam = true; selection.AppInfo = appInfo; - selection.IconStaticID = appInfo?.Value?.GetChild("common")?.GetChild("icon")?.ToString(); - selection.ClientIconStaticID = appInfo?.Value?.GetChild("common")?.GetChild("clienticon")?.ToString(); + selection.ProductUrl = "https://store.steampowered.com/app/" + appId; + selection.IconUrl = IconGrabber.SteamAppImagesPath + @$"\{appId}\{appInfo?.Value?.GetChild("common")?.GetChild("icon")?.ToString()}.jpg"; + selection.ClientIconUrl = IconGrabber.SteamAppImagesPath + @$"\{appId}\{appInfo?.Value?.GetChild("common")?.GetChild("clienticon")?.ToString()}.ico"; if (Program.Canceled) return; Program.Invoke(selectionTreeView, delegate @@ -209,11 +212,11 @@ internal partial class SelectForm : CustomForm programNode.Checked = selection.Enabled; programNode.Remove(); selectionTreeView.Nodes.Add(programNode); - foreach (KeyValuePair pair in dlc) + foreach (KeyValuePair pair in dlc) { if (Program.Canceled || programNode is null) return; string appId = pair.Key; - (string name, string iconStaticId) dlcApp = pair.Value; + (string name, string icon) dlcApp = pair.Value; selection.AllDlc[appId] = dlcApp; if (allCheckBox.Checked) selection.SelectedDlc[appId] = dlcApp; TreeNode dlcNode = TreeNodes.Find(s => s.Name == appId) ?? new(); @@ -239,10 +242,11 @@ internal partial class SelectForm : CustomForm Dictionary> games = new(); foreach (Manifest manifest in epicGames) { - string id = manifest.CatalogNamespace; + string @namespace = manifest.CatalogNamespace; + string mainId = manifest.MainGameCatalogItemId; string name = manifest.DisplayName; string directory = manifest.InstallLocation; - ProgramSelection selection = ProgramSelection.FromId(id); + ProgramSelection selection = ProgramSelection.FromId(@namespace); if (Program.Canceled) return; if (Program.IsGameBlocked(name, directory)) continue; AddToRemainingGames(name); @@ -256,19 +260,19 @@ internal partial class SelectForm : CustomForm return; } if (Program.Canceled) return; - ConcurrentDictionary dlc = new(); + ConcurrentDictionary dlc = new(); List dlcTasks = new(); - List<(string id, string name)> dlcIds = await EpicStore.ParseDlcAppIds(id); + List<(string id, string name, string product, string icon)> dlcIds = await EpicStore.ParseDlcAppIds(@namespace); if (dlcIds.Count > 0) { - foreach ((string id, string name) in dlcIds) + foreach ((string id, string name, string product, string icon) in dlcIds) { if (Program.Canceled) return; AddToRemainingDLCs(id); Task task = Task.Run(() => { if (Program.Canceled) return; - dlc[id] = name; + dlc[id] = (name, product, icon); RemoveFromRemainingDLCs(id); progress.Report(++CompleteTasks); }); @@ -293,32 +297,37 @@ internal partial class SelectForm : CustomForm selection ??= new(); if (allCheckBox.Checked) selection.Enabled = true; selection.Usable = true; - selection.Id = id; + selection.Id = @namespace; selection.Name = name; selection.RootDirectory = directory; selection.DllDirectories = dllDirectories; + foreach (KeyValuePair pair in dlc) + if (pair.Value.name == selection.Name) + { + selection.ProductUrl = "https://www.epicgames.com/store/product/" + pair.Value.product; + selection.IconUrl = pair.Value.icon; + } if (Program.Canceled) return; Program.Invoke(selectionTreeView, delegate { if (Program.Canceled) return; - TreeNode programNode = TreeNodes.Find(s => s.Name == id) ?? new(); - programNode.Name = id; + TreeNode programNode = TreeNodes.Find(s => s.Name == @namespace) ?? new(); + programNode.Name = @namespace; programNode.Text = name; programNode.Checked = selection.Enabled; programNode.Remove(); selectionTreeView.Nodes.Add(programNode); - foreach (KeyValuePair pair in dlc) + foreach (KeyValuePair pair in dlc) { if (Program.Canceled || programNode is null) return; string dlcId = pair.Key; - string dlcName = pair.Value; - (string name, string iconStaticId) dlcApp = (dlcName, null); // temporary? + (string name, string icon) dlcApp = (pair.Value.name, pair.Value.icon); selection.AllDlc[dlcId] = dlcApp; if (allCheckBox.Checked) selection.SelectedDlc[dlcId] = dlcApp; TreeNode dlcNode = TreeNodes.Find(s => s.Name == dlcId) ?? new(); dlcNode.Name = dlcId; - dlcNode.Text = dlcName; + dlcNode.Text = dlcApp.name; dlcNode.Checked = selection.SelectedDlc.ContainsKey(dlcId); dlcNode.Remove(); programNode.Nodes.Add(dlcNode); @@ -523,12 +532,14 @@ internal partial class SelectForm : CustomForm images["SteamDB"] = await HttpClientManager.GetImageFromUrl("https://steamdb.info/favicon.ico"); images["Steam Store"] = await HttpClientManager.GetImageFromUrl("https://store.steampowered.com/favicon.ico"); images["Steam Community"] = await HttpClientManager.GetImageFromUrl("https://steamcommunity.com/favicon.ico"); + images["ScreamDB"] = await HttpClientManager.GetImageFromUrl("https://scream-db.web.app/favicon.ico"); + images["Epic Games"] = await HttpClientManager.GetImageFromUrl("https://www.epicgames.com/favicon.ico"); }); Image Image(string identifier) => images.GetValueOrDefault(identifier, null); - void TrySetImageAsync(ToolStripMenuItem menuItem, string appId, string iconStaticId, bool client = false) => + void TrySetImageAsync(ToolStripMenuItem menuItem, string appId, string iconUrl, bool client = false) => Task.Run(async () => { - menuItem.Image = client ? await IconGrabber.GetSteamClientIcon(appId, iconStaticId) : await IconGrabber.GetSteamIcon(appId, iconStaticId); + menuItem.Image = await HttpClientManager.GetImageFromUrl(iconUrl); images[client ? "ClientIcon_" + appId : "Icon_" + appId] = menuItem.Image; }); selectionTreeView.NodeMouseClick += (sender, e) => @@ -537,7 +548,7 @@ internal partial class SelectForm : CustomForm TreeNode parentNode = node.Parent; string id = node.Name; ProgramSelection selection = ProgramSelection.FromId(id); - (string gameAppId, (string name, string iconStaticId) app)? dlc = null; + (string gameAppId, (string name, string icon) app)? dlc = null; if (selection is null) dlc = ProgramSelection.GetDlcFromId(id); if (e.Button == MouseButtons.Right && node.Bounds.Contains(e.Location)) { @@ -546,17 +557,17 @@ internal partial class SelectForm : CustomForm ToolStripMenuItem header = new(selection?.Name ?? node.Text, Image("Icon_" + id)); if (header.Image is null) { - string iconStaticId = dlc?.app.iconStaticId ?? selection?.IconStaticID; - if (iconStaticId is not null) - TrySetImageAsync(header, id, iconStaticId); + string icon = dlc?.app.icon ?? selection?.IconUrl; + if (icon is not null) + TrySetImageAsync(header, id, icon); else if (dlc is not null) { string gameAppId = dlc.Value.gameAppId; header.Image = Image("Icon_" + gameAppId); ProgramSelection gameSelection = ProgramSelection.FromId(gameAppId); - iconStaticId = gameSelection?.IconStaticID; - if (header.Image is null && iconStaticId is not null) - TrySetImageAsync(header, gameAppId, iconStaticId); + icon = gameSelection?.IconUrl; + if (header.Image is null && icon is not null) + TrySetImageAsync(header, gameAppId, icon); } } nodeContextMenu.Items.Add(header); @@ -683,19 +694,23 @@ internal partial class SelectForm : CustomForm nodeContextMenu.Items.Add(new ToolStripMenuItem("Open SteamDB", Image("SteamDB"), new EventHandler((sender, e) => Diagnostics.OpenUrlInInternetBrowser("https://steamdb.info/app/" + id)))); nodeContextMenu.Items.Add(new ToolStripMenuItem("Open Steam Store", Image("Steam Store"), - new EventHandler((sender, e) => Diagnostics.OpenUrlInInternetBrowser("https://store.steampowered.com/app/" + id)))); + new EventHandler((sender, e) => Diagnostics.OpenUrlInInternetBrowser(selection.ProductUrl)))); ToolStripMenuItem steamCommunity = new("Open Steam Community", Image("ClientIcon_" + id), new EventHandler((sender, e) => Diagnostics.OpenUrlInInternetBrowser("https://steamcommunity.com/app/" + id))); nodeContextMenu.Items.Add(steamCommunity); if (steamCommunity.Image is null) { steamCommunity.Image = Image("Steam Community"); - TrySetImageAsync(steamCommunity, id, selection.ClientIconStaticID, true); + TrySetImageAsync(steamCommunity, id, selection.ClientIconUrl, true); } } else { - // Epic Games links? + nodeContextMenu.Items.Add(new ToolStripSeparator()); + nodeContextMenu.Items.Add(new ToolStripMenuItem("Open ScreamDB", Image("ScreamDB"), + new EventHandler((sender, e) => Diagnostics.OpenUrlInInternetBrowser("https://scream-db.web.app/offers/" + id)))); + nodeContextMenu.Items.Add(new ToolStripMenuItem("Open Epic Games Store", Image("Epic Games"), + new EventHandler((sender, e) => Diagnostics.OpenUrlInInternetBrowser(selection.ProductUrl)))); } } nodeContextMenu.Show(selectionTreeView, e.Location); diff --git a/CreamInstaller/ProgramSelection.cs b/CreamInstaller/ProgramSelection.cs index f530e7b..655d199 100644 --- a/CreamInstaller/ProgramSelection.cs +++ b/CreamInstaller/ProgramSelection.cs @@ -15,8 +15,10 @@ internal class ProgramSelection internal string Id = "0"; internal string Name = "Program"; - internal string IconStaticID = null; - internal string ClientIconStaticID = null; + internal string ProductUrl = null; + + internal string IconUrl = null; + internal string ClientIconUrl = null; internal string RootDirectory = null; internal List DllDirectories = null; @@ -24,9 +26,9 @@ internal class ProgramSelection internal bool IsSteam = false; internal VProperty AppInfo = null; - internal readonly SortedList AllDlc = new(); - internal readonly SortedList SelectedDlc = new(); - internal readonly List>> ExtraDlc = new(); + internal readonly SortedList AllDlc = new(); + internal readonly SortedList SelectedDlc = new(); + internal readonly List>> ExtraDlc = new(); internal bool AreDllsLocked { @@ -53,7 +55,7 @@ internal class ProgramSelection } } - private void Toggle(string dlcAppId, (string name, string iconStaticId) dlcApp, bool enabled) + private void Toggle(string dlcAppId, (string name, string icon) dlcApp, bool enabled) { if (enabled) SelectedDlc[dlcAppId] = dlcApp; else SelectedDlc.Remove(dlcAppId); @@ -61,10 +63,10 @@ internal class ProgramSelection internal void ToggleDlc(string dlcAppId, bool enabled) { - foreach (KeyValuePair pair in AllDlc) + foreach (KeyValuePair pair in AllDlc) { string appId = pair.Key; - (string name, string iconStaticId) dlcApp = pair.Value; + (string name, string icon) dlcApp = pair.Value; if (appId == dlcAppId) { Toggle(appId, dlcApp, enabled); @@ -77,10 +79,10 @@ internal class ProgramSelection internal void ToggleAllDlc(bool enabled) { if (!enabled) SelectedDlc.Clear(); - else foreach (KeyValuePair pair in AllDlc) + else foreach (KeyValuePair pair in AllDlc) { string appId = pair.Key; - (string name, string iconStaticId) dlcApp = pair.Value; + (string name, string icon) dlcApp = pair.Value; Toggle(appId, dlcApp, enabled); } Enabled = SelectedDlc.Any(); @@ -116,10 +118,10 @@ internal class ProgramSelection internal static ProgramSelection FromId(string id) => AllUsable.Find(s => s.Id == id); - internal static (string gameAppId, (string name, string iconStaticId) app)? GetDlcFromId(string appId) + internal static (string gameAppId, (string name, string icon) app)? GetDlcFromId(string appId) { foreach (ProgramSelection selection in AllUsable) - foreach (KeyValuePair pair in selection.AllDlc) + foreach (KeyValuePair pair in selection.AllDlc) if (pair.Key == appId) return (selection.Id, pair.Value); return null; } diff --git a/CreamInstaller/Utility/IconGrabber.cs b/CreamInstaller/Utility/IconGrabber.cs index a5be27c..d3b1bc5 100644 --- a/CreamInstaller/Utility/IconGrabber.cs +++ b/CreamInstaller/Utility/IconGrabber.cs @@ -1,7 +1,6 @@ using System; using System.Drawing; using System.IO; -using System.Threading.Tasks; namespace CreamInstaller.Utility; @@ -13,9 +12,7 @@ internal static class IconGrabber return Icon.FromHandle(dialogIconBitmap.GetHicon()); } - private static readonly string SteamAppImagesPath = "https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/apps/"; - internal static async Task GetSteamIcon(string steamAppId, string iconStaticId) => await HttpClientManager.GetImageFromUrl(SteamAppImagesPath + $"/{steamAppId}/{iconStaticId}.jpg"); - internal static async Task GetSteamClientIcon(string steamAppId, string clientIconStaticId) => await HttpClientManager.GetImageFromUrl(SteamAppImagesPath + $"/{steamAppId}/{clientIconStaticId}.ico"); + internal static readonly string SteamAppImagesPath = "https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/apps/"; internal static Image GetFileIconImage(string path) => File.Exists(path) ? Icon.ExtractAssociatedIcon(path).ToBitmap() : null;