Add Test Game Generator for Steam and Epic

- Added Test Game Generator to the Debug window for creating fake installed game entries for testing
- Updated ThemeManager to properly theme ListBox controls in dark mode
- Labels now use transparent backgrounds for better appearance
This commit is contained in:
Frog
2026-05-27 01:20:03 -07:00
parent 2f993bfe3b
commit 593f396c54
9 changed files with 798 additions and 6 deletions
+20 -3
View File
@@ -32,16 +32,31 @@ partial class DebugForm
private void InitializeComponent()
{
debugTextBox = new RichTextBox();
testGameButton = new Button();
SuspendLayout();
//
// testGameButton
//
testGameButton.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
testGameButton.AutoSize = true;
testGameButton.AutoSizeMode = AutoSizeMode.GrowAndShrink;
testGameButton.Location = new System.Drawing.Point(10, 10);
testGameButton.Name = "testGameButton";
testGameButton.Padding = new Padding(3, 0, 3, 0);
testGameButton.Size = new System.Drawing.Size(540, 25);
testGameButton.TabIndex = 1;
testGameButton.Text = "Test Game";
testGameButton.UseVisualStyleBackColor = true;
testGameButton.Click += OnTestGame;
//
// debugTextBox
//
debugTextBox.Dock = DockStyle.Fill;
debugTextBox.Location = new System.Drawing.Point(10, 10);
debugTextBox.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
debugTextBox.Location = new System.Drawing.Point(10, 41);
debugTextBox.Name = "debugTextBox";
debugTextBox.ReadOnly = true;
debugTextBox.ScrollBars = RichTextBoxScrollBars.ForcedBoth;
debugTextBox.Size = new System.Drawing.Size(540, 317);
debugTextBox.Size = new System.Drawing.Size(540, 286);
debugTextBox.TabIndex = 0;
debugTextBox.TabStop = false;
debugTextBox.Text = "";
@@ -52,6 +67,7 @@ partial class DebugForm
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new System.Drawing.Size(560, 337);
ControlBox = false;
Controls.Add(testGameButton);
Controls.Add(debugTextBox);
FormBorderStyle = FormBorderStyle.FixedSingle;
MaximizeBox = false;
@@ -68,4 +84,5 @@ partial class DebugForm
#endregion
private RichTextBox debugTextBox;
private Button testGameButton;
}
+6
View File
@@ -81,4 +81,10 @@ internal sealed partial class DebugForm : CustomForm
debugTextBox.AppendText(text, color, true);
});
}
private void OnTestGame(object sender, EventArgs e)
{
using TestGameForm form = new(this);
_ = form.ShowDialog(this);
}
}
+215
View File
@@ -0,0 +1,215 @@
using System.ComponentModel;
using System.Windows.Forms;
namespace CreamInstaller.Forms;
partial class TestGameForm
{
private IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && components is not null)
components.Dispose();
base.Dispose(disposing);
}
// All coordinates are based on ClientSize = 560 x 330
// Left margin = 12, right edge of usable area = 548 (560 - 12)
// Usable width = 536
private void InitializeComponent()
{
platformGroupBox = new GroupBox();
steamRadioButton = new RadioButton();
epicRadioButton = new RadioButton();
appIdLabel = new Label();
appIdTextBox = new TextBox();
gameNameLabel = new Label();
gameNameTextBox = new TextBox();
epicSearchButton = new Button();
epicResultsListBox = new ListBox();
dlcGroupBox = new GroupBox();
dlcListBox = new ListBox();
dlcIdLabel = new Label();
dlcIdTextBox = new TextBox();
dlcNameLabel = new Label();
dlcNameTextBox = new TextBox();
addDlcButton = new Button();
removeDlcButton = new Button();
generateButton = new Button();
clearButton = new Button();
closeButton = new Button();
statusLabel = new Label();
platformGroupBox.SuspendLayout();
dlcGroupBox.SuspendLayout();
SuspendLayout();
// ── Platform group box ── y=8, h=44
platformGroupBox.Location = new System.Drawing.Point(12, 8);
platformGroupBox.Size = new System.Drawing.Size(536, 44);
platformGroupBox.TabStop = false;
platformGroupBox.Text = "Platform";
platformGroupBox.Controls.Add(steamRadioButton);
platformGroupBox.Controls.Add(epicRadioButton);
steamRadioButton.AutoSize = true;
steamRadioButton.Checked = true;
steamRadioButton.Location = new System.Drawing.Point(10, 17);
steamRadioButton.TabStop = true;
steamRadioButton.Text = "Steam";
steamRadioButton.CheckedChanged += OnPlatformChanged;
epicRadioButton.AutoSize = true;
epicRadioButton.Location = new System.Drawing.Point(80, 17);
epicRadioButton.Text = "Epic";
epicRadioButton.CheckedChanged += OnPlatformChanged;
// ── App ID row ── y=62
appIdLabel.AutoSize = true;
appIdLabel.Location = new System.Drawing.Point(12, 66);
appIdLabel.Text = "App ID:";
appIdTextBox.Location = new System.Drawing.Point(105, 63);
appIdTextBox.Size = new System.Drawing.Size(443, 23);
appIdTextBox.PlaceholderText = "e.g. 480";
// ── Game Name row ── y=96
gameNameLabel.AutoSize = true;
gameNameLabel.Location = new System.Drawing.Point(12, 100);
gameNameLabel.Text = "Game Name:";
// Steam: full width; Epic: leaves room for Search button (75px + 4px gap)
gameNameTextBox.Location = new System.Drawing.Point(105, 97);
gameNameTextBox.Size = new System.Drawing.Size(443, 23);
epicSearchButton.Location = new System.Drawing.Point(468, 97);
epicSearchButton.Size = new System.Drawing.Size(80, 23);
epicSearchButton.Text = "Search";
epicSearchButton.Visible = false;
epicSearchButton.Click += OnEpicSearch;
// ── Epic results list ── y=130, same slot as DLC group
epicResultsListBox.Location = new System.Drawing.Point(12, 130);
epicResultsListBox.Size = new System.Drawing.Size(536, 80);
epicResultsListBox.Visible = false;
epicResultsListBox.SelectedIndexChanged += OnEpicResultSelected;
// ── DLC group box ── y=130, h=130
dlcGroupBox.Location = new System.Drawing.Point(12, 130);
dlcGroupBox.Size = new System.Drawing.Size(536, 130);
dlcGroupBox.TabStop = false;
dlcGroupBox.Text = "DLC Entries (Steam only)";
dlcGroupBox.Controls.Add(dlcListBox);
dlcGroupBox.Controls.Add(dlcIdLabel);
dlcGroupBox.Controls.Add(dlcIdTextBox);
dlcGroupBox.Controls.Add(dlcNameLabel);
dlcGroupBox.Controls.Add(dlcNameTextBox);
dlcGroupBox.Controls.Add(addDlcButton);
dlcGroupBox.Controls.Add(removeDlcButton);
dlcListBox.Location = new System.Drawing.Point(6, 20);
dlcListBox.Size = new System.Drawing.Size(524, 60);
// DLC row inside group box — left-to-right:
// "DLC ID:" label + 70px box + "DLC Name:" label + 160px box + "Add"(60) + "Remove"(70)
// Total: ~48 + 70 + ~72 + 160 + 60 + 70 = 480 (fits in 524)
dlcIdLabel.AutoSize = true;
dlcIdLabel.Location = new System.Drawing.Point(6, 92);
dlcIdLabel.Text = "DLC ID:";
dlcIdTextBox.Location = new System.Drawing.Point(62, 89);
dlcIdTextBox.Size = new System.Drawing.Size(70, 23);
dlcIdTextBox.PlaceholderText = "e.g. 12345";
dlcNameLabel.AutoSize = true;
dlcNameLabel.Location = new System.Drawing.Point(140, 92);
dlcNameLabel.Text = "DLC Name:";
dlcNameTextBox.Location = new System.Drawing.Point(216, 89);
dlcNameTextBox.Size = new System.Drawing.Size(184, 23);
dlcNameTextBox.PlaceholderText = "e.g. Test DLC";
addDlcButton.Location = new System.Drawing.Point(406, 89);
addDlcButton.Size = new System.Drawing.Size(52, 23);
addDlcButton.Text = "Add";
addDlcButton.Click += OnAddDlc;
removeDlcButton.Location = new System.Drawing.Point(462, 89);
removeDlcButton.Size = new System.Drawing.Size(62, 23);
removeDlcButton.Text = "Remove";
removeDlcButton.Click += OnRemoveDlc;
// ── Action buttons ── y=270
generateButton.Location = new System.Drawing.Point(12, 270);
generateButton.Size = new System.Drawing.Size(150, 26);
generateButton.Text = "Generate Test Game";
generateButton.Click += OnGenerate;
clearButton.Location = new System.Drawing.Point(168, 270);
clearButton.Size = new System.Drawing.Size(110, 26);
clearButton.Text = "Clear All Tests";
clearButton.Click += OnClearAll;
closeButton.Location = new System.Drawing.Point(284, 270);
closeButton.Size = new System.Drawing.Size(70, 26);
closeButton.Text = "Close";
closeButton.Click += OnClose;
// ── Status label ── y=302
statusLabel.Location = new System.Drawing.Point(12, 302);
statusLabel.Size = new System.Drawing.Size(536, 20);
statusLabel.Font = new System.Drawing.Font("Segoe UI", 8.25F);
// ── Form ──
AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new System.Drawing.Size(560, 328);
FormBorderStyle = FormBorderStyle.FixedDialog;
MaximizeBox = false;
MinimizeBox = false;
StartPosition = FormStartPosition.CenterParent;
Text = "Test Game Generator";
Controls.Add(platformGroupBox);
Controls.Add(appIdLabel);
Controls.Add(appIdTextBox);
Controls.Add(gameNameLabel);
Controls.Add(gameNameTextBox);
Controls.Add(epicSearchButton);
Controls.Add(epicResultsListBox);
Controls.Add(dlcGroupBox);
Controls.Add(generateButton);
Controls.Add(clearButton);
Controls.Add(closeButton);
Controls.Add(statusLabel);
platformGroupBox.ResumeLayout(false);
platformGroupBox.PerformLayout();
dlcGroupBox.ResumeLayout(false);
dlcGroupBox.PerformLayout();
ResumeLayout(false);
PerformLayout();
}
private GroupBox platformGroupBox;
private RadioButton steamRadioButton;
private RadioButton epicRadioButton;
private Label appIdLabel;
private TextBox appIdTextBox;
private Label gameNameLabel;
private TextBox gameNameTextBox;
private Button epicSearchButton;
private ListBox epicResultsListBox;
private GroupBox dlcGroupBox;
private ListBox dlcListBox;
private Label dlcIdLabel;
private TextBox dlcIdTextBox;
private Label dlcNameLabel;
private TextBox dlcNameTextBox;
private Button addDlcButton;
private Button removeDlcButton;
private Button generateButton;
private Button clearButton;
private Button closeButton;
private Label statusLabel;
}
+362
View File
@@ -0,0 +1,362 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using CreamInstaller.Components;
using CreamInstaller.Platforms.Epic;
using CreamInstaller.Platforms.Steam;
using CreamInstaller.Utility;
namespace CreamInstaller.Forms;
internal sealed partial class TestGameForm : CustomForm
{
private static readonly string TestGamesRoot =
Path.Combine(ProgramData.DirectoryPath, "TestGames");
private static readonly List<string> CreatedDirectories = [];
// Steam DLC entries per-form: (dlcId, dlcName)
private readonly List<(string id, string name)> dlcEntries = [];
// Cached Epic search results from the last search: (namespace, name)
private readonly List<(string ns, string name)> epicSearchResults = [];
private bool IsEpicMode => epicRadioButton.Checked;
internal TestGameForm(IWin32Window owner) : base(owner)
{
InitializeComponent();
appIdTextBox.Leave += OnAppIdLeave;
RefreshDlcList();
UpdatePlatformMode();
}
private void UpdatePlatformMode()
{
bool epic = IsEpicMode;
// App ID row: Steam only
appIdLabel.Visible = !epic;
appIdTextBox.Visible = !epic;
// Search button: Epic only — shrink the game name box to make room
epicSearchButton.Visible = epic;
gameNameTextBox.Size = new System.Drawing.Size(epic ? 354 : 443, 23);
// Placeholder text — call RefreshCueBanner to flush the Win32 cue so only one text shows
gameNameTextBox.PlaceholderText = epic ? "Enter game name and click Search" : "e.g. Spacewar";
NativeMethods.RefreshCueBanner(gameNameTextBox);
// DLC group and Epic results share the same vertical slot
dlcGroupBox.Visible = !epic;
epicResultsListBox.Visible = false; // hidden until search runs
if (!epic)
epicSearchResults.Clear();
SetStatus(epic
? "Enter a game name and click Search to find it on the Epic store."
: "Enter the App ID, then tab out to auto-detect the game name.");
}
private void OnPlatformChanged(object sender, EventArgs e) => UpdatePlatformMode();
// ── Steam: auto-detect name from AppID ──────────────────────────────────
private async void OnAppIdLeave(object sender, EventArgs e)
{
if (IsEpicMode)
return;
string appId = appIdTextBox.Text.Trim();
if (string.IsNullOrWhiteSpace(appId) || !int.TryParse(appId, out _))
return;
if (!string.IsNullOrWhiteSpace(gameNameTextBox.Text))
return;
SetStatus("Looking up game name . . .");
generateButton.Enabled = false;
string name = await Task.Run(async () =>
{
// Use a dedicated client with a neutral UA so Steam's store API doesn't reject the request.
using System.Net.Http.HttpClient client = new();
client.DefaultRequestHeaders.UserAgent.ParseAdd($"{Program.Name}/{Program.Version}");
string url = $"https://store.steampowered.com/api/appdetails?appids={appId}&filters=basic";
try
{
string json = await client.GetStringAsync(url);
Newtonsoft.Json.Linq.JObject root = Newtonsoft.Json.Linq.JObject.Parse(json);
string title = root[appId]?["data"]?["name"]?.ToString();
if (!string.IsNullOrWhiteSpace(title))
return title;
}
catch { /* fall through to SteamCMD */ }
CmdAppData cmdData = await SteamCMD.GetAppInfo(appId);
return cmdData?.Common?.Name;
});
generateButton.Enabled = true;
if (name is not null)
{
gameNameTextBox.Text = name;
SetStatus($"✓ Game name detected: {name}");
}
else
{
SetStatus("Could not auto-detect name — enter it manually.");
}
}
// ── Epic: search by name ─────────────────────────────────────────────────
private async void OnEpicSearch(object sender, EventArgs e)
{
string keyword = gameNameTextBox.Text.Trim();
if (string.IsNullOrWhiteSpace(keyword))
{
SetStatus("Enter a game name to search.");
return;
}
SetStatus("Searching Epic store . . .");
epicSearchButton.Enabled = false;
generateButton.Enabled = false;
epicResultsListBox.Items.Clear();
epicResultsListBox.Visible = false;
epicSearchResults.Clear();
List<(string ns, string name)> results = await EpicStore.QuerySearch(keyword);
epicSearchButton.Enabled = true;
generateButton.Enabled = true;
if (results.Count == 0)
{
SetStatus("No results found. Try a different name.");
return;
}
epicSearchResults.AddRange(results);
foreach ((string _, string name) in results)
epicResultsListBox.Items.Add(name);
epicResultsListBox.Visible = true;
SetStatus($"Found {results.Count} result(s). Select one to use it.");
}
private void OnEpicResultSelected(object sender, EventArgs e)
{
int idx = epicResultsListBox.SelectedIndex;
if (idx < 0 || idx >= epicSearchResults.Count)
return;
gameNameTextBox.Text = epicSearchResults[idx].name;
SetStatus($"✓ Selected: {epicSearchResults[idx].name}");
}
// ── DLC (Steam) ──────────────────────────────────────────────────────────
private void OnAddDlc(object sender, EventArgs e)
{
string dlcId = dlcIdTextBox.Text.Trim();
string dlcName = dlcNameTextBox.Text.Trim();
if (string.IsNullOrWhiteSpace(dlcId) || !int.TryParse(dlcId, out _))
{
SetStatus("DLC ID must be a valid integer.");
return;
}
if (string.IsNullOrWhiteSpace(dlcName))
{
SetStatus("DLC Name cannot be empty.");
return;
}
if (dlcEntries.Any(d => d.id == dlcId))
{
SetStatus($"DLC ID {dlcId} is already in the list.");
return;
}
dlcEntries.Add((dlcId, dlcName));
RefreshDlcList();
dlcIdTextBox.Clear();
dlcNameTextBox.Clear();
SetStatus($"Added DLC: {dlcId} = {dlcName}");
}
private void OnRemoveDlc(object sender, EventArgs e)
{
if (dlcListBox.SelectedIndex < 0)
return;
dlcEntries.RemoveAt(dlcListBox.SelectedIndex);
RefreshDlcList();
SetStatus("Removed selected DLC entry.");
}
private void OnDlcListBoxSelectionChanged(object sender, EventArgs e) { }
private void RefreshDlcList()
{
dlcListBox.Items.Clear();
foreach ((string id, string name) in dlcEntries)
dlcListBox.Items.Add($"{id} = {name}");
}
// ── Generate ────────────────────────────────────────────────────────────
private void OnGenerate(object sender, EventArgs e)
{
if (IsEpicMode)
GenerateEpic();
else
GenerateSteam();
}
private void GenerateSteam()
{
string appId = appIdTextBox.Text.Trim();
string gameName = gameNameTextBox.Text.Trim();
if (string.IsNullOrWhiteSpace(appId) || !int.TryParse(appId, out _))
{
SetStatus("App ID must be a valid integer.");
return;
}
if (string.IsNullOrWhiteSpace(gameName))
{
SetStatus("Game Name cannot be empty.");
return;
}
if (SteamLibrary.TestGames.Any(g => g.appId == appId))
{
SetStatus($"A test game with App ID {appId} already exists.");
return;
}
try
{
string gameDir = Path.Combine(TestGamesRoot, $"steam_{appId}_{SanitizeName(gameName)}");
Directory.CreateDirectory(gameDir);
string dllPath = Path.Combine(gameDir, "steam_api64.dll");
WriteSteamApiStub(dllPath);
CreatedDirectories.Add(gameDir);
SteamLibrary.TestGames.Add((appId, gameName, "public", 1, gameDir));
ProgramData.Log($"[TestGame] Steam: {gameName} ({appId}) at {gameDir}");
SetStatus($"✓ Steam test game '{gameName}' ({appId}) generated. Press Rescan.");
}
catch (Exception ex)
{
SetStatus($"Error: {ex.Message}");
}
}
private void GenerateEpic()
{
string gameName = gameNameTextBox.Text.Trim();
if (string.IsNullOrWhiteSpace(gameName))
{
SetStatus("Game Name cannot be empty. Search for a game first.");
return;
}
// Use the selected search result namespace if available, otherwise derive a stub
string catalogNamespace;
int idx = epicResultsListBox.SelectedIndex;
if (idx >= 0 && idx < epicSearchResults.Count)
{
catalogNamespace = epicSearchResults[idx].ns;
gameName = epicSearchResults[idx].name;
}
else
{
catalogNamespace = $"test_{SanitizeName(gameName).ToLowerInvariant()}";
}
if (EpicLibrary.TestManifests.Any(m => m.CatalogNamespace == catalogNamespace))
{
SetStatus("An Epic test game with that namespace already exists.");
return;
}
try
{
string gameDir = Path.Combine(TestGamesRoot, $"epic_{SanitizeName(gameName)}");
Directory.CreateDirectory(gameDir);
// Stub DLL so Epic DLL-directory scanning finds the game
string dllPath = Path.Combine(gameDir, "EOSSDK-Win64-Shipping.dll");
WriteSteamApiStub(dllPath);
CreatedDirectories.Add(gameDir);
EpicLibrary.TestManifests.Add(new Manifest
{
DisplayName = gameName,
CatalogNamespace = catalogNamespace,
InstallLocation = gameDir
});
ProgramData.Log($"[TestGame] Epic: {gameName} ({catalogNamespace}) at {gameDir}");
SetStatus($"✓ Epic test game '{gameName}' generated. Press Rescan.");
}
catch (Exception ex)
{
SetStatus($"Error: {ex.Message}");
}
}
// ── Clear / Close ────────────────────────────────────────────────────────
private void OnClearAll(object sender, EventArgs e)
{
SteamLibrary.TestGames.Clear();
EpicLibrary.TestManifests.Clear();
foreach (string dir in CreatedDirectories)
try { Directory.Delete(dir, true); } catch { /* best-effort */ }
CreatedDirectories.Clear();
dlcEntries.Clear();
RefreshDlcList();
epicSearchResults.Clear();
epicResultsListBox.Items.Clear();
epicResultsListBox.Visible = false;
SetStatus("All test games cleared. Press Rescan in the main window.");
}
private void OnClose(object sender, EventArgs e) => Close();
// ── Helpers ──────────────────────────────────────────────────────────────
private void SetStatus(string message)
{
statusLabel.Text = message;
statusLabel.ForeColor = message.StartsWith("✓", StringComparison.Ordinal)
? System.Drawing.Color.Green
: System.Drawing.Color.FromArgb(212, 212, 212);
}
private static string SanitizeName(string name)
{
char[] invalid = Path.GetInvalidFileNameChars();
return new string(name.Select(c => invalid.Contains(c) ? '_' : c).ToArray());
}
private static void WriteSteamApiStub(string path)
{
byte[] mzStub =
[
0x4D, 0x5A,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
];
File.WriteAllBytes(path, mzStub);
}
}
+120
View File
@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
@@ -28,10 +28,17 @@ internal static class EpicLibrary
}
}
internal static readonly List<Manifest> TestManifests = [];
internal static async Task<List<Manifest>> GetGames()
=> await Task.Run(async () =>
{
List<Manifest> games = new();
foreach (Manifest test in TestManifests)
if (games.All(g => g.CatalogNamespace != test.CatalogNamespace))
games.Add(test);
string manifests = EpicManifestsPath;
if (manifests.DirectoryExists())
foreach (string item in manifests.EnumerateDirectory("*.item"))
@@ -132,6 +132,53 @@ internal static class EpicStore
public static bool EpicBool = true;
internal static async Task<List<(string @namespace, string name)>> QuerySearch(string keyword)
{
List<(string, string)> results = [];
try
{
string query = """
query searchByKeyword($keywords: String!) {
Catalog {
searchStore(keywords: $keywords, category: "games/edition/base", count: 10, country: "US", locale: "en-US", allowCountries: "US") {
elements {
title
namespace
}
}
}
}
""";
var payload = new { query, variables = new { keywords = keyword } };
string payloadJson = JsonConvert.SerializeObject(payload);
using HttpContent content = new StringContent(payloadJson, System.Text.Encoding.UTF8, "application/json");
HttpClient client = HttpClientManager.HttpClient;
if (client is null)
return results;
HttpResponseMessage httpResponse =
await client.PostAsync(new Uri("https://launcher.store.epicgames.com/graphql"), content);
_ = httpResponse.EnsureSuccessStatusCode();
string response = await httpResponse.Content.ReadAsStringAsync();
Newtonsoft.Json.Linq.JObject root = Newtonsoft.Json.Linq.JObject.Parse(response);
Newtonsoft.Json.Linq.JToken elements = root["data"]?["Catalog"]?["searchStore"]?["elements"];
if (elements is null)
return results;
foreach (Newtonsoft.Json.Linq.JToken el in elements)
{
string name = el["title"]?.ToString();
string ns = el["namespace"]?.ToString();
if (!string.IsNullOrWhiteSpace(name) && !string.IsNullOrWhiteSpace(ns)
&& results.All(r => r.Item1 != ns))
results.Add((ns, name));
}
}
catch
{
// ignored
}
return results;
}
private static async Task<Response> QueryGraphQL(string categoryNamespace)
{
try
@@ -10,6 +10,9 @@ namespace CreamInstaller.Platforms.Steam;
internal static class SteamLibrary
{
internal static readonly List<(string appId, string name, string branch, int buildId, string gameDirectory)>
TestGames = [];
private static string installPath;
internal static string InstallPath
@@ -41,6 +44,11 @@ internal static class SteamLibrary
games.Add(game);
}
foreach ((string appId, string name, string branch, int buildId, string gameDirectory) testGame in
TestGames.Where(t => games.All(g => g.appId != t.appId)))
games.Add(testGame);
if (TestGames.Count > 0)
ProgramData.Log($"[Steam] Injected {TestGames.Count} test game(s).");
ProgramData.Log($"[Steam] Total games detected: {games.Count}");
return games;
});
+13 -3
View File
@@ -180,9 +180,9 @@ internal static class ThemeManager
ll.VisitedLinkColor = DarkLink;
break;
// Labels: dark background, light foreground
// Labels: transparent so they blend with whatever container they sit in
case Label lbl:
lbl.BackColor = DarkBack;
lbl.BackColor = Color.Transparent;
lbl.ForeColor = DarkFore;
break;
@@ -206,6 +206,12 @@ internal static class ThemeManager
rtb.ForeColor = DarkFore;
break;
// ListBox follows alternate dark background
case ListBox lb:
lb.BackColor = DarkBackAlt;
lb.ForeColor = DarkFore;
break;
// TextBox follows alternate dark background
case TextBox tb:
tb.BackColor = DarkBackAlt;
@@ -249,7 +255,7 @@ internal static class ThemeManager
ll.VisitedLinkColor = SystemColors.HotTrack;
break;
case Label lbl:
lbl.BackColor = LightBack;
lbl.BackColor = Color.Transparent;
lbl.ForeColor = LightFore;
break;
case ProgressBar pb:
@@ -266,6 +272,10 @@ internal static class ThemeManager
rtb.BackColor = LightBack;
rtb.ForeColor = LightFore;
break;
case ListBox lb:
lb.BackColor = LightBackAlt;
lb.ForeColor = LightFore;
break;
case TextBox tb:
tb.BackColor = LightBackAlt;
tb.ForeColor = LightFore;