- Overhauled and made OnTreeViewNodeCheckedChanged method more stable
- Made node sub-text display more specific to only game and dlc ids
- Fixed program validating tree nodes based on the pre-Epic-support id system
- Overhauled Epic Games entitlement querying and parsing
- Added back ScreamAPI entitlement selection, with preparations to add catalog item selection
This commit is contained in:
pointfeev 2022-03-04 18:10:34 -05:00
parent 40fe83c0b9
commit 8c8c007684
8 changed files with 241 additions and 140 deletions

View file

@ -34,7 +34,8 @@ internal class CustomTreeView : TreeView
Font subFont = new(font.FontFamily, font.SizeInPoints, FontStyle.Regular, font.Unit, font.GdiCharSet, font.GdiVerticalFont);
string subText = node.Name;
if (string.IsNullOrWhiteSpace(subText) || subText == "ParadoxLauncher" || subText[0] == 'v' && Version.TryParse(subText[1..], out _))
if (string.IsNullOrWhiteSpace(subText) || subText == "ParadoxLauncher"
|| ProgramSelection.FromId(subText) is null && ProgramSelection.GetDlcFromId(subText) is null)
return;
Size subSize = TextRenderer.MeasureText(graphics, subText, subFont);

View file

@ -5,7 +5,7 @@
<UseWindowsForms>True</UseWindowsForms>
<ApplicationIcon>Resources\ini.ico</ApplicationIcon>
<IncludeAllContentForSelfExtract>true</IncludeAllContentForSelfExtract>
<Version>3.0.1.1</Version>
<Version>3.0.2.0</Version>
<PackageIcon>Resources\ini.ico</PackageIcon>
<PackageIconUrl />
<Description />

View file

@ -15,18 +15,41 @@ namespace CreamInstaller.Epic;
internal static class EpicStore
{
internal static async Task<List<(string id, string name, string product, string icon, string developer)>> ParseDlcIds(string categoryNamespace)
// need a method to query catalog items
internal static async Task<List<(string id, string name, string product, string icon, string developer)>> QueryEntitlements(Manifest manifest)
{
// this method does not yet find ALL dlcs
string @namespace = manifest.CatalogNamespace;
string mainId = manifest.MainGameCatalogItemId;
List<(string id, string name, string product, string icon, string developer)> dlcIds = new();
Response response = await QueryGraphQL(categoryNamespace);
Response response = await QueryGraphQL(@namespace);
if (response is null) return dlcIds;
try { File.WriteAllText(ProgramData.AppInfoPath + @$"\{categoryNamespace}.json", JsonConvert.SerializeObject(response, Formatting.Indented)); } catch { }
List<Element> elements = new(response.Data.Catalog.CatalogOffers.Elements);
foreach (Element element in elements)
try { File.WriteAllText(ProgramData.AppInfoPath + @$"\{@namespace}.json", JsonConvert.SerializeObject(response, Formatting.Indented)); } catch { }
List<Element> storeElements = new(response.Data.Catalog.SearchStore.Elements);
foreach (Element element in storeElements)
{
string product = null;
try { product = element.CatalogNs.Mappings[0].PageSlug; } catch { }
string title = element.Title;
string product = (element.CatalogNs is not null && element.CatalogNs.Mappings.Any())
? element.CatalogNs.Mappings.First().PageSlug : null;
string icon = null;
for (int i = 0; i < element.KeyImages?.Length; i++)
{
KeyImage keyImage = element.KeyImages[i];
if (keyImage.Type == "DieselStoreFront")
{
icon = keyImage.Url;
break;
}
}
foreach (Item item in element.Items)
dlcIds.Populate(item.Id, title, product, icon, null, canOverwrite: element.Items.Length == 1);
}
List<Element> catalogElements = new(response.Data.Catalog.CatalogOffers.Elements);
foreach (Element element in catalogElements)
{
string title = element.Title;
string product = (element.CatalogNs is not null && element.CatalogNs.Mappings.Any())
? element.CatalogNs.Mappings.First().PageSlug : null;
string icon = null;
for (int i = 0; i < element.KeyImages?.Length; i++)
{
@ -38,16 +61,31 @@ internal static class EpicStore
}
}
foreach (Item item in element.Items)
{
(string id, string name, string product, string icon, string developer) app = (item.Id, element.Title, product, icon, item.Developer);
if (!dlcIds.Any(a => a.id == app.id))
dlcIds.Add(app);
}
dlcIds.Populate(item.Id, title, product, icon, item.Developer, canOverwrite: element.Items.Length == 1);
}
return dlcIds;
}
internal static async Task<Response> QueryGraphQL(string categoryNamespace)
private static void Populate(this List<(string id, string name, string product, string icon, string developer)> dlcIds, string id, string title, string product, string icon, string developer, bool canOverwrite = false)
{
if (id == null) return;
bool found = false;
for (int i = 0; i < dlcIds.Count; i++)
{
(string id, string name, string product, string icon, string developer) app = dlcIds[i];
if (app.id == id)
{
found = true;
dlcIds[i] = canOverwrite
? (app.id, title ?? app.name, product ?? app.product, icon ?? app.icon, developer ?? app.developer)
: (app.id, app.name ?? title, app.product ?? product, app.icon ?? icon, app.developer ?? developer);
break;
}
}
if (!found) dlcIds.Add((id, title, product, icon, developer));
}
private static async Task<Response> QueryGraphQL(string categoryNamespace)
{
string encoded = HttpUtility.UrlEncode(categoryNamespace);
Request request = new(encoded);

View file

@ -13,6 +13,21 @@ internal class Request
[JsonProperty(PropertyName = "query")]
private string _gqlQuery => @"query searchOffers($namespace: String!) {
Catalog {
searchStore(category: ""*"", namespace: $namespace){
elements {
id
title
developer
items {
id
}
catalogNs {
mappings(pageType: ""productHome"") {
pageSlug
}
}
}
}
catalogOffers(
namespace: $namespace
params: {
@ -20,6 +35,7 @@ internal class Request
}
) {
elements {
id
title
keyImages {
type
@ -27,6 +43,7 @@ internal class Request
}
items {
id
title
developer
}
catalogNs {
@ -52,15 +69,6 @@ internal class Request
private class Variables
{
[JsonProperty(PropertyName = "category")]
private string _category => "games/edition/base|bundles/games|editors|software/edition/base";
[JsonProperty(PropertyName = "count")]
private int _count => 1000;
[JsonProperty(PropertyName = "keywords")]
private string _keywords => "";
[JsonProperty(PropertyName = "namespace")]
private string _namespace { get; set; }

View file

@ -17,10 +17,19 @@ public class Data
public class Catalog
{
[JsonProperty(PropertyName = "searchStore")]
public SearchStore SearchStore { get; protected set; }
[JsonProperty(PropertyName = "catalogOffers")]
public CatalogOffers CatalogOffers { get; protected set; }
}
public class SearchStore
{
[JsonProperty(PropertyName = "elements")]
public Element[] Elements { get; protected set; }
}
public class CatalogOffers
{
[JsonProperty(PropertyName = "elements")]
@ -29,6 +38,9 @@ public class CatalogOffers
public class Element
{
[JsonProperty(PropertyName = "id")]
public string Id { get; protected set; }
[JsonProperty(PropertyName = "title")]
public string Title { get; protected set; }
@ -47,6 +59,9 @@ public class Item
[JsonProperty(PropertyName = "id")]
public string Id { get; protected set; }
[JsonProperty(PropertyName = "title")]
public string Title { get; protected set; }
[JsonProperty(PropertyName = "developer")]
public string Developer { get; protected set; }
}

View file

@ -53,22 +53,22 @@ internal partial class InstallForm : CustomForm
}
}
internal static void WriteCreamConfiguration(StreamWriter writer, string steamAppId, string name, SortedList<string, (string name, string icon)> steamDlcApps, InstallForm installForm = null)
internal static void WriteCreamConfiguration(StreamWriter writer, string appId, string name, SortedList<string, (DlcType type, string name, string icon)> dlc, InstallForm installForm = null)
{
writer.WriteLine($"; {name}");
writer.WriteLine("[steam]");
writer.WriteLine($"appid = {steamAppId}");
writer.WriteLine($"appid = {appId}");
writer.WriteLine();
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<string, (string name, string icon)> pair in steamDlcApps)
installForm.UpdateUser($"Added game to cream_api.ini with appid {appId} ({name})", InstallationLog.Resource, info: false);
foreach (KeyValuePair<string, (DlcType type, string name, string icon)> pair in dlc)
{
string appId = pair.Key;
(string dlcName, _) = pair.Value;
writer.WriteLine($"{appId} = {dlcName}");
string dlcId = pair.Key;
(_, string dlcName, _) = pair.Value;
writer.WriteLine($"{dlcId} = {dlcName}");
if (installForm is not null)
installForm.UpdateUser($"Added DLC to cream_api.ini with appid {appId} ({dlcName})", InstallationLog.Resource, info: false);
installForm.UpdateUser($"Added DLC to cream_api.ini with appid {dlcId} ({dlcName})", InstallationLog.Resource, info: false);
}
}
@ -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<string, string, SortedList<string, (string name, string icon)>> extraAppDlc in selection.ExtraDlc)
foreach (Tuple<string, string, SortedList<string, (DlcType type, string name, string icon)>> extraAppDlc in selection.ExtraDlc)
WriteCreamConfiguration(writer, extraAppDlc.Item1, extraAppDlc.Item2, extraAppDlc.Item3, installForm);
writer.Flush();
writer.Close();
});
internal static void WriteScreamConfiguration(StreamWriter writer, SortedList<string, (string name, string icon)> dlcApps, InstallForm installForm = null)
internal static void WriteScreamConfiguration(StreamWriter writer, SortedList<string, (DlcType type, string name, string icon)> dlc, InstallForm installForm = null)
{
writer.WriteLine("{");
writer.WriteLine(" \"version\": 2,");
@ -154,34 +154,54 @@ internal partial class InstallForm : CustomForm
writer.WriteLine(" \"eos_logging\": false,");
writer.WriteLine(" \"block_metrics\": false,");
writer.WriteLine(" \"catalog_items\": {");
writer.WriteLine(" \"unlock_all\": true,"); //writer.WriteLine(" \"unlock_all\": false,");
writer.WriteLine(" \"override\": []"); //writer.WriteLine(" \"override\": [");
/*KeyValuePair<string, (string name, string icon)> last = dlcApps.Last();
foreach (KeyValuePair<string, (string name, string icon)> pair in dlcApps)
IEnumerable<KeyValuePair<string, (DlcType type, string name, string icon)>> catalogItems = dlc.Where(pair => pair.Value.type == DlcType.CatalogItem);
if (catalogItems.Any())
{
string id = pair.Key;
(string name, _) = pair.Value;
writer.WriteLine($" \"{id}\"{(pair.Equals(last) ? "" : ",")}");
if (installForm is not null)
installForm.UpdateUser($"Added DLC to ScreamAPI.json with id {id} ({name})", InstallationLog.Resource, info: false);
writer.WriteLine(" \"unlock_all\": false,");
writer.WriteLine(" \"override\": [");
KeyValuePair<string, (DlcType type, string name, string icon)> lastCatalogItem = catalogItems.Last();
foreach (KeyValuePair<string, (DlcType type, string name, string icon)> pair in catalogItems)
{
string id = pair.Key;
(_, string name, _) = pair.Value;
writer.WriteLine($" \"{id}\"{(pair.Equals(lastCatalogItem) ? "" : ",")}");
if (installForm is not null)
installForm.UpdateUser($"Added catalog item to ScreamAPI.json with id {id} ({name})", InstallationLog.Resource, info: false);
}
writer.WriteLine(" ]");
}
else
{
writer.WriteLine(" \"unlock_all\": true,");
writer.WriteLine(" \"override\": []");
}
writer.WriteLine(" ]");*/
writer.WriteLine(" },");
writer.WriteLine(" \"entitlements\": {");
writer.WriteLine(" \"unlock_all\": true,"); //writer.WriteLine(" \"unlock_all\": false,");
writer.WriteLine(" \"auto_inject\": true,"); //writer.WriteLine(" \"auto_inject\": false,");
writer.WriteLine(" \"inject\": []"); //writer.WriteLine(" \"inject\": [");
/*foreach (KeyValuePair<string, (string name, string icon)> pair in dlcApps)
IEnumerable<KeyValuePair<string, (DlcType type, string name, string icon)>> entitlements = dlc.Where(pair => pair.Value.type == DlcType.Entitlement);
if (entitlements.Any())
{
string id = pair.Key;
(string name, _) = pair.Value;
writer.WriteLine($" \"{id}\"{(pair.Equals(last) ? "" : ",")}");
writer.WriteLine(" \"unlock_all\": false,");
writer.WriteLine(" \"auto_inject\": false,");
writer.WriteLine(" \"inject\": [");
KeyValuePair<string, (DlcType type, string name, string icon)> lastEntitlement = entitlements.Last();
foreach (KeyValuePair<string, (DlcType type, string name, string icon)> pair in entitlements)
{
string id = pair.Key;
(_, string name, _) = pair.Value;
writer.WriteLine($" \"{id}\"{(pair.Equals(lastEntitlement) ? "" : ",")}");
if (installForm is not null)
installForm.UpdateUser($"Added entitlement to ScreamAPI.json with id {id} ({name})", InstallationLog.Resource, info: false);
}
writer.WriteLine(" ]");
}
else
{
writer.WriteLine(" \"unlock_all\": true,");
writer.WriteLine(" \"auto_inject\": true,");
writer.WriteLine(" \"inject\": []");
}
writer.WriteLine(" ]");*/
writer.WriteLine(" }");
writer.WriteLine("}");
if (installForm is not null)
installForm.UpdateUser($"Created 'unlock_all: true' ScreamAPI.json configuration (temporary until I figure out how to properly get all DLC ids)", InstallationLog.Resource, info: false);
}
internal static async Task UninstallScreamAPI(string directory, InstallForm installForm = null) => await Task.Run(() =>
@ -252,7 +272,7 @@ internal partial class InstallForm : CustomForm
StreamWriter writer = new(sApi, true, Encoding.UTF8);
if (selection.Id != "ParadoxLauncher")
WriteScreamConfiguration(writer, selection.SelectedDlc, installForm);
foreach (Tuple<string, string, SortedList<string, (string name, string icon)>> extraAppDlc in selection.ExtraDlc)
foreach (Tuple<string, string, SortedList<string, (DlcType type, string name, string icon)>> extraAppDlc in selection.ExtraDlc)
WriteScreamConfiguration(writer, extraAppDlc.Item3, installForm);
writer.Flush();
writer.Close();

View file

@ -7,6 +7,13 @@ using Gameloop.Vdf.Linq;
namespace CreamInstaller;
internal enum DlcType
{
Default = 0,
CatalogItem = 1,
Entitlement = 2
}
internal class ProgramSelection
{
internal bool Enabled = false;
@ -16,7 +23,6 @@ internal class ProgramSelection
internal string Name = "Program";
internal string ProductUrl = null;
internal string IconUrl = null;
internal string ClientIconUrl = null;
@ -28,9 +34,9 @@ internal class ProgramSelection
internal bool IsSteam = false;
internal VProperty AppInfo = null;
internal readonly SortedList<string, (string name, string icon)> AllDlc = new();
internal readonly SortedList<string, (string name, string icon)> SelectedDlc = new();
internal readonly List<Tuple<string, string, SortedList<string, (string name, string icon)>>> ExtraDlc = new();
internal readonly SortedList<string, (DlcType type, string name, string icon)> AllDlc = new();
internal readonly SortedList<string, (DlcType type, string name, string icon)> SelectedDlc = new();
internal readonly List<Tuple<string, string, SortedList<string, (DlcType type, string name, string icon)>>> ExtraDlc = new(); // for Paradox Launcher
internal bool AreDllsLocked
{
@ -57,19 +63,19 @@ internal class ProgramSelection
}
}
private void Toggle(string dlcAppId, (string name, string icon) dlcApp, bool enabled)
private void Toggle(string dlcAppId, (DlcType type, string name, string icon) dlcApp, bool enabled)
{
if (enabled) SelectedDlc[dlcAppId] = dlcApp;
else SelectedDlc.Remove(dlcAppId);
}
internal void ToggleDlc(string dlcAppId, bool enabled)
internal void ToggleDlc(string dlcId, bool enabled)
{
foreach (KeyValuePair<string, (string name, string icon)> pair in AllDlc)
foreach (KeyValuePair<string, (DlcType type, string name, string icon)> pair in AllDlc)
{
string appId = pair.Key;
(string name, string icon) dlcApp = pair.Value;
if (appId == dlcAppId)
(DlcType type, string name, string icon) dlcApp = pair.Value;
if (appId == dlcId)
{
Toggle(appId, dlcApp, enabled);
break;
@ -78,18 +84,6 @@ internal class ProgramSelection
Enabled = SelectedDlc.Any();
}
internal void ToggleAllDlc(bool enabled)
{
if (!enabled) SelectedDlc.Clear();
else foreach (KeyValuePair<string, (string name, string icon)> pair in AllDlc)
{
string appId = pair.Key;
(string name, string icon) dlcApp = pair.Value;
Toggle(appId, dlcApp, enabled);
}
Enabled = SelectedDlc.Any();
}
internal ProgramSelection() => All.Add(this);
internal void Validate()
@ -118,13 +112,13 @@ internal class ProgramSelection
internal static List<ProgramSelection> AllUsableEnabled => AllUsable.FindAll(s => s.Enabled);
internal static ProgramSelection FromId(string id) => AllUsable.Find(s => s.Id == id);
internal static ProgramSelection FromId(string gameId) => AllUsable.Find(s => s.Id == gameId);
internal static (string gameAppId, (string name, string icon) app)? GetDlcFromId(string appId)
internal static (string gameId, (DlcType type, string name, string icon) app)? GetDlcFromId(string dlcId)
{
foreach (ProgramSelection selection in AllUsable)
foreach (KeyValuePair<string, (string name, string icon)> pair in selection.AllDlc)
if (pair.Key == appId) return (selection.Id, pair.Value);
foreach (KeyValuePair<string, (DlcType type, string name, string icon)> pair in selection.AllDlc)
if (pair.Key == dlcId) return (selection.Id, pair.Value);
return null;
}
}

View file

@ -140,7 +140,7 @@ internal partial class SelectForm : CustomForm
return;
}
if (Program.Canceled) return;
ConcurrentDictionary<string, (string name, string icon)> dlc = new();
ConcurrentDictionary<string, (DlcType type, string name, string icon)> dlc = new();
List<Task> dlcTasks = new();
List<string> dlcIds = await SteamCMD.ParseDlcAppIds(appInfo);
await SteamStore.ParseDlcAppIds(appId, dlcIds);
@ -167,7 +167,7 @@ internal partial class SelectForm : CustomForm
}
if (Program.Canceled) return;
if (!string.IsNullOrWhiteSpace(dlcName))
dlc[dlcAppId] = (dlcName, dlcIconStaticId);
dlc[dlcAppId] = (DlcType.Default, dlcName, dlcIconStaticId);
RemoveFromRemainingDLCs(dlcAppId);
progress.Report(++CompleteTasks);
});
@ -190,7 +190,7 @@ internal partial class SelectForm : CustomForm
}
selection ??= new();
if (allCheckBox.Checked) selection.Enabled = true;
selection.Enabled = allCheckBox.Checked || selection.SelectedDlc.Any();
selection.Usable = true;
selection.Id = appId;
selection.Name = name;
@ -213,11 +213,11 @@ internal partial class SelectForm : CustomForm
programNode.Checked = selection.Enabled;
programNode.Remove();
selectionTreeView.Nodes.Add(programNode);
foreach (KeyValuePair<string, (string name, string icon)> pair in dlc)
foreach (KeyValuePair<string, (DlcType type, string name, string icon)> pair in dlc)
{
if (Program.Canceled || programNode is null) return;
string appId = pair.Key;
(string name, string icon) dlcApp = pair.Value;
(DlcType type, 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();
@ -243,7 +243,6 @@ internal partial class SelectForm : CustomForm
foreach (Manifest manifest in epicGames)
{
string @namespace = manifest.CatalogNamespace;
string mainId = manifest.MainGameCatalogItemId;
string name = manifest.DisplayName;
string directory = manifest.InstallLocation;
ProgramSelection selection = ProgramSelection.FromId(@namespace);
@ -260,19 +259,19 @@ internal partial class SelectForm : CustomForm
return;
}
if (Program.Canceled) return;
ConcurrentDictionary<string, (string name, string product, string icon, string developer)> dlc = new();
ConcurrentDictionary<string, (string name, string product, string icon, string developer)> entitlements = new();
List<Task> dlcTasks = new();
List<(string id, string name, string product, string icon, string developer)> dlcIds = await EpicStore.ParseDlcIds(@namespace);
if (dlcIds.Count > 0)
List<(string id, string name, string product, string icon, string developer)> entitlementIds = await EpicStore.QueryEntitlements(manifest);
if (entitlementIds.Any())
{
foreach ((string id, string name, string product, string icon, string developer) in dlcIds)
foreach ((string id, string name, string product, string icon, string developer) in entitlementIds)
{
if (Program.Canceled) return;
AddToRemainingDLCs(id);
Task task = Task.Run(() =>
{
if (Program.Canceled) return;
dlc[id] = (name, product, icon, developer);
entitlements[id] = (name, product, icon, developer);
RemoveFromRemainingDLCs(id);
progress.Report(++CompleteTasks);
});
@ -282,7 +281,7 @@ internal partial class SelectForm : CustomForm
Thread.Sleep(10); // to reduce control & window freezing
}
}
else
if (/*!catalogItems.Any() && */!entitlements.Any())
{
RemoveFromRemainingGames(name);
return;
@ -295,13 +294,13 @@ internal partial class SelectForm : CustomForm
}
selection ??= new();
if (allCheckBox.Checked) selection.Enabled = true;
selection.Enabled = allCheckBox.Checked || selection.SelectedDlc.Any();
selection.Usable = true;
selection.Id = @namespace;
selection.Name = name;
selection.RootDirectory = directory;
selection.DllDirectories = dllDirectories;
foreach (KeyValuePair<string, (string name, string product, string icon, string developer)> pair in dlc)
foreach (KeyValuePair<string, (string name, string product, string icon, string developer)> pair in entitlements)
if (pair.Value.name == selection.Name)
{
selection.ProductUrl = "https://www.epicgames.com/store/product/" + pair.Value.product;
@ -319,20 +318,35 @@ internal partial class SelectForm : CustomForm
programNode.Checked = selection.Enabled;
programNode.Remove();
selectionTreeView.Nodes.Add(programNode);
/*foreach (KeyValuePair<string, (string name, string product, string icon, string developer)> pair in dlc)
/*TreeNode catalogItemsNode = TreeNodes.Find(s => s.Name == @namespace + "_catalogItems") ?? new();
catalogItemsNode.Name = @namespace + "_catalogItems";
catalogItemsNode.Text = "Catalog Items";
catalogItemsNode.Checked = selection.SelectedDlc.Any(pair => pair.Value.type == DlcType.CatalogItem);
catalogItemsNode.Remove();
programNode.Nodes.Add(catalogItemsNode);*/
if (entitlements.Any())
{
if (Program.Canceled || programNode is null) return;
string dlcId = pair.Key;
(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 = dlcApp.name;
dlcNode.Checked = selection.SelectedDlc.ContainsKey(dlcId);
dlcNode.Remove();
programNode.Nodes.Add(dlcNode);
}*/
/*TreeNode entitlementsNode = TreeNodes.Find(s => s.Name == @namespace + "_entitlements") ?? new();
entitlementsNode.Name = @namespace + "_entitlements";
entitlementsNode.Text = "Entitlements";
entitlementsNode.Checked = selection.SelectedDlc.Any(pair => pair.Value.type == DlcType.Entitlement);
entitlementsNode.Remove();
programNode.Nodes.Add(entitlementsNode);*/
foreach (KeyValuePair<string, (string name, string product, string icon, string developer)> pair in entitlements)
{
if (Program.Canceled || programNode is null/* || entitlementsNode is null*/) return;
string dlcId = pair.Key;
(DlcType type, string name, string icon) dlcApp = (DlcType.Entitlement, 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 = dlcApp.name;
dlcNode.Checked = selection.SelectedDlc.ContainsKey(dlcId);
dlcNode.Remove();
programNode.Nodes.Add(dlcNode); //entitlementsNode.Nodes.Add(dlcNode);
}
}
});
if (Program.Canceled) return;
RemoveFromRemainingGames(name);
@ -395,7 +409,8 @@ internal partial class SelectForm : CustomForm
ProgramSelection.ValidateAll();
TreeNodes.ForEach(node =>
{
if (!int.TryParse(node.Name, out int appId) || node.Parent is null && ProgramSelection.FromId(node.Name) is null) node.Remove();
if (node.Parent is null && ProgramSelection.FromId(node.Name) is null)
node.Remove();
});
await GetApplicablePrograms(iProgress);
await SteamCMD.Cleanup();
@ -422,37 +437,47 @@ internal partial class SelectForm : CustomForm
{
if (e.Action == TreeViewAction.Unknown) return;
TreeNode node = e.Node;
if (node is not null)
{
string appId = node.Name;
ProgramSelection selection = ProgramSelection.FromId(appId);
if (selection is null)
{
TreeNode parent = node.Parent;
if (parent is not null)
{
string gameAppId = parent.Name;
ProgramSelection.FromId(gameAppId).ToggleDlc(appId, node.Checked);
parent.Checked = parent.Nodes.Cast<TreeNode>().ToList().Any(treeNode => treeNode.Checked);
}
}
else
{
if (selection.AllDlc.Any())
{
selection.ToggleAllDlc(node.Checked);
node.Nodes.Cast<TreeNode>().ToList().ForEach(treeNode => treeNode.Checked = node.Checked);
}
else selection.Enabled = node.Checked;
allCheckBox.CheckedChanged -= OnAllCheckBoxChanged;
allCheckBox.Checked = TreeNodes.TrueForAll(treeNode => treeNode.Checked);
allCheckBox.CheckedChanged += OnAllCheckBoxChanged;
}
}
if (node is null) return;
CheckNode(node);
SyncNodeParents(node);
SyncNodeDescendants(node);
allCheckBox.CheckedChanged -= OnAllCheckBoxChanged;
allCheckBox.Checked = TreeNodes.TrueForAll(treeNode => treeNode.Checked);
allCheckBox.CheckedChanged += OnAllCheckBoxChanged;
installButton.Enabled = ProgramSelection.AllUsableEnabled.Any();
uninstallButton.Enabled = installButton.Enabled;
}
private static void SyncNodeParents(TreeNode node)
{
TreeNode parentNode = node.Parent;
if (parentNode is not null)
{
parentNode.Checked = parentNode.Nodes.Cast<TreeNode>().ToList().Any(childNode => childNode.Checked);
SyncNodeParents(parentNode);
}
}
private static void SyncNodeDescendants(TreeNode node) =>
node.Nodes.Cast<TreeNode>().ToList().ForEach(childNode =>
{
childNode.Checked = node.Checked;
CheckNode(childNode);
SyncNodeDescendants(childNode);
});
private static void CheckNode(TreeNode node)
{
(string gameId, (DlcType type, string name, string icon) app)? dlc = ProgramSelection.GetDlcFromId(node.Name);
if (dlc.HasValue)
{
(string gameId, _) = dlc.Value;
ProgramSelection selection = ProgramSelection.FromId(gameId);
if (selection is not null)
selection.ToggleDlc(node.Name, node.Checked);
}
}
internal List<TreeNode> TreeNodes => GatherTreeNodes(selectionTreeView.Nodes);
private List<TreeNode> GatherTreeNodes(TreeNodeCollection nodeCollection)
{
@ -547,14 +572,14 @@ internal partial class SelectForm : CustomForm
selectionTreeView.NodeMouseClick += (sender, e) =>
{
TreeNode node = e.Node;
TreeNode parentNode = node.Parent;
if (!node.Bounds.Contains(e.Location) || e.Button != MouseButtons.Right) return;
selectionTreeView.SelectedNode = node;
string id = node.Name;
ProgramSelection selection = ProgramSelection.FromId(id);
(string gameAppId, (string name, string icon) app)? dlc = null;
(string gameAppId, (DlcType type, 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))
if (selection is not null || dlc is not null)
{
selectionTreeView.SelectedNode = node;
nodeContextMenu.Items.Clear();
ToolStripMenuItem header = new(selection?.Name ?? node.Text, Image("Icon_" + id));
if (header.Image is null)