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:
Frog
2026-07-17 00:34:06 -07:00
parent 970ee53bfc
commit 92379aebfd
22 changed files with 498 additions and 268 deletions
+12 -17
View File
@@ -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);
}
}
+20 -16
View File
@@ -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;
}
+68 -38
View File
@@ -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()
});
}
+7 -7
View File
@@ -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 =>
+2 -2
View File
@@ -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();
+7 -7
View File
@@ -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;
});
}
+2 -2
View File
@@ -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;
}
+11 -11
View File
@@ -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;
+16 -16
View File
@@ -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;
+24 -24
View File
@@ -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;
});
}
+3 -3
View File
@@ -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
{
+76 -2
View File
@@ -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()
+23
View File
@@ -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()
+4
View File
@@ -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;
}
+20 -2
View File
@@ -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()
+79 -6
View File
@@ -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()
+40 -2
View File
@@ -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);
}
}
+5 -5
View File
@@ -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);
}
}
+51 -80
View File
@@ -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())
+2 -2
View File
@@ -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}"); }
}
// -----------------------------------------------------------------