mirror of
https://github.com/FroggMaster/CreamInstaller.git
synced 2026-07-28 14:47:04 -07:00
- Change Refresh Queries to "Refresh Game Data" or "Refresh DLC Data" depending on what is right clicked upon - Change Refresh Queries to only Refresh the selected Game/DLC Data, rather than all games data. - Remove constant API retries, reduce attempted to 1 it's pointless when we hammer the API to retry over and over again. We get rate limited and even less data is returned. (Should simulatenously improve scanning to some extent - Limit the amount of parallel API requests that we perform to avoid hammering the API (probably will still get rated limited if you scan a shit load of games though) - Remove timeout on DLC name queries, was killing SteamCMD before it could resolve the name (Should ensure we get less Unknown DLC data) - Add shared helper for DLC name resolution, consolidates duplicate StoreAPI and SteamCMD fallback (Again, should hopefully ensure there's less unknown DLC data) - Add shared method for game record construction, removes duplicate code in install and persist paths - Store DLCs per-game by ID in memory during scan to prevent duplicate DLC entries from displaying - Read DLC names from CreamAPI configs alongside IDs for better DLC name matching before API requests
332 lines
14 KiB
C#
332 lines
14 KiB
C#
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
|
|
{
|
|
None = 0,
|
|
Paradox,
|
|
Steam,
|
|
Epic,
|
|
Ubisoft
|
|
}
|
|
|
|
internal sealed class Selection : IEquatable<Selection>
|
|
{
|
|
internal const string DefaultProxy = "winmm";
|
|
|
|
internal static readonly ConcurrentDictionary<Selection, byte> All = new();
|
|
|
|
internal readonly ConcurrentDictionary<string, SelectionDLC> DLCById = new();
|
|
internal readonly HashSet<string> DllDirectories;
|
|
internal readonly List<(string directory, BinaryType binaryType)> ExecutableDirectories;
|
|
internal readonly HashSet<Selection> ExtraSelections = [];
|
|
internal readonly string Id;
|
|
internal readonly string Name;
|
|
internal readonly Platform Platform;
|
|
internal readonly string RootDirectory;
|
|
internal readonly TreeNode TreeNode;
|
|
internal string Icon;
|
|
internal bool UseExtraProtection;
|
|
internal bool UseProxy;
|
|
internal string Proxy;
|
|
internal string Product;
|
|
internal string Publisher;
|
|
internal string SubIcon;
|
|
internal string Website;
|
|
internal InstalledUnlocker InstalledUnlocker;
|
|
internal bool SteamApiDllMissing;
|
|
|
|
internal IEnumerable<string> GetAvailableProxies()
|
|
{
|
|
if (!Program.UseSmokeAPI && Platform is Platform.Steam or Platform.Paradox)
|
|
return CreamAPI.ProxyDLLs;
|
|
if (Program.UseSmokeAPI && Platform is Platform.Steam or Platform.Paradox)
|
|
return SmokeAPI.ProxyDLLs;
|
|
return EmbeddedResources.Where(r => r.StartsWith("Koaloader", StringComparison.Ordinal)).Select(p =>
|
|
{
|
|
p.GetProxyInfoFromIdentifier(out string proxyName, out _);
|
|
return proxyName;
|
|
}).ToHashSet();
|
|
}
|
|
|
|
private Selection(Platform platform, string id, string name, string rootDirectory, HashSet<string> dllDirectories,
|
|
List<(string directory, BinaryType binaryType)> executableDirectories)
|
|
{
|
|
Platform = platform;
|
|
Id = id;
|
|
Name = name;
|
|
RootDirectory = rootDirectory;
|
|
DllDirectories = dllDirectories;
|
|
ExecutableDirectories = executableDirectories;
|
|
_ = All.TryAdd(this, default);
|
|
TreeNode = new() { Tag = Platform, Name = Id, Text = Name };
|
|
MainForm selectForm = MainForm.Current;
|
|
if (selectForm is null)
|
|
return;
|
|
Enabled = selectForm.allCheckBox.Checked;
|
|
UseProxy = false;
|
|
}
|
|
|
|
internal static IEnumerable<Selection> AllEnabled => All.Keys.Where(s => s.Enabled);
|
|
|
|
internal bool Enabled
|
|
{
|
|
get => TreeNode.Checked;
|
|
set => TreeNode.Checked = value;
|
|
}
|
|
|
|
internal IEnumerable<SelectionDLC> DLC => DLCById.Values;
|
|
|
|
internal InstalledGameRecord ToInstalledGameRecord(InstalledGameRecord existing = null) => new()
|
|
{
|
|
Platform = Platform,
|
|
Id = Id,
|
|
Name = Name,
|
|
RootDirectory = RootDirectory,
|
|
Unlocker = InstalledUnlocker,
|
|
UseProxy = existing?.UseProxy ?? UseProxy,
|
|
ProxyDllName = (existing?.UseProxy ?? UseProxy) ? (existing?.ProxyDllName ?? Proxy ?? DefaultProxy) : null,
|
|
UseExtraProtection = existing?.UseExtraProtection ?? UseExtraProtection,
|
|
Dlc = DLC.Select(dlc => new InstalledDlcRecord
|
|
{
|
|
DlcType = dlc.Type.ToString(),
|
|
Id = dlc.Id,
|
|
Name = dlc.Name,
|
|
Enabled = dlc.Enabled
|
|
}).ToList()
|
|
};
|
|
|
|
public bool Equals(Selection other) => other is not null &&
|
|
(ReferenceEquals(this, other) ||
|
|
Id == other.Id && Platform == other.Platform);
|
|
|
|
internal static Selection GetOrCreate(Platform platform, string id, string name, string rootDirectory,
|
|
HashSet<string> dllDirectories,
|
|
List<(string directory, BinaryType binaryType)> executableDirectories)
|
|
=> FromId(platform, id) ??
|
|
new Selection(platform, id, name, rootDirectory, dllDirectories, executableDirectories);
|
|
|
|
internal void Remove()
|
|
{
|
|
_ = All.TryRemove(this, out _);
|
|
TreeNode.Remove();
|
|
foreach (SelectionDLC dlc in DLCById.Values.ToList())
|
|
dlc.Selection = null;
|
|
}
|
|
|
|
private void Validate(List<(Platform platform, string id, string name)> programsToScan)
|
|
{
|
|
if (programsToScan is null || !programsToScan.Any(p => p.platform == Platform && p.id == Id))
|
|
{
|
|
Remove();
|
|
return;
|
|
}
|
|
|
|
if (Program.IsGameBlocked(Name, RootDirectory))
|
|
{
|
|
Remove();
|
|
return;
|
|
}
|
|
|
|
if (!RootDirectory.DirectoryExists())
|
|
{
|
|
Remove();
|
|
return;
|
|
}
|
|
|
|
if (!SteamApiDllMissing)
|
|
{
|
|
_ = DllDirectories.RemoveWhere(directory => !directory.DirectoryExists());
|
|
if (DllDirectories.Count < 1)
|
|
Remove();
|
|
}
|
|
}
|
|
|
|
internal static void ValidateAll(List<(Platform platform, string id, string name)> programsToScan)
|
|
{
|
|
foreach (Selection selection in All.Keys.ToHashSet())
|
|
selection.Validate(programsToScan);
|
|
}
|
|
|
|
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())
|
|
{
|
|
ProgramData.Log.Info($"[Unlocker] SmokeAPI detected | Game: {Name} ({Id})", LogDestination.Unlocker);
|
|
return InstalledUnlocker.SmokeAPI;
|
|
}
|
|
|
|
directory.GetCreamApiComponents(out _, out _, out _, out _, out string creamConfig);
|
|
if (creamConfig.FileExists())
|
|
{
|
|
ProgramData.Log.Info($"[Unlocker] CreamAPI detected | Game: {Name} ({Id})", LogDestination.Unlocker);
|
|
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)))
|
|
{
|
|
ProgramData.Log.Info($"[Unlocker] SmokeAPI detected (via _o files) | Game: {Name} ({Id})", LogDestination.Unlocker);
|
|
return InstalledUnlocker.SmokeAPI;
|
|
}
|
|
ProgramData.Log.Info($"[Unlocker] CreamAPI detected (via _o files) | Game: {Name} ({Id})", LogDestination.Unlocker);
|
|
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())
|
|
{
|
|
ProgramData.Log.Info($"[Unlocker] ScreamAPI detected | Game: {Name} ({Id})", LogDestination.Unlocker);
|
|
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())
|
|
{
|
|
ProgramData.Log.Info($"[Unlocker] UplayR1 detected | Game: {Name} ({Id})", LogDestination.Unlocker);
|
|
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())
|
|
{
|
|
ProgramData.Log.Info($"[Unlocker] UplayR2 detected | Game: {Name} ({Id})", LogDestination.Unlocker);
|
|
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())
|
|
{
|
|
ProgramData.Log.Info($"[Unlocker] Koaloader detected | Game: {Name} ({Id})", LogDestination.Unlocker);
|
|
return InstalledUnlocker.Koaloader;
|
|
}
|
|
|
|
if (Platform is Platform.Steam or Platform.Paradox)
|
|
{
|
|
directory.GetSmokeApiComponents(out _, out _, out _, out _, out _, out string smokeConfig, out _, out _, out _);
|
|
if (smokeConfig.FileExists())
|
|
{
|
|
ProgramData.Log.Info($"[Unlocker] SmokeAPI detected (proxy) | Game: {Name} ({Id})", LogDestination.Unlocker);
|
|
return InstalledUnlocker.SmokeAPI;
|
|
}
|
|
directory.GetCreamApiComponents(out _, out _, out _, out _, out string creamConfig);
|
|
if (creamConfig.FileExists())
|
|
{
|
|
ProgramData.Log.Info($"[Unlocker] CreamAPI detected (proxy) | Game: {Name} ({Id})", LogDestination.Unlocker);
|
|
return InstalledUnlocker.CreamAPI;
|
|
}
|
|
if (directory.GetSmokeApiProxies().Any(proxy =>
|
|
proxy.FileExists() && (proxy.IsResourceFile(ResourceIdentifier.Steamworks32) ||
|
|
proxy.IsResourceFile(ResourceIdentifier.Steamworks64))))
|
|
{
|
|
ProgramData.Log.Info($"[Unlocker] SmokeAPI proxy DLL detected | Game: {Name} ({Id})", LogDestination.Unlocker);
|
|
return InstalledUnlocker.SmokeAPI;
|
|
}
|
|
if (directory.GetCreamApiProxies().Any(proxy =>
|
|
proxy.FileExists() && (proxy.IsResourceFile(ResourceIdentifier.Steamworks32) ||
|
|
proxy.IsResourceFile(ResourceIdentifier.Steamworks64))))
|
|
{
|
|
ProgramData.Log.Info($"[Unlocker] CreamAPI proxy DLL detected | Game: {Name} ({Id})", LogDestination.Unlocker);
|
|
return InstalledUnlocker.CreamAPI;
|
|
}
|
|
}
|
|
}
|
|
|
|
ProgramData.Log.Info($"[Unlocker] No installed unlocker found | Game: {Name} ({Id})", LogDestination.Unlocker);
|
|
return InstalledUnlocker.None;
|
|
}
|
|
|
|
internal string DetectInstalledProxy()
|
|
{
|
|
HashSet<string> knownProxies = ["winmm", "winhttp", "version"];
|
|
foreach (string directory in DllDirectories)
|
|
foreach (string proxy in knownProxies)
|
|
{
|
|
string proxyPath = directory + @"\" + proxy + ".dll";
|
|
if (proxyPath.FileExists())
|
|
return proxy;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private void ReadCreamApiConfig(string configPath)
|
|
{
|
|
try
|
|
{
|
|
if (!configPath.FileExists())
|
|
{
|
|
ProgramData.Log.Info($"[CreamAPI] Config not found: {configPath} | Game: {Name} ({Id})", LogDestination.Unlocker);
|
|
return;
|
|
}
|
|
|
|
ProgramData.Log.Info($"[CreamAPI] Reading config: {configPath} | Game: {Name} ({Id})", LogDestination.Unlocker);
|
|
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);
|
|
ProgramData.Log.Info($"[CreamAPI] ExtraProtection = {UseExtraProtection} | Game: {Name} ({Id})", LogDestination.Unlocker);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
ProgramData.Log.Error($"[Unlocker] Error reading config: {configPath} | Game: {Name} ({Id})", e);
|
|
}
|
|
}
|
|
|
|
public override bool Equals(object obj) => ReferenceEquals(this, obj) || obj is Selection other && Equals(other);
|
|
|
|
public override int GetHashCode() => HashCode.Combine(Id, (int)Platform);
|
|
} |