From 0c3edf1ee7b68f0d74f8947289fdd763fe3a2845 Mon Sep 17 00:00:00 2001 From: Frog Date: Mon, 15 Jun 2026 02:55:24 -0700 Subject: [PATCH] Further Enhancements to Recall Games Installed With DLC Unlockers - Rename JSON Proxy field to "Proxy DLL Name" and always store the resolved name instead of null for default convention - Add DetectInstalledProxy() to find proxy dlls winmm/winhttp/version.dll - Fixes to ensure all detected unlockers are persisted in installed.json on scan so pre-existing unlockers are tracked even if not installed via CreamInstaller - Restore proxy and extra protection state from saved records for both scanned and previously scanned games on reload - Detect proxy DLLs from disk for previously scanned game reconstruction --- CreamInstaller/Forms/InstallForm.cs | 2 +- CreamInstaller/Forms/SelectForm.cs | 54 ++++++++++++++++++++++----- CreamInstaller/Selection.cs | 15 +++++++- CreamInstaller/Utility/ProgramData.cs | 13 ++++--- 4 files changed, 67 insertions(+), 17 deletions(-) diff --git a/CreamInstaller/Forms/InstallForm.cs b/CreamInstaller/Forms/InstallForm.cs index 794fa40..7557bdf 100644 --- a/CreamInstaller/Forms/InstallForm.cs +++ b/CreamInstaller/Forms/InstallForm.cs @@ -371,7 +371,7 @@ internal sealed partial class InstallForm : CustomForm RootDirectory = selection.RootDirectory, Unlocker = unlocker, UseProxy = selection.UseProxy, - Proxy = selection.Proxy, + ProxyDllName = selection.UseProxy ? selection.Proxy ?? Selection.DefaultProxy : null, UseExtraProtection = selection.UseExtraProtection, Dlc = selection.DLC.Select(dlc => new InstalledDlcRecord { diff --git a/CreamInstaller/Forms/SelectForm.cs b/CreamInstaller/Forms/SelectForm.cs index c72b9d3..dd9c158 100644 --- a/CreamInstaller/Forms/SelectForm.cs +++ b/CreamInstaller/Forms/SelectForm.cs @@ -1111,16 +1111,23 @@ internal sealed partial class SelectForm : CustomForm List toRemove = []; foreach (InstalledGameRecord record in saved) { - // Already in the list from this scan — just ensure unlocker is set + // Already in the list from this scan; ensure unlocker, proxy, and extra protection are set Selection existing = Selection.FromId(record.Platform, record.Id); if (existing is not null) { if (existing.InstalledUnlocker == InstalledUnlocker.None) existing.InstalledUnlocker = record.Unlocker; + if (record.UseProxy) + { + existing.UseProxy = true; + existing.Proxy = record.ProxyDllName; + } + if (record.UseExtraProtection) + existing.UseExtraProtection = true; continue; } - // Root directory no longer exists — mark for removal + // Root directory no longer exists mark for removal if (!record.RootDirectory.DirectoryExists()) { toRemove.Add(record); @@ -1144,8 +1151,20 @@ internal sealed partial class SelectForm : CustomForm selection.InstalledUnlocker = selection.DetectInstalledUnlocker(); if (selection.InstalledUnlocker == InstalledUnlocker.None) selection.InstalledUnlocker = record.Unlocker; - selection.UseProxy = record.UseProxy; - selection.Proxy = record.Proxy; + if (selection.InstalledUnlocker != InstalledUnlocker.None) + { + string detectedProxy = selection.DetectInstalledProxy(); + if (detectedProxy is not null) + { + selection.UseProxy = true; + selection.Proxy = detectedProxy; + } + else + { + selection.UseProxy = record.UseProxy; + selection.Proxy = record.ProxyDllName; + } + } selection.UseExtraProtection = record.UseExtraProtection; Invoke(delegate @@ -1382,9 +1401,20 @@ internal sealed partial class SelectForm : CustomForm ProgramData.WriteExtraProtectionChoices(extraProtectionChoices); loadButton.Enabled = CanLoadSelections(); - // Detect installed unlockers from disk for all selections + // Detect installed unlockers and proxy DLLs from disk for all selections foreach (Selection selection in Selection.All.Keys) + { selection.InstalledUnlocker = selection.DetectInstalledUnlocker(); + if (selection.InstalledUnlocker != InstalledUnlocker.None) + { + string detectedProxy = selection.DetectInstalledProxy(); + if (detectedProxy is not null) + { + selection.UseProxy = true; + selection.Proxy = detectedProxy; + } + } + } // Merge with persisted installed game records for any saved games not yet having a detected unlocker List installedRecords = ProgramData.ReadInstalledGames(); @@ -1397,11 +1427,14 @@ internal sealed partial class SelectForm : CustomForm selection.InstalledUnlocker = record.Unlocker; } - // Persist all selections with a detected DLC unlocker to installed.json, so they are tracked - // even when the unlocker was installed outside of CreamInstaller + // Persist any selections with a detected unlocker to installed.json, preserving existing + // proxy/extrapolation data from the saved record so detection does not overwrite prior install state foreach (Selection selection in Selection.All.Keys) { if (selection.InstalledUnlocker != InstalledUnlocker.None) + { + InstalledGameRecord existing = installedRecords.FirstOrDefault(r => + r.Platform == selection.Platform && r.Id == selection.Id); ProgramData.UpsertInstalledGame(new InstalledGameRecord { Platform = selection.Platform, @@ -1409,9 +1442,9 @@ internal sealed partial class SelectForm : CustomForm Name = selection.Name, RootDirectory = selection.RootDirectory, Unlocker = selection.InstalledUnlocker, - UseProxy = selection.UseProxy, - Proxy = selection.Proxy, - UseExtraProtection = selection.UseExtraProtection, + UseProxy = existing?.UseProxy ?? false, + ProxyDllName = existing?.UseProxy == true ? existing.ProxyDllName : null, + UseExtraProtection = existing?.UseExtraProtection ?? false, Dlc = selection.DLC.Select(dlc => new InstalledDlcRecord { DlcType = dlc.Type.ToString(), @@ -1419,6 +1452,7 @@ internal sealed partial class SelectForm : CustomForm Name = dlc.Name }).ToList() }); + } } OnProxyChanged(); diff --git a/CreamInstaller/Selection.cs b/CreamInstaller/Selection.cs index e92ab44..489abe3 100644 --- a/CreamInstaller/Selection.cs +++ b/CreamInstaller/Selection.cs @@ -163,7 +163,7 @@ internal sealed class Selection : IEquatable return InstalledUnlocker.CreamAPI; } - // Fallback: config was deleted but _o files remain — identify by replacement DLL content + // 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()) @@ -226,6 +226,19 @@ internal sealed class Selection : IEquatable return InstalledUnlocker.None; } + internal string DetectInstalledProxy() + { + HashSet 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 diff --git a/CreamInstaller/Utility/ProgramData.cs b/CreamInstaller/Utility/ProgramData.cs index cfa1d63..a1b4f9a 100644 --- a/CreamInstaller/Utility/ProgramData.cs +++ b/CreamInstaller/Utility/ProgramData.cs @@ -37,7 +37,10 @@ internal sealed class InstalledGameRecord public string RootDirectory { get; set; } public InstalledUnlocker Unlocker { get; set; } public bool UseProxy { get; set; } - public string Proxy { get; set; } + + [JsonProperty("Proxy DLL Name")] + public string ProxyDllName { get; set; } + public bool UseExtraProtection { get; set; } public List Dlc { get; set; } = []; } @@ -84,7 +87,7 @@ internal static event Action OnLogError; } catch { - // ignored — logging must never crash the application + // ignored; logging must never crash the application } } @@ -99,7 +102,7 @@ internal static event Action OnLogError; } catch { - // ignored — logging must never crash the application + // ignored; logging must never crash the application } } @@ -114,7 +117,7 @@ internal static event Action OnLogError; } catch { - // ignored — logging must never crash the application + // ignored; logging must never crash the application } OnLogWarning?.Invoke(message); } @@ -132,7 +135,7 @@ internal static event Action OnLogError; } catch { - // ignored — logging must never crash the application + // ignored; logging must never crash the application } OnLogError?.Invoke(message); }