diff --git a/CreamInstaller/Forms/DebugForm.cs b/CreamInstaller/Forms/DebugForm.cs index ae99b43..60b4e76 100644 --- a/CreamInstaller/Forms/DebugForm.cs +++ b/CreamInstaller/Forms/DebugForm.cs @@ -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); } } diff --git a/CreamInstaller/Forms/InstallForm.cs b/CreamInstaller/Forms/InstallForm.cs index 54a0332..e817cce 100644 --- a/CreamInstaller/Forms/InstallForm.cs +++ b/CreamInstaller/Forms/InstallForm.cs @@ -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; } diff --git a/CreamInstaller/Forms/SelectForm.cs b/CreamInstaller/Forms/SelectForm.cs index d0cfd2a..d331fe2 100644 --- a/CreamInstaller/Forms/SelectForm.cs +++ b/CreamInstaller/Forms/SelectForm.cs @@ -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 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 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 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 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() }); } diff --git a/CreamInstaller/Forms/TestGameForm.cs b/CreamInstaller/Forms/TestGameForm.cs index 75c51e3..25d4154 100644 --- a/CreamInstaller/Forms/TestGameForm.cs +++ b/CreamInstaller/Forms/TestGameForm.cs @@ -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 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 => diff --git a/CreamInstaller/Forms/UpdateForm.cs b/CreamInstaller/Forms/UpdateForm.cs index ddbfd31..2fc8f84 100644 --- a/CreamInstaller/Forms/UpdateForm.cs +++ b/CreamInstaller/Forms/UpdateForm.cs @@ -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(); diff --git a/CreamInstaller/Platforms/Epic/EpicLibrary.cs b/CreamInstaller/Platforms/Epic/EpicLibrary.cs index e04c78d..fa2e0e5 100644 --- a/CreamInstaller/Platforms/Epic/EpicLibrary.cs +++ b/CreamInstaller/Platforms/Epic/EpicLibrary.cs @@ -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; }); } \ No newline at end of file diff --git a/CreamInstaller/Platforms/Epic/EpicStore.cs b/CreamInstaller/Platforms/Epic/EpicStore.cs index 5361d07..46cc568 100644 --- a/CreamInstaller/Platforms/Epic/EpicStore.cs +++ b/CreamInstaller/Platforms/Epic/EpicStore.cs @@ -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 = diff --git a/CreamInstaller/Platforms/Steam/SteamCMD.WebAPI.cs b/CreamInstaller/Platforms/Steam/SteamCMD.WebAPI.cs index 4dc389f..2154b35 100644 --- a/CreamInstaller/Platforms/Steam/SteamCMD.WebAPI.cs +++ b/CreamInstaller/Platforms/Steam/SteamCMD.WebAPI.cs @@ -12,7 +12,7 @@ internal static partial class SteamCMD private const int CooldownGame = 600; private const int CooldownDlc = 1200; - private static async Task QueryWebAPI(string appId, bool isDlc = false, int attempts = 0) + internal static async Task 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; } diff --git a/CreamInstaller/Platforms/Steam/SteamCMD.cs b/CreamInstaller/Platforms/Steam/SteamCMD.cs index 3518fdf..8986c39 100644 --- a/CreamInstaller/Platforms/Steam/SteamCMD.cs +++ b/CreamInstaller/Platforms/Steam/SteamCMD.cs @@ -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() 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; diff --git a/CreamInstaller/Platforms/Steam/SteamLibrary.cs b/CreamInstaller/Platforms/Steam/SteamLibrary.cs index 2928dfd..597854d 100644 --- a/CreamInstaller/Platforms/Steam/SteamLibrary.cs +++ b/CreamInstaller/Platforms/Steam/SteamLibrary.cs @@ -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 seenAppIds = new(); HashSet 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; diff --git a/CreamInstaller/Platforms/Steam/SteamStore.cs b/CreamInstaller/Platforms/Steam/SteamStore.cs index eba016b..d775fd1 100644 --- a/CreamInstaller/Platforms/Steam/SteamStore.cs +++ b/CreamInstaller/Platforms/Steam/SteamStore.cs @@ -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; } diff --git a/CreamInstaller/Platforms/Ubisoft/UbisoftLibrary.cs b/CreamInstaller/Platforms/Ubisoft/UbisoftLibrary.cs index a5ebe7c..d6d5bd8 100644 --- a/CreamInstaller/Platforms/Ubisoft/UbisoftLibrary.cs +++ b/CreamInstaller/Platforms/Ubisoft/UbisoftLibrary.cs @@ -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; }); } \ No newline at end of file diff --git a/CreamInstaller/Program.cs b/CreamInstaller/Program.cs index 6af66ed..60ef0d6 100644 --- a/CreamInstaller/Program.cs +++ b/CreamInstaller/Program.cs @@ -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 { diff --git a/CreamInstaller/Resources/CreamAPI.cs b/CreamInstaller/Resources/CreamAPI.cs index fe57fcf..33237ed 100644 --- a/CreamInstaller/Resources/CreamAPI.cs +++ b/CreamInstaller/Resources/CreamAPI.cs @@ -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 ReadConfigDlcIds(string directory) + { + directory.GetCreamApiComponents(out _, out _, out _, out _, out string config); + if (!config.FileExists()) + return null; + + try + { + HashSet 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> ResourceMD5s = new() diff --git a/CreamInstaller/Resources/Koaloader.cs b/CreamInstaller/Resources/Koaloader.cs index df674d1..783bc95 100644 --- a/CreamInstaller/Resources/Koaloader.cs +++ b/CreamInstaller/Resources/Koaloader.cs @@ -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> ResourceMD5s = new() diff --git a/CreamInstaller/Resources/Resources.cs b/CreamInstaller/Resources/Resources.cs index b75a4f8..4476db2 100644 --- a/CreamInstaller/Resources/Resources.cs +++ b/CreamInstaller/Resources/Resources.cs @@ -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; } diff --git a/CreamInstaller/Resources/ScreamAPI.cs b/CreamInstaller/Resources/ScreamAPI.cs index 1d36640..8554333 100644 --- a/CreamInstaller/Resources/ScreamAPI.cs +++ b/CreamInstaller/Resources/ScreamAPI.cs @@ -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 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> ResourceMD5s = new() diff --git a/CreamInstaller/Resources/SmokeAPI.cs b/CreamInstaller/Resources/SmokeAPI.cs index 1e5b5c0..ca8eebb 100644 --- a/CreamInstaller/Resources/SmokeAPI.cs +++ b/CreamInstaller/Resources/SmokeAPI.cs @@ -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 enabledDlcIds, HashSet 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 enabled = []; + HashSet 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> ResourceMD5s = new() diff --git a/CreamInstaller/Selection.cs b/CreamInstaller/Selection.cs index 489abe3..fd99a1e 100644 --- a/CreamInstaller/Selection.cs +++ b/CreamInstaller/Selection.cs @@ -154,11 +154,15 @@ internal sealed class Selection : IEquatable 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 { 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 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 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 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 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 { 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); } } diff --git a/CreamInstaller/Utility/HttpClientManager.cs b/CreamInstaller/Utility/HttpClientManager.cs index 9d1293c..b634778 100644 --- a/CreamInstaller/Utility/HttpClientManager.cs +++ b/CreamInstaller/Utility/HttpClientManager.cs @@ -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); } } diff --git a/CreamInstaller/Utility/ProgramData.cs b/CreamInstaller/Utility/ProgramData.cs index 1a638e1..499a083 100644 --- a/CreamInstaller/Utility/ProgramData.cs +++ b/CreamInstaller/Utility/ProgramData.cs @@ -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 OnLog; -internal static event Action OnLogSteam; -internal static event Action OnLogWarning; -internal static event Action OnLogError; +internal readonly record struct LogEventArgs(string Message, LogDestination Destination, LogLevel Level, Exception Exception = null); + +internal static event Action OnLog; private static string FormatLogEntry(string message) { @@ -140,59 +144,57 @@ internal static event Action 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 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 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()) diff --git a/CreamInstaller/Utility/ThemeManager.cs b/CreamInstaller/Utility/ThemeManager.cs index ba204f9..62ab9c0 100644 --- a/CreamInstaller/Utility/ThemeManager.cs +++ b/CreamInstaller/Utility/ThemeManager.cs @@ -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}"); } } // -----------------------------------------------------------------