Persist DLC State / Detect DLC State from DLC Unlocker Configs / Refactor Logging

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