mirror of
https://github.com/FroggMaster/CreamInstaller.git
synced 2026-07-28 14:47:04 -07:00
Persist DLC State / Detect DLC State from DLC Unlocker Configs / Refactor Logging
- Save DLC enabled state to installed.json during install - Restore DLC enabled state from installed.json when loading saved installed games on launch - Added SmokeAPI.ReadConfigDlcIds() to read SmokeAPI.config.json and extract enabled/disabled DLC IDs from override_dlc_status and extra_dlcs - Added CreamAPI.ReadConfigDlcIds() to read cream_api.ini and extract DLC IDs from [dlc] section - On scan, when a SmokeAPI or CreamAPI unlocker is detected, read its config and adjust DLC enabled state accordingly - Removed legacy dlc.json entirely (DlcChoicesPath, ReadDlcChoices, WriteDlcChoices, and all references) since installed.json now fully manages DLC state - Refactored logging methods ProgramData.Log/LogWarning/LogError with a better structured Log.Info/Warn/Error and LogDestination enum for routing logs to the correct file (Scan, Steam, Unlocker, App)
This commit is contained in:
@@ -68,27 +68,22 @@ internal sealed partial class DebugForm : CustomForm
|
||||
if (!IsOpen)
|
||||
{
|
||||
IsOpen = true;
|
||||
ProgramData.OnLog += msg =>
|
||||
ProgramData.OnLog += args =>
|
||||
{
|
||||
Color color = msg switch
|
||||
Color color = args.Level switch
|
||||
{
|
||||
string m when m.Contains("not found", StringComparison.OrdinalIgnoreCase) => LogTextBox.Failure,
|
||||
string m when m.Contains("Skipping", StringComparison.Ordinal) || m.Contains("skipped", StringComparison.Ordinal) || m.Contains("not accessible", StringComparison.Ordinal) => LogTextBox.Warning,
|
||||
_ => LogTextBox.Action
|
||||
LogLevel.Warning => LogTextBox.Warning,
|
||||
LogLevel.Error => LogTextBox.Error,
|
||||
_ => args.Message switch
|
||||
{
|
||||
string m when m.Contains("not found", StringComparison.OrdinalIgnoreCase) => LogTextBox.Failure,
|
||||
string m when m.Contains("Skipping", StringComparison.Ordinal) || m.Contains("skipped", StringComparison.Ordinal) || m.Contains("not accessible", StringComparison.Ordinal) => LogTextBox.Warning,
|
||||
string m when m.Contains("failed", StringComparison.OrdinalIgnoreCase) || m.Contains("timed out", StringComparison.OrdinalIgnoreCase) || m.Contains("cancelled", StringComparison.OrdinalIgnoreCase) || m.Contains("rate limited", StringComparison.OrdinalIgnoreCase) || m.Contains("unsuccessful", StringComparison.OrdinalIgnoreCase) || m.Contains("exceeded", StringComparison.OrdinalIgnoreCase) => LogTextBox.Failure,
|
||||
_ => LogTextBox.Action
|
||||
}
|
||||
};
|
||||
Log(msg, color);
|
||||
Log(args.Message, color);
|
||||
};
|
||||
ProgramData.OnLogSteam += msg =>
|
||||
{
|
||||
Color color = msg switch
|
||||
{
|
||||
string m when m.Contains("failed", StringComparison.OrdinalIgnoreCase) || m.Contains("timed out", StringComparison.OrdinalIgnoreCase) || m.Contains("cancelled", StringComparison.OrdinalIgnoreCase) || m.Contains("rate limited", StringComparison.OrdinalIgnoreCase) || m.Contains("unsuccessful", StringComparison.OrdinalIgnoreCase) || m.Contains("exceeded", StringComparison.OrdinalIgnoreCase) => LogTextBox.Failure,
|
||||
_ => LogTextBox.Action
|
||||
};
|
||||
Log(msg, color);
|
||||
};
|
||||
ProgramData.OnLogWarning += msg => Log(msg, LogTextBox.Warning);
|
||||
ProgramData.OnLogError += msg => Log(msg, LogTextBox.Error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -330,39 +330,47 @@ internal sealed partial class InstallForm : CustomForm
|
||||
{
|
||||
operationsCount = activeSelections.Count;
|
||||
completeOperationsCount = 0;
|
||||
ProgramData.Log.Info($"[InstallForm] Starting {(uninstalling ? "uninstall" : "install")} for {operationsCount} program(s)", LogDestination.Unlocker);
|
||||
foreach (Selection selection in activeSelections)
|
||||
{
|
||||
if (Program.Canceled)
|
||||
throw new CustomMessageException("The operation was canceled.");
|
||||
try
|
||||
{
|
||||
ProgramData.Log.Info($"[InstallForm] {(uninstalling ? "Uninstalling" : "Installing")} | Game: {selection.Name} ({selection.Id}) | Platform: {selection.Platform}", LogDestination.Unlocker);
|
||||
await OperateFor(selection);
|
||||
if (Program.Canceled)
|
||||
throw new CustomMessageException("The operation was canceled.");
|
||||
UpdateUser($"Operation succeeded for {selection.Name}.", LogTextBox.Success);
|
||||
ProgramData.Log.Info($"[InstallForm] Operation succeeded | Game: {selection.Name} ({selection.Id})", LogDestination.Unlocker);
|
||||
_ = activeSelections.Remove(selection);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
UpdateUser($"Operation failed for {selection.Name}: " + exception, LogTextBox.Error);
|
||||
ProgramData.Log.Info($"[InstallForm] Operation failed: {exception.Message} | Game: {selection.Name} ({selection.Id})", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
++completeOperationsCount;
|
||||
}
|
||||
|
||||
// Persist install/uninstall results
|
||||
ProgramData.Log.Info($"[InstallForm] Persisting install/uninstall results to installed.json", LogDestination.Unlocker);
|
||||
foreach (Selection selection in Selection.AllEnabled)
|
||||
{
|
||||
if (uninstalling)
|
||||
{
|
||||
selection.InstalledUnlocker = InstalledUnlocker.None;
|
||||
ProgramData.RemoveInstalledGame(selection.Platform, selection.Id);
|
||||
ProgramData.Log.Info($"[InstallForm] Removed from installed.json | Game: {selection.Name} ({selection.Id})", LogDestination.Unlocker);
|
||||
}
|
||||
else
|
||||
{
|
||||
InstalledUnlocker unlocker = selection.DetectInstalledUnlocker();
|
||||
selection.InstalledUnlocker = unlocker;
|
||||
if (unlocker != InstalledUnlocker.None)
|
||||
{
|
||||
int dlcCount = selection.DLC.Count();
|
||||
ProgramData.UpsertInstalledGame(new InstalledGameRecord
|
||||
{
|
||||
Platform = selection.Platform,
|
||||
@@ -377,35 +385,29 @@ internal sealed partial class InstallForm : CustomForm
|
||||
{
|
||||
DlcType = dlc.Type.ToString(),
|
||||
Id = dlc.Id,
|
||||
Name = dlc.Name
|
||||
Name = dlc.Name,
|
||||
Enabled = dlc.Enabled
|
||||
}).ToList()
|
||||
});
|
||||
ProgramData.Log.Info($"[InstallForm] Saved to installed.json: {unlocker} with {dlcCount} DLCs | Game: {selection.Name} ({selection.Id})", LogDestination.Unlocker);
|
||||
}
|
||||
else
|
||||
ProgramData.Log.Info($"[InstallForm] No unlocker detected after install | Game: {selection.Name} ({selection.Id})", LogDestination.Unlocker);
|
||||
}
|
||||
}
|
||||
|
||||
// Persist DLC checkbox state so it is restored on next scan without requiring a manual Save
|
||||
if (!uninstalling)
|
||||
{
|
||||
List<(Platform platform, string gameId, string dlcId)> dlcChoices = ProgramData.ReadDlcChoices().ToList();
|
||||
foreach (SelectionDLC dlc in SelectionDLC.All.Keys)
|
||||
{
|
||||
_ = dlcChoices.RemoveAll(n =>
|
||||
n.platform == dlc.Selection.Platform && n.gameId == dlc.Selection.Id && n.dlcId == dlc.Id);
|
||||
if (dlc.Name == "Unknown" ? dlc.Enabled : !dlc.Enabled)
|
||||
dlcChoices.Add((dlc.Selection.Platform, dlc.Selection.Id, dlc.Id));
|
||||
}
|
||||
ProgramData.WriteDlcChoices(dlcChoices);
|
||||
}
|
||||
|
||||
SelectForm.Current?.Invoke(() => SelectForm.Current?.InvalidateGameList());
|
||||
|
||||
Program.Cleanup();
|
||||
int activeCount = activeSelections.Count;
|
||||
if (activeCount > 0)
|
||||
{
|
||||
ProgramData.Log.Info($"[InstallForm] Operation completed with {activeCount} failure(s)", LogDestination.Unlocker);
|
||||
if (activeCount == 1)
|
||||
throw new CustomMessageException($"Operation failed for {activeSelections.First().Name}.");
|
||||
else
|
||||
throw new CustomMessageException($"Operation failed for {activeCount} programs.");
|
||||
}
|
||||
ProgramData.Log.Info($"[InstallForm] All operations completed successfully", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
private async void Start()
|
||||
@@ -423,12 +425,14 @@ internal sealed partial class InstallForm : CustomForm
|
||||
$"DLC unlocker(s) successfully {(uninstalling ? "uninstalled" : "installed and generated")} for " +
|
||||
selectionCount + " program(s).",
|
||||
LogTextBox.Success);
|
||||
ProgramData.Log.Info($"[InstallForm] Successfully {(uninstalling ? "uninstalled" : "installed and generated")} for {selectionCount} program(s)", LogDestination.Unlocker);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
UpdateUser(
|
||||
$"DLC unlocker {(uninstalling ? "uninstallation" : "installation and/or generation")} failed: " +
|
||||
exception, LogTextBox.Error);
|
||||
ProgramData.Log.Info($"[InstallForm] Operation failed: {exception.Message}", LogDestination.Unlocker);
|
||||
retryButton.Enabled = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
@@ -156,7 +156,7 @@ internal sealed partial class SelectForm : CustomForm
|
||||
if (!uninstallAll && programsToScan is { Count: > 0 })
|
||||
{
|
||||
string platforms = string.Join(", ", programsToScan.Select(p => p.platform.ToString()).Distinct());
|
||||
ProgramData.Log($"[Scan] User selected {programsToScan.Count} game(s) for scanning on {platforms}");
|
||||
ProgramData.Log.Info($"[Scan] User selected {programsToScan.Count} game(s) for scanning on {platforms}", LogDestination.Scan);
|
||||
}
|
||||
List<Task> appTasks = new();
|
||||
if (uninstallAll || programsToScan.Any(c => c.platform is Platform.Paradox))
|
||||
@@ -189,7 +189,7 @@ internal sealed partial class SelectForm : CustomForm
|
||||
steamCount = steamGames.Count;
|
||||
steamSeconds = steamLibTimer.Elapsed.TotalSeconds;
|
||||
totalLibraryScanSeconds += steamSeconds;
|
||||
ProgramData.Log($"[Steam] Scanned library: {steamCount} games in {steamSeconds:F1}s");
|
||||
ProgramData.Log.Info($"[Steam] Scanned library: {steamCount} games in {steamSeconds:F1}s", LogDestination.Scan);
|
||||
totalGamesDetected += steamCount;
|
||||
int steamToProcess = 0, steamBlocked = 0, steamNotSelected = 0;
|
||||
steamGamesToCheck = steamGames.Count;
|
||||
@@ -203,7 +203,7 @@ internal sealed partial class SelectForm : CustomForm
|
||||
if (blockReason is not null)
|
||||
{
|
||||
steamBlocked++;
|
||||
ProgramData.Log($"[Steam] Skipping blocked game: {name} ({appId}) — {blockReason}");
|
||||
ProgramData.Log.Info($"[Steam] Skipping blocked game: {name} ({appId}) — {blockReason}", LogDestination.Scan);
|
||||
_ = Interlocked.Decrement(ref steamGamesToCheck);
|
||||
continue;
|
||||
}
|
||||
@@ -226,7 +226,7 @@ internal sealed partial class SelectForm : CustomForm
|
||||
if (steamApiDllMissing)
|
||||
{
|
||||
dllDirectories = [];
|
||||
ProgramData.Log($"[Steam] {name} ({appId}): no steam_api.dll or steam_api64.dll found — forced proxying will be used");
|
||||
ProgramData.Log.Info($"[Steam] {name} ({appId}): no steam_api.dll or steam_api64.dll found — forced proxying will be used", LogDestination.Scan);
|
||||
if (uninstallAll)
|
||||
{
|
||||
_ = Interlocked.Decrement(ref steamGamesToCheck);
|
||||
@@ -254,7 +254,7 @@ internal sealed partial class SelectForm : CustomForm
|
||||
CmdAppData cmdAppData = await WithTimeout(SteamCMD.GetAppInfo(appId, branch, buildId), 16000);
|
||||
if (storeAppData is null && cmdAppData is null)
|
||||
{
|
||||
ProgramData.Log($"[Steam] Skipping {name} ({appId}): no store data from Steam Store or SteamCMD — unable to determine DLCs");
|
||||
ProgramData.Log.Info($"[Steam] Skipping {name} ({appId}): no store data from Steam Store or SteamCMD — unable to determine DLCs", LogDestination.Scan);
|
||||
RemoveFromRemainingGames(name);
|
||||
return;
|
||||
}
|
||||
@@ -370,7 +370,7 @@ internal sealed partial class SelectForm : CustomForm
|
||||
}
|
||||
else
|
||||
{
|
||||
ProgramData.Log($"[Steam] Skipping {name} ({appId}): no DLC entries found in store data");
|
||||
ProgramData.Log.Info($"[Steam] Skipping {name} ({appId}): no DLC entries found in store data", LogDestination.Scan);
|
||||
RemoveFromRemainingGames(name);
|
||||
return;
|
||||
}
|
||||
@@ -387,7 +387,7 @@ internal sealed partial class SelectForm : CustomForm
|
||||
gameQueriesDone.TrySetResult();
|
||||
if (dlc.IsEmpty)
|
||||
{
|
||||
ProgramData.Log($"[Steam] Skipping {name} ({appId}): no DLCs remained after processing");
|
||||
ProgramData.Log.Info($"[Steam] Skipping {name} ({appId}): no DLCs remained after processing", LogDestination.Scan);
|
||||
RemoveFromRemainingGames(name);
|
||||
return;
|
||||
}
|
||||
@@ -437,7 +437,7 @@ internal sealed partial class SelectForm : CustomForm
|
||||
appTasks.Add(task);
|
||||
}
|
||||
if (!uninstallAll)
|
||||
ProgramData.Log($"[Steam] Will process {steamToProcess} selected game(s) for DLC scan ({steamBlocked} blocked, {steamNotSelected} not in current selection)");
|
||||
ProgramData.Log.Info($"[Steam] Will process {steamToProcess} selected game(s) for DLC scan ({steamBlocked} blocked, {steamNotSelected} not in current selection)", LogDestination.Scan);
|
||||
}
|
||||
|
||||
if (uninstallAll || programsToScan.Any(c => c.platform is Platform.Epic))
|
||||
@@ -448,7 +448,7 @@ internal sealed partial class SelectForm : CustomForm
|
||||
epicCount = epicGames.Count;
|
||||
epicSeconds = epicLibTimer.Elapsed.TotalSeconds;
|
||||
totalLibraryScanSeconds += epicSeconds;
|
||||
ProgramData.Log($"[Epic] Scanned library: {epicCount} games in {epicSeconds:F1}s");
|
||||
ProgramData.Log.Info($"[Epic] Scanned library: {epicCount} games in {epicSeconds:F1}s", LogDestination.Scan);
|
||||
totalGamesDetected += epicCount;
|
||||
int epicToProcess = 0, epicBlocked = 0, epicNotSelected = 0;
|
||||
foreach (Manifest manifest in epicGames)
|
||||
@@ -464,7 +464,7 @@ internal sealed partial class SelectForm : CustomForm
|
||||
if (blockReason is not null)
|
||||
{
|
||||
epicBlocked++;
|
||||
ProgramData.Log($"[Epic] Skipping blocked game: {name} ({@namespace}) — {blockReason}");
|
||||
ProgramData.Log.Info($"[Epic] Skipping blocked game: {name} ({@namespace}) — {blockReason}", LogDestination.Scan);
|
||||
continue;
|
||||
}
|
||||
if (!programsToScan.Any(c => c.platform is Platform.Epic && c.id == @namespace))
|
||||
@@ -482,7 +482,7 @@ internal sealed partial class SelectForm : CustomForm
|
||||
HashSet<string> dllDirectories = await directory.GetDllDirectoriesFromGameDirectory(Platform.Epic);
|
||||
if (dllDirectories is null)
|
||||
{
|
||||
ProgramData.Log($"[Epic] Skipping {name} ({@namespace}): no EOSSDK-Win32-Shipping.dll or EOSSDK-Win64-Shipping.dll found. Game directory may be incomplete");
|
||||
ProgramData.Log.Info($"[Epic] Skipping {name} ({@namespace}): no EOSSDK-Win32-Shipping.dll or EOSSDK-Win64-Shipping.dll found. Game directory may be incomplete", LogDestination.Scan);
|
||||
RemoveFromRemainingGames(name);
|
||||
return;
|
||||
}
|
||||
@@ -534,7 +534,7 @@ internal sealed partial class SelectForm : CustomForm
|
||||
|
||||
if (catalogItems.IsEmpty)
|
||||
{
|
||||
ProgramData.Log($"[Epic] Skipping {name} ({@namespace}): no catalog/DLC entries found");
|
||||
ProgramData.Log.Info($"[Epic] Skipping {name} ({@namespace}): no catalog/DLC entries found", LogDestination.Scan);
|
||||
RemoveFromRemainingGames(name);
|
||||
return;
|
||||
}
|
||||
@@ -575,7 +575,7 @@ internal sealed partial class SelectForm : CustomForm
|
||||
appTasks.Add(task);
|
||||
}
|
||||
if (!uninstallAll)
|
||||
ProgramData.Log($"[Epic] Will process {epicToProcess} selected game(s) for DLC scan ({epicBlocked} blocked, {epicNotSelected} not in current selection)");
|
||||
ProgramData.Log.Info($"[Epic] Will process {epicToProcess} selected game(s) for DLC scan ({epicBlocked} blocked, {epicNotSelected} not in current selection)", LogDestination.Scan);
|
||||
}
|
||||
|
||||
if (uninstallAll || programsToScan.Any(c => c.platform is Platform.Ubisoft))
|
||||
@@ -586,7 +586,7 @@ internal sealed partial class SelectForm : CustomForm
|
||||
ubisoftCount = ubisoftGames.Count;
|
||||
ubiSeconds = ubiLibTimer.Elapsed.TotalSeconds;
|
||||
totalLibraryScanSeconds += ubiSeconds;
|
||||
ProgramData.Log($"[Ubisoft] Scanned library: {ubisoftCount} games in {ubiSeconds:F1}s");
|
||||
ProgramData.Log.Info($"[Ubisoft] Scanned library: {ubisoftCount} games in {ubiSeconds:F1}s", LogDestination.Scan);
|
||||
totalGamesDetected += ubisoftCount;
|
||||
int ubiToProcess = 0, ubiBlocked = 0, ubiNotSelected = 0;
|
||||
foreach ((string gameId, string name, string gameDirectory) in ubisoftGames)
|
||||
@@ -599,7 +599,7 @@ internal sealed partial class SelectForm : CustomForm
|
||||
if (blockReason is not null)
|
||||
{
|
||||
ubiBlocked++;
|
||||
ProgramData.Log($"[Ubisoft] Skipping blocked game: {name} ({gameId}) — {blockReason}");
|
||||
ProgramData.Log.Info($"[Ubisoft] Skipping blocked game: {name} ({gameId}) — {blockReason}", LogDestination.Scan);
|
||||
continue;
|
||||
}
|
||||
if (!programsToScan.Any(c => c.platform is Platform.Ubisoft && c.id == gameId))
|
||||
@@ -618,7 +618,7 @@ internal sealed partial class SelectForm : CustomForm
|
||||
await gameDirectory.GetDllDirectoriesFromGameDirectory(Platform.Ubisoft);
|
||||
if (dllDirectories is null)
|
||||
{
|
||||
ProgramData.Log($"[Ubisoft] Skipping {name} ({gameId}): no uplay_r1_loader.dll or uplay_r1_loader64.dll found");
|
||||
ProgramData.Log.Info($"[Ubisoft] Skipping {name} ({gameId}): no uplay_r1_loader.dll or uplay_r1_loader64.dll found", LogDestination.Scan);
|
||||
RemoveFromRemainingGames(name);
|
||||
return;
|
||||
}
|
||||
@@ -653,7 +653,7 @@ internal sealed partial class SelectForm : CustomForm
|
||||
appTasks.Add(task);
|
||||
}
|
||||
if (!uninstallAll)
|
||||
ProgramData.Log($"[Ubisoft] Will process {ubiToProcess} selected game(s) ({ubiBlocked} blocked, {ubiNotSelected} not in current selection)");
|
||||
ProgramData.Log.Info($"[Ubisoft] Will process {ubiToProcess} selected game(s) ({ubiBlocked} blocked, {ubiNotSelected} not in current selection)", LogDestination.Scan);
|
||||
}
|
||||
|
||||
Stopwatch gameDlcTimer = Stopwatch.StartNew();
|
||||
@@ -666,14 +666,14 @@ internal sealed partial class SelectForm : CustomForm
|
||||
if (!uninstallAll)
|
||||
{
|
||||
if (steamCount > 0)
|
||||
ProgramData.Log($"[Steam] Total games detected: {steamCount} in {(steamSeconds >= 60 ? $"{steamSeconds / 60:F1} minutes" : $"{steamSeconds:F1}s")}");
|
||||
ProgramData.Log.Info($"[Steam] Total games detected: {steamCount} in {(steamSeconds >= 60 ? $"{steamSeconds / 60:F1} minutes" : $"{steamSeconds:F1}s")}", LogDestination.Scan);
|
||||
if (epicCount > 0)
|
||||
ProgramData.Log($"[Epic] Total games detected: {epicCount} in {(epicSeconds >= 60 ? $"{epicSeconds / 60:F1} minutes" : $"{epicSeconds:F1}s")}");
|
||||
ProgramData.Log.Info($"[Epic] Total games detected: {epicCount} in {(epicSeconds >= 60 ? $"{epicSeconds / 60:F1} minutes" : $"{epicSeconds:F1}s")}", LogDestination.Scan);
|
||||
if (ubisoftCount > 0)
|
||||
ProgramData.Log($"[Ubisoft] Total games detected: {ubisoftCount} in {(ubiSeconds >= 60 ? $"{ubiSeconds / 60:F1} minutes" : $"{ubiSeconds:F1}s")}");
|
||||
ProgramData.Log.Info($"[Ubisoft] Total games detected: {ubisoftCount} in {(ubiSeconds >= 60 ? $"{ubiSeconds / 60:F1} minutes" : $"{ubiSeconds:F1}s")}", LogDestination.Scan);
|
||||
}
|
||||
ProgramData.Log($"[Scan] Game and DLC data gathering: {gameDlcTimer.Elapsed.TotalSeconds:F1}s");
|
||||
ProgramData.Log($"[Scan] Scan completed in {scanTimer.Elapsed.TotalSeconds:F1}s");
|
||||
ProgramData.Log.Info($"[Scan] Game and DLC data gathering: {gameDlcTimer.Elapsed.TotalSeconds:F1}s", LogDestination.Scan);
|
||||
ProgramData.Log.Info($"[Scan] Scan completed in {scanTimer.Elapsed.TotalSeconds:F1}s", LogDestination.Scan);
|
||||
}
|
||||
|
||||
private async void OnLoad(bool forceScan = false, bool forceProvideChoices = false)
|
||||
@@ -697,14 +697,14 @@ internal sealed partial class SelectForm : CustomForm
|
||||
ShowProgressBar();
|
||||
await ProgramData.Setup(this);
|
||||
ProgramData.ClearLog();
|
||||
ProgramData.Log($"[Scan] CreamInstaller {Program.Version} — scan started at {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
|
||||
ProgramData.Log.Info($"[Scan] CreamInstaller {Program.Version} — scan started at {DateTime.Now:yyyy-MM-dd HH:mm:ss}", LogDestination.Scan);
|
||||
bool scan = forceScan;
|
||||
// On initial launch, if the user has games with installed DLC unlockers, don't re-display the scan window.
|
||||
bool skipScanDialog = initialLoad && programsToScan is null && ProgramData.ReadInstalledGames() is { Count: > 0 };
|
||||
initialLoad = false;
|
||||
if (skipScanDialog)
|
||||
{
|
||||
ProgramData.Log("[Scan] Found previously installed DLC unlockers; skipping scan window on initial launch");
|
||||
ProgramData.Log.Info("[Scan] Found previously installed DLC unlockers; skipping scan window on initial launch", LogDestination.Scan);
|
||||
progressLabel.Text = "Loading previously installed DLC unlockers from last session...";
|
||||
}
|
||||
if (!scan && (programsToScan is null || programsToScan.Count < 1 || forceProvideChoices) && !skipScanDialog)
|
||||
@@ -734,7 +734,7 @@ internal sealed partial class SelectForm : CustomForm
|
||||
programsToScan is not null &&
|
||||
programsToScan.Any(p => p.platform is Platform.Ubisoft && p.id == gameId)));
|
||||
selectionTimer.Stop();
|
||||
ProgramData.Log($"[Total] Total time spent detecting games and libraries: {(selectionTimer.Elapsed.TotalSeconds >= 60 ? $"{selectionTimer.Elapsed.TotalSeconds / 60:F1} minutes" : $"{selectionTimer.Elapsed.TotalSeconds:F1}s")}");
|
||||
ProgramData.Log.Info($"[Total] Total time spent detecting games and libraries: {(selectionTimer.Elapsed.TotalSeconds >= 60 ? $"{selectionTimer.Elapsed.TotalSeconds / 60:F1} minutes" : $"{selectionTimer.Elapsed.TotalSeconds:F1}s")}", LogDestination.Scan);
|
||||
if (gameChoices.Count > 0)
|
||||
{
|
||||
using SelectDialogForm form = new(this);
|
||||
@@ -816,7 +816,7 @@ internal sealed partial class SelectForm : CustomForm
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ProgramData.LogError("SelectForm OnLoad failed", ex);
|
||||
ProgramData.Log.Error("SelectForm OnLoad failed", ex);
|
||||
// Show error and clean up
|
||||
ex.HandleException(this);
|
||||
HideProgressBar();
|
||||
@@ -1171,6 +1171,7 @@ internal sealed partial class SelectForm : CustomForm
|
||||
continue;
|
||||
SelectionDLC dlc = SelectionDLC.GetOrCreate(dlcType, record.Id, dlcRecord.Id, dlcRecord.Name);
|
||||
dlc.Selection = selection;
|
||||
dlc.Enabled = dlcRecord.Enabled;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1311,16 +1312,6 @@ internal sealed partial class SelectForm : CustomForm
|
||||
|
||||
private void LoadSelections()
|
||||
{
|
||||
List<(Platform platform, string gameId, string dlcId)> dlcChoices = ProgramData.ReadDlcChoices().ToList();
|
||||
foreach (SelectionDLC dlc in SelectionDLC.All.Keys)
|
||||
{
|
||||
dlc.Enabled = dlcChoices.Any(c =>
|
||||
c.platform == dlc.Selection?.Platform && c.gameId == dlc.Selection?.Id && c.dlcId == dlc.Id)
|
||||
? dlc.Name == "Unknown"
|
||||
: dlc.Name != "Unknown";
|
||||
OnTreeViewNodeCheckedChanged("OnLoadSelections", new(dlc.TreeNode, TreeViewAction.ByMouse));
|
||||
}
|
||||
|
||||
List<(Platform platform, string id, string proxy, bool enabled)> proxyChoices =
|
||||
ProgramData.ReadProxyChoices().ToList();
|
||||
foreach (Selection selection in Selection.All.Keys)
|
||||
@@ -1373,6 +1364,44 @@ internal sealed partial class SelectForm : CustomForm
|
||||
}
|
||||
}
|
||||
|
||||
// Read config files for detected unlockers and adjust DLC enabled state accordingly
|
||||
foreach (Selection selection in Selection.All.Keys)
|
||||
{
|
||||
if (selection.InstalledUnlocker == InstalledUnlocker.SmokeAPI)
|
||||
{
|
||||
foreach (string directory in selection.DllDirectories)
|
||||
{
|
||||
var (enabledIds, disabledIds) = SmokeAPI.ReadConfigDlcIds(directory);
|
||||
if (enabledIds is not null) // config was found and read
|
||||
{
|
||||
foreach (SelectionDLC dlc in selection.DLC)
|
||||
{
|
||||
if (enabledIds.Contains(dlc.Id))
|
||||
dlc.Enabled = true;
|
||||
else if (disabledIds.Contains(dlc.Id))
|
||||
dlc.Enabled = false;
|
||||
else
|
||||
dlc.Enabled = false; // not in config at all
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (selection.InstalledUnlocker == InstalledUnlocker.CreamAPI)
|
||||
{
|
||||
foreach (string directory in selection.DllDirectories)
|
||||
{
|
||||
HashSet<string> enabledIds = CreamAPI.ReadConfigDlcIds(directory);
|
||||
if (enabledIds is not null)
|
||||
{
|
||||
foreach (SelectionDLC dlc in selection.DLC)
|
||||
dlc.Enabled = enabledIds.Contains(dlc.Id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
@@ -1406,7 +1435,8 @@ internal sealed partial class SelectForm : CustomForm
|
||||
{
|
||||
DlcType = dlc.Type.ToString(),
|
||||
Id = dlc.Id,
|
||||
Name = dlc.Name
|
||||
Name = dlc.Name,
|
||||
Enabled = dlc.Enabled
|
||||
}).ToList()
|
||||
});
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ internal sealed partial class TestGameForm : CustomForm
|
||||
if (!string.IsNullOrWhiteSpace(title))
|
||||
return title;
|
||||
}
|
||||
catch (Exception ex) { ProgramData.LogWarning($"[TestGame] Store name lookup failed for AppID {appId}: {ex.Message}"); /* fall through to SteamCMD */ }
|
||||
catch (Exception ex) { ProgramData.Log.Warn($"[TestGame] Store name lookup failed for AppID {appId}: {ex.Message}"); /* fall through to SteamCMD */ }
|
||||
|
||||
CmdAppData cmdData = await SteamCMD.GetAppInfo(appId);
|
||||
return cmdData?.Common?.Name;
|
||||
@@ -288,7 +288,7 @@ internal sealed partial class TestGameForm : CustomForm
|
||||
|
||||
CreatedDirectories.Add(gameDir);
|
||||
SteamLibrary.TestGames.Add((appId, gameName, "public", 1, gameDir));
|
||||
ProgramData.Log($"[TestGame] Steam: {gameName} ({appId}) at {gameDir}");
|
||||
ProgramData.Log.Info($"[TestGame] Steam: {gameName} ({appId}) at {gameDir}", LogDestination.Scan);
|
||||
SetStatus($"✓ Steam test game '{gameName}' ({appId}) generated. Press Rescan.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -343,7 +343,7 @@ internal sealed partial class TestGameForm : CustomForm
|
||||
InstallLocation = gameDir
|
||||
});
|
||||
|
||||
ProgramData.Log($"[TestGame] Epic: {gameName} ({catalogNamespace}) at {gameDir}");
|
||||
ProgramData.Log.Info($"[TestGame] Epic: {gameName} ({catalogNamespace}) at {gameDir}", LogDestination.Scan);
|
||||
SetStatus($"✓ Epic test game '{gameName}' generated. Press Rescan.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -393,7 +393,7 @@ internal sealed partial class TestGameForm : CustomForm
|
||||
|
||||
CreatedDirectories.Add(gameDir);
|
||||
UbisoftLibrary.TestGames.Add((gameId, gameName, gameDir));
|
||||
ProgramData.Log($"[TestGame] Ubisoft: {gameName} ({gameId}) at {gameDir}");
|
||||
ProgramData.Log.Info($"[TestGame] Ubisoft: {gameName} ({gameId}) at {gameDir}", LogDestination.Scan);
|
||||
SetStatus($"✓ Ubisoft test game '{gameName}' ({gameId}) generated. Press Rescan.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -410,17 +410,17 @@ internal sealed partial class TestGameForm : CustomForm
|
||||
EpicLibrary.TestManifests.Clear();
|
||||
UbisoftLibrary.TestGames.Clear();
|
||||
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.Log.Warn($"[TestGame] Cleanup deletion failed for {dir}: {ex.Message}"); }
|
||||
CreatedDirectories.Clear();
|
||||
if (Directory.Exists(TestGamesRoot))
|
||||
try { Directory.Delete(TestGamesRoot, true); } catch (Exception ex) { ProgramData.LogWarning($"[TestGame] Cleanup failed to delete TestGames root: {ex.Message}"); }
|
||||
try { Directory.Delete(TestGamesRoot, true); } catch (Exception ex) { ProgramData.Log.Warn($"[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.");
|
||||
ProgramData.Log.Info($"[TestGame] Removed {removed} stale installed-game record(s) from test games.", LogDestination.Scan);
|
||||
}
|
||||
// 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 =>
|
||||
|
||||
@@ -106,7 +106,7 @@ internal sealed partial class UpdateForm : CustomForm
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ProgramData.LogError("UpdateForm OnLoad failed", ex);
|
||||
ProgramData.Log.Error("UpdateForm OnLoad failed", ex);
|
||||
#if DEBUG
|
||||
ex.HandleFatalException();
|
||||
#else
|
||||
@@ -260,7 +260,7 @@ internal sealed partial class UpdateForm : CustomForm
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ProgramData.LogError("UpdateForm OnUpdate failed", ex);
|
||||
ProgramData.Log.Error("UpdateForm OnUpdate failed", ex);
|
||||
// Show error to user
|
||||
ex.HandleException(this, Program.Name + " encountered an unexpected error during update");
|
||||
StartProgram();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
@@ -42,10 +42,10 @@ internal static class EpicLibrary
|
||||
games.Add(test);
|
||||
|
||||
string manifests = EpicManifestsPath;
|
||||
ProgramData.Log($"[Epic] Manifests directory: {manifests ?? "(not found)"}");
|
||||
ProgramData.Log.Info($"[Epic] Manifests directory: {manifests ?? "(not found)"}", LogDestination.Scan);
|
||||
if (manifests.DirectoryExists())
|
||||
{
|
||||
ProgramData.Log($"[Epic] Scanning manifests: {manifests}");
|
||||
ProgramData.Log.Info($"[Epic] Scanning manifests: {manifests}", LogDestination.Scan);
|
||||
foreach (string item in manifests.EnumerateDirectory("*.item"))
|
||||
{
|
||||
if (Program.Canceled)
|
||||
@@ -59,7 +59,7 @@ internal static class EpicLibrary
|
||||
&& games.All(g => g.CatalogNamespace != manifest.CatalogNamespace))
|
||||
{
|
||||
games.Add(manifest);
|
||||
ProgramData.Log($"[Epic] Detected game: {manifest.DisplayName} ({manifest.CatalogNamespace}) | Dir: {manifest.InstallLocation}");
|
||||
ProgramData.Log.Info($"[Epic] Detected game: {manifest.DisplayName} ({manifest.CatalogNamespace}) | Dir: {manifest.InstallLocation}", LogDestination.Scan);
|
||||
}
|
||||
}
|
||||
catch
|
||||
@@ -75,11 +75,11 @@ internal static class EpicLibrary
|
||||
await HeroicLibrary.GetGames(games);
|
||||
int heroicCount = games.Count - beforeHeroic;
|
||||
if (heroicCount > 0)
|
||||
ProgramData.Log($"[Epic] Found {heroicCount} game(s) from Heroic Games Launcher");
|
||||
ProgramData.Log.Info($"[Epic] Found {heroicCount} game(s) from Heroic Games Launcher", LogDestination.Scan);
|
||||
if (TestManifests.Count > 0)
|
||||
ProgramData.Log($"[Epic] Injected {TestManifests.Count} test game(s).");
|
||||
ProgramData.Log.Info($"[Epic] Injected {TestManifests.Count} test game(s).", LogDestination.Scan);
|
||||
timer.Stop();
|
||||
ProgramData.Log($"[Epic] Total games detected: {games.Count} in {(timer.Elapsed.TotalSeconds >= 60 ? $"{timer.Elapsed.TotalSeconds / 60:F1} minutes" : $"{timer.Elapsed.TotalSeconds:F1}s")}");
|
||||
ProgramData.Log.Info($"[Epic] Total games detected: {games.Count} in {(timer.Elapsed.TotalSeconds >= 60 ? $"{timer.Elapsed.TotalSeconds / 60:F1} minutes" : $"{timer.Elapsed.TotalSeconds:F1}s")}", LogDestination.Scan);
|
||||
return games;
|
||||
});
|
||||
}
|
||||
@@ -33,7 +33,7 @@ internal static class EpicStore
|
||||
response = await QueryGraphQL(categoryNamespace);
|
||||
if (response is null)
|
||||
{
|
||||
ProgramData.LogWarning("Epic QueryGraphQL returned null for " + categoryNamespace);
|
||||
ProgramData.Log.Warn("Epic QueryGraphQL returned null for " + categoryNamespace);
|
||||
}
|
||||
try
|
||||
{
|
||||
@@ -187,7 +187,7 @@ internal static class EpicStore
|
||||
HttpClient client = HttpClientManager.HttpClient;
|
||||
if (client is null)
|
||||
{
|
||||
ProgramData.LogWarning("Epic GraphQL client returned null");
|
||||
ProgramData.Log.Warn("Epic GraphQL client returned null");
|
||||
return null;
|
||||
}
|
||||
HttpResponseMessage httpResponse =
|
||||
|
||||
@@ -12,7 +12,7 @@ internal static partial class SteamCMD
|
||||
private const int CooldownGame = 600;
|
||||
private const int CooldownDlc = 1200;
|
||||
|
||||
private static async Task<CmdAppData> QueryWebAPI(string appId, bool isDlc = false, int attempts = 0)
|
||||
internal static async Task<CmdAppData> QueryWebAPI(string appId, bool isDlc = false, int attempts = 0)
|
||||
{
|
||||
while (!Program.Canceled)
|
||||
{
|
||||
@@ -23,11 +23,11 @@ internal static partial class SteamCMD
|
||||
{
|
||||
(string response, bool permanentFailure) =
|
||||
await HttpClientManager.EnsureGet($"https://api.steamcmd.net/v1/info/{appId}");
|
||||
if (permanentFailure)
|
||||
if (permanentFailure)
|
||||
{
|
||||
ProgramData.LogSteam("[SteamAPI] SteamCMD web API query failed on attempt #" + attempts + " for " +
|
||||
appId + (isDlc ? " (DLC)" : "") +
|
||||
": Permanent failure, aborting retries");
|
||||
ProgramData.Log.Info("[SteamAPI] SteamCMD web API query failed on attempt #" + attempts + " for " +
|
||||
appId + (isDlc ? " (DLC)" : "") +
|
||||
": Permanent failure, aborting retries", LogDestination.Steam);
|
||||
return null;
|
||||
}
|
||||
if (response is not null)
|
||||
@@ -44,38 +44,38 @@ internal static partial class SteamCMD
|
||||
{
|
||||
cacheFile.WriteFile(JsonConvert.SerializeObject(data, Formatting.Indented));
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (Exception e)
|
||||
{
|
||||
ProgramData.LogSteam("[SteamAPI] SteamCMD web API query failed on attempt #" + attempts +
|
||||
" for " + appId + (isDlc ? " (DLC)" : "")
|
||||
+ ": Unsuccessful serialization (" + e.Message + ")");
|
||||
ProgramData.Log.Info("[SteamAPI] SteamCMD web API query failed on attempt #" + attempts +
|
||||
" for " + appId + (isDlc ? " (DLC)" : "")
|
||||
+ ": Unsuccessful serialization (" + e.Message + ")", LogDestination.Steam);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
else
|
||||
ProgramData.LogSteam(
|
||||
ProgramData.Log.Info(
|
||||
"[SteamAPI] SteamCMD web API query failed on attempt #" + attempts + " for " + appId +
|
||||
(isDlc ? " (DLC)" : "")
|
||||
+ ": No data");
|
||||
+ ": No data", LogDestination.Steam);
|
||||
}
|
||||
else
|
||||
ProgramData.LogSteam(
|
||||
ProgramData.Log.Info(
|
||||
"[SteamAPI] SteamCMD web API query failed on attempt #" + attempts + " for " + appId +
|
||||
(isDlc ? " (DLC)" : "")
|
||||
+ ": Status not success (" + appDetails?.Status + ")");
|
||||
+ ": Status not success (" + appDetails?.Status + ")", LogDestination.Steam);
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (Exception e)
|
||||
{
|
||||
ProgramData.LogSteam("[SteamAPI] SteamCMD web API query failed on attempt #" + attempts + " for " +
|
||||
appId + (isDlc ? " (DLC)" : "")
|
||||
+ ": Unsuccessful deserialization (" + e.Message + ")");
|
||||
ProgramData.Log.Info("[SteamAPI] SteamCMD web API query failed on attempt #" + attempts + " for " +
|
||||
appId + (isDlc ? " (DLC)" : "")
|
||||
+ ": Unsuccessful deserialization (" + e.Message + ")", LogDestination.Steam);
|
||||
}
|
||||
}
|
||||
else
|
||||
ProgramData.LogSteam(
|
||||
ProgramData.Log.Info(
|
||||
"[SteamAPI] SteamCMD web API query failed on attempt #" + attempts + " for " + appId +
|
||||
(isDlc ? " (DLC)" : "") +
|
||||
": Response null");
|
||||
": Response null", LogDestination.Steam);
|
||||
}
|
||||
|
||||
if (cachedExists)
|
||||
@@ -92,7 +92,7 @@ internal static partial class SteamCMD
|
||||
break;
|
||||
if (attempts > 3)
|
||||
{
|
||||
ProgramData.LogSteam("[SteamAPI] Failed to query SteamCMD web API after 3 tries: " + appId);
|
||||
ProgramData.Log.Info("[SteamAPI] Failed to query SteamCMD web API after 3 tries: " + appId, LogDestination.Steam);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -254,7 +254,7 @@ internal static partial class SteamCMD
|
||||
attempts++;
|
||||
if (attempts > 10)
|
||||
{
|
||||
ProgramData.LogSteam("[SteamCMD] Failed to query SteamCMD after 10 tries: " + appId + " (" + branch + ")");
|
||||
ProgramData.Log.Info("[SteamCMD] Failed to query SteamCMD after 10 tries: " + appId + " (" + branch + ")", LogDestination.Steam);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -273,9 +273,9 @@ internal static partial class SteamCMD
|
||||
}
|
||||
else
|
||||
{
|
||||
ProgramData.LogSteam(
|
||||
ProgramData.Log.Info(
|
||||
"[SteamCMD] SteamCMD query failed on attempt #" + attempts + " for " + appId + " (" + branch +
|
||||
"): Bad output");
|
||||
"): Bad output", LogDestination.Steam);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -283,9 +283,9 @@ internal static partial class SteamCMD
|
||||
if (!ValveDataFile.TryDeserialize(output, out VProperty appInfo) || appInfo.Value is VValue)
|
||||
{
|
||||
appUpdateFile.DeleteFile();
|
||||
ProgramData.LogSteam(
|
||||
ProgramData.Log.Info(
|
||||
"[SteamCMD] SteamCMD query failed on attempt #" + attempts + " for " + appId + " (" + branch +
|
||||
"): Deserialization failed");
|
||||
"): Deserialization failed", LogDestination.Steam);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -295,9 +295,9 @@ internal static partial class SteamCMD
|
||||
if (appInfo.ToJson().Value.ToObject<CmdAppData>() is not { } cmdAppData)
|
||||
{
|
||||
appUpdateFile.DeleteFile();
|
||||
ProgramData.LogSteam(
|
||||
ProgramData.Log.Info(
|
||||
"[SteamCMD] SteamCMD query failed on attempt #" + attempts + " for " + appId + " (" + branch +
|
||||
"): VDF-JSON conversion failed");
|
||||
"): VDF-JSON conversion failed", LogDestination.Steam);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -306,9 +306,9 @@ internal static partial class SteamCMD
|
||||
catch (Exception e)
|
||||
{
|
||||
appUpdateFile.DeleteFile();
|
||||
ProgramData.LogSteam(
|
||||
ProgramData.Log.Info(
|
||||
"[SteamCMD] SteamCMD query failed on attempt #" + attempts + " for " + appId + " (" + branch +
|
||||
"): VDF-JSON conversion failed (" + e.Message + ")");
|
||||
"): VDF-JSON conversion failed (" + e.Message + ")", LogDestination.Steam);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -326,9 +326,9 @@ internal static partial class SteamCMD
|
||||
foreach (string dlcAppUpdateFile in dlcAppIds.Select(id => $@"{AppInfoPath}\{id}.vdf"))
|
||||
dlcAppUpdateFile.DeleteFile();
|
||||
appUpdateFile.DeleteFile();
|
||||
ProgramData.LogSteam(
|
||||
ProgramData.Log.Info(
|
||||
"[SteamCMD] SteamCMD query skipped on attempt #" + attempts + " for " + appId + " (" + branch +
|
||||
"): Outdated cache");
|
||||
"): Outdated cache", LogDestination.Steam);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
@@ -35,12 +35,12 @@ internal static class SteamLibrary
|
||||
List<(string appId, string name, string branch, int buildId, string gameDirectory)> games = new();
|
||||
HashSet<string> seenAppIds = new();
|
||||
HashSet<string> gameLibraryDirectories = await GetLibraryDirectories();
|
||||
ProgramData.Log($"[Steam] Found {gameLibraryDirectories.Count} library folder(s).");
|
||||
ProgramData.Log.Info($"[Steam] Found {gameLibraryDirectories.Count} library folder(s).", LogDestination.Scan);
|
||||
foreach (string libraryDirectory in gameLibraryDirectories)
|
||||
{
|
||||
if (Program.Canceled)
|
||||
return games;
|
||||
ProgramData.Log($"[Steam] Scanning library: {libraryDirectory}");
|
||||
ProgramData.Log.Info($"[Steam] Scanning library: {libraryDirectory}", LogDestination.Scan);
|
||||
foreach ((string appId, string name, string branch, int buildId, string gameDirectory) game in
|
||||
await GetGamesFromLibraryDirectory(libraryDirectory))
|
||||
{
|
||||
@@ -53,9 +53,9 @@ internal static class SteamLibrary
|
||||
TestGames.Where(t => !seenAppIds.Contains(t.appId)))
|
||||
games.Add(testGame);
|
||||
if (TestGames.Count > 0)
|
||||
ProgramData.Log($"[Steam] Injected {TestGames.Count} test game(s).");
|
||||
ProgramData.Log.Info($"[Steam] Injected {TestGames.Count} test game(s).", LogDestination.Scan);
|
||||
timer.Stop();
|
||||
ProgramData.Log($"[Steam] Total games detected: {games.Count} in {(timer.Elapsed.TotalSeconds >= 60 ? $"{timer.Elapsed.TotalSeconds / 60:F1} minutes" : $"{timer.Elapsed.TotalSeconds:F1}s")}");
|
||||
ProgramData.Log.Info($"[Steam] Total games detected: {games.Count} in {(timer.Elapsed.TotalSeconds >= 60 ? $"{timer.Elapsed.TotalSeconds / 60:F1} minutes" : $"{timer.Elapsed.TotalSeconds:F1}s")}", LogDestination.Scan);
|
||||
return games;
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ internal static class SteamLibrary
|
||||
{
|
||||
if (Program.Canceled || !libraryDirectory.DirectoryExists())
|
||||
{
|
||||
ProgramData.Log($"[Steam] Skipping library (not found or canceled): {libraryDirectory}");
|
||||
ProgramData.Log.Info($"[Steam] Skipping library (not found or canceled): {libraryDirectory}", LogDestination.Scan);
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ internal static class SteamLibrary
|
||||
}
|
||||
if (!ValveDataFile.TryDeserialize(file.ReadFile(), out VProperty result))
|
||||
{
|
||||
ProgramData.Log($"[Steam] Failed to deserialize ACF: {file}");
|
||||
ProgramData.Log.Info($"[Steam] Failed to deserialize ACF: {file}", LogDestination.Scan);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ internal static class SteamLibrary
|
||||
if (string.IsNullOrWhiteSpace(appId) || string.IsNullOrWhiteSpace(installdir) ||
|
||||
string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(buildId))
|
||||
{
|
||||
ProgramData.Log($"[Steam] Skipping ACF with missing fields: {file}");
|
||||
ProgramData.Log.Info($"[Steam] Skipping ACF with missing fields: {file}", LogDestination.Scan);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ internal static class SteamLibrary
|
||||
string gameDirectory = rawGameDirectory.ResolvePath();
|
||||
if (gameDirectory is null)
|
||||
{
|
||||
ProgramData.Log($"[Steam] Game directory not found (drive may be slow or disconnected): {rawGameDirectory} | App: {name} ({appId})");
|
||||
ProgramData.Log.Info($"[Steam] Game directory not found (drive may be slow or disconnected): {rawGameDirectory} | App: {name} ({appId})", LogDestination.Scan);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ internal static class SteamLibrary
|
||||
branch = "public";
|
||||
|
||||
if (gamesDict.TryAdd(appId, (name, branch, buildIdInt, gameDirectory)))
|
||||
ProgramData.Log($"[Steam] Detected game: {name} ({appId}) | Branch: {branch} | Dir: {gameDirectory}");
|
||||
ProgramData.Log.Info($"[Steam] Detected game: {name} ({appId}) | Branch: {branch} | Dir: {gameDirectory}", LogDestination.Scan);
|
||||
});
|
||||
|
||||
List<(string appId, string name, string branch, int buildId, string gameDirectory)> games = new(gamesDict.Count);
|
||||
@@ -137,25 +137,25 @@ internal static class SteamLibrary
|
||||
string steamInstallPath = InstallPath;
|
||||
if (steamInstallPath == null || !steamInstallPath.DirectoryExists())
|
||||
{
|
||||
ProgramData.Log($"[Steam] Steam install path not found or inaccessible: {steamInstallPath ?? "(null)"}");
|
||||
ProgramData.Log.Info($"[Steam] Steam install path not found or inaccessible: {steamInstallPath ?? "(null)"}", LogDestination.Scan);
|
||||
return libraryDirectories;
|
||||
}
|
||||
|
||||
string libraryFolder = steamInstallPath + @"\steamapps";
|
||||
if (!libraryFolder.DirectoryExists())
|
||||
{
|
||||
ProgramData.Log($"[Steam] Default steamapps folder not found: {libraryFolder}");
|
||||
ProgramData.Log.Info($"[Steam] Default steamapps folder not found: {libraryFolder}", LogDestination.Scan);
|
||||
return libraryDirectories;
|
||||
}
|
||||
|
||||
_ = libraryDirectories.Add(libraryFolder);
|
||||
ProgramData.Log($"[Steam] Default library folder: {libraryFolder}");
|
||||
ProgramData.Log.Info($"[Steam] Default library folder: {libraryFolder}", LogDestination.Scan);
|
||||
|
||||
string libraryFolders = libraryFolder + @"\libraryfolders.vdf";
|
||||
if (!libraryFolders.FileExists() ||
|
||||
!ValveDataFile.TryDeserialize(libraryFolders.ReadFile(), out VProperty result))
|
||||
{
|
||||
ProgramData.Log($"[Steam] libraryfolders.vdf not found or failed to parse: {libraryFolders}");
|
||||
ProgramData.Log.Info($"[Steam] libraryfolders.vdf not found or failed to parse: {libraryFolders}", LogDestination.Scan);
|
||||
return libraryDirectories;
|
||||
}
|
||||
|
||||
@@ -174,12 +174,12 @@ internal static class SteamLibrary
|
||||
|
||||
if (resolvedPath is null)
|
||||
{
|
||||
ProgramData.Log($"[Steam] External library not accessible (drive may be disconnected or letter changed): {steamappsPath}");
|
||||
ProgramData.Log.Info($"[Steam] External library not accessible (drive may be disconnected or letter changed): {steamappsPath}", LogDestination.Scan);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (libraryDirectories.Add(resolvedPath))
|
||||
ProgramData.Log($"[Steam] Additional library folder found: {resolvedPath}");
|
||||
ProgramData.Log.Info($"[Steam] Additional library folder found: {resolvedPath}", LogDestination.Scan);
|
||||
}
|
||||
|
||||
return libraryDirectories;
|
||||
|
||||
@@ -59,8 +59,8 @@ internal static class SteamStore
|
||||
await HttpClientManager.EnsureGet($"https://store.steampowered.com/api/appdetails?appids={appId}");
|
||||
if (permanentFailure)
|
||||
{
|
||||
ProgramData.LogSteam(
|
||||
"[SteamAPI] " + FormatErrorLog(attempts, appId, gameName, isDlc, "Permanent failure, aborting retries", parentGameName, parentGameAppId));
|
||||
ProgramData.Log.Info(
|
||||
"[SteamAPI] " + FormatErrorLog(attempts, appId, gameName, isDlc, "Permanent failure, aborting retries", parentGameName, parentGameAppId), LogDestination.Steam);
|
||||
return null;
|
||||
}
|
||||
if (response is not null)
|
||||
@@ -81,8 +81,8 @@ internal static class SteamStore
|
||||
|
||||
if (!storeAppDetails.Success)
|
||||
{
|
||||
ProgramData.LogSteam(
|
||||
"[SteamAPI] " + FormatErrorLog(attempts, appId, gameName, isDlc, "Query unsuccessful", parentGameName, parentGameAppId));
|
||||
ProgramData.Log.Info(
|
||||
"[SteamAPI] " + FormatErrorLog(attempts, appId, gameName, isDlc, "Query unsuccessful", parentGameName, parentGameAppId), LogDestination.Steam);
|
||||
if (data is null)
|
||||
return null;
|
||||
}
|
||||
@@ -95,35 +95,35 @@ internal static class SteamStore
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ProgramData.LogSteam(
|
||||
"[SteamAPI] " + FormatErrorLog(attempts, appId, gameName, isDlc, $"Unsuccessful serialization ({e.Message})", parentGameName, parentGameAppId));
|
||||
ProgramData.Log.Info(
|
||||
"[SteamAPI] " + FormatErrorLog(attempts, appId, gameName, isDlc, $"Unsuccessful serialization ({e.Message})", parentGameName, parentGameAppId), LogDestination.Steam);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
ProgramData.LogSteam(
|
||||
"[SteamAPI] " + FormatErrorLog(attempts, appId, gameName, isDlc, "Response data null", parentGameName, parentGameAppId));
|
||||
ProgramData.Log.Info(
|
||||
"[SteamAPI] " + FormatErrorLog(attempts, appId, gameName, isDlc, "Response data null", parentGameName, parentGameAppId), LogDestination.Steam);
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
ProgramData.Log.Info(
|
||||
"[SteamAPI] " + FormatErrorLog(attempts, appId, gameName, isDlc, "Response details null", parentGameName, parentGameAppId), LogDestination.Steam);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ProgramData.LogSteam(
|
||||
"[SteamAPI] " + FormatErrorLog(attempts, appId, gameName, isDlc, "Response details null", parentGameName, parentGameAppId));
|
||||
ProgramData.Log.Info(
|
||||
"[SteamAPI] " + FormatErrorLog(attempts, appId, gameName, isDlc, $"Unsuccessful deserialization ({e.Message})", parentGameName, parentGameAppId), LogDestination.Steam);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
else
|
||||
{
|
||||
ProgramData.LogSteam(
|
||||
"[SteamAPI] " + FormatErrorLog(attempts, appId, gameName, isDlc, $"Unsuccessful deserialization ({e.Message})", parentGameName, parentGameAppId));
|
||||
ProgramData.Log.Info(
|
||||
"[SteamAPI] " + FormatErrorLog(attempts, appId, gameName, isDlc, "Response deserialization null", parentGameName, parentGameAppId), LogDestination.Steam);
|
||||
}
|
||||
else
|
||||
{
|
||||
ProgramData.LogSteam(
|
||||
"[SteamAPI] " + FormatErrorLog(attempts, appId, gameName, isDlc, "Response deserialization null", parentGameName, parentGameAppId));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ProgramData.LogSteam(
|
||||
"[SteamAPI] " + FormatErrorLog(attempts, appId, gameName, isDlc, "Null or empty response", parentGameName, parentGameAppId));
|
||||
ProgramData.Log.Info(
|
||||
"[SteamAPI] " + FormatErrorLog(attempts, appId, gameName, isDlc, "Null or empty response", parentGameName, parentGameAppId), LogDestination.Steam);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,8 +141,8 @@ internal static class SteamStore
|
||||
break;
|
||||
if (attempts > 3)
|
||||
{
|
||||
ProgramData.LogSteam(
|
||||
"[SteamAPI] " + FormatErrorLog(attempts, appId, gameName, isDlc, "Maximum retry attempts exceeded (3)", parentGameName, parentGameAppId));
|
||||
ProgramData.Log.Info(
|
||||
"[SteamAPI] " + FormatErrorLog(attempts, appId, gameName, isDlc, "Maximum retry attempts exceeded (3)", parentGameName, parentGameAppId), LogDestination.Steam);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -31,7 +31,7 @@ internal static class UbisoftLibrary
|
||||
RegistryKey installsKey = InstallsKey;
|
||||
if (installsKey is not null)
|
||||
{
|
||||
ProgramData.Log($"[Ubisoft] Scanning registry: HKLM\\SOFTWARE\\WOW6432Node\\Ubisoft\\Launcher\\Installs");
|
||||
ProgramData.Log.Info($"[Ubisoft] Scanning registry: HKLM\\SOFTWARE\\WOW6432Node\\Ubisoft\\Launcher\\Installs", LogDestination.Scan);
|
||||
foreach (string gameId in installsKey.GetSubKeyNames())
|
||||
{
|
||||
RegistryKey installKey = installsKey.OpenSubKey(gameId);
|
||||
@@ -39,22 +39,22 @@ internal static class UbisoftLibrary
|
||||
if (installDir is not null && games.All(g => g.gameId != gameId))
|
||||
{
|
||||
games.Add((gameId, new DirectoryInfo(installDir).Name, installDir));
|
||||
ProgramData.Log($"[Ubisoft] Detected game: {new DirectoryInfo(installDir).Name} ({gameId}) | Dir: {installDir}");
|
||||
ProgramData.Log.Info($"[Ubisoft] Detected game: {new DirectoryInfo(installDir).Name} ({gameId}) | Dir: {installDir}", LogDestination.Scan);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ProgramData.Log($"[Ubisoft] Registry key not found: HKLM\\SOFTWARE\\WOW6432Node\\Ubisoft\\Launcher\\Installs");
|
||||
ProgramData.Log.Info($"[Ubisoft] Registry key not found: HKLM\\SOFTWARE\\WOW6432Node\\Ubisoft\\Launcher\\Installs", LogDestination.Scan);
|
||||
}
|
||||
|
||||
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).");
|
||||
ProgramData.Log.Info($"[Ubisoft] Injected {TestGames.Count} test game(s).", LogDestination.Scan);
|
||||
timer.Stop();
|
||||
ProgramData.Log($"[Ubisoft] Total games detected: {games.Count} in {(timer.Elapsed.TotalSeconds >= 60 ? $"{timer.Elapsed.TotalSeconds / 60:F1} minutes" : $"{timer.Elapsed.TotalSeconds:F1}s")}");
|
||||
ProgramData.Log.Info($"[Ubisoft] Total games detected: {games.Count} in {(timer.Elapsed.TotalSeconds >= 60 ? $"{timer.Elapsed.TotalSeconds / 60:F1} minutes" : $"{timer.Elapsed.TotalSeconds:F1}s")}", LogDestination.Scan);
|
||||
return games;
|
||||
});
|
||||
}
|
||||
@@ -142,7 +142,7 @@ internal static class Program
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ProgramData.LogWarning($"Cleanup failed: {ex.Message}");
|
||||
ProgramData.Log.Warn($"Cleanup failed: {ex.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -158,12 +158,12 @@ internal static class Program
|
||||
// Wait up to 5 seconds for graceful cleanup
|
||||
if (!cleanupTask.Wait(TimeSpan.FromSeconds(5)))
|
||||
{
|
||||
ProgramData.LogWarning("Cleanup timed out during application exit");
|
||||
ProgramData.Log.Warn("Cleanup timed out during application exit");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ProgramData.LogWarning($"Cleanup exception during exit: {ex.Message}");
|
||||
ProgramData.Log.Warn($"Cleanup exception during exit: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -27,6 +28,45 @@ internal static class CreamAPI
|
||||
config = directory + @"\cream_api.ini";
|
||||
}
|
||||
|
||||
internal static HashSet<string> ReadConfigDlcIds(string directory)
|
||||
{
|
||||
directory.GetCreamApiComponents(out _, out _, out _, out _, out string config);
|
||||
if (!config.FileExists())
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
HashSet<string> dlcIds = [];
|
||||
string[] lines = File.ReadAllLines(config, Encoding.Default);
|
||||
bool inDlcSection = false;
|
||||
foreach (string line in lines)
|
||||
{
|
||||
string trimmed = line.Trim();
|
||||
if (trimmed.StartsWith("[dlc]", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
inDlcSection = true;
|
||||
continue;
|
||||
}
|
||||
if (inDlcSection && trimmed.StartsWith("["))
|
||||
break;
|
||||
if (inDlcSection && trimmed.Contains('='))
|
||||
{
|
||||
string id = trimmed.Split('=')[0].Trim();
|
||||
if (!string.IsNullOrEmpty(id))
|
||||
dlcIds.Add(id);
|
||||
}
|
||||
}
|
||||
|
||||
ProgramData.Log.Info($"[CreamAPI] Read config: {config} — {dlcIds.Count} DLC", LogDestination.Unlocker);
|
||||
return dlcIds;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ProgramData.Log.Error($"[CreamAPI] Error reading config: {config}", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void CheckConfig(string directory, Selection selection, InstallForm installForm = null)
|
||||
{
|
||||
directory.GetCreamApiComponents(out _, out _, out _, out _, out string config);
|
||||
@@ -36,6 +76,7 @@ internal static class CreamAPI
|
||||
.SelectMany(extraDlc => extraDlc))
|
||||
_ = dlc.Add(extraDlc);
|
||||
|
||||
ProgramData.Log.Info($"[CreamAPI] Generating configuration with {dlc.Count} DLCs | Config: {config} | Game: {selection.Name} ({selection.Id})", LogDestination.Unlocker);
|
||||
config.DeleteFile();
|
||||
installForm?.UpdateUser($"Deleted unnecessary configuration: {Path.GetFileName(config)}", LogTextBox.Action, false);
|
||||
config.CreateFile(true, installForm)?.Close();
|
||||
@@ -45,6 +86,7 @@ internal static class CreamAPI
|
||||
selection.UseExtraProtection, installForm);
|
||||
writer.Flush();
|
||||
writer.Close();
|
||||
ProgramData.Log.Info($"[CreamAPI] Configuration generated successfully: {config} | Game: {selection.Name} ({selection.Id})", LogDestination.Unlocker);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -72,6 +114,7 @@ internal static class CreamAPI
|
||||
installForm?.UpdateUser($"Added DLC to cream_api.ini with appid {dlcId} ({dlcName})",
|
||||
LogTextBox.Action, false);
|
||||
}
|
||||
ProgramData.Log.Info($"[CreamAPI] Wrote {dlc.Count} DLC entries to cream_api.ini | AppId: {appId} ({name})", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
private static void DeleteSmokeApiComponents(string directory, InstallForm installForm = null)
|
||||
@@ -117,6 +160,7 @@ internal static class CreamAPI
|
||||
internal static async Task Uninstall(string directory, InstallForm installForm = null, bool deleteOthers = true)
|
||||
=> await Task.Run(async () =>
|
||||
{
|
||||
ProgramData.Log.Info($"[CreamAPI] Uninstalling from directory: {directory}", LogDestination.Unlocker);
|
||||
DeleteSmokeApiComponents(directory, installForm);
|
||||
|
||||
directory.GetCreamApiComponents(out string api32, out string api32_o, out string api64, out string api64_o,
|
||||
@@ -133,6 +177,7 @@ internal static class CreamAPI
|
||||
installForm?.UpdateUser(
|
||||
$"Restored Steamworks: {Path.GetFileName(api32_o)} -> {Path.GetFileName(api32)}", LogTextBox.Action,
|
||||
false);
|
||||
ProgramData.Log.Info($"[CreamAPI] Restored original steam_api.dll from backup", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
if (api64_o.FileExists())
|
||||
@@ -147,24 +192,31 @@ internal static class CreamAPI
|
||||
installForm?.UpdateUser(
|
||||
$"Restored Steamworks: {Path.GetFileName(api64_o)} -> {Path.GetFileName(api64)}", LogTextBox.Action,
|
||||
false);
|
||||
ProgramData.Log.Info($"[CreamAPI] Restored original steam_api64.dll from backup", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
if (!deleteOthers)
|
||||
{
|
||||
ProgramData.Log.Info($"[CreamAPI] Uninstall completed (partial) for directory: {directory}", LogDestination.Unlocker);
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.FileExists())
|
||||
{
|
||||
config.DeleteFile();
|
||||
installForm?.UpdateUser($"Deleted configuration: {Path.GetFileName(config)}", LogTextBox.Action, false);
|
||||
ProgramData.Log.Info($"[CreamAPI] Deleted configuration: {config}", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
await SmokeAPI.Uninstall(directory, installForm, false);
|
||||
ProgramData.Log.Info($"[CreamAPI] Uninstall completed for directory: {directory}", LogDestination.Unlocker);
|
||||
});
|
||||
|
||||
internal static async Task Install(string directory, Selection selection, InstallForm installForm = null,
|
||||
bool generateConfig = true)
|
||||
=> await Task.Run(() =>
|
||||
{
|
||||
ProgramData.Log.Info($"[CreamAPI] Installing to directory: {directory} | Game: {selection.Name} ({selection.Id}) | GenerateConfig: {generateConfig}", LogDestination.Unlocker);
|
||||
DeleteSmokeApiComponents(directory, installForm);
|
||||
|
||||
directory.GetCreamApiComponents(out string api32, out string api32_o, out string api64, out string api64_o,
|
||||
@@ -174,6 +226,7 @@ internal static class CreamAPI
|
||||
api32.MoveFile(api32_o!, true);
|
||||
installForm?.UpdateUser($"Renamed Steamworks: {Path.GetFileName(api32)} -> {Path.GetFileName(api32_o)}",
|
||||
LogTextBox.Action, false);
|
||||
ProgramData.Log.Info($"[CreamAPI] Backed up steam_api.dll -> steam_api_o.dll", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
if (api32_o.FileExists())
|
||||
@@ -193,48 +246,63 @@ internal static class CreamAPI
|
||||
{
|
||||
"CreamAPI.steam_api64.dll".WriteManifestResource(api64);
|
||||
installForm?.UpdateUser($"Wrote CreamAPI: {Path.GetFileName(api64)}", LogTextBox.Action, false);
|
||||
ProgramData.Log.Info($"[CreamAPI] Wrote 64-bit CreamAPI DLL to: {api64}", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
if (generateConfig)
|
||||
{
|
||||
CheckConfig(directory, selection, installForm);
|
||||
ProgramData.Log.Info($"[CreamAPI] Configuration generated | Game: {selection.Name} ({selection.Id})", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
ProgramData.Log.Info($"[CreamAPI] Install completed for directory: {directory} | Game: {selection.Name} ({selection.Id})", LogDestination.Unlocker);
|
||||
});
|
||||
|
||||
internal static async Task ProxyUninstall(string directory, InstallForm installForm = null,
|
||||
bool deleteOthers = true)
|
||||
=> await Task.Run(() =>
|
||||
{
|
||||
ProgramData.Log.Info($"[CreamAPI] Proxy uninstall from directory: {directory}", LogDestination.Unlocker);
|
||||
foreach (string proxy in directory.GetCreamApiProxies().Where(proxy =>
|
||||
proxy.FileExists() && (proxy.IsResourceFile(ResourceIdentifier.Steamworks32) ||
|
||||
proxy.IsResourceFile(ResourceIdentifier.Steamworks64))))
|
||||
{
|
||||
proxy.DeleteFile(true);
|
||||
installForm?.UpdateUser($"Deleted CreamAPI: {Path.GetFileName(proxy)}", LogTextBox.Action, false);
|
||||
ProgramData.Log.Info($"[CreamAPI] Deleted proxy DLL: {proxy}", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
if (!deleteOthers)
|
||||
{
|
||||
ProgramData.Log.Info($"[CreamAPI] Proxy uninstall completed (partial) for directory: {directory}", LogDestination.Unlocker);
|
||||
return;
|
||||
}
|
||||
directory.GetCreamApiComponents(out _, out _, out _, out _, out string config);
|
||||
if (config.FileExists())
|
||||
{
|
||||
config.DeleteFile();
|
||||
installForm?.UpdateUser($"Deleted configuration: {Path.GetFileName(config)}", LogTextBox.Action, false);
|
||||
ProgramData.Log.Info($"[CreamAPI] Deleted configuration: {config}", LogDestination.Unlocker);
|
||||
}
|
||||
ProgramData.Log.Info($"[CreamAPI] Proxy uninstall completed for directory: {directory}", LogDestination.Unlocker);
|
||||
});
|
||||
|
||||
internal static async Task ProxyInstall(string directory, BinaryType binaryType, Selection selection,
|
||||
InstallForm installForm = null, bool generateConfig = true)
|
||||
=> await Task.Run(async () =>
|
||||
{
|
||||
ProgramData.Log.Info($"[CreamAPI] Proxy install to directory: {directory} | Proxy: {selection.Proxy ?? Selection.DefaultProxy} | Game: {selection.Name} ({selection.Id}) | GenerateConfig: {generateConfig}", LogDestination.Unlocker);
|
||||
await Koaloader.Uninstall(directory, selection.RootDirectory, installForm);
|
||||
|
||||
string proxy = selection.Proxy ?? Selection.DefaultProxy;
|
||||
string path = directory + @"\" + proxy + ".dll";
|
||||
foreach (string _path in directory.GetCreamApiProxies().Where(p =>
|
||||
p != path && p.FileExists() && (p.IsResourceFile(ResourceIdentifier.Steamworks32) ||
|
||||
p.IsResourceFile(ResourceIdentifier.Steamworks64))))
|
||||
p.IsResourceFile(ResourceIdentifier.Steamworks64))))
|
||||
{
|
||||
_path.DeleteFile(true);
|
||||
installForm?.UpdateUser($"Deleted CreamAPI: {Path.GetFileName(_path)}", LogTextBox.Action, false);
|
||||
ProgramData.Log.Info($"[CreamAPI] Deleted old proxy DLL: {_path}", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
if (path.FileExists() && !path.IsResourceFile(ResourceIdentifier.Steamworks32) &&
|
||||
@@ -247,9 +315,15 @@ internal static class CreamAPI
|
||||
$"Wrote {(binaryType == BinaryType.BIT32 ? "32-bit" : "64-bit")} CreamAPI: {Path.GetFileName(path)}",
|
||||
LogTextBox.Action,
|
||||
false);
|
||||
ProgramData.Log.Info($"[CreamAPI] Wrote proxy DLL: {path}", LogDestination.Unlocker);
|
||||
|
||||
if (generateConfig)
|
||||
{
|
||||
CheckConfig(directory, selection, installForm);
|
||||
ProgramData.Log.Info($"[CreamAPI] Configuration generated for proxy install | Game: {selection.Name} ({selection.Id})", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
ProgramData.Log.Info($"[CreamAPI] Proxy install completed for directory: {directory} | Game: {selection.Name} ({selection.Id})", LogDestination.Unlocker);
|
||||
});
|
||||
|
||||
internal static readonly Dictionary<ResourceIdentifier, HashSet<string>> ResourceMD5s = new()
|
||||
|
||||
@@ -152,33 +152,42 @@ internal static class Koaloader
|
||||
bool deleteConfig = true)
|
||||
=> await Task.Run(async () =>
|
||||
{
|
||||
ProgramData.Log.Info($"[Koaloader] Uninstalling from directory: {directory}", LogDestination.Unlocker);
|
||||
directory.GetKoaloaderComponents(out string old_config, out string config, out string log);
|
||||
int proxyCount = 0;
|
||||
foreach (string proxyPath in directory.GetKoaloaderProxies().Where(proxyPath
|
||||
=> proxyPath.FileExists() && proxyPath.IsResourceFile(ResourceIdentifier.Koaloader)))
|
||||
{
|
||||
proxyPath.DeleteFile(true);
|
||||
installForm?.UpdateUser($"Deleted Koaloader: {Path.GetFileName(proxyPath)}", LogTextBox.Action, false);
|
||||
proxyCount++;
|
||||
}
|
||||
|
||||
int autoLoadCount = 0;
|
||||
foreach ((string unlocker, string path) in AutoLoadDLLs
|
||||
.Select(pair => (pair.unlocker, path: directory + @"\" + pair.dll))
|
||||
.Where(pair => pair.path.FileExists() && pair.path.IsResourceFile()))
|
||||
{
|
||||
path.DeleteFile(true);
|
||||
installForm?.UpdateUser($"Deleted {unlocker}: {Path.GetFileName(path)}", LogTextBox.Action, false);
|
||||
autoLoadCount++;
|
||||
}
|
||||
if (proxyCount > 0 || autoLoadCount > 0)
|
||||
ProgramData.Log.Info($"[Koaloader] Deleted {proxyCount} proxies, {autoLoadCount} auto-load DLLs | Directory: {directory}", LogDestination.Unlocker);
|
||||
|
||||
if (deleteConfig && old_config.FileExists())
|
||||
{
|
||||
old_config.DeleteFile();
|
||||
installForm?.UpdateUser($"Deleted configuration: {Path.GetFileName(old_config)}", LogTextBox.Action,
|
||||
false);
|
||||
ProgramData.Log.Info($"[Koaloader] Deleted old config: {old_config}", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
if (deleteConfig && config.FileExists())
|
||||
{
|
||||
config.DeleteFile();
|
||||
installForm?.UpdateUser($"Deleted configuration: {Path.GetFileName(config)}", LogTextBox.Action, false);
|
||||
ProgramData.Log.Info($"[Koaloader] Deleted config: {config}", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
await SmokeAPI.Uninstall(directory, installForm, deleteConfig);
|
||||
@@ -187,6 +196,7 @@ internal static class Koaloader
|
||||
await UplayR2.Uninstall(directory, installForm, deleteConfig);
|
||||
if (rootDirectory is not null && directory != rootDirectory)
|
||||
await Uninstall(rootDirectory, null, installForm, deleteConfig);
|
||||
ProgramData.Log.Info($"[Koaloader] Uninstall completed for directory: {directory}", LogDestination.Unlocker);
|
||||
});
|
||||
|
||||
internal static async Task Install(string directory, BinaryType binaryType, Selection selection,
|
||||
@@ -194,6 +204,7 @@ internal static class Koaloader
|
||||
InstallForm installForm = null, bool generateConfig = true)
|
||||
=> await Task.Run(async () =>
|
||||
{
|
||||
ProgramData.Log.Info($"[Koaloader] Installing to directory: {directory} | Proxy: {selection.Proxy ?? Selection.DefaultProxy} | Game: {selection.Name} ({selection.Id}) | GenerateConfig: {generateConfig}", LogDestination.Unlocker);
|
||||
await CreamAPI.ProxyUninstall(directory, installForm);
|
||||
|
||||
string proxy = selection.Proxy ?? Selection.DefaultProxy;
|
||||
@@ -203,6 +214,7 @@ internal static class Koaloader
|
||||
{
|
||||
_path.DeleteFile(true);
|
||||
installForm?.UpdateUser($"Deleted Koaloader: {Path.GetFileName(_path)}", LogTextBox.Action, false);
|
||||
ProgramData.Log.Info($"[Koaloader] Deleted old proxy: {_path}", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
if (path.FileExists() && !path.IsResourceFile(ResourceIdentifier.Koaloader))
|
||||
@@ -213,6 +225,7 @@ internal static class Koaloader
|
||||
$"Wrote {(binaryType == BinaryType.BIT32 ? "32-bit" : "64-bit")} Koaloader: {Path.GetFileName(path)}",
|
||||
LogTextBox.Action,
|
||||
false);
|
||||
ProgramData.Log.Info($"[Koaloader] Wrote proxy DLL: {path}", LogDestination.Unlocker);
|
||||
bool bit32 = false, bit64 = false;
|
||||
foreach (string executable in directory.EnumerateDirectory("*.exe"))
|
||||
if (executable.TryGetFileBinaryType(out BinaryType binaryType))
|
||||
@@ -277,6 +290,7 @@ internal static class Koaloader
|
||||
LogTextBox.Action, false);
|
||||
}
|
||||
|
||||
ProgramData.Log.Info($"[Koaloader] Calling SmokeAPI.CheckConfig | Game: {selection.Name} ({selection.Id})", LogDestination.Unlocker);
|
||||
SmokeAPI.CheckConfig(rootDirectory ?? directory, selection, installForm);
|
||||
}
|
||||
|
||||
@@ -328,6 +342,7 @@ internal static class Koaloader
|
||||
LogTextBox.Action, false);
|
||||
}
|
||||
|
||||
ProgramData.Log.Info($"[Koaloader] Calling ScreamAPI.CheckConfig | Game: {selection.Name} ({selection.Id})", LogDestination.Unlocker);
|
||||
ScreamAPI.CheckConfig(rootDirectory ?? directory, selection, installForm);
|
||||
break;
|
||||
}
|
||||
@@ -379,6 +394,7 @@ internal static class Koaloader
|
||||
LogTextBox.Action, false);
|
||||
}
|
||||
|
||||
ProgramData.Log.Info($"[Koaloader] Calling UplayR1.CheckConfig | Game: {selection.Name} ({selection.Id})", LogDestination.Unlocker);
|
||||
UplayR1.CheckConfig(rootDirectory ?? directory, selection, installForm);
|
||||
if (bit32)
|
||||
{
|
||||
@@ -426,13 +442,20 @@ internal static class Koaloader
|
||||
LogTextBox.Action, false);
|
||||
}
|
||||
|
||||
ProgramData.Log.Info($"[Koaloader] Calling UplayR2.CheckConfig | Game: {selection.Name} ({selection.Id})", LogDestination.Unlocker);
|
||||
UplayR2.CheckConfig(rootDirectory ?? directory, selection, installForm);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (generateConfig)
|
||||
{
|
||||
ProgramData.Log.Info($"[Koaloader] Calling Koaloader.CheckConfig | Game: {selection.Name} ({selection.Id})", LogDestination.Unlocker);
|
||||
CheckConfig(directory, installForm);
|
||||
ProgramData.Log.Info($"[Koaloader] Configuration generated | Game: {selection.Name} ({selection.Id})", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
ProgramData.Log.Info($"[Koaloader] Install completed for directory: {directory} | Game: {selection.Name} ({selection.Id})", LogDestination.Unlocker);
|
||||
});
|
||||
|
||||
internal static readonly Dictionary<ResourceIdentifier, HashSet<string>> ResourceMD5s = new()
|
||||
|
||||
@@ -46,6 +46,7 @@ internal static class Resources
|
||||
|
||||
internal static void WriteManifestResource(this string resourceIdentifier, string filePath)
|
||||
{
|
||||
ProgramData.Log.Info($"[Resources] Writing manifest resource: {resourceIdentifier} -> {filePath}", LogDestination.Scan);
|
||||
while (!Program.Canceled)
|
||||
try
|
||||
{
|
||||
@@ -58,6 +59,7 @@ internal static class Resources
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ProgramData.Log.Error($"[Resources] Failed to write manifest resource: {resourceIdentifier} -> {filePath}", e);
|
||||
if (filePath.IOWarn("Failed to write a crucial manifest resource (" + resourceIdentifier + ")", e) is
|
||||
not DialogResult.OK)
|
||||
break;
|
||||
@@ -66,6 +68,7 @@ internal static class Resources
|
||||
|
||||
internal static bool WriteResource(this byte[] resource, string filePath)
|
||||
{
|
||||
ProgramData.Log.Info($"[Resources] Writing resource ({resource.Length} bytes) -> {filePath}", LogDestination.Scan);
|
||||
while (!Program.Canceled)
|
||||
try
|
||||
{
|
||||
@@ -76,6 +79,7 @@ internal static class Resources
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ProgramData.Log.Error($"[Resources] Failed to write resource -> {filePath}", e);
|
||||
if (filePath.IOWarn("Failed to write a crucial resource", e) is not DialogResult.OK)
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -61,8 +61,7 @@ internal static class ScreamAPI
|
||||
installForm?.UpdateUser($"Deleted unnecessary configuration: {Path.GetFileName(config)}", LogTextBox.Action,
|
||||
false);
|
||||
}
|
||||
/*if (installForm is not null)
|
||||
installForm.UpdateUser("Generating ScreamAPI configuration for " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation);*/
|
||||
ProgramData.Log.Info($"[ScreamAPI] Generating configuration with {overrideCatalogItems.Count} locked catalog items, {injectedEntitlements.Count} injected entitlements | Config: {config} | Game: {selection.Name} ({selection.Id})", LogDestination.Unlocker);
|
||||
config.CreateFile(true, installForm)?.Close();
|
||||
StreamWriter writer = new(config, true, Encoding.UTF8);
|
||||
WriteConfig(writer,
|
||||
@@ -71,6 +70,7 @@ internal static class ScreamAPI
|
||||
installForm);
|
||||
writer.Flush();
|
||||
writer.Close();
|
||||
ProgramData.Log.Info($"[ScreamAPI] Configuration generated: {config} | Game: {selection.Name} ({selection.Id})", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
private static void WriteConfig(StreamWriter writer, SortedList<string, SelectionDLC> overrideCatalogItems,
|
||||
@@ -136,6 +136,7 @@ internal static class ScreamAPI
|
||||
internal static async Task Uninstall(string directory, InstallForm installForm = null, bool deleteOthers = true)
|
||||
=> await Task.Run(() =>
|
||||
{
|
||||
ProgramData.Log.Info($"[ScreamAPI] Uninstalling from directory: {directory}", LogDestination.Unlocker);
|
||||
directory.GetScreamApiComponents(out string api32, out string api32_o, out string api64, out string api64_o,
|
||||
out string old_config, out string config, out string old_log, out string log);
|
||||
if (api32_o.FileExists())
|
||||
@@ -149,6 +150,7 @@ internal static class ScreamAPI
|
||||
api32_o.MoveFile(api32!);
|
||||
installForm?.UpdateUser($"Restored EOS: {Path.GetFileName(api32_o)} -> {Path.GetFileName(api32)}",
|
||||
LogTextBox.Action, false);
|
||||
ProgramData.Log.Info($"[ScreamAPI] Restored original EOSSDK-Win32-Shipping.dll from backup", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
if (api64_o.FileExists())
|
||||
@@ -162,14 +164,19 @@ internal static class ScreamAPI
|
||||
api64_o.MoveFile(api64!);
|
||||
installForm?.UpdateUser($"Restored EOS: {Path.GetFileName(api64_o)} -> {Path.GetFileName(api64)}",
|
||||
LogTextBox.Action, false);
|
||||
ProgramData.Log.Info($"[ScreamAPI] Restored original EOSSDK-Win64-Shipping.dll from backup", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
if (!deleteOthers)
|
||||
{
|
||||
ProgramData.Log.Info($"[ScreamAPI] Uninstall completed (partial) for directory: {directory}", LogDestination.Unlocker);
|
||||
return;
|
||||
}
|
||||
if (config.FileExists())
|
||||
{
|
||||
config.DeleteFile();
|
||||
installForm?.UpdateUser($"Deleted configuration: {Path.GetFileName(config)}", LogTextBox.Action, false);
|
||||
ProgramData.Log.Info($"[ScreamAPI] Deleted config: {config}", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
if (log.FileExists())
|
||||
@@ -177,12 +184,14 @@ internal static class ScreamAPI
|
||||
log.DeleteFile();
|
||||
installForm?.UpdateUser($"Deleted log: {Path.GetFileName(log)}", LogTextBox.Action, false);
|
||||
}
|
||||
ProgramData.Log.Info($"[ScreamAPI] Uninstall completed for directory: {directory}", LogDestination.Unlocker);
|
||||
});
|
||||
|
||||
internal static async Task Install(string directory, Selection selection, InstallForm installForm = null,
|
||||
bool generateConfig = true)
|
||||
=> await Task.Run(() =>
|
||||
{
|
||||
ProgramData.Log.Info($"[ScreamAPI] Installing to directory: {directory} | Game: {selection.Name} ({selection.Id}) | GenerateConfig: {generateConfig}", LogDestination.Unlocker);
|
||||
directory.GetScreamApiComponents(out string api32, out string api32_o, out string api64, out string api64_o,
|
||||
out _, out _, out _, out _);
|
||||
if (api32.FileExists() && !api32_o.FileExists())
|
||||
@@ -190,12 +199,14 @@ internal static class ScreamAPI
|
||||
api32.MoveFile(api32_o!, true);
|
||||
installForm?.UpdateUser($"Renamed EOS: {Path.GetFileName(api32)} -> {Path.GetFileName(api32_o)}",
|
||||
LogTextBox.Action, false);
|
||||
ProgramData.Log.Info($"[ScreamAPI] Backed up EOSSDK-Win32-Shipping.dll", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
if (api32_o.FileExists())
|
||||
{
|
||||
"ScreamAPI.EOSSDK-Win32-Shipping.dll".WriteManifestResource(api32);
|
||||
installForm?.UpdateUser($"Wrote ScreamAPI: {Path.GetFileName(api32)}", LogTextBox.Action, false);
|
||||
ProgramData.Log.Info($"[ScreamAPI] Wrote 32-bit ScreamAPI DLL", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
if (api64.FileExists() && !api64_o.FileExists())
|
||||
@@ -203,16 +214,23 @@ internal static class ScreamAPI
|
||||
api64.MoveFile(api64_o!, true);
|
||||
installForm?.UpdateUser($"Renamed EOS: {Path.GetFileName(api64)} -> {Path.GetFileName(api64_o)}",
|
||||
LogTextBox.Action, false);
|
||||
ProgramData.Log.Info($"[ScreamAPI] Backed up EOSSDK-Win64-Shipping.dll", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
if (api64_o.FileExists())
|
||||
{
|
||||
"ScreamAPI.EOSSDK-Win64-Shipping.dll".WriteManifestResource(api64);
|
||||
installForm?.UpdateUser($"Wrote ScreamAPI: {Path.GetFileName(api64)}", LogTextBox.Action, false);
|
||||
ProgramData.Log.Info($"[ScreamAPI] Wrote 64-bit ScreamAPI DLL", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
if (generateConfig)
|
||||
{
|
||||
CheckConfig(directory, selection, installForm);
|
||||
ProgramData.Log.Info($"[ScreamAPI] Configuration generated | Game: {selection.Name} ({selection.Id})", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
ProgramData.Log.Info($"[ScreamAPI] Install completed for directory: {directory} | Game: {selection.Name} ({selection.Id})", LogDestination.Unlocker);
|
||||
});
|
||||
|
||||
internal static readonly Dictionary<ResourceIdentifier, HashSet<string>> ResourceMD5s = new()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -6,6 +7,7 @@ using System.Threading.Tasks;
|
||||
using CreamInstaller.Components;
|
||||
using CreamInstaller.Forms;
|
||||
using CreamInstaller.Utility;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using static CreamInstaller.Resources.Resources;
|
||||
|
||||
namespace CreamInstaller.Resources;
|
||||
@@ -32,6 +34,41 @@ internal static class SmokeAPI
|
||||
cache = directory + @"\SmokeAPI.cache.json";
|
||||
}
|
||||
|
||||
internal static (HashSet<string> enabledDlcIds, HashSet<string> disabledDlcIds) ReadConfigDlcIds(string directory)
|
||||
{
|
||||
directory.GetSmokeApiComponents(out _, out _, out _, out _, out _, out string config, out _, out _, out _);
|
||||
if (!config.FileExists())
|
||||
return (null, null);
|
||||
|
||||
try
|
||||
{
|
||||
string json = File.ReadAllText(config, Encoding.UTF8);
|
||||
JObject obj = JObject.Parse(json);
|
||||
|
||||
HashSet<string> enabled = [];
|
||||
HashSet<string> disabled = [];
|
||||
|
||||
if (obj["override_dlc_status"] is JObject overrideStatus)
|
||||
foreach (JProperty prop in overrideStatus.Properties())
|
||||
if (prop.Value.ToString() == "locked")
|
||||
disabled.Add(prop.Name);
|
||||
|
||||
if (obj["extra_dlcs"] is JObject extraDlcs)
|
||||
foreach (JProperty appProp in extraDlcs.Properties())
|
||||
if (appProp.Value["dlcs"] is JObject dlcs)
|
||||
foreach (JProperty dlcProp in dlcs.Properties())
|
||||
enabled.Add(dlcProp.Name);
|
||||
|
||||
ProgramData.Log.Info($"[SmokeAPI] Read config: {config} — {enabled.Count} enabled, {disabled.Count} disabled DLC", LogDestination.Unlocker);
|
||||
return (enabled, disabled);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ProgramData.Log.Error($"[SmokeAPI] Error reading config: {config}", e);
|
||||
return (null, null);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void CheckConfig(string directory, Selection selection, InstallForm installForm = null)
|
||||
{
|
||||
directory.GetSmokeApiComponents(out _, out _, out _, out _, out string old_config, out string config, out _,
|
||||
@@ -62,6 +99,7 @@ internal static class SmokeAPI
|
||||
old_config.DeleteFile();
|
||||
installForm?.UpdateUser($"Deleted old configuration: {Path.GetFileName(old_config)}", LogTextBox.Action,
|
||||
false);
|
||||
ProgramData.Log.Info($"[SmokeAPI] Deleted old config: {old_config} | Game: {selection.Name} ({selection.Id})", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
if (config.FileExists())
|
||||
@@ -70,8 +108,7 @@ internal static class SmokeAPI
|
||||
installForm?.UpdateUser($"Deleted unnecessary configuration: {Path.GetFileName(config)}", LogTextBox.Action,
|
||||
false);
|
||||
}
|
||||
/*if (installForm is not null)
|
||||
installForm.UpdateUser("Generating SmokeAPI configuration for " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation);*/
|
||||
ProgramData.Log.Info($"[SmokeAPI] Generating configuration with {overrideDlc.Count} locked DLCs, {injectDlc.Count} injected DLCs | Config: {config} | Game: {selection.Name} ({selection.Id})", LogDestination.Unlocker);
|
||||
config.CreateFile(true, installForm)?.Close();
|
||||
StreamWriter writer = new(config, true, Encoding.UTF8);
|
||||
WriteConfig(writer, selection.Id,
|
||||
@@ -81,6 +118,7 @@ internal static class SmokeAPI
|
||||
new(injectDlc.ToDictionary(dlc => dlc.Id, dlc => dlc), PlatformIdComparer.String), installForm);
|
||||
writer.Flush();
|
||||
writer.Close();
|
||||
ProgramData.Log.Info($"[SmokeAPI] Configuration generated: {config} | Game: {selection.Name} ({selection.Id})", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
private static void WriteConfig(StreamWriter writer, string appId,
|
||||
@@ -173,6 +211,7 @@ internal static class SmokeAPI
|
||||
writer.WriteLine(" \"extra_dlcs\": {}");
|
||||
|
||||
writer.WriteLine("}");
|
||||
ProgramData.Log.Info($"[SmokeAPI] Wrote config with {overrideDlc.Count} locked DLCs, {injectDlc.Count} injected DLCs, {extraApps.Count} extra apps | AppId: {appId}", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
private static void DeleteCreamApiComponents(string directory, InstallForm installForm = null)
|
||||
@@ -189,6 +228,7 @@ internal static class SmokeAPI
|
||||
internal static async Task Uninstall(string directory, InstallForm installForm = null, bool deleteOthers = true)
|
||||
=> await Task.Run(async () =>
|
||||
{
|
||||
ProgramData.Log.Info($"[SmokeAPI] Uninstalling from directory: {directory}", LogDestination.Unlocker);
|
||||
DeleteCreamApiComponents(directory, installForm);
|
||||
|
||||
directory.GetSmokeApiComponents(out string api32, out string api32_o, out string api64, out string api64_o,
|
||||
@@ -205,6 +245,7 @@ internal static class SmokeAPI
|
||||
installForm?.UpdateUser(
|
||||
$"Restored Steamworks: {Path.GetFileName(api32_o)} -> {Path.GetFileName(api32)}", LogTextBox.Action,
|
||||
false);
|
||||
ProgramData.Log.Info($"[SmokeAPI] Restored original steam_api.dll from backup", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
if (api64_o.FileExists())
|
||||
@@ -219,28 +260,35 @@ internal static class SmokeAPI
|
||||
installForm?.UpdateUser(
|
||||
$"Restored Steamworks: {Path.GetFileName(api64_o)} -> {Path.GetFileName(api64)}", LogTextBox.Action,
|
||||
false);
|
||||
ProgramData.Log.Info($"[SmokeAPI] Restored original steam_api64.dll from backup", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
if (!deleteOthers)
|
||||
{
|
||||
ProgramData.Log.Info($"[SmokeAPI] Uninstall completed (partial) for directory: {directory}", LogDestination.Unlocker);
|
||||
return;
|
||||
}
|
||||
|
||||
if (old_config.FileExists())
|
||||
{
|
||||
old_config.DeleteFile();
|
||||
installForm?.UpdateUser($"Deleted configuration: {Path.GetFileName(old_config)}", LogTextBox.Action,
|
||||
false);
|
||||
ProgramData.Log.Info($"[SmokeAPI] Deleted old config: {old_config}", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
if (config.FileExists())
|
||||
{
|
||||
config.DeleteFile();
|
||||
installForm?.UpdateUser($"Deleted configuration: {Path.GetFileName(config)}", LogTextBox.Action, false);
|
||||
ProgramData.Log.Info($"[SmokeAPI] Deleted config: {config}", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
if (cache.FileExists())
|
||||
{
|
||||
cache.DeleteFile();
|
||||
installForm?.UpdateUser($"Deleted cache: {Path.GetFileName(cache)}", LogTextBox.Action, false);
|
||||
ProgramData.Log.Info($"[SmokeAPI] Deleted cache: {cache}", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
if (old_log.FileExists())
|
||||
@@ -256,12 +304,14 @@ internal static class SmokeAPI
|
||||
}
|
||||
|
||||
await CreamAPI.Uninstall(directory, installForm, false);
|
||||
ProgramData.Log.Info($"[SmokeAPI] Uninstall completed for directory: {directory}", LogDestination.Unlocker);
|
||||
});
|
||||
|
||||
internal static async Task Install(string directory, Selection selection, InstallForm installForm = null,
|
||||
bool generateConfig = true)
|
||||
=> await Task.Run(() =>
|
||||
{
|
||||
ProgramData.Log.Info($"[SmokeAPI] Installing to directory: {directory} | Game: {selection.Name} ({selection.Id}) | GenerateConfig: {generateConfig}", LogDestination.Unlocker);
|
||||
DeleteCreamApiComponents(directory, installForm);
|
||||
|
||||
directory.GetSmokeApiComponents(out string api32, out string api32_o, out string api64, out string api64_o,
|
||||
@@ -271,12 +321,14 @@ internal static class SmokeAPI
|
||||
api32.MoveFile(api32_o!, true);
|
||||
installForm?.UpdateUser($"Renamed Steamworks: {Path.GetFileName(api32)} -> {Path.GetFileName(api32_o)}",
|
||||
LogTextBox.Action, false);
|
||||
ProgramData.Log.Info($"[SmokeAPI] Backed up steam_api.dll -> steam_api_o.dll", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
if (api32_o.FileExists())
|
||||
{
|
||||
"SmokeAPI.steam_api.dll".WriteManifestResource(api32);
|
||||
installForm?.UpdateUser($"Wrote SmokeAPI: {Path.GetFileName(api32)}", LogTextBox.Action, false);
|
||||
ProgramData.Log.Info($"[SmokeAPI] Wrote 32-bit SmokeAPI DLL", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
if (api64.FileExists() && !api64_o.FileExists())
|
||||
@@ -284,55 +336,71 @@ internal static class SmokeAPI
|
||||
api64.MoveFile(api64_o!, true);
|
||||
installForm?.UpdateUser($"Renamed Steamworks: {Path.GetFileName(api64)} -> {Path.GetFileName(api64_o)}",
|
||||
LogTextBox.Action, false);
|
||||
ProgramData.Log.Info($"[SmokeAPI] Backed up steam_api64.dll -> steam_api64_o.dll", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
if (api64_o.FileExists())
|
||||
{
|
||||
"SmokeAPI.steam_api64.dll".WriteManifestResource(api64);
|
||||
installForm?.UpdateUser($"Wrote SmokeAPI: {Path.GetFileName(api64)}", LogTextBox.Action, false);
|
||||
ProgramData.Log.Info($"[SmokeAPI] Wrote 64-bit SmokeAPI DLL", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
if (generateConfig)
|
||||
{
|
||||
CheckConfig(directory, selection, installForm);
|
||||
ProgramData.Log.Info($"[SmokeAPI] Configuration generated | Game: {selection.Name} ({selection.Id})", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
ProgramData.Log.Info($"[SmokeAPI] Install completed for directory: {directory} | Game: {selection.Name} ({selection.Id})", LogDestination.Unlocker);
|
||||
});
|
||||
|
||||
internal static async Task ProxyUninstall(string directory, InstallForm installForm = null,
|
||||
bool deleteOthers = true)
|
||||
=> await Task.Run(() =>
|
||||
{
|
||||
ProgramData.Log.Info($"[SmokeAPI] Proxy uninstall from directory: {directory}", LogDestination.Unlocker);
|
||||
foreach (string proxy in directory.GetSmokeApiProxies().Where(proxy =>
|
||||
proxy.FileExists() && (proxy.IsResourceFile(ResourceIdentifier.Steamworks32) ||
|
||||
proxy.IsResourceFile(ResourceIdentifier.Steamworks64))))
|
||||
{
|
||||
proxy.DeleteFile(true);
|
||||
installForm?.UpdateUser($"Deleted SmokeAPI: {Path.GetFileName(proxy)}", LogTextBox.Action, false);
|
||||
ProgramData.Log.Info($"[SmokeAPI] Deleted proxy DLL: {proxy}", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
if (!deleteOthers)
|
||||
{
|
||||
ProgramData.Log.Info($"[SmokeAPI] Proxy uninstall completed (partial) for directory: {directory}", LogDestination.Unlocker);
|
||||
return;
|
||||
}
|
||||
directory.GetSmokeApiComponents(out _, out _, out _, out _, out string old_config, out string config, out _,
|
||||
out _, out _);
|
||||
if (config.FileExists())
|
||||
{
|
||||
config.DeleteFile();
|
||||
installForm?.UpdateUser($"Deleted configuration: {Path.GetFileName(config)}", LogTextBox.Action, false);
|
||||
ProgramData.Log.Info($"[SmokeAPI] Deleted config: {config}", LogDestination.Unlocker);
|
||||
}
|
||||
ProgramData.Log.Info($"[SmokeAPI] Proxy uninstall completed for directory: {directory}", LogDestination.Unlocker);
|
||||
});
|
||||
|
||||
internal static async Task ProxyInstall(string directory, BinaryType binaryType, Selection selection,
|
||||
InstallForm installForm = null, bool generateConfig = true)
|
||||
=> await Task.Run(async () =>
|
||||
{
|
||||
ProgramData.Log.Info($"[SmokeAPI] Proxy install to directory: {directory} | Proxy: {selection.Proxy ?? Selection.DefaultProxy} | Game: {selection.Name} ({selection.Id}) | GenerateConfig: {generateConfig}", LogDestination.Unlocker);
|
||||
await Koaloader.Uninstall(directory, selection.RootDirectory, installForm);
|
||||
|
||||
string proxy = selection.Proxy ?? Selection.DefaultProxy;
|
||||
string path = directory + @"\" + proxy + ".dll";
|
||||
foreach (string _path in directory.GetSmokeApiProxies().Where(p =>
|
||||
p != path && p.FileExists() && (p.IsResourceFile(ResourceIdentifier.Steamworks32) ||
|
||||
p.IsResourceFile(ResourceIdentifier.Steamworks64))))
|
||||
p.IsResourceFile(ResourceIdentifier.Steamworks64))))
|
||||
{
|
||||
_path.DeleteFile(true);
|
||||
installForm?.UpdateUser($"Deleted SmokeAPI: {Path.GetFileName(_path)}", LogTextBox.Action, false);
|
||||
ProgramData.Log.Info($"[SmokeAPI] Deleted old proxy DLL: {_path}", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
if (path.FileExists() && !path.IsResourceFile(ResourceIdentifier.Steamworks32) &&
|
||||
@@ -343,11 +411,16 @@ internal static class SmokeAPI
|
||||
.WriteManifestResource(path);
|
||||
installForm?.UpdateUser(
|
||||
$"Wrote {(binaryType == BinaryType.BIT32 ? "32-bit" : "64-bit")} SmokeAPI: {Path.GetFileName(path)}",
|
||||
LogTextBox.Action,
|
||||
false);
|
||||
LogTextBox.Action, false);
|
||||
ProgramData.Log.Info($"[SmokeAPI] Wrote proxy DLL: {path}", LogDestination.Unlocker);
|
||||
|
||||
if (generateConfig)
|
||||
{
|
||||
CheckConfig(directory, selection, installForm);
|
||||
ProgramData.Log.Info($"[SmokeAPI] Configuration generated for proxy install | Game: {selection.Name} ({selection.Id})", LogDestination.Unlocker);
|
||||
}
|
||||
|
||||
ProgramData.Log.Info($"[SmokeAPI] Proxy install completed for directory: {directory} | Game: {selection.Name} ({selection.Id})", LogDestination.Unlocker);
|
||||
});
|
||||
|
||||
internal static readonly Dictionary<ResourceIdentifier, HashSet<string>> ResourceMD5s = new()
|
||||
|
||||
@@ -154,11 +154,15 @@ internal sealed class Selection : IEquatable<Selection>
|
||||
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.Scan);
|
||||
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.Scan);
|
||||
ReadCreamApiConfig(creamConfig);
|
||||
return InstalledUnlocker.CreamAPI;
|
||||
}
|
||||
@@ -170,7 +174,11 @@ internal sealed class Selection : IEquatable<Selection>
|
||||
{
|
||||
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.Scan);
|
||||
return InstalledUnlocker.SmokeAPI;
|
||||
}
|
||||
ProgramData.Log.Info($"[Unlocker] CreamAPI detected (via _o files) | Game: {Name} ({Id})", LogDestination.Scan);
|
||||
return InstalledUnlocker.CreamAPI;
|
||||
}
|
||||
}
|
||||
@@ -180,7 +188,10 @@ internal sealed class Selection : IEquatable<Selection>
|
||||
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.Scan);
|
||||
return InstalledUnlocker.ScreamAPI;
|
||||
}
|
||||
}
|
||||
|
||||
if (Platform is Platform.Ubisoft)
|
||||
@@ -188,11 +199,17 @@ internal sealed class Selection : IEquatable<Selection>
|
||||
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.Scan);
|
||||
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.Scan);
|
||||
return InstalledUnlocker.UplayR2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,27 +219,43 @@ internal sealed class Selection : IEquatable<Selection>
|
||||
if (directory.GetKoaloaderProxies().Any(proxy =>
|
||||
proxy.FileExists() && proxy.IsResourceFile(ResourceIdentifier.Koaloader))
|
||||
|| config.FileExists())
|
||||
{
|
||||
ProgramData.Log.Info($"[Unlocker] Koaloader detected | Game: {Name} ({Id})", LogDestination.Scan);
|
||||
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.Scan);
|
||||
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.Scan);
|
||||
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.Scan);
|
||||
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.Scan);
|
||||
return InstalledUnlocker.CreamAPI;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ProgramData.Log.Info($"[Unlocker] No installed unlocker found | Game: {Name} ({Id})", LogDestination.Scan);
|
||||
return InstalledUnlocker.None;
|
||||
}
|
||||
|
||||
@@ -244,8 +277,12 @@ internal sealed class Selection : IEquatable<Selection>
|
||||
try
|
||||
{
|
||||
if (!configPath.FileExists())
|
||||
{
|
||||
ProgramData.Log.Info($"[Unlocker] Config not found: {configPath} | Game: {Name} ({Id})", LogDestination.Scan);
|
||||
return;
|
||||
}
|
||||
|
||||
ProgramData.Log.Info($"[Unlocker] Reading config: {configPath} | Game: {Name} ({Id})", LogDestination.Scan);
|
||||
string[] lines = File.ReadAllLines(configPath);
|
||||
foreach (string line in lines)
|
||||
{
|
||||
@@ -257,14 +294,15 @@ internal sealed class Selection : IEquatable<Selection>
|
||||
{
|
||||
string value = parts[1].Trim();
|
||||
UseExtraProtection = value.Equals("true", StringComparison.OrdinalIgnoreCase);
|
||||
ProgramData.Log.Info($"[Unlocker] ExtraProtection = {UseExtraProtection} | Game: {Name} ({Id})", LogDestination.Scan);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
catch (Exception e)
|
||||
{
|
||||
// If we can't read the config, leave UseExtraProtection at its default value
|
||||
ProgramData.Log.Error($"[Unlocker] Error reading config: {configPath} | Game: {Name} ({Id})", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -89,25 +89,25 @@ internal static class HttpClientManager
|
||||
bool permanent = code is >= 400 and < 500 and not 429;
|
||||
string label = permanent ? "Permanent failure" : code == 429 ? "Too many requests" : "Get request failed";
|
||||
string statusInfo = $" (HTTP {code}{(permanent ? " - Permanent" : code == 429 ? " - Rate Limited" : "")})";
|
||||
ProgramData.LogSteam($"[SteamAPI] {label} to {url}{statusInfo}: {e.Message}");
|
||||
ProgramData.Log.Info($"[SteamAPI] {label} to {url}{statusInfo}: {e.Message}", LogDestination.Steam);
|
||||
return (null, permanent);
|
||||
}
|
||||
ProgramData.LogSteam($"[SteamAPI] Get request failed to {url}: {e.Message}");
|
||||
ProgramData.Log.Info($"[SteamAPI] Get request failed to {url}: {e.Message}", LogDestination.Steam);
|
||||
return (null, false);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
ProgramData.LogSteam("[SteamAPI] Get request timed out for " + url);
|
||||
ProgramData.Log.Info("[SteamAPI] Get request timed out for " + url, LogDestination.Steam);
|
||||
return (null, false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
ProgramData.LogSteam("[SteamAPI] Get request was cancelled for " + url);
|
||||
ProgramData.Log.Info("[SteamAPI] Get request was cancelled for " + url, LogDestination.Steam);
|
||||
return (null, false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ProgramData.LogSteam("[SteamAPI] Get request failed to " + url + ": " + e.Message);
|
||||
ProgramData.Log.Info("[SteamAPI] Get request failed to " + url + ": " + e.Message, LogDestination.Steam);
|
||||
return (null, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,10 @@ using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace CreamInstaller.Utility;
|
||||
|
||||
internal enum LogDestination { App, Scan, Steam, Unlocker }
|
||||
|
||||
internal enum LogLevel { Info, Warning, Error }
|
||||
|
||||
internal enum InstalledUnlocker
|
||||
{
|
||||
None = 0,
|
||||
@@ -29,6 +33,7 @@ internal sealed class InstalledDlcRecord
|
||||
public string DlcType { get; set; }
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InstalledGameRecord
|
||||
@@ -68,7 +73,6 @@ internal static class ProgramData
|
||||
|
||||
private static readonly string OldProgramChoicesPath = DirectoryPath + @"\choices.txt";
|
||||
private static readonly string ProgramChoicesPath = DirectoryPath + @"\choices.json";
|
||||
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 = CachePath + @"\installed.json";
|
||||
@@ -76,11 +80,11 @@ internal static class ProgramData
|
||||
internal static readonly string ScanLogPath = Path.Combine(DirectoryPath, "game-scan.log");
|
||||
internal static readonly string SteamLogPath = Path.Combine(DirectoryPath, "cream-steam.log");
|
||||
internal static readonly string AppLogPath = Path.Combine(DirectoryPath, "CreamInstaller.log");
|
||||
internal static readonly string UnlockerLogPath = Path.Combine(DirectoryPath, "unlocker.log");
|
||||
|
||||
internal static event Action<string> OnLog;
|
||||
internal static event Action<string> OnLogSteam;
|
||||
internal static event Action<string> OnLogWarning;
|
||||
internal static event Action<string> OnLogError;
|
||||
internal readonly record struct LogEventArgs(string Message, LogDestination Destination, LogLevel Level, Exception Exception = null);
|
||||
|
||||
internal static event Action<LogEventArgs> OnLog;
|
||||
|
||||
private static string FormatLogEntry(string message)
|
||||
{
|
||||
@@ -140,59 +144,57 @@ internal static event Action<string> OnLogError;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Log(string message)
|
||||
private static string GetLogPath(LogDestination destination) => destination switch
|
||||
{
|
||||
try
|
||||
{
|
||||
LogChannel.Writer.TryWrite(new LogEntry(ScanLogPath, FormatLogEntry(message)));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored; logging must never crash the application
|
||||
}
|
||||
OnLog?.Invoke(message);
|
||||
}
|
||||
LogDestination.Scan => ScanLogPath,
|
||||
LogDestination.Steam => SteamLogPath,
|
||||
LogDestination.Unlocker => UnlockerLogPath,
|
||||
_ => AppLogPath
|
||||
};
|
||||
|
||||
internal static void LogSteam(string message)
|
||||
internal static class Log
|
||||
{
|
||||
try
|
||||
public static void Info(string message, LogDestination destination = LogDestination.App)
|
||||
{
|
||||
LogChannel.Writer.TryWrite(new LogEntry(SteamLogPath, FormatLogEntry(message)));
|
||||
try
|
||||
{
|
||||
LogChannel.Writer.TryWrite(new LogEntry(GetLogPath(destination), FormatLogEntry(message)));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored; logging must never crash the application
|
||||
}
|
||||
OnLog?.Invoke(new LogEventArgs(message, destination, LogLevel.Info));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored; logging must never crash the application
|
||||
}
|
||||
OnLogSteam?.Invoke(message);
|
||||
}
|
||||
|
||||
internal static void LogWarning(string message)
|
||||
{
|
||||
try
|
||||
public static void Warn(string message)
|
||||
{
|
||||
LogChannel.Writer.TryWrite(new LogEntry(AppLogPath, FormatLogEntry($"[WARN] {message}")));
|
||||
try
|
||||
{
|
||||
LogChannel.Writer.TryWrite(new LogEntry(AppLogPath, FormatLogEntry($"[WARN] {message}")));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored; logging must never crash the application
|
||||
}
|
||||
OnLog?.Invoke(new LogEventArgs(message, LogDestination.App, LogLevel.Warning));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored; logging must never crash the application
|
||||
}
|
||||
OnLogWarning?.Invoke(message);
|
||||
}
|
||||
|
||||
internal static void LogError(string message, Exception ex = null)
|
||||
{
|
||||
try
|
||||
public static void Error(string message, Exception ex = null)
|
||||
{
|
||||
string entry = ex is not null
|
||||
? FormatLogErrorEntry(message, ex)
|
||||
: FormatLogEntry($"[ERROR] {message}");
|
||||
LogChannel.Writer.TryWrite(new LogEntry(AppLogPath, entry));
|
||||
try
|
||||
{
|
||||
string entry = ex is not null
|
||||
? FormatLogErrorEntry(message, ex)
|
||||
: FormatLogEntry($"[ERROR] {message}");
|
||||
LogChannel.Writer.TryWrite(new LogEntry(AppLogPath, entry));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored; logging must never crash the application
|
||||
}
|
||||
OnLog?.Invoke(new LogEventArgs(message, LogDestination.App, LogLevel.Error, ex));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored; logging must never crash the application
|
||||
}
|
||||
OnLogError?.Invoke(message);
|
||||
}
|
||||
|
||||
internal static void ClearLog()
|
||||
@@ -203,6 +205,8 @@ internal static event Action<string> OnLogError;
|
||||
File.Delete(ScanLogPath);
|
||||
if (File.Exists(SteamLogPath))
|
||||
File.Delete(SteamLogPath);
|
||||
if (File.Exists(UnlockerLogPath))
|
||||
File.Delete(UnlockerLogPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -323,39 +327,6 @@ internal static event Action<string> OnLogError;
|
||||
}
|
||||
}
|
||||
|
||||
internal static IEnumerable<(Platform platform, string gameId, string dlcId)> ReadDlcChoices()
|
||||
{
|
||||
if (DlcChoicesPath.FileExists())
|
||||
try
|
||||
{
|
||||
if (JsonConvert.DeserializeObject(DlcChoicesPath.ReadFile(),
|
||||
typeof(IEnumerable<(Platform platform, string gameId, string dlcId)>)) is
|
||||
IEnumerable<(Platform platform, string gameId, string dlcId)> choices)
|
||||
return choices;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
internal static void WriteDlcChoices(List<(Platform platform, string gameId, string dlcId)> choices)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (choices is null || choices.Count == 0)
|
||||
DlcChoicesPath.DeleteFile();
|
||||
else
|
||||
DlcChoicesPath.WriteFile(JsonConvert.SerializeObject(choices));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
internal static IEnumerable<(Platform platform, string id, string proxy, bool enabled)> ReadProxyChoices()
|
||||
{
|
||||
if (KoaloaderProxyChoicesPath.FileExists())
|
||||
|
||||
@@ -365,7 +365,7 @@ internal static class ThemeManager
|
||||
int useDark = IsDark ? 1 : 0;
|
||||
NativeMethods.EnableDarkTitleBar(form.Handle, useDark);
|
||||
}
|
||||
catch (Exception ex) { ProgramData.LogWarning($"[Theme] Title bar theming failed: {ex.Message}"); }
|
||||
catch (Exception ex) { ProgramData.Log.Warn($"[Theme] Title bar theming failed: {ex.Message}"); }
|
||||
}
|
||||
|
||||
private static void TryApplyScrollbarTheme(Control control, bool dark)
|
||||
@@ -375,7 +375,7 @@ internal static class ThemeManager
|
||||
string theme = dark ? "DarkMode_Explorer" : null;
|
||||
NativeImports.SetWindowTheme(control.Handle, theme, null);
|
||||
}
|
||||
catch (Exception ex) { ProgramData.LogWarning($"[Theme] Scrollbar theming failed: {ex.Message}"); }
|
||||
catch (Exception ex) { ProgramData.Log.Warn($"[Theme] Scrollbar theming failed: {ex.Message}"); }
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user