mirror of
https://github.com/FroggMaster/CreamInstaller.git
synced 2026-07-28 14:47:04 -07:00
Improvements to Test Game Form
- Adds the ability to Test Ubisoft Games > Has a search function with a "public" API. - Remove the DLC section from Steam Game Test Window, never really going to use this as originally the idea with test games was primarily to test DLC resolution. - Ensure the game name is cleared whens witching between test platforms - Proper cleanup of test data if the Clear All Tests button is clicked, ensures test games get cleared from the selection list as well.
This commit is contained in:
@@ -194,7 +194,7 @@ internal sealed partial class SelectForm : CustomForm
|
|||||||
return;
|
return;
|
||||||
if (!uninstallAll)
|
if (!uninstallAll)
|
||||||
{
|
{
|
||||||
string? blockReason = Program.GetGameBlockedReason(name, gameDirectory);
|
var blockReason = Program.GetGameBlockedReason(name, gameDirectory);
|
||||||
if (blockReason is not null)
|
if (blockReason is not null)
|
||||||
{
|
{
|
||||||
steamBlocked++;
|
steamBlocked++;
|
||||||
@@ -448,7 +448,7 @@ internal sealed partial class SelectForm : CustomForm
|
|||||||
return;
|
return;
|
||||||
if (!uninstallAll)
|
if (!uninstallAll)
|
||||||
{
|
{
|
||||||
string? blockReason = Program.GetGameBlockedReason(name, directory);
|
var blockReason = Program.GetGameBlockedReason(name, directory);
|
||||||
if (blockReason is not null)
|
if (blockReason is not null)
|
||||||
{
|
{
|
||||||
epicBlocked++;
|
epicBlocked++;
|
||||||
@@ -581,7 +581,7 @@ internal sealed partial class SelectForm : CustomForm
|
|||||||
return;
|
return;
|
||||||
if (!uninstallAll)
|
if (!uninstallAll)
|
||||||
{
|
{
|
||||||
string? blockReason = Program.GetGameBlockedReason(name, gameDirectory);
|
var blockReason = Program.GetGameBlockedReason(name, gameDirectory);
|
||||||
if (blockReason is not null)
|
if (blockReason is not null)
|
||||||
{
|
{
|
||||||
ubiBlocked++;
|
ubiBlocked++;
|
||||||
|
|||||||
+27
-65
@@ -23,26 +23,20 @@ partial class TestGameForm
|
|||||||
platformGroupBox = new GroupBox();
|
platformGroupBox = new GroupBox();
|
||||||
steamRadioButton = new RadioButton();
|
steamRadioButton = new RadioButton();
|
||||||
epicRadioButton = new RadioButton();
|
epicRadioButton = new RadioButton();
|
||||||
|
ubisoftRadioButton = new RadioButton();
|
||||||
appIdLabel = new Label();
|
appIdLabel = new Label();
|
||||||
appIdTextBox = new TextBox();
|
appIdTextBox = new TextBox();
|
||||||
gameNameLabel = new Label();
|
gameNameLabel = new Label();
|
||||||
gameNameTextBox = new TextBox();
|
gameNameTextBox = new TextBox();
|
||||||
epicSearchButton = new Button();
|
epicSearchButton = new Button();
|
||||||
|
ubisoftSearchButton = new Button();
|
||||||
epicResultsListBox = new ListBox();
|
epicResultsListBox = new ListBox();
|
||||||
dlcGroupBox = new GroupBox();
|
ubisoftResultsListBox = new ListBox();
|
||||||
dlcListBox = new ListBox();
|
|
||||||
dlcIdLabel = new Label();
|
|
||||||
dlcIdTextBox = new TextBox();
|
|
||||||
dlcNameLabel = new Label();
|
|
||||||
dlcNameTextBox = new TextBox();
|
|
||||||
addDlcButton = new Button();
|
|
||||||
removeDlcButton = new Button();
|
|
||||||
generateButton = new Button();
|
generateButton = new Button();
|
||||||
clearButton = new Button();
|
clearButton = new Button();
|
||||||
closeButton = new Button();
|
closeButton = new Button();
|
||||||
statusLabel = new Label();
|
statusLabel = new Label();
|
||||||
platformGroupBox.SuspendLayout();
|
platformGroupBox.SuspendLayout();
|
||||||
dlcGroupBox.SuspendLayout();
|
|
||||||
SuspendLayout();
|
SuspendLayout();
|
||||||
|
|
||||||
// ── Platform group box ── y=8, h=44
|
// ── Platform group box ── y=8, h=44
|
||||||
@@ -52,6 +46,7 @@ partial class TestGameForm
|
|||||||
platformGroupBox.Text = "Platform";
|
platformGroupBox.Text = "Platform";
|
||||||
platformGroupBox.Controls.Add(steamRadioButton);
|
platformGroupBox.Controls.Add(steamRadioButton);
|
||||||
platformGroupBox.Controls.Add(epicRadioButton);
|
platformGroupBox.Controls.Add(epicRadioButton);
|
||||||
|
platformGroupBox.Controls.Add(ubisoftRadioButton);
|
||||||
|
|
||||||
steamRadioButton.AutoSize = true;
|
steamRadioButton.AutoSize = true;
|
||||||
steamRadioButton.Checked = true;
|
steamRadioButton.Checked = true;
|
||||||
@@ -65,6 +60,11 @@ partial class TestGameForm
|
|||||||
epicRadioButton.Text = "Epic";
|
epicRadioButton.Text = "Epic";
|
||||||
epicRadioButton.CheckedChanged += OnPlatformChanged;
|
epicRadioButton.CheckedChanged += OnPlatformChanged;
|
||||||
|
|
||||||
|
ubisoftRadioButton.AutoSize = true;
|
||||||
|
ubisoftRadioButton.Location = new System.Drawing.Point(140, 17);
|
||||||
|
ubisoftRadioButton.Text = "Ubisoft";
|
||||||
|
ubisoftRadioButton.CheckedChanged += OnPlatformChanged;
|
||||||
|
|
||||||
// ── App ID row ── y=62
|
// ── App ID row ── y=62
|
||||||
appIdLabel.AutoSize = true;
|
appIdLabel.AutoSize = true;
|
||||||
appIdLabel.Location = new System.Drawing.Point(12, 66);
|
appIdLabel.Location = new System.Drawing.Point(12, 66);
|
||||||
@@ -89,58 +89,26 @@ partial class TestGameForm
|
|||||||
epicSearchButton.Visible = false;
|
epicSearchButton.Visible = false;
|
||||||
epicSearchButton.Click += OnEpicSearch;
|
epicSearchButton.Click += OnEpicSearch;
|
||||||
|
|
||||||
|
// ── Ubisoft search button ── shares same position as Epic button (mutually exclusive)
|
||||||
|
ubisoftSearchButton.Location = new System.Drawing.Point(468, 97);
|
||||||
|
ubisoftSearchButton.Size = new System.Drawing.Size(80, 23);
|
||||||
|
ubisoftSearchButton.Text = "Search";
|
||||||
|
ubisoftSearchButton.Visible = false;
|
||||||
|
ubisoftSearchButton.Click += OnUbisoftSearch;
|
||||||
|
|
||||||
// ── Epic results list ── y=130, same slot as DLC group
|
// ── Epic results list ── y=130, same slot as DLC group
|
||||||
epicResultsListBox.Location = new System.Drawing.Point(12, 130);
|
epicResultsListBox.Location = new System.Drawing.Point(12, 130);
|
||||||
epicResultsListBox.Size = new System.Drawing.Size(536, 80);
|
epicResultsListBox.Size = new System.Drawing.Size(536, 80);
|
||||||
epicResultsListBox.Visible = false;
|
epicResultsListBox.Visible = false;
|
||||||
epicResultsListBox.SelectedIndexChanged += OnEpicResultSelected;
|
epicResultsListBox.SelectedIndexChanged += OnEpicResultSelected;
|
||||||
|
|
||||||
// ── DLC group box ── y=130, h=130
|
// ── Ubisoft results list ── shares same position as Epic results (mutually exclusive)
|
||||||
dlcGroupBox.Location = new System.Drawing.Point(12, 130);
|
ubisoftResultsListBox.Location = new System.Drawing.Point(12, 130);
|
||||||
dlcGroupBox.Size = new System.Drawing.Size(536, 130);
|
ubisoftResultsListBox.Size = new System.Drawing.Size(536, 80);
|
||||||
dlcGroupBox.TabStop = false;
|
ubisoftResultsListBox.Visible = false;
|
||||||
dlcGroupBox.Text = "DLC Entries (Steam only)";
|
ubisoftResultsListBox.SelectedIndexChanged += OnUbisoftResultSelected;
|
||||||
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);
|
// ── Action buttons ── y=220 (was y=270 with DLC section)
|
||||||
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.Location = new System.Drawing.Point(12, 270);
|
||||||
generateButton.Size = new System.Drawing.Size(150, 26);
|
generateButton.Size = new System.Drawing.Size(150, 26);
|
||||||
generateButton.Text = "Generate Test Game";
|
generateButton.Text = "Generate Test Game";
|
||||||
@@ -176,8 +144,9 @@ partial class TestGameForm
|
|||||||
Controls.Add(gameNameLabel);
|
Controls.Add(gameNameLabel);
|
||||||
Controls.Add(gameNameTextBox);
|
Controls.Add(gameNameTextBox);
|
||||||
Controls.Add(epicSearchButton);
|
Controls.Add(epicSearchButton);
|
||||||
|
Controls.Add(ubisoftSearchButton);
|
||||||
Controls.Add(epicResultsListBox);
|
Controls.Add(epicResultsListBox);
|
||||||
Controls.Add(dlcGroupBox);
|
Controls.Add(ubisoftResultsListBox);
|
||||||
Controls.Add(generateButton);
|
Controls.Add(generateButton);
|
||||||
Controls.Add(clearButton);
|
Controls.Add(clearButton);
|
||||||
Controls.Add(closeButton);
|
Controls.Add(closeButton);
|
||||||
@@ -185,8 +154,6 @@ partial class TestGameForm
|
|||||||
|
|
||||||
platformGroupBox.ResumeLayout(false);
|
platformGroupBox.ResumeLayout(false);
|
||||||
platformGroupBox.PerformLayout();
|
platformGroupBox.PerformLayout();
|
||||||
dlcGroupBox.ResumeLayout(false);
|
|
||||||
dlcGroupBox.PerformLayout();
|
|
||||||
ResumeLayout(false);
|
ResumeLayout(false);
|
||||||
PerformLayout();
|
PerformLayout();
|
||||||
}
|
}
|
||||||
@@ -194,20 +161,15 @@ partial class TestGameForm
|
|||||||
private GroupBox platformGroupBox;
|
private GroupBox platformGroupBox;
|
||||||
private RadioButton steamRadioButton;
|
private RadioButton steamRadioButton;
|
||||||
private RadioButton epicRadioButton;
|
private RadioButton epicRadioButton;
|
||||||
|
private RadioButton ubisoftRadioButton;
|
||||||
|
private Button ubisoftSearchButton;
|
||||||
|
private ListBox ubisoftResultsListBox;
|
||||||
private Label appIdLabel;
|
private Label appIdLabel;
|
||||||
private TextBox appIdTextBox;
|
private TextBox appIdTextBox;
|
||||||
private Label gameNameLabel;
|
private Label gameNameLabel;
|
||||||
private TextBox gameNameTextBox;
|
private TextBox gameNameTextBox;
|
||||||
private Button epicSearchButton;
|
private Button epicSearchButton;
|
||||||
private ListBox epicResultsListBox;
|
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 generateButton;
|
||||||
private Button clearButton;
|
private Button clearButton;
|
||||||
private Button closeButton;
|
private Button closeButton;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using System.Windows.Forms;
|
|||||||
using CreamInstaller.Components;
|
using CreamInstaller.Components;
|
||||||
using CreamInstaller.Platforms.Epic;
|
using CreamInstaller.Platforms.Epic;
|
||||||
using CreamInstaller.Platforms.Steam;
|
using CreamInstaller.Platforms.Steam;
|
||||||
|
using CreamInstaller.Platforms.Ubisoft;
|
||||||
using CreamInstaller.Utility;
|
using CreamInstaller.Utility;
|
||||||
|
|
||||||
namespace CreamInstaller.Forms;
|
namespace CreamInstaller.Forms;
|
||||||
@@ -18,48 +19,63 @@ internal sealed partial class TestGameForm : CustomForm
|
|||||||
|
|
||||||
private static readonly List<string> CreatedDirectories = [];
|
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)
|
// Cached Epic search results from the last search: (namespace, name)
|
||||||
private readonly List<(string ns, string name)> epicSearchResults = [];
|
private readonly List<(string ns, string name)> epicSearchResults = [];
|
||||||
|
|
||||||
|
// Cached Ubisoft search results from the last search: (id, name)
|
||||||
|
private readonly List<(string id, string name)> ubisoftSearchResults = [];
|
||||||
|
|
||||||
private bool IsEpicMode => epicRadioButton.Checked;
|
private bool IsEpicMode => epicRadioButton.Checked;
|
||||||
|
|
||||||
|
private bool IsUbisoftMode => ubisoftRadioButton.Checked;
|
||||||
|
|
||||||
internal TestGameForm(IWin32Window owner) : base(owner)
|
internal TestGameForm(IWin32Window owner) : base(owner)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
appIdTextBox.Leave += OnAppIdLeave;
|
appIdTextBox.Leave += OnAppIdLeave;
|
||||||
RefreshDlcList();
|
appIdTextBox.KeyDown += OnAppIdKeyDown;
|
||||||
|
gameNameTextBox.KeyDown += OnGameNameKeyDown;
|
||||||
UpdatePlatformMode();
|
UpdatePlatformMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdatePlatformMode()
|
private void UpdatePlatformMode()
|
||||||
{
|
{
|
||||||
bool epic = IsEpicMode;
|
bool epic = IsEpicMode;
|
||||||
|
bool ubisoft = IsUbisoftMode;
|
||||||
|
|
||||||
// App ID row: Steam only
|
// App ID row: only Steam needs it; Epic and Ubisoft use name search
|
||||||
appIdLabel.Visible = !epic;
|
appIdLabel.Visible = !epic && !ubisoft;
|
||||||
appIdTextBox.Visible = !epic;
|
appIdTextBox.Visible = !epic && !ubisoft;
|
||||||
|
appIdLabel.Text = "App ID:";
|
||||||
|
appIdTextBox.PlaceholderText = "e.g. 480";
|
||||||
|
NativeMethods.RefreshCueBanner(appIdTextBox);
|
||||||
|
|
||||||
// Search button: Epic only — shrink the game name box to make room
|
// Search button: Epic and Ubisoft — shrink the game name box to make room
|
||||||
|
bool showSearch = epic || ubisoft;
|
||||||
epicSearchButton.Visible = epic;
|
epicSearchButton.Visible = epic;
|
||||||
gameNameTextBox.Size = new System.Drawing.Size(epic ? 354 : 443, 23);
|
ubisoftSearchButton.Visible = ubisoft;
|
||||||
|
gameNameTextBox.Size = new System.Drawing.Size(showSearch ? 354 : 443, 23);
|
||||||
|
|
||||||
// Placeholder text — call RefreshCueBanner to flush the Win32 cue so only one text shows
|
// 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";
|
gameNameTextBox.PlaceholderText = showSearch
|
||||||
|
? "Enter a game name and press Enter to search"
|
||||||
|
: "e.g. Spacewar";
|
||||||
NativeMethods.RefreshCueBanner(gameNameTextBox);
|
NativeMethods.RefreshCueBanner(gameNameTextBox);
|
||||||
|
|
||||||
// DLC group and Epic results share the same vertical slot
|
// Clear game name and results when switching mode
|
||||||
dlcGroupBox.Visible = !epic;
|
gameNameTextBox.Clear();
|
||||||
epicResultsListBox.Visible = false; // hidden until search runs
|
epicResultsListBox.Visible = false;
|
||||||
|
ubisoftResultsListBox.Visible = false;
|
||||||
|
|
||||||
if (!epic)
|
if (!epic)
|
||||||
epicSearchResults.Clear();
|
epicSearchResults.Clear();
|
||||||
|
|
||||||
SetStatus(epic
|
if (!ubisoft)
|
||||||
? "Enter a game name and click Search to find it on the Epic store."
|
ubisoftSearchResults.Clear();
|
||||||
: "Enter the App ID, then tab out to auto-detect the game name.");
|
|
||||||
|
SetStatus(showSearch
|
||||||
|
? "Enter a game name and press Enter to search."
|
||||||
|
: "Enter the App ID and press Enter to search.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPlatformChanged(object sender, EventArgs e) => UpdatePlatformMode();
|
private void OnPlatformChanged(object sender, EventArgs e) => UpdatePlatformMode();
|
||||||
@@ -68,7 +84,7 @@ internal sealed partial class TestGameForm : CustomForm
|
|||||||
|
|
||||||
private async void OnAppIdLeave(object sender, EventArgs e)
|
private async void OnAppIdLeave(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (IsEpicMode)
|
if (IsEpicMode || IsUbisoftMode)
|
||||||
return;
|
return;
|
||||||
string appId = appIdTextBox.Text.Trim();
|
string appId = appIdTextBox.Text.Trim();
|
||||||
if (string.IsNullOrWhiteSpace(appId) || !int.TryParse(appId, out _))
|
if (string.IsNullOrWhiteSpace(appId) || !int.TryParse(appId, out _))
|
||||||
@@ -107,7 +123,7 @@ internal sealed partial class TestGameForm : CustomForm
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SetStatus("Could not auto-detect name — enter it manually.");
|
SetStatus("Could not auto-detect name; enter it manually.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,53 +173,74 @@ internal sealed partial class TestGameForm : CustomForm
|
|||||||
SetStatus($"✓ Selected: {epicSearchResults[idx].name}");
|
SetStatus($"✓ Selected: {epicSearchResults[idx].name}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── DLC (Steam) ──────────────────────────────────────────────────────────
|
// ── Ubisoft: search by name ──────────────────────────────────────────────
|
||||||
|
|
||||||
private void OnAddDlc(object sender, EventArgs e)
|
private async void OnUbisoftSearch(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
string dlcId = dlcIdTextBox.Text.Trim();
|
string keyword = gameNameTextBox.Text.Trim();
|
||||||
string dlcName = dlcNameTextBox.Text.Trim();
|
if (string.IsNullOrWhiteSpace(keyword))
|
||||||
if (string.IsNullOrWhiteSpace(dlcId) || !int.TryParse(dlcId, out _))
|
|
||||||
{
|
{
|
||||||
SetStatus("DLC ID must be a valid integer.");
|
SetStatus("Enter a game name to search.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(dlcName))
|
SetStatus("Searching Ubisoft store . . .");
|
||||||
|
ubisoftSearchButton.Enabled = false;
|
||||||
|
generateButton.Enabled = false;
|
||||||
|
ubisoftResultsListBox.Items.Clear();
|
||||||
|
ubisoftResultsListBox.Visible = false;
|
||||||
|
ubisoftSearchResults.Clear();
|
||||||
|
|
||||||
|
List<(string id, string name)> results = await UbisoftStore.QuerySearch(keyword);
|
||||||
|
|
||||||
|
ubisoftSearchButton.Enabled = true;
|
||||||
|
generateButton.Enabled = true;
|
||||||
|
|
||||||
|
if (results.Count == 0)
|
||||||
{
|
{
|
||||||
SetStatus("DLC Name cannot be empty.");
|
SetStatus("No results found. Try a different name.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dlcEntries.Any(d => d.id == dlcId))
|
ubisoftSearchResults.AddRange(results);
|
||||||
{
|
foreach ((string _, string name) in results)
|
||||||
SetStatus($"DLC ID {dlcId} is already in the list.");
|
ubisoftResultsListBox.Items.Add(name);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dlcEntries.Add((dlcId, dlcName));
|
ubisoftResultsListBox.Visible = true;
|
||||||
RefreshDlcList();
|
SetStatus($"Found {results.Count} result(s). Select one to use it.");
|
||||||
dlcIdTextBox.Clear();
|
|
||||||
dlcNameTextBox.Clear();
|
|
||||||
SetStatus($"Added DLC: {dlcId} = {dlcName}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnRemoveDlc(object sender, EventArgs e)
|
private void OnUbisoftResultSelected(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (dlcListBox.SelectedIndex < 0)
|
int idx = ubisoftResultsListBox.SelectedIndex;
|
||||||
|
if (idx < 0 || idx >= ubisoftSearchResults.Count)
|
||||||
return;
|
return;
|
||||||
dlcEntries.RemoveAt(dlcListBox.SelectedIndex);
|
gameNameTextBox.Text = ubisoftSearchResults[idx].name;
|
||||||
RefreshDlcList();
|
SetStatus($"✓ Selected: {ubisoftSearchResults[idx].name}");
|
||||||
SetStatus("Removed selected DLC entry.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDlcListBoxSelectionChanged(object sender, EventArgs e) { }
|
// ── Enter key handlers ───────────────────────────────────────────────────
|
||||||
|
|
||||||
private void RefreshDlcList()
|
private void OnAppIdKeyDown(object sender, KeyEventArgs e)
|
||||||
{
|
{
|
||||||
dlcListBox.Items.Clear();
|
if (e.KeyCode == Keys.Enter && !IsEpicMode && !IsUbisoftMode)
|
||||||
foreach ((string id, string name) in dlcEntries)
|
{
|
||||||
dlcListBox.Items.Add($"{id} = {name}");
|
e.SuppressKeyPress = true;
|
||||||
|
OnAppIdLeave(sender, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGameNameKeyDown(object sender, KeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.KeyCode != Keys.Enter)
|
||||||
|
return;
|
||||||
|
e.SuppressKeyPress = true;
|
||||||
|
if (IsEpicMode)
|
||||||
|
OnEpicSearch(sender, e);
|
||||||
|
else if (IsUbisoftMode)
|
||||||
|
OnUbisoftSearch(sender, e);
|
||||||
|
else
|
||||||
|
OnAppIdLeave(sender, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Generate ────────────────────────────────────────────────────────────
|
// ── Generate ────────────────────────────────────────────────────────────
|
||||||
@@ -212,6 +249,8 @@ internal sealed partial class TestGameForm : CustomForm
|
|||||||
{
|
{
|
||||||
if (IsEpicMode)
|
if (IsEpicMode)
|
||||||
GenerateEpic();
|
GenerateEpic();
|
||||||
|
else if (IsUbisoftMode)
|
||||||
|
GenerateUbisoft();
|
||||||
else
|
else
|
||||||
GenerateSteam();
|
GenerateSteam();
|
||||||
}
|
}
|
||||||
@@ -245,7 +284,7 @@ internal sealed partial class TestGameForm : CustomForm
|
|||||||
Directory.CreateDirectory(gameDir);
|
Directory.CreateDirectory(gameDir);
|
||||||
|
|
||||||
string dllPath = Path.Combine(gameDir, "steam_api64.dll");
|
string dllPath = Path.Combine(gameDir, "steam_api64.dll");
|
||||||
WriteSteamApiStub(dllPath);
|
WriteDllStub(dllPath);
|
||||||
|
|
||||||
CreatedDirectories.Add(gameDir);
|
CreatedDirectories.Add(gameDir);
|
||||||
SteamLibrary.TestGames.Add((appId, gameName, "public", 1, gameDir));
|
SteamLibrary.TestGames.Add((appId, gameName, "public", 1, gameDir));
|
||||||
@@ -293,7 +332,7 @@ internal sealed partial class TestGameForm : CustomForm
|
|||||||
|
|
||||||
// Stub DLL so Epic DLL-directory scanning finds the game
|
// Stub DLL so Epic DLL-directory scanning finds the game
|
||||||
string dllPath = Path.Combine(gameDir, "EOSSDK-Win64-Shipping.dll");
|
string dllPath = Path.Combine(gameDir, "EOSSDK-Win64-Shipping.dll");
|
||||||
WriteSteamApiStub(dllPath);
|
WriteDllStub(dllPath);
|
||||||
|
|
||||||
CreatedDirectories.Add(gameDir);
|
CreatedDirectories.Add(gameDir);
|
||||||
|
|
||||||
@@ -313,20 +352,86 @@ internal sealed partial class TestGameForm : CustomForm
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void GenerateUbisoft()
|
||||||
|
{
|
||||||
|
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 ID if available, otherwise derive a stub
|
||||||
|
string gameId;
|
||||||
|
int selIdx = ubisoftResultsListBox.SelectedIndex;
|
||||||
|
if (selIdx >= 0 && selIdx < ubisoftSearchResults.Count)
|
||||||
|
{
|
||||||
|
gameId = ubisoftSearchResults[selIdx].id;
|
||||||
|
gameName = ubisoftSearchResults[selIdx].name;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gameId = $"test_{SanitizeName(gameName).ToLowerInvariant()}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (UbisoftLibrary.TestGames.Any(g => g.gameId == gameId))
|
||||||
|
{
|
||||||
|
SetStatus("An Ubisoft test game with that ID already exists.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string gameDir = Path.Combine(TestGamesRoot, $"ubisoft_{SanitizeName(gameId)}_{SanitizeName(gameName)}");
|
||||||
|
Directory.CreateDirectory(gameDir);
|
||||||
|
|
||||||
|
// Write stub DLLs for both Uplay R1 and R2 unlocker detection
|
||||||
|
WriteDllStub(Path.Combine(gameDir, "uplay_r1_loader.dll"));
|
||||||
|
WriteDllStub(Path.Combine(gameDir, "uplay_r1_loader64.dll"));
|
||||||
|
WriteDllStub(Path.Combine(gameDir, "upc_r2_loader.dll"));
|
||||||
|
WriteDllStub(Path.Combine(gameDir, "upc_r2_loader64.dll"));
|
||||||
|
|
||||||
|
CreatedDirectories.Add(gameDir);
|
||||||
|
UbisoftLibrary.TestGames.Add((gameId, gameName, gameDir));
|
||||||
|
ProgramData.Log($"[TestGame] Ubisoft: {gameName} ({gameId}) at {gameDir}");
|
||||||
|
SetStatus($"✓ Ubisoft test game '{gameName}' ({gameId}) generated. Press Rescan.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
SetStatus($"Error: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Clear / Close ────────────────────────────────────────────────────────
|
// ── Clear / Close ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
private void OnClearAll(object sender, EventArgs e)
|
private void OnClearAll(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
SteamLibrary.TestGames.Clear();
|
SteamLibrary.TestGames.Clear();
|
||||||
EpicLibrary.TestManifests.Clear();
|
EpicLibrary.TestManifests.Clear();
|
||||||
|
UbisoftLibrary.TestGames.Clear();
|
||||||
foreach (string dir in CreatedDirectories)
|
foreach (string dir in CreatedDirectories)
|
||||||
try { Directory.Delete(dir, true); } catch (Exception ex) { ProgramData.LogWarning($"[TestGame] Cleanup deletion failed for {dir}: {ex.Message}"); }
|
try { Directory.Delete(dir, true); } catch (Exception ex) { ProgramData.LogWarning($"[TestGame] Cleanup deletion failed for {dir}: {ex.Message}"); }
|
||||||
CreatedDirectories.Clear();
|
CreatedDirectories.Clear();
|
||||||
dlcEntries.Clear();
|
if (Directory.Exists(TestGamesRoot))
|
||||||
RefreshDlcList();
|
try { Directory.Delete(TestGamesRoot, true); } catch (Exception ex) { ProgramData.LogWarning($"[TestGame] Cleanup failed to delete TestGames root: {ex.Message}"); }
|
||||||
|
// Remove any installed.json records for test games (e.g. if an unlocker was installed to a test game)
|
||||||
|
List<InstalledGameRecord> installedRecords = ProgramData.ReadInstalledGames();
|
||||||
|
int removed = installedRecords.RemoveAll(r => r.RootDirectory?.StartsWith(TestGamesRoot, StringComparison.OrdinalIgnoreCase) == true);
|
||||||
|
if (removed > 0)
|
||||||
|
{
|
||||||
|
ProgramData.WriteInstalledGames(installedRecords);
|
||||||
|
ProgramData.Log($"[TestGame] Removed {removed} stale installed-game record(s) from test games.");
|
||||||
|
}
|
||||||
|
// Remove any Selection entries under the TestGames root so the main game list updates immediately
|
||||||
|
foreach (Selection selection in Selection.All.Keys.ToHashSet().Where(s =>
|
||||||
|
s.RootDirectory?.StartsWith(TestGamesRoot, StringComparison.OrdinalIgnoreCase) == true))
|
||||||
|
selection.Remove();
|
||||||
epicSearchResults.Clear();
|
epicSearchResults.Clear();
|
||||||
epicResultsListBox.Items.Clear();
|
epicResultsListBox.Items.Clear();
|
||||||
epicResultsListBox.Visible = false;
|
epicResultsListBox.Visible = false;
|
||||||
|
ubisoftSearchResults.Clear();
|
||||||
|
ubisoftResultsListBox.Items.Clear();
|
||||||
|
ubisoftResultsListBox.Visible = false;
|
||||||
SetStatus("All test games cleared. Press Rescan in the main window.");
|
SetStatus("All test games cleared. Press Rescan in the main window.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -337,8 +442,7 @@ internal sealed partial class TestGameForm : CustomForm
|
|||||||
private void SetStatus(string message)
|
private void SetStatus(string message)
|
||||||
{
|
{
|
||||||
statusLabel.Text = message;
|
statusLabel.Text = message;
|
||||||
statusLabel.ForeColor = message.StartsWith("✓", StringComparison.Ordinal)
|
statusLabel.ForeColor = message.StartsWith('✓') ? System.Drawing.Color.Green
|
||||||
? System.Drawing.Color.Green
|
|
||||||
: System.Drawing.Color.FromArgb(212, 212, 212);
|
: System.Drawing.Color.FromArgb(212, 212, 212);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,7 +452,7 @@ internal sealed partial class TestGameForm : CustomForm
|
|||||||
return new string(name.Select(c => invalid.Contains(c) ? '_' : c).ToArray());
|
return new string(name.Select(c => invalid.Contains(c) ? '_' : c).ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void WriteSteamApiStub(string path)
|
private static void WriteDllStub(string path)
|
||||||
{
|
{
|
||||||
byte[] mzStub =
|
byte[] mzStub =
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ namespace CreamInstaller.Platforms.Ubisoft;
|
|||||||
|
|
||||||
internal static class UbisoftLibrary
|
internal static class UbisoftLibrary
|
||||||
{
|
{
|
||||||
|
internal static readonly List<(string gameId, string name, string gameDirectory)> TestGames = [];
|
||||||
|
|
||||||
private static RegistryKey installsKey;
|
private static RegistryKey installsKey;
|
||||||
|
|
||||||
private static RegistryKey InstallsKey
|
private static RegistryKey InstallsKey
|
||||||
@@ -25,15 +27,20 @@ internal static class UbisoftLibrary
|
|||||||
{
|
{
|
||||||
List<(string gameId, string name, string gameDirectory)> games = new();
|
List<(string gameId, string name, string gameDirectory)> games = new();
|
||||||
RegistryKey installsKey = InstallsKey;
|
RegistryKey installsKey = InstallsKey;
|
||||||
if (installsKey is null)
|
if (installsKey is not null)
|
||||||
return games;
|
foreach (string gameId in installsKey.GetSubKeyNames())
|
||||||
foreach (string gameId in installsKey.GetSubKeyNames())
|
{
|
||||||
{
|
RegistryKey installKey = installsKey.OpenSubKey(gameId);
|
||||||
RegistryKey installKey = installsKey.OpenSubKey(gameId);
|
string installDir = installKey?.GetValue("InstallDir")?.ToString()?.ResolvePath();
|
||||||
string installDir = installKey?.GetValue("InstallDir")?.ToString()?.ResolvePath();
|
if (installDir is not null && games.All(g => g.gameId != gameId))
|
||||||
if (installDir is not null && games.All(g => g.gameId != gameId))
|
games.Add((gameId, new DirectoryInfo(installDir).Name, installDir));
|
||||||
games.Add((gameId, new DirectoryInfo(installDir).Name, installDir));
|
}
|
||||||
}
|
|
||||||
|
foreach ((string gameId, string name, string gameDirectory) testGame in
|
||||||
|
TestGames.Where(t => games.All(g => g.gameId != t.gameId)))
|
||||||
|
games.Add(testGame);
|
||||||
|
if (TestGames.Count > 0)
|
||||||
|
ProgramData.Log($"[Ubisoft] Injected {TestGames.Count} test game(s).");
|
||||||
|
|
||||||
return games;
|
return games;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CreamInstaller.Utility;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace CreamInstaller.Platforms.Ubisoft;
|
||||||
|
|
||||||
|
internal static class UbisoftStore
|
||||||
|
{
|
||||||
|
private const string AlgoliaAppId = "XELY3U4LOD";
|
||||||
|
private const string AlgoliaApiKey = "5638539fd9edb8f2c6b024b49ec375bd";
|
||||||
|
|
||||||
|
internal static async Task<List<(string id, string name)>> QuerySearch(string keyword)
|
||||||
|
{
|
||||||
|
List<(string, string)> results = [];
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string url = $"https://{AlgoliaAppId.ToLowerInvariant()}-dsn.algolia.net/1/indexes/*/queries" +
|
||||||
|
"?x-algolia-agent=Algolia%20for%20JavaScript%20(3.35.1)%3B%20Browser" +
|
||||||
|
$"&x-algolia-application-id={AlgoliaAppId}" +
|
||||||
|
$"&x-algolia-api-key={AlgoliaApiKey}";
|
||||||
|
|
||||||
|
var requestBody = new
|
||||||
|
{
|
||||||
|
requests = new[]
|
||||||
|
{
|
||||||
|
new
|
||||||
|
{
|
||||||
|
indexName = "us_product_suggestion",
|
||||||
|
query = keyword,
|
||||||
|
@params = "hitsPerPage=1000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
string payloadJson = JsonConvert.SerializeObject(requestBody);
|
||||||
|
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(url), content);
|
||||||
|
_ = httpResponse.EnsureSuccessStatusCode();
|
||||||
|
string response = await httpResponse.Content.ReadAsStringAsync();
|
||||||
|
JObject root = JObject.Parse(response);
|
||||||
|
JToken hits = root["results"]?[0]?["hits"];
|
||||||
|
if (hits is null)
|
||||||
|
return results;
|
||||||
|
|
||||||
|
// Extract significant query words (2+ chars) for substring filtering
|
||||||
|
string[] queryTerms = keyword.Split([' '], StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Where(t => t.Length >= 2)
|
||||||
|
.Select(t => t.ToLowerInvariant())
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
foreach (JToken hit in hits)
|
||||||
|
{
|
||||||
|
string title = hit["title"]?.ToString();
|
||||||
|
string id = hit["objectID"]?.ToString();
|
||||||
|
string platform = hit["Platform"]?.ToString();
|
||||||
|
string productType = hit["productTypeSelect"]?.ToString();
|
||||||
|
string edition = hit["Edition"]?.ToString() ?? "";
|
||||||
|
string game = hit["Game"]?.ToString() ?? "";
|
||||||
|
|
||||||
|
// Skip non-game items (currency, merchandise, etc.)
|
||||||
|
bool isGame = string.IsNullOrEmpty(productType) ||
|
||||||
|
string.Equals(productType, "game", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
// Skip DLCs, extensions, season passes, currency packs
|
||||||
|
if (!string.IsNullOrEmpty(edition) &&
|
||||||
|
(edition.Contains("Extension", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
edition.Contains("Season Pass", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
edition.Contains("Currency", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
edition.Contains("Pack", StringComparison.OrdinalIgnoreCase)))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Also skip items not available on PC
|
||||||
|
bool isPc = string.IsNullOrEmpty(platform) ||
|
||||||
|
platform.Contains("PC", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(title) || string.IsNullOrWhiteSpace(id)
|
||||||
|
|| !isGame || !isPc || results.Any(r => r.Item1 == id))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Post-filter: at least one query word must appear as a substring
|
||||||
|
// in the title or Game field. This eliminates Algolia's overly
|
||||||
|
// aggressive fuzzy/typo matches (e.g. "AssAss" → "Star Wars").
|
||||||
|
if (queryTerms.Length > 0)
|
||||||
|
{
|
||||||
|
string lowerTitle = title.ToLowerInvariant();
|
||||||
|
string lowerGame = game.ToLowerInvariant();
|
||||||
|
if (!queryTerms.Any(t => lowerTitle.Contains(t) || lowerGame.Contains(t)))
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
results.Add((id, title));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignored — search failure is non-fatal
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user