mirror of
https://github.com/FroggMaster/CreamInstaller.git
synced 2026-06-12 11:01:23 -07:00
Recall Installed DLC Locker for Games / Labels for DLC Unlockers / Additional Extra Protection Changes
- Added method to remember the games you've installed so they don't need to be reselected. - Added labels for to display if CreamAPI / SmokeAPI DLC Unlockers are installed - Logic to ensure the Extra Protection checkbox displays at the appropriate time - Added logic to read Extra Protection state from cream_api.ini when CreamAPI is detected
This commit is contained in:
@@ -257,7 +257,58 @@ internal sealed class CustomTreeView : TreeView
|
||||
graphics.FillRectangle(brush, bounds);
|
||||
}
|
||||
|
||||
if (!Program.UseSmokeAPI)
|
||||
// Unlocker badge
|
||||
if (selection.InstalledUnlocker != InstalledUnlocker.None)
|
||||
{
|
||||
string badgeText = selection.InstalledUnlocker.ToString();
|
||||
size = TextRenderer.MeasureText(graphics, badgeText, font, Size.Empty, TextFormatFlags.NoPadding);
|
||||
const int badgePadding = 3;
|
||||
Rectangle badgeBounds = new(bounds.X + bounds.Width + 2, bounds.Y + 1, size.Width + badgePadding * 2, bounds.Height - 2);
|
||||
selectionBounds = new(selectionBounds.Location, selectionBounds.Size + new Size(badgeBounds.Width + 2, 0));
|
||||
|
||||
// Get theme-appropriate colors for each unlocker from ThemeManager
|
||||
Color badgeBack, badgeBorder;
|
||||
switch (selection.InstalledUnlocker)
|
||||
{
|
||||
case InstalledUnlocker.SmokeAPI:
|
||||
badgeBack = highlighted
|
||||
? ThemeManager.SmokeAPIBadgeBackgroundHighlightColor
|
||||
: ThemeManager.SmokeAPIBadgeBackgroundColor;
|
||||
badgeBorder = ThemeManager.SmokeAPIBadgeBorderColor;
|
||||
break;
|
||||
case InstalledUnlocker.CreamAPI:
|
||||
badgeBack = highlighted
|
||||
? ThemeManager.CreamAPIBadgeBackgroundHighlightColor
|
||||
: ThemeManager.CreamAPIBadgeBackgroundColor;
|
||||
badgeBorder = ThemeManager.CreamAPIBadgeBorderColor;
|
||||
break;
|
||||
default:
|
||||
badgeBack = highlighted
|
||||
? ThemeManager.DefaultBadgeBackgroundHighlightColor
|
||||
: ThemeManager.DefaultBadgeBackgroundColor;
|
||||
badgeBorder = ThemeManager.DefaultBadgeBorderColor;
|
||||
break;
|
||||
}
|
||||
|
||||
using (SolidBrush badgeBrush = new(badgeBack))
|
||||
graphics.FillRectangle(badgeBrush, badgeBounds);
|
||||
using (Pen badgePen = new(badgeBorder))
|
||||
graphics.DrawRectangle(badgePen, badgeBounds);
|
||||
TextRenderer.DrawText(graphics, badgeText, font,
|
||||
new Point(badgeBounds.X + badgePadding, badgeBounds.Y + 1),
|
||||
Color.White, TextFormatFlags.NoPadding);
|
||||
bounds = bounds with { X = badgeBounds.X, Width = badgeBounds.Width + 2 };
|
||||
}
|
||||
|
||||
// Show Extra Protection checkbox for CreamAPI:
|
||||
// - When CreamAPI is installed, OR
|
||||
// - When no unlocker is installed yet AND user hasn't enabled SmokeAPI mode, OR
|
||||
// - When SmokeAPI is installed BUT user has disabled SmokeAPI mode (about to replace with CreamAPI)
|
||||
bool showExtraProtection = selection.InstalledUnlocker == InstalledUnlocker.CreamAPI ||
|
||||
(selection.InstalledUnlocker == InstalledUnlocker.None && !Program.UseSmokeAPI) ||
|
||||
(selection.InstalledUnlocker == InstalledUnlocker.SmokeAPI && !Program.UseSmokeAPI);
|
||||
|
||||
if (showExtraProtection)
|
||||
{
|
||||
CheckBoxState extraProtState = selection.UseExtraProtection
|
||||
? (Enabled ? CheckBoxState.CheckedNormal : CheckBoxState.CheckedDisabled)
|
||||
|
||||
@@ -9,7 +9,6 @@ using CreamInstaller.Resources;
|
||||
using CreamInstaller.Utility;
|
||||
using static CreamInstaller.Platforms.Paradox.ParadoxLauncher;
|
||||
using static CreamInstaller.Resources.Resources;
|
||||
|
||||
namespace CreamInstaller.Forms;
|
||||
|
||||
internal sealed partial class InstallForm : CustomForm
|
||||
@@ -351,6 +350,41 @@ internal sealed partial class InstallForm : CustomForm
|
||||
++completeOperationsCount;
|
||||
}
|
||||
|
||||
// Persist install/uninstall results
|
||||
foreach (Selection selection in Selection.AllEnabled)
|
||||
{
|
||||
if (uninstalling)
|
||||
{
|
||||
selection.InstalledUnlocker = InstalledUnlocker.None;
|
||||
ProgramData.RemoveInstalledGame(selection.Platform, selection.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
InstalledUnlocker unlocker = selection.DetectInstalledUnlocker();
|
||||
selection.InstalledUnlocker = unlocker;
|
||||
if (unlocker != InstalledUnlocker.None)
|
||||
ProgramData.UpsertInstalledGame(new InstalledGameRecord
|
||||
{
|
||||
Platform = selection.Platform,
|
||||
Id = selection.Id,
|
||||
Name = selection.Name,
|
||||
RootDirectory = selection.RootDirectory,
|
||||
Unlocker = unlocker,
|
||||
UseProxy = selection.UseProxy,
|
||||
Proxy = selection.Proxy,
|
||||
UseExtraProtection = selection.UseExtraProtection,
|
||||
Dlc = selection.DLC.Select(dlc => new InstalledDlcRecord
|
||||
{
|
||||
DlcType = dlc.Type.ToString(),
|
||||
Id = dlc.Id,
|
||||
Name = dlc.Name
|
||||
}).ToList()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
SelectForm.Current?.Invoke(() => SelectForm.Current?.InvalidateGameList());
|
||||
|
||||
Program.Cleanup();
|
||||
int activeCount = activeSelections.Count;
|
||||
if (activeCount > 0)
|
||||
|
||||
@@ -689,6 +689,7 @@ internal sealed partial class SelectForm : CustomForm
|
||||
}
|
||||
|
||||
OnLoadSelections(null, null);
|
||||
await LoadSavedInstalledGames();
|
||||
HideProgressBar();
|
||||
selectionTreeView.Enabled = !Selection.All.IsEmpty;
|
||||
allCheckBox.Enabled = selectionTreeView.Enabled;
|
||||
@@ -974,6 +975,79 @@ internal sealed partial class SelectForm : CustomForm
|
||||
contextMenuStrip.Refresh();
|
||||
});
|
||||
|
||||
private async Task LoadSavedInstalledGames()
|
||||
{
|
||||
List<InstalledGameRecord> saved = ProgramData.ReadInstalledGames();
|
||||
if (saved.Count == 0)
|
||||
return;
|
||||
|
||||
List<InstalledGameRecord> toRemove = [];
|
||||
foreach (InstalledGameRecord record in saved)
|
||||
{
|
||||
// Already in the list from this scan — just ensure unlocker is set
|
||||
Selection existing = Selection.FromId(record.Platform, record.Id);
|
||||
if (existing is not null)
|
||||
{
|
||||
if (existing.InstalledUnlocker == InstalledUnlocker.None)
|
||||
existing.InstalledUnlocker = record.Unlocker;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Root directory no longer exists — mark for removal
|
||||
if (!record.RootDirectory.DirectoryExists())
|
||||
{
|
||||
toRemove.Add(record);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Reconstruct a minimal Selection from the saved record
|
||||
HashSet<string> dllDirectories =
|
||||
await record.RootDirectory.GetDllDirectoriesFromGameDirectory(record.Platform);
|
||||
if (dllDirectories is null || dllDirectories.Count == 0)
|
||||
{
|
||||
toRemove.Add(record);
|
||||
continue;
|
||||
}
|
||||
|
||||
List<(string directory, BinaryType binaryType)> executableDirectories =
|
||||
await record.RootDirectory.GetExecutableDirectories(true);
|
||||
|
||||
Selection selection = Selection.FromId(record.Platform, record.Id) ?? Selection.GetOrCreate(record.Platform, record.Id, record.Name,
|
||||
record.RootDirectory, dllDirectories, executableDirectories);
|
||||
selection.InstalledUnlocker = selection.DetectInstalledUnlocker();
|
||||
if (selection.InstalledUnlocker == InstalledUnlocker.None)
|
||||
selection.InstalledUnlocker = record.Unlocker;
|
||||
selection.UseProxy = record.UseProxy;
|
||||
selection.Proxy = record.Proxy;
|
||||
selection.UseExtraProtection = record.UseExtraProtection;
|
||||
|
||||
Invoke(delegate
|
||||
{
|
||||
if (selection.TreeNode.TreeView is null)
|
||||
_ = selectionTreeView.Nodes.Add(selection.TreeNode);
|
||||
|
||||
// Restore DLC children from saved record
|
||||
if (record.Dlc != null && record.Dlc.Count > 0)
|
||||
{
|
||||
foreach (InstalledDlcRecord dlcRecord in record.Dlc)
|
||||
{
|
||||
if (!Enum.TryParse(dlcRecord.DlcType, out DLCType dlcType))
|
||||
continue;
|
||||
SelectionDLC dlc = SelectionDLC.GetOrCreate(dlcType, record.Id, dlcRecord.Id, dlcRecord.Name);
|
||||
dlc.Selection = selection;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Clean up records for games that are gone
|
||||
if (toRemove.Count > 0)
|
||||
{
|
||||
List<InstalledGameRecord> updated = saved.Except(toRemove).ToList();
|
||||
ProgramData.WriteInstalledGames(updated);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLoad(object sender, EventArgs _)
|
||||
{
|
||||
retry:
|
||||
@@ -1177,6 +1251,21 @@ internal sealed partial class SelectForm : CustomForm
|
||||
ProgramData.WriteExtraProtectionChoices(extraProtectionChoices);
|
||||
loadButton.Enabled = CanLoadSelections();
|
||||
|
||||
// Detect installed unlockers from disk for all selections
|
||||
foreach (Selection selection in Selection.All.Keys)
|
||||
selection.InstalledUnlocker = selection.DetectInstalledUnlocker();
|
||||
|
||||
// Merge with persisted installed game records for any saved games not yet having a detected unlocker
|
||||
List<InstalledGameRecord> installedRecords = ProgramData.ReadInstalledGames();
|
||||
foreach (InstalledGameRecord record in installedRecords)
|
||||
{
|
||||
Selection selection = Selection.FromId(record.Platform, record.Id);
|
||||
if (selection is null)
|
||||
continue;
|
||||
if (selection.InstalledUnlocker == InstalledUnlocker.None && record.Unlocker != InstalledUnlocker.None)
|
||||
selection.InstalledUnlocker = record.Unlocker;
|
||||
}
|
||||
|
||||
OnProxyChanged();
|
||||
}
|
||||
|
||||
@@ -1206,6 +1295,8 @@ internal sealed partial class SelectForm : CustomForm
|
||||
OnProxyChanged();
|
||||
}
|
||||
|
||||
internal void InvalidateGameList() => selectionTreeView.Invalidate();
|
||||
|
||||
internal void OnProxyChanged()
|
||||
{
|
||||
selectionTreeView.Invalidate();
|
||||
@@ -1257,7 +1348,9 @@ internal sealed partial class SelectForm : CustomForm
|
||||
private void OnUseSmokeAPICheckBoxChanged(object sender, EventArgs e)
|
||||
{
|
||||
Program.UseSmokeAPI = useSmokeAPICheckBox.Checked;
|
||||
OnLoad(forceProvideChoices: false);
|
||||
selectionTreeView.Invalidate();
|
||||
saveButton.Enabled = CanSaveSelections();
|
||||
resetButton.Enabled = CanResetSelections();
|
||||
}
|
||||
|
||||
private void OnUseSmokeAPIHelpButtonClicked(object sender, EventArgs e)
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using CreamInstaller.Forms;
|
||||
using CreamInstaller.Platforms.Epic;
|
||||
using CreamInstaller.Platforms.Steam;
|
||||
using CreamInstaller.Platforms.Ubisoft;
|
||||
using CreamInstaller.Resources;
|
||||
using CreamInstaller.Utility;
|
||||
using static CreamInstaller.Resources.Resources;
|
||||
|
||||
namespace CreamInstaller;
|
||||
|
||||
public enum Platform
|
||||
@@ -41,6 +44,7 @@ internal sealed class Selection : IEquatable<Selection>
|
||||
internal string Publisher;
|
||||
internal string SubIcon;
|
||||
internal string Website;
|
||||
internal InstalledUnlocker InstalledUnlocker;
|
||||
|
||||
internal IEnumerable<string> GetAvailableProxies()
|
||||
{
|
||||
@@ -135,6 +139,100 @@ internal sealed class Selection : IEquatable<Selection>
|
||||
internal static Selection FromId(Platform platform, string gameId) =>
|
||||
All.Keys.FirstOrDefault(s => s.Platform == platform && s.Id == gameId);
|
||||
|
||||
internal InstalledUnlocker DetectInstalledUnlocker()
|
||||
{
|
||||
foreach (string directory in DllDirectories)
|
||||
{
|
||||
if (Platform is Platform.Steam or Platform.Paradox)
|
||||
{
|
||||
// Use uniquely-named config files to distinguish CreamAPI from SmokeAPI.
|
||||
// Both share steam_api_o.dll so the _o files alone are ambiguous.
|
||||
directory.GetSmokeApiComponents(out _, out _, out _, out _, out string smokeOldConfig,
|
||||
out string smokeConfig, out _, out _, out _);
|
||||
if (smokeConfig.FileExists() || smokeOldConfig.FileExists())
|
||||
return InstalledUnlocker.SmokeAPI;
|
||||
|
||||
directory.GetCreamApiComponents(out _, out _, out _, out _, out string creamConfig);
|
||||
if (creamConfig.FileExists())
|
||||
{
|
||||
ReadCreamApiConfig(creamConfig);
|
||||
return InstalledUnlocker.CreamAPI;
|
||||
}
|
||||
|
||||
// Fallback: config was deleted but _o files remain — identify by replacement DLL content
|
||||
directory.GetSmokeApiComponents(out string smokeApi32, out string api32_o,
|
||||
out string smokeApi64, out string api64_o, out _, out _, out _, out _, out _);
|
||||
if (api32_o.FileExists() || api64_o.FileExists())
|
||||
{
|
||||
if ((smokeApi32.FileExists() && smokeApi32.IsResourceFile(ResourceIdentifier.Steamworks32))
|
||||
|| (smokeApi64.FileExists() && smokeApi64.IsResourceFile(ResourceIdentifier.Steamworks64)))
|
||||
return InstalledUnlocker.SmokeAPI;
|
||||
return InstalledUnlocker.CreamAPI;
|
||||
}
|
||||
}
|
||||
|
||||
if (Platform is Platform.Epic or Platform.Paradox)
|
||||
{
|
||||
directory.GetScreamApiComponents(out _, out string api32_o, out _, out string api64_o,
|
||||
out _, out string config, out _, out _);
|
||||
if (config.FileExists() || api32_o.FileExists() || api64_o.FileExists())
|
||||
return InstalledUnlocker.ScreamAPI;
|
||||
}
|
||||
|
||||
if (Platform is Platform.Ubisoft)
|
||||
{
|
||||
directory.GetUplayR1Components(out _, out string api32_o, out _, out string api64_o,
|
||||
out string config, out _);
|
||||
if (config.FileExists() || api32_o.FileExists() || api64_o.FileExists())
|
||||
return InstalledUnlocker.UplayR1;
|
||||
directory.GetUplayR2Components(out _, out _, out _, out api32_o, out _, out api64_o,
|
||||
out config, out _);
|
||||
if (config.FileExists() || api32_o.FileExists() || api64_o.FileExists())
|
||||
return InstalledUnlocker.UplayR2;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ((string directory, _) in ExecutableDirectories)
|
||||
{
|
||||
directory.GetKoaloaderComponents(out _, out string config, out _);
|
||||
if (directory.GetKoaloaderProxies().Any(proxy =>
|
||||
proxy.FileExists() && proxy.IsResourceFile(ResourceIdentifier.Koaloader))
|
||||
|| config.FileExists())
|
||||
return InstalledUnlocker.Koaloader;
|
||||
}
|
||||
|
||||
return InstalledUnlocker.None;
|
||||
}
|
||||
|
||||
private void ReadCreamApiConfig(string configPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!configPath.FileExists())
|
||||
return;
|
||||
|
||||
string[] lines = File.ReadAllLines(configPath);
|
||||
foreach (string line in lines)
|
||||
{
|
||||
string trimmed = line.Trim();
|
||||
if (trimmed.StartsWith("extraprotection", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string[] parts = trimmed.Split('=');
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
string value = parts[1].Trim();
|
||||
UseExtraProtection = value.Equals("true", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// If we can't read the config, leave UseExtraProtection at its default value
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals(object obj) => ReferenceEquals(this, obj) || obj is Selection other && Equals(other);
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(Id, (int)Platform);
|
||||
|
||||
@@ -22,17 +22,20 @@ internal sealed class SelectionDLC : IEquatable<SelectionDLC>
|
||||
internal readonly string Name;
|
||||
internal readonly TreeNode TreeNode;
|
||||
internal readonly DLCType Type;
|
||||
internal readonly string GameId;
|
||||
internal string Icon;
|
||||
internal string Product;
|
||||
internal string Publisher;
|
||||
private Selection selection;
|
||||
|
||||
private SelectionDLC(DLCType type, string id, string name)
|
||||
private SelectionDLC(DLCType type, string gameId, string id, string name)
|
||||
{
|
||||
Type = type;
|
||||
GameId = gameId;
|
||||
Id = id;
|
||||
Name = name;
|
||||
TreeNode = new() { Tag = Type, Name = Id, Text = Name };
|
||||
_ = All.TryAdd(this, 0);
|
||||
}
|
||||
|
||||
internal bool Enabled
|
||||
@@ -65,15 +68,15 @@ internal sealed class SelectionDLC : IEquatable<SelectionDLC>
|
||||
|
||||
public bool Equals(SelectionDLC other)
|
||||
=> other is not null && (ReferenceEquals(this, other) ||
|
||||
Type == other.Type && Selection?.Id == other.Selection?.Id && Id == other.Id);
|
||||
Type == other.Type && GameId == other.GameId && Id == other.Id);
|
||||
|
||||
internal static SelectionDLC GetOrCreate(DLCType type, string gameId, string id, string name)
|
||||
=> FromId(type, gameId, id) ?? new SelectionDLC(type, id, name);
|
||||
=> FromId(type, gameId, id) ?? new SelectionDLC(type, gameId, id, name);
|
||||
|
||||
internal static SelectionDLC FromId(DLCType type, string gameId, string dlcId)
|
||||
=> All.Keys.FirstOrDefault(dlc => dlc.Type == type && dlc.Selection?.Id == gameId && dlc.Id == dlcId);
|
||||
=> All.Keys.FirstOrDefault(dlc => dlc.Type == type && dlc.GameId == gameId && dlc.Id == dlcId);
|
||||
|
||||
public override bool Equals(object obj) => ReferenceEquals(this, obj) || obj is SelectionDLC other && Equals(other);
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine((int)Type, Selection?.Id, Id);
|
||||
public override int GetHashCode() => HashCode.Combine((int)Type, GameId, Id);
|
||||
}
|
||||
@@ -6,10 +6,42 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using CreamInstaller;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CreamInstaller.Utility;
|
||||
|
||||
internal enum InstalledUnlocker
|
||||
{
|
||||
None = 0,
|
||||
CreamAPI,
|
||||
SmokeAPI,
|
||||
ScreamAPI,
|
||||
UplayR1,
|
||||
UplayR2,
|
||||
Koaloader
|
||||
}
|
||||
|
||||
internal sealed class InstalledDlcRecord
|
||||
{
|
||||
public string DlcType { get; set; }
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InstalledGameRecord
|
||||
{
|
||||
public Platform Platform { get; set; }
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string RootDirectory { get; set; }
|
||||
public InstalledUnlocker Unlocker { get; set; }
|
||||
public bool UseProxy { get; set; }
|
||||
public string Proxy { get; set; }
|
||||
public bool UseExtraProtection { get; set; }
|
||||
public List<InstalledDlcRecord> Dlc { get; set; } = [];
|
||||
}
|
||||
|
||||
internal static class ProgramData
|
||||
{
|
||||
private static readonly string DirectoryPathOld =
|
||||
@@ -30,6 +62,7 @@ internal static class ProgramData
|
||||
private static readonly string DlcChoicesPath = DirectoryPath + @"\dlc.json";
|
||||
private static readonly string KoaloaderProxyChoicesPath = DirectoryPath + @"\proxies.json";
|
||||
private static readonly string ExtraProtectionChoicesPath = DirectoryPath + @"\extraprotection.json";
|
||||
private static readonly string InstalledGamesPath = DirectoryPath + @"\installed.json";
|
||||
|
||||
internal static readonly string LogPath = DirectoryPath + @"\scan.log";
|
||||
|
||||
@@ -263,4 +296,52 @@ internal static class ProgramData
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
internal static List<InstalledGameRecord> ReadInstalledGames()
|
||||
{
|
||||
if (InstalledGamesPath.FileExists())
|
||||
try
|
||||
{
|
||||
if (JsonConvert.DeserializeObject<List<InstalledGameRecord>>(InstalledGamesPath.ReadFile()) is
|
||||
{ } records)
|
||||
return records;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
internal static void WriteInstalledGames(IEnumerable<InstalledGameRecord> records)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<InstalledGameRecord> list = records?.ToList() ?? [];
|
||||
if (list.Count == 0)
|
||||
InstalledGamesPath.DeleteFile();
|
||||
else
|
||||
InstalledGamesPath.WriteFile(JsonConvert.SerializeObject(list, Formatting.Indented));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
internal static void UpsertInstalledGame(InstalledGameRecord record)
|
||||
{
|
||||
List<InstalledGameRecord> records = ReadInstalledGames();
|
||||
_ = records.RemoveAll(r => r.Platform == record.Platform && r.Id == record.Id);
|
||||
records.Add(record);
|
||||
WriteInstalledGames(records);
|
||||
}
|
||||
|
||||
internal static void RemoveInstalledGame(Platform platform, string id)
|
||||
{
|
||||
List<InstalledGameRecord> records = ReadInstalledGames();
|
||||
if (records.RemoveAll(r => r.Platform == platform && r.Id == id) > 0)
|
||||
WriteInstalledGames(records);
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,17 @@ internal static class ThemeManager
|
||||
private static readonly Color DarkComboBorder = DarkBorder; // #3F3F46
|
||||
private static readonly Color DarkComboText = DarkFore; // #D4D4D4
|
||||
|
||||
// Badge colors for unlockers
|
||||
private static readonly Color CreamAPIBadgeBack = ColorTranslator.FromHtml("#C8A078"); // Creamy latte
|
||||
private static readonly Color CreamAPIBadgeBackHighlight = ColorTranslator.FromHtml("#B48C64");
|
||||
private static readonly Color CreamAPIBadgeBorder = ColorTranslator.FromHtml("#DCB48C");
|
||||
private static readonly Color SmokeAPIBadgeBack = ColorTranslator.FromHtml("#69696E"); // Smoky grey
|
||||
private static readonly Color SmokeAPIBadgeBackHighlight = ColorTranslator.FromHtml("#5A5A5F");
|
||||
private static readonly Color SmokeAPIBadgeBorder = ColorTranslator.FromHtml("#8C8C91");
|
||||
private static readonly Color DefaultBadgeBack = ColorTranslator.FromHtml("#008C46"); // Default green
|
||||
private static readonly Color DefaultBadgeBackHighlight = ColorTranslator.FromHtml("#00783C");
|
||||
private static readonly Color DefaultBadgeBorder = ColorTranslator.FromHtml("#00B45A");
|
||||
|
||||
// ----------------------------
|
||||
// Light mode colors (system defaults)
|
||||
// ----------------------------
|
||||
@@ -76,6 +87,17 @@ internal static class ThemeManager
|
||||
|
||||
internal static Color CustomTreeViewComboTextColor => IsDark ? DarkComboText : LightComboText;
|
||||
|
||||
// Badge colors for unlockers
|
||||
internal static Color CreamAPIBadgeBackgroundColor => CreamAPIBadgeBack;
|
||||
internal static Color CreamAPIBadgeBackgroundHighlightColor => CreamAPIBadgeBackHighlight;
|
||||
internal static Color CreamAPIBadgeBorderColor => CreamAPIBadgeBorder;
|
||||
internal static Color SmokeAPIBadgeBackgroundColor => SmokeAPIBadgeBack;
|
||||
internal static Color SmokeAPIBadgeBackgroundHighlightColor => SmokeAPIBadgeBackHighlight;
|
||||
internal static Color SmokeAPIBadgeBorderColor => SmokeAPIBadgeBorder;
|
||||
internal static Color DefaultBadgeBackgroundColor => DefaultBadgeBack;
|
||||
internal static Color DefaultBadgeBackgroundHighlightColor => DefaultBadgeBackHighlight;
|
||||
internal static Color DefaultBadgeBorderColor => DefaultBadgeBorder;
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Public / Internal API
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user