From dea8a24c309cec08e1771af1a5603dec5e727553 Mon Sep 17 00:00:00 2001 From: Frog Date: Fri, 12 Jun 2026 23:53:20 -0700 Subject: [PATCH] Improve Logging / Add Forced Proxy for Steam Games with no SteamAPI DLL Closes #28 - Adds logic to allow for a Steam Game with no SteamAPI dll to use forced proxy methods. (This is not guranteed to work and thus these games are tagged with the "Proxy Only" platform tag. - Adds tooltips, and displays them for Unsupported/Proxy Only games if hovered with the mouse. - Adds improved logging to the application, breaks logs into 3 primary logs: game-scan.log, cream-steamcmd.log and CreamInstaller.log --- CreamInstaller/Components/CustomTreeView.cs | 16 ++++- CreamInstaller/Forms/DebugForm.cs | 9 +++ CreamInstaller/Forms/SelectForm.cs | 34 +++++++--- CreamInstaller/Forms/TestGameForm.cs | 4 +- CreamInstaller/Forms/UpdateForm.cs | 9 +-- CreamInstaller/Platforms/Epic/EpicStore.cs | 12 +--- .../Platforms/Steam/SteamCMD.WebAPI.cs | 56 +++++------------ CreamInstaller/Platforms/Steam/SteamCMD.cs | 47 ++++---------- CreamInstaller/Platforms/Steam/SteamStore.cs | 59 ++++-------------- CreamInstaller/Program.cs | 14 +---- CreamInstaller/Selection.cs | 28 ++++++++- CreamInstaller/Utility/HttpClientManager.cs | 30 ++------- CreamInstaller/Utility/ProgramData.cs | 62 +++++++++++++++++-- CreamInstaller/Utility/ThemeManager.cs | 8 +-- 14 files changed, 191 insertions(+), 197 deletions(-) diff --git a/CreamInstaller/Components/CustomTreeView.cs b/CreamInstaller/Components/CustomTreeView.cs index b6e89a2..f125bf9 100644 --- a/CreamInstaller/Components/CustomTreeView.cs +++ b/CreamInstaller/Components/CustomTreeView.cs @@ -34,7 +34,8 @@ internal sealed class CustomTreeView : TreeView internal CustomTreeView() { - DrawMode = TreeViewDrawMode.OwnerDrawAll; + ShowNodeToolTips = true; + DrawMode = TreeViewDrawMode.OwnerDrawAll; Invalidated += OnInvalidated; DrawNode += DrawTreeNode; Disposed += OnDisposed; @@ -218,7 +219,16 @@ internal sealed class CustomTreeView : TreeView SelectionDLC dlc = SelectionDLC.FromId(dlcType, node.Parent?.Name, id); text = dlc?.Selection != null ? dlc.Selection.Platform.ToString() : dlcType.ToString(); } - else text = platform.ToString(); + else + { + text = platform.ToString(); + if (platform is Platform.Steam) + { + Selection selection = Selection.FromId(platform, id); + if (selection is not null && selection.SteamApiDllMissing) + text = "Proxy Only"; + } + } Size size = TextRenderer.MeasureText(graphics, text, font); bounds = bounds with { X = bounds.X + bounds.Width, Width = size.Width }; @@ -485,6 +495,8 @@ internal sealed class CustomTreeView : TreeView _ = checkBoxBounds.Remove(pair.Key); else if (pair.Value.Contains(clickPoint)) { + if (pair.Key.SteamApiDllMissing) + return; pair.Key.UseProxy = !pair.Key.UseProxy; selectForm?.OnProxyChanged(); break; diff --git a/CreamInstaller/Forms/DebugForm.cs b/CreamInstaller/Forms/DebugForm.cs index c8ba989..1622638 100644 --- a/CreamInstaller/Forms/DebugForm.cs +++ b/CreamInstaller/Forms/DebugForm.cs @@ -11,6 +11,8 @@ internal sealed partial class DebugForm : CustomForm private static DebugForm current; private static readonly object currentLock = new(); + internal static bool IsOpen { get; private set; } + private Form attachedForm; private DebugForm() @@ -62,6 +64,13 @@ internal sealed partial class DebugForm : CustomForm attachedForm.SizeChanged += OnChange; attachedForm.VisibleChanged += OnChange; UpdateAttachment(); + + if (!IsOpen) + { + IsOpen = true; + ProgramData.OnLogWarning += msg => Log(msg, LogTextBox.Warning); + ProgramData.OnLogError += msg => Log(msg, LogTextBox.Error); + } } private void OnChange(object sender, EventArgs args) => UpdateAttachment(); diff --git a/CreamInstaller/Forms/SelectForm.cs b/CreamInstaller/Forms/SelectForm.cs index 938428d..ef8a383 100644 --- a/CreamInstaller/Forms/SelectForm.cs +++ b/CreamInstaller/Forms/SelectForm.cs @@ -189,11 +189,16 @@ internal sealed partial class SelectForm : CustomForm return; HashSet dllDirectories = await gameDirectory.GetDllDirectoriesFromGameDirectory(Platform.Steam); - if (dllDirectories is null) + bool steamApiDllMissing = dllDirectories is null; + if (steamApiDllMissing) { - _ = Interlocked.Decrement(ref steamGamesToCheck); - RemoveFromRemainingGames(name); - return; + dllDirectories = []; + if (uninstallAll) + { + _ = Interlocked.Decrement(ref steamGamesToCheck); + RemoveFromRemainingGames(name); + return; + } } if (uninstallAll) @@ -348,6 +353,20 @@ internal sealed partial class SelectForm : CustomForm Selection selection = Selection.GetOrCreate(Platform.Steam, appId, storeAppData?.Name ?? name, gameDirectory, dllDirectories, await gameDirectory.GetExecutableDirectories(true)); + selection.SteamApiDllMissing = steamApiDllMissing; + if (steamApiDllMissing) + { + selection.UseProxy = true; + bool has64 = selection.ExecutableDirectories.Any(d => d.binaryType == BinaryType.BIT64); + bool has32 = selection.ExecutableDirectories.Any(d => d.binaryType == BinaryType.BIT32); + string dllName = (has64, has32) switch + { + (true, true) => "steam_api.dll / steam_api64.dll", + (true, false) => "steam_api64.dll", + _ => "steam_api.dll" + }; + selection.TreeNode.ToolTipText = dllName + " was not detected in the game directory. Only proxy installation is available."; + } selection.Product = "https://store.steampowered.com/app/" + appId; selection.Icon = IconGrabber.SteamAppImagesPath + @$"\{appId}\{cmdAppData?.Common?.Icon}.jpg"; selection.SubIcon = storeAppData?.HeaderImage ?? IconGrabber.SteamAppImagesPath @@ -717,10 +736,7 @@ internal sealed partial class SelectForm : CustomForm } catch (Exception ex) { - // Handle exceptions in async void to prevent unobserved exceptions -#if DEBUG - System.Diagnostics.Debug.WriteLine($"OnLoad exception: {ex.Message}"); -#endif + ProgramData.LogError("SelectForm OnLoad failed", ex); // Show error and clean up ex.HandleException(this); HideProgressBar(); @@ -1263,7 +1279,7 @@ internal sealed partial class SelectForm : CustomForm selection.Proxy = currentProxy == Selection.DefaultProxy ? currentProxy : proxy; } } - else + else if (!selection.SteamApiDllMissing) { selection.UseProxy = false; selection.Proxy = null; diff --git a/CreamInstaller/Forms/TestGameForm.cs b/CreamInstaller/Forms/TestGameForm.cs index 5d27d1a..5920b07 100644 --- a/CreamInstaller/Forms/TestGameForm.cs +++ b/CreamInstaller/Forms/TestGameForm.cs @@ -92,7 +92,7 @@ internal sealed partial class TestGameForm : CustomForm if (!string.IsNullOrWhiteSpace(title)) return title; } - catch { /* fall through to SteamCMD */ } + catch (Exception ex) { ProgramData.LogWarning($"[TestGame] Store name lookup failed for AppID {appId}: {ex.Message}"); /* fall through to SteamCMD */ } CmdAppData cmdData = await SteamCMD.GetAppInfo(appId); return cmdData?.Common?.Name; @@ -320,7 +320,7 @@ internal sealed partial class TestGameForm : CustomForm SteamLibrary.TestGames.Clear(); EpicLibrary.TestManifests.Clear(); foreach (string dir in CreatedDirectories) - try { Directory.Delete(dir, true); } catch { /* best-effort */ } + try { Directory.Delete(dir, true); } catch (Exception ex) { ProgramData.LogWarning($"[TestGame] Cleanup deletion failed for {dir}: {ex.Message}"); } CreatedDirectories.Clear(); dlcEntries.Clear(); RefreshDlcList(); diff --git a/CreamInstaller/Forms/UpdateForm.cs b/CreamInstaller/Forms/UpdateForm.cs index c2a172a..4b4eb72 100644 --- a/CreamInstaller/Forms/UpdateForm.cs +++ b/CreamInstaller/Forms/UpdateForm.cs @@ -106,12 +106,10 @@ internal sealed partial class UpdateForm : CustomForm } catch (Exception ex) { - // Handle exceptions in async void to prevent unobserved exceptions + ProgramData.LogError("UpdateForm OnLoad failed", ex); #if DEBUG - System.Diagnostics.Debug.WriteLine($"OnLoad exception: {ex.Message}"); ex.HandleFatalException(); #else - // In release, try to continue gracefully StartProgram(); #endif } @@ -262,10 +260,7 @@ internal sealed partial class UpdateForm : CustomForm } catch (Exception ex) { - // Handle exceptions in async void event handler to prevent unobserved exceptions -#if DEBUG - System.Diagnostics.Debug.WriteLine($"OnUpdate exception: {ex.Message}"); -#endif + ProgramData.LogError("UpdateForm OnUpdate failed", ex); // Show error to user ex.HandleException(this, Program.Name + " encountered an unexpected error during update"); StartProgram(); diff --git a/CreamInstaller/Platforms/Epic/EpicStore.cs b/CreamInstaller/Platforms/Epic/EpicStore.cs index 2fa51d7..5361d07 100644 --- a/CreamInstaller/Platforms/Epic/EpicStore.cs +++ b/CreamInstaller/Platforms/Epic/EpicStore.cs @@ -8,9 +8,7 @@ using CreamInstaller.Platforms.Epic.GraphQL; using CreamInstaller.Utility; using Newtonsoft.Json; -#if DEBUG -using CreamInstaller.Forms; -#endif + namespace CreamInstaller.Platforms.Epic; @@ -33,12 +31,10 @@ internal static class EpicStore if (!cachedExists || ProgramData.CheckCooldown(categoryNamespace, Cooldown)) { response = await QueryGraphQL(categoryNamespace); -#if DEBUG if (response is null) { - DebugForm.Current.Log("ES: QueryGraphQL returned null"); + ProgramData.LogWarning("Epic QueryGraphQL returned null for " + categoryNamespace); } -#endif try { cacheFile.WriteFile(JsonConvert.SerializeObject(response, Formatting.Indented)); @@ -191,9 +187,7 @@ internal static class EpicStore HttpClient client = HttpClientManager.HttpClient; if (client is null) { -#if DEBUG - DebugForm.Current.Log("ES: Client returned null"); -#endif + ProgramData.LogWarning("Epic GraphQL client returned null"); return null; } HttpResponseMessage httpResponse = diff --git a/CreamInstaller/Platforms/Steam/SteamCMD.WebAPI.cs b/CreamInstaller/Platforms/Steam/SteamCMD.WebAPI.cs index 5be6339..c29a6a4 100644 --- a/CreamInstaller/Platforms/Steam/SteamCMD.WebAPI.cs +++ b/CreamInstaller/Platforms/Steam/SteamCMD.WebAPI.cs @@ -2,7 +2,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using CreamInstaller.Forms; using CreamInstaller.Utility; using Newtonsoft.Json; @@ -38,61 +37,38 @@ internal static partial class SteamCMD { cacheFile.WriteFile(JsonConvert.SerializeObject(data, Formatting.Indented)); } - catch -#if DEBUG - (Exception e) + catch (Exception e) { - DebugForm.Current.Log("SteamCMD web API query failed on attempt #" + attempts + - " for " + appId + (isDlc ? " (DLC)" : "") - + ": Unsuccessful serialization (" + e.Message + ")"); + ProgramData.LogSteamCmd("SteamCMD web API query failed on attempt #" + attempts + + " for " + appId + (isDlc ? " (DLC)" : "") + + ": Unsuccessful serialization (" + e.Message + ")"); } -#else - { - // ignored - } -#endif return data; } -#if DEBUG else - DebugForm.Current.Log( + ProgramData.LogSteamCmd( "SteamCMD web API query failed on attempt #" + attempts + " for " + appId + (isDlc ? " (DLC)" : "") - + ": No data", - LogTextBox.Warning); -#endif + + ": No data"); } -#if DEBUG else - DebugForm.Current.Log( + ProgramData.LogSteamCmd( "SteamCMD web API query failed on attempt #" + attempts + " for " + appId + (isDlc ? " (DLC)" : "") - + ": Status not success (" + appDetails?.Status + ")", - LogTextBox.Warning); -#endif + + ": Status not success (" + appDetails?.Status + ")"); } - catch -#if DEBUG - (Exception e) + catch (Exception e) { - DebugForm.Current.Log("SteamCMD web API query failed on attempt #" + attempts + " for " + - appId + (isDlc ? " (DLC)" : "") - + ": Unsuccessful deserialization (" + e.Message + ")"); + ProgramData.LogSteamCmd("SteamCMD web API query failed on attempt #" + attempts + " for " + + appId + (isDlc ? " (DLC)" : "") + + ": Unsuccessful deserialization (" + e.Message + ")"); } -#else - { - // ignored - } -#endif } -#if DEBUG else - DebugForm.Current.Log( + ProgramData.LogSteamCmd( "SteamCMD web API query failed on attempt #" + attempts + " for " + appId + (isDlc ? " (DLC)" : "") + - ": Response null", - LogTextBox.Warning); -#endif + ": Response null"); } if (cachedExists) @@ -109,9 +85,7 @@ internal static partial class SteamCMD break; if (attempts > 10) { -#if DEBUG - DebugForm.Current.Log("Failed to query SteamCMD web API after 10 tries: " + appId); -#endif + ProgramData.LogSteamCmd("Failed to query SteamCMD web API after 10 tries: " + appId); break; } diff --git a/CreamInstaller/Platforms/Steam/SteamCMD.cs b/CreamInstaller/Platforms/Steam/SteamCMD.cs index d74e376..dc25031 100644 --- a/CreamInstaller/Platforms/Steam/SteamCMD.cs +++ b/CreamInstaller/Platforms/Steam/SteamCMD.cs @@ -12,9 +12,6 @@ using CreamInstaller.Resources; using CreamInstaller.Utility; using Gameloop.Vdf.JsonConverter; using Gameloop.Vdf.Linq; -#if DEBUG -using CreamInstaller.Forms; -#endif namespace CreamInstaller.Platforms.Steam; @@ -210,10 +207,7 @@ internal static partial class SteamCMD attempts++; if (attempts > 10) { -#if DEBUG - DebugForm.Current.Log("Failed to query SteamCMD after 10 tries: " + appId + " (" + branch + ")", - LogTextBox.Warning); -#endif + ProgramData.LogSteamCmd("Failed to query SteamCMD after 10 tries: " + appId + " (" + branch + ")"); break; } @@ -232,12 +226,9 @@ internal static partial class SteamCMD } else { -#if DEBUG - DebugForm.Current.Log( + ProgramData.LogSteamCmd( "SteamCMD query failed on attempt #" + attempts + " for " + appId + " (" + branch + - "): Bad output", - LogTextBox.Warning); -#endif + "): Bad output"); continue; } } @@ -245,12 +236,9 @@ internal static partial class SteamCMD if (!ValveDataFile.TryDeserialize(output, out VProperty appInfo) || appInfo.Value is VValue) { appUpdateFile.DeleteFile(); -#if DEBUG - DebugForm.Current.Log( + ProgramData.LogSteamCmd( "SteamCMD query failed on attempt #" + attempts + " for " + appId + " (" + branch + - "): Deserialization failed", - LogTextBox.Warning); -#endif + "): Deserialization failed"); continue; } @@ -260,29 +248,20 @@ internal static partial class SteamCMD if (appInfo.ToJson().Value.ToObject() is not { } cmdAppData) { appUpdateFile.DeleteFile(); -#if DEBUG - DebugForm.Current.Log( + ProgramData.LogSteamCmd( "SteamCMD query failed on attempt #" + attempts + " for " + appId + " (" + branch + - "): VDF-JSON conversion failed", - LogTextBox.Warning); -#endif + "): VDF-JSON conversion failed"); continue; } appData = cmdAppData; } - catch -#if DEBUG - (Exception e) -#endif + catch (Exception e) { appUpdateFile.DeleteFile(); -#if DEBUG - DebugForm.Current.Log( + ProgramData.LogSteamCmd( "SteamCMD query failed on attempt #" + attempts + " for " + appId + " (" + branch + - "): VDF-JSON conversion failed (" + e.Message + ")", - LogTextBox.Warning); -#endif + "): VDF-JSON conversion failed (" + e.Message + ")"); continue; } @@ -300,11 +279,9 @@ internal static partial class SteamCMD foreach (string dlcAppUpdateFile in dlcAppIds.Select(id => $@"{AppInfoPath}\{id}.vdf")) dlcAppUpdateFile.DeleteFile(); appUpdateFile.DeleteFile(); -#if DEBUG - DebugForm.Current.Log( + ProgramData.LogSteamCmd( "SteamCMD query skipped on attempt #" + attempts + " for " + appId + " (" + branch + - "): Outdated cache", LogTextBox.Warning); -#endif + "): Outdated cache"); } return null; diff --git a/CreamInstaller/Platforms/Steam/SteamStore.cs b/CreamInstaller/Platforms/Steam/SteamStore.cs index 3507b8e..67f62c8 100644 --- a/CreamInstaller/Platforms/Steam/SteamStore.cs +++ b/CreamInstaller/Platforms/Steam/SteamStore.cs @@ -3,13 +3,10 @@ using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; +using System; using CreamInstaller.Utility; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -#if DEBUG -using System; -using CreamInstaller.Forms; -#endif namespace CreamInstaller.Platforms.Steam; @@ -18,7 +15,6 @@ internal static class SteamStore private const int CooldownGame = 600; private const int CooldownDlc = 1200; -#if DEBUG private static string FormatErrorLog(int attempts, string appId, string gameName, bool isDlc, string reason, string parentGameName = null, string parentGameAppId = null) { @@ -35,7 +31,6 @@ internal static class SteamStore string type = isDlc ? "DLC" : "Game"; return $"[SteamQuery][Attempt {attempts}][FAILED] AppId: {appId} | Name: \"{gameName}\" | Type: {type} | Reason: {reason}"; } -#endif internal static async Task> ParseDlcAppIds(StoreAppData storeAppData) => await Task.Run(() => @@ -80,11 +75,8 @@ internal static class SteamStore if (!storeAppDetails.Success) { -#if DEBUG - DebugForm.Current.Log( - FormatErrorLog(attempts, appId, gameName, isDlc, "Query unsuccessful", parentGameName, parentGameAppId), - LogTextBox.Warning); -#endif + ProgramData.LogSteamCmd( + FormatErrorLog(attempts, appId, gameName, isDlc, "Query unsuccessful", parentGameName, parentGameAppId)); if (data is null) return null; } @@ -95,61 +87,38 @@ internal static class SteamStore { cacheFile.WriteFile(JsonConvert.SerializeObject(data, Formatting.Indented)); } - catch -#if DEBUG - (Exception e) + catch (Exception e) { - DebugForm.Current.Log( + ProgramData.LogSteamCmd( FormatErrorLog(attempts, appId, gameName, isDlc, $"Unsuccessful serialization ({e.Message})", parentGameName, parentGameAppId)); } -#else - { - // ignored - } -#endif return data; } -#if DEBUG - DebugForm.Current.Log( + ProgramData.LogSteamCmd( FormatErrorLog(attempts, appId, gameName, isDlc, "Response data null", parentGameName, parentGameAppId)); -#endif } -#if DEBUG else { - DebugForm.Current.Log( + ProgramData.LogSteamCmd( FormatErrorLog(attempts, appId, gameName, isDlc, "Response details null", parentGameName, parentGameAppId)); } -#endif } - catch -#if DEBUG - (Exception e) + catch (Exception e) { - DebugForm.Current.Log( + ProgramData.LogSteamCmd( FormatErrorLog(attempts, appId, gameName, isDlc, $"Unsuccessful deserialization ({e.Message})", parentGameName, parentGameAppId)); } -#else - { - // ignored - } -#endif -#if DEBUG else { - DebugForm.Current.Log( + ProgramData.LogSteamCmd( FormatErrorLog(attempts, appId, gameName, isDlc, "Response deserialization null", parentGameName, parentGameAppId)); } -#endif } -#if DEBUG else { - DebugForm.Current.Log( - FormatErrorLog(attempts, appId, gameName, isDlc, "Null or empty response", parentGameName, parentGameAppId), - LogTextBox.Warning); + ProgramData.LogSteamCmd( + FormatErrorLog(attempts, appId, gameName, isDlc, "Null or empty response", parentGameName, parentGameAppId)); } -#endif } if (cachedExists) @@ -166,10 +135,8 @@ internal static class SteamStore break; if (attempts > 10) { -#if DEBUG - DebugForm.Current.Log( + ProgramData.LogSteamCmd( FormatErrorLog(attempts, appId, gameName, isDlc, "Maximum retry attempts exceeded (10)", parentGameName, parentGameAppId)); -#endif break; } diff --git a/CreamInstaller/Program.cs b/CreamInstaller/Program.cs index 2f79e22..08370a7 100644 --- a/CreamInstaller/Program.cs +++ b/CreamInstaller/Program.cs @@ -130,10 +130,7 @@ internal static class Program } catch (Exception ex) { -#if DEBUG - System.Diagnostics.Debug.WriteLine($"Cleanup failed: {ex.Message}"); -#endif - // Swallow exceptions during fire-and-forget cleanup + ProgramData.LogWarning($"Cleanup failed: {ex.Message}"); } }); } @@ -149,17 +146,12 @@ internal static class Program // Wait up to 5 seconds for graceful cleanup if (!cleanupTask.Wait(TimeSpan.FromSeconds(5))) { -#if DEBUG - System.Diagnostics.Debug.WriteLine("Cleanup timed out during application exit"); -#endif + ProgramData.LogWarning("Cleanup timed out during application exit"); } } catch (Exception ex) { -#if DEBUG - System.Diagnostics.Debug.WriteLine($"Cleanup exception during exit: {ex.Message}"); -#endif - // Ignore exceptions during shutdown + ProgramData.LogWarning($"Cleanup exception during exit: {ex.Message}"); } finally { diff --git a/CreamInstaller/Selection.cs b/CreamInstaller/Selection.cs index 63a3713..e92ab44 100644 --- a/CreamInstaller/Selection.cs +++ b/CreamInstaller/Selection.cs @@ -45,6 +45,7 @@ internal sealed class Selection : IEquatable internal string SubIcon; internal string Website; internal InstalledUnlocker InstalledUnlocker; + internal bool SteamApiDllMissing; internal IEnumerable GetAvailableProxies() { @@ -125,9 +126,12 @@ internal sealed class Selection : IEquatable return; } - _ = DllDirectories.RemoveWhere(directory => !directory.DirectoryExists()); - if (DllDirectories.Count < 1) - Remove(); + if (!SteamApiDllMissing) + { + _ = DllDirectories.RemoveWhere(directory => !directory.DirectoryExists()); + if (DllDirectories.Count < 1) + Remove(); + } } internal static void ValidateAll(List<(Platform platform, string id, string name)> programsToScan) @@ -199,6 +203,24 @@ internal sealed class Selection : IEquatable proxy.FileExists() && proxy.IsResourceFile(ResourceIdentifier.Koaloader)) || config.FileExists()) return InstalledUnlocker.Koaloader; + + if (Platform is Platform.Steam or Platform.Paradox) + { + directory.GetSmokeApiComponents(out _, out _, out _, out _, out _, out string smokeConfig, out _, out _, out _); + if (smokeConfig.FileExists()) + return InstalledUnlocker.SmokeAPI; + directory.GetCreamApiComponents(out _, out _, out _, out _, out string creamConfig); + if (creamConfig.FileExists()) + return InstalledUnlocker.CreamAPI; + if (directory.GetSmokeApiProxies().Any(proxy => + proxy.FileExists() && (proxy.IsResourceFile(ResourceIdentifier.Steamworks32) || + proxy.IsResourceFile(ResourceIdentifier.Steamworks64)))) + return InstalledUnlocker.SmokeAPI; + if (directory.GetCreamApiProxies().Any(proxy => + proxy.FileExists() && (proxy.IsResourceFile(ResourceIdentifier.Steamworks32) || + proxy.IsResourceFile(ResourceIdentifier.Steamworks64)))) + return InstalledUnlocker.CreamAPI; + } } return InstalledUnlocker.None; diff --git a/CreamInstaller/Utility/HttpClientManager.cs b/CreamInstaller/Utility/HttpClientManager.cs index 6ae72b1..5439489 100644 --- a/CreamInstaller/Utility/HttpClientManager.cs +++ b/CreamInstaller/Utility/HttpClientManager.cs @@ -5,9 +5,7 @@ using System.Globalization; using System.Net; using System.Net.Http; using System.Threading.Tasks; -#if DEBUG -using CreamInstaller.Forms; -#endif + namespace CreamInstaller.Utility; @@ -87,44 +85,28 @@ internal static class HttpClientManager { if (e.StatusCode != HttpStatusCode.TooManyRequests) { -#if DEBUG string statusInfo = e.StatusCode.HasValue ? $" (HTTP {(int)e.StatusCode.Value})" : ""; - DebugForm.Current.Log($"Get request failed to {url}{statusInfo}: {e}", LogTextBox.Warning); -#endif + ProgramData.LogWarning($"Get request failed to {url}{statusInfo}: {e.Message}"); return null; } -#if DEBUG - DebugForm.Current.Log($"Too many requests to {url} (HTTP 429 - Rate Limited)", LogTextBox.Error); -#endif - // do something special? + ProgramData.LogWarning($"Too many requests to {url} (HTTP 429 - Rate Limited)"); return null; } catch (TaskCanceledException) { -#if DEBUG - DebugForm.Current.Log("Get request timed out for " + url, LogTextBox.Warning); -#endif + ProgramData.LogWarning("Get request timed out for " + url); return null; } catch (OperationCanceledException) { -#if DEBUG - DebugForm.Current.Log("Get request was cancelled for " + url, LogTextBox.Warning); -#endif + ProgramData.LogWarning("Get request was cancelled for " + url); return null; } -#if DEBUG catch (Exception e) { - DebugForm.Current.Log("Get request failed to " + url + ": " + e, LogTextBox.Warning); + ProgramData.LogWarning("Get request failed to " + url + ": " + e.Message); return null; } -#else - catch - { - return null; - } -#endif } internal static async Task GetImageFromUrl(string url) diff --git a/CreamInstaller/Utility/ProgramData.cs b/CreamInstaller/Utility/ProgramData.cs index 3c1e3fa..e6dbd45 100644 --- a/CreamInstaller/Utility/ProgramData.cs +++ b/CreamInstaller/Utility/ProgramData.cs @@ -64,7 +64,12 @@ internal static class ProgramData private static readonly string ExtraProtectionChoicesPath = DirectoryPath + @"\extraprotection.json"; private static readonly string InstalledGamesPath = DirectoryPath + @"\installed.json"; - internal static readonly string LogPath = DirectoryPath + @"\scan.log"; + internal static readonly string ScanLogPath = Path.Combine(DirectoryPath, "game-scan.log"); +internal static readonly string SteamCmdLogPath = Path.Combine(DirectoryPath, "cream-steamcmd.log"); +internal static readonly string AppLogPath = Path.Combine(DirectoryPath, "CreamInstaller.log"); + +internal static event Action OnLogWarning; +internal static event Action OnLogError; private static readonly object LogLock = new(); @@ -75,7 +80,7 @@ internal static class ProgramData string timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture); string entry = $"[{timestamp}] {message}{Environment.NewLine}"; lock (LogLock) - File.AppendAllText(LogPath, entry, Encoding.UTF8); + File.AppendAllText(ScanLogPath, entry, Encoding.UTF8); } catch { @@ -83,12 +88,61 @@ internal static class ProgramData } } + internal static void LogSteamCmd(string message) + { + try + { + string timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture); + string entry = $"[{timestamp}] [SteamCMD] {message}{Environment.NewLine}"; + lock (LogLock) + File.AppendAllText(SteamCmdLogPath, entry, Encoding.UTF8); + } + catch + { + // ignored — logging must never crash the application + } + } + + internal static void LogWarning(string message) + { + try + { + string timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture); + string entry = $"[{timestamp}] [WARN] {message}{Environment.NewLine}"; + lock (LogLock) + File.AppendAllText(AppLogPath, entry, Encoding.UTF8); + } + catch + { + // ignored — logging must never crash the application + } + OnLogWarning?.Invoke(message); + } + + internal static void LogError(string message, Exception ex = null) + { + try + { + string timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture); + string entry = ex is not null + ? $"[{timestamp}] [ERROR] {message}{Environment.NewLine}[{timestamp}] [ERROR] Exception: {ex}{Environment.NewLine}" + : $"[{timestamp}] [ERROR] {message}{Environment.NewLine}"; + lock (LogLock) + File.AppendAllText(AppLogPath, entry, Encoding.UTF8); + } + catch + { + // ignored — logging must never crash the application + } + OnLogError?.Invoke(message); + } + internal static void ClearLog() { try { - if (File.Exists(LogPath)) - File.Delete(LogPath); + if (File.Exists(ScanLogPath)) + File.Delete(ScanLogPath); } catch { diff --git a/CreamInstaller/Utility/ThemeManager.cs b/CreamInstaller/Utility/ThemeManager.cs index 8f7ac29..ef5538b 100644 --- a/CreamInstaller/Utility/ThemeManager.cs +++ b/CreamInstaller/Utility/ThemeManager.cs @@ -335,7 +335,7 @@ internal static class ThemeManager int useDark = IsDark ? 1 : 0; NativeMethods.EnableDarkTitleBar(form.Handle, useDark); } - catch { } + catch (Exception ex) { ProgramData.LogWarning($"[Theme] Title bar theming failed: {ex.Message}"); } } private static void TryApplyScrollbarTheme(Control control, bool dark) @@ -345,7 +345,7 @@ internal static class ThemeManager string theme = dark ? "DarkMode_Explorer" : null; NativeImports.SetWindowTheme(control.Handle, theme, null); } - catch { } + catch (Exception ex) { ProgramData.LogWarning($"[Theme] Scrollbar theming failed: {ex.Message}"); } } // ----------------------------------------------------------------- @@ -454,7 +454,7 @@ internal static class ThemeManager // button is centralized here so theming resides in ThemeManager. // ----------------------------------------------------------------- - // Dark checkbox colors – matched to how the system renders the "All" CheckBox control + // Dark checkbox colors � matched to how the system renders the "All" CheckBox control // in dark mode: dark fill, mid-gray border, light foreground tick. private static readonly Color DarkCbBorder = ColorTranslator.FromHtml("#6B6B6B"); private static readonly Color DarkCbDisabledBorder = ColorTranslator.FromHtml("#454545"); @@ -478,7 +478,7 @@ internal static class ThemeManager if (isChecked && enabled) { - // Checked + enabled: accent fill, no border, white tick — matches Windows 11 dark CheckBox + // Checked + enabled: accent fill, no border, white tick � matches Windows 11 dark CheckBox using SolidBrush fillBrush = new(Accent); g.FillPath(fillBrush, path);