From 66cf72faeb2f4b84d5be77ba1307a5fd3c729708 Mon Sep 17 00:00:00 2001 From: Frog Date: Mon, 1 Jun 2026 00:01:59 -0700 Subject: [PATCH] Add Extra Protection Option for CreamAPI Closes #21 - Adds the ability to enable ExtraProtection for CreamAPI, this is required for games that check the integrity of the steam_api.dll Related Work Items: #2, #21 --- CreamInstaller/Components/CustomTreeView.cs | 49 +++++++++++++++++++++ CreamInstaller/Forms/SelectForm.cs | 42 ++++++++++++++++-- CreamInstaller/Resources/CreamAPI.cs | 7 +-- CreamInstaller/Selection.cs | 1 + CreamInstaller/Utility/ProgramData.cs | 34 ++++++++++++++ 5 files changed, 127 insertions(+), 6 deletions(-) diff --git a/CreamInstaller/Components/CustomTreeView.cs b/CreamInstaller/Components/CustomTreeView.cs index 47b8e36..aa54f98 100644 --- a/CreamInstaller/Components/CustomTreeView.cs +++ b/CreamInstaller/Components/CustomTreeView.cs @@ -14,8 +14,10 @@ namespace CreamInstaller.Components; internal sealed class CustomTreeView : TreeView { private const string ProxyToggleString = "Proxy"; + private const string ExtraProtectionToggleString = "Extra Protection"; private readonly Dictionary checkBoxBounds = []; + private readonly Dictionary extraProtectionCheckBoxBounds = []; private readonly Dictionary comboBoxBounds = []; private readonly Dictionary selectionBounds = []; @@ -62,6 +64,7 @@ internal sealed class CustomTreeView : TreeView private void OnInvalidated(object sender, EventArgs e) { checkBoxBounds.Clear(); + extraProtectionCheckBoxBounds.Clear(); comboBoxBounds.Clear(); selectionBounds.Clear(); backBrush?.Dispose(); @@ -254,6 +257,42 @@ internal sealed class CustomTreeView : TreeView graphics.FillRectangle(brush, bounds); } + if (!Program.UseSmokeAPI) + { + CheckBoxState extraProtState = selection.UseExtraProtection + ? (Enabled ? CheckBoxState.CheckedNormal : CheckBoxState.CheckedDisabled) + : (Enabled ? CheckBoxState.UncheckedNormal : CheckBoxState.UncheckedDisabled); + size = CheckBoxRenderer.GetGlyphSize(graphics, extraProtState); + bounds = bounds with { X = bounds.X + bounds.Width, Width = size.Width }; + selectionBounds = new(selectionBounds.Location, selectionBounds.Size + bounds.Size with { Height = 0 }); + Rectangle extraProtCheckBoxBounds = bounds; + graphics.FillRectangle(brush, bounds); + point = new(bounds.Left, bounds.Top + bounds.Height / 2 - size.Height / 2 - 1); + if (dark) + ThemeManager.DrawDarkCheckBox(graphics, point, size, selection.UseExtraProtection, Enabled); + else + CheckBoxRenderer.DrawCheckBox(graphics, point, extraProtState); + + text = ExtraProtectionToggleString; + size = TextRenderer.MeasureText(graphics, text, font); + int leftEP = 1; + bounds = bounds with { X = bounds.X + bounds.Width, Width = size.Width + leftEP }; + selectionBounds = new(selectionBounds.Location, selectionBounds.Size + bounds.Size with { Height = 0 }); + extraProtCheckBoxBounds = new(extraProtCheckBoxBounds.Location, extraProtCheckBoxBounds.Size + bounds.Size with { Height = 0 }); + graphics.FillRectangle(brush, bounds); + point = new(bounds.Location.X - 1 + leftEP, bounds.Location.Y + 1); + TextRenderer.DrawText(graphics, text, font, point, + Enabled ? ThemeManager.CustomTreeViewProxyColor : ThemeManager.CustomTreeViewDisabledProxyColor, + TextFormatFlags.Default); + + extraProtectionCheckBoxBounds[selection] = RectangleToClient(extraProtCheckBoxBounds); + + // Add spacing before proxy checkbox + size = new(4, 0); + bounds = bounds with { X = bounds.X + bounds.Width, Width = size.Width }; + graphics.FillRectangle(brush, bounds); + } + CheckBoxState proxyState = selection.UseProxy ? (Enabled ? CheckBoxState.CheckedNormal : CheckBoxState.CheckedDisabled) : (Enabled ? CheckBoxState.UncheckedNormal : CheckBoxState.UncheckedDisabled); @@ -399,5 +438,15 @@ internal sealed class CustomTreeView : TreeView selectForm?.OnProxyChanged(); break; } + + foreach (KeyValuePair pair in extraProtectionCheckBoxBounds) + if (!Selection.All.ContainsKey(pair.Key)) + _ = extraProtectionCheckBoxBounds.Remove(pair.Key); + else if (pair.Value.Contains(clickPoint)) + { + pair.Key.UseExtraProtection = !pair.Key.UseExtraProtection; + selectForm?.OnExtraProtectionChanged(); + break; + } } } \ No newline at end of file diff --git a/CreamInstaller/Forms/SelectForm.cs b/CreamInstaller/Forms/SelectForm.cs index 1c897c7..99442fb 100644 --- a/CreamInstaller/Forms/SelectForm.cs +++ b/CreamInstaller/Forms/SelectForm.cs @@ -1065,13 +1065,18 @@ internal sealed partial class SelectForm : CustomForm private static bool AreProxySelectionsDefault() => Selection.All.Keys.All(selection => !selection.UseProxy); + private static bool AreExtraProtectionSelectionsDefault() => Selection.All.Keys.All(selection => !selection.UseExtraProtection); + private bool CanSaveDlc() => installButton.Enabled && (ProgramData.ReadDlcChoices().Any() || !AreSelectionsDefault()); private static bool CanSaveProxy() => ProgramData.ReadProxyChoices().Any() || !AreProxySelectionsDefault(); - private bool CanSaveSelections() => CanSaveDlc() || CanSaveProxy(); + private static bool CanSaveExtraProtection() => + ProgramData.ReadExtraProtectionChoices().Any() || !AreExtraProtectionSelectionsDefault(); + + private bool CanSaveSelections() => CanSaveDlc() || CanSaveProxy() || CanSaveExtraProtection(); private void OnSaveSelections(object sender, EventArgs e) { @@ -1099,6 +1104,17 @@ internal sealed partial class SelectForm : CustomForm ProgramData.WriteProxyChoices(proxyChoices); + List<(Platform platform, string id)> extraProtectionChoices = + ProgramData.ReadExtraProtectionChoices().ToList(); + foreach (Selection selection in Selection.All.Keys) + { + _ = extraProtectionChoices.RemoveAll(c => c.platform == selection.Platform && c.id == selection.Id); + if (selection.UseExtraProtection) + extraProtectionChoices.Add((selection.Platform, selection.Id)); + } + + ProgramData.WriteExtraProtectionChoices(extraProtectionChoices); + loadButton.Enabled = CanLoadSelections(); saveButton.Enabled = CanSaveSelections(); } @@ -1107,7 +1123,9 @@ internal sealed partial class SelectForm : CustomForm private static bool CanLoadProxy() => ProgramData.ReadProxyChoices().Any(); - private static bool CanLoadSelections() => CanLoadDlc() || CanLoadProxy(); + private static bool CanLoadExtraProtection() => ProgramData.ReadExtraProtectionChoices().Any(); + + private static bool CanLoadSelections() => CanLoadDlc() || CanLoadProxy() || CanLoadExtraProtection(); private void OnLoadSelections(object sender, EventArgs e) { @@ -1149,6 +1167,14 @@ internal sealed partial class SelectForm : CustomForm } ProgramData.WriteProxyChoices(proxyChoices); + + List<(Platform platform, string id)> extraProtectionChoices = + ProgramData.ReadExtraProtectionChoices().ToList(); + foreach (Selection selection in Selection.All.Keys) + selection.UseExtraProtection = extraProtectionChoices.Any(c => + c.platform == selection.Platform && c.id == selection.Id); + + ProgramData.WriteExtraProtectionChoices(extraProtectionChoices); loadButton.Enabled = CanLoadSelections(); OnProxyChanged(); @@ -1158,7 +1184,9 @@ internal sealed partial class SelectForm : CustomForm private static bool CanResetProxy() => !AreProxySelectionsDefault(); - private bool CanResetSelections() => CanResetDlc() || CanResetProxy(); + private static bool CanResetExtraProtection() => !AreExtraProtectionSelectionsDefault(); + + private bool CanResetSelections() => CanResetDlc() || CanResetProxy() || CanResetExtraProtection(); private void OnResetSelections(object sender, EventArgs e) { @@ -1172,6 +1200,7 @@ internal sealed partial class SelectForm : CustomForm { selection.UseProxy = false; selection.Proxy = null; + selection.UseExtraProtection = false; } OnProxyChanged(); @@ -1187,6 +1216,13 @@ internal sealed partial class SelectForm : CustomForm proxyAllCheckBox.CheckedChanged += OnProxyAllCheckBoxChanged; } + internal void OnExtraProtectionChanged() + { + selectionTreeView.Invalidate(); + saveButton.Enabled = CanSaveSelections(); + resetButton.Enabled = CanResetSelections(); + } + private void OnBlockProtectedGamesCheckBoxChanged(object sender, EventArgs e) { Program.BlockProtectedGames = blockedGamesCheckBox.Checked; diff --git a/CreamInstaller/Resources/CreamAPI.cs b/CreamInstaller/Resources/CreamAPI.cs index 9820d7b..fe57fcf 100644 --- a/CreamInstaller/Resources/CreamAPI.cs +++ b/CreamInstaller/Resources/CreamAPI.cs @@ -41,14 +41,15 @@ internal static class CreamAPI config.CreateFile(true, installForm)?.Close(); StreamWriter writer = new(config, true, Encoding.Default); WriteConfig(writer, selection.Name, !int.TryParse(selection.Id, out _) ? "0" : selection.Id, - new(dlc.ToDictionary(_dlc => _dlc.Id, _dlc => _dlc.Name), PlatformIdComparer.String), installForm); + new(dlc.ToDictionary(_dlc => _dlc.Id, _dlc => _dlc.Name), PlatformIdComparer.String), + selection.UseExtraProtection, installForm); writer.Flush(); writer.Close(); return; } private static void WriteConfig(StreamWriter writer, string name, string appId, - SortedList dlc, InstallForm installForm = null) + SortedList dlc, bool extraProtection = false, InstallForm installForm = null) { writer.WriteLine($"; {name}"); writer.WriteLine("[steam]"); @@ -58,7 +59,7 @@ internal static class CreamAPI writer.WriteLine("unlockall = false"); writer.WriteLine("orgapi = steam_api_o.dll"); writer.WriteLine("orgapi64 = steam_api64_o.dll"); - writer.WriteLine("extraprotection = false"); // we may want to set this on by default? + writer.WriteLine($"extraprotection = {(extraProtection ? "true" : "false")}"); writer.WriteLine("forceoffline = false"); writer.WriteLine(); writer.WriteLine("[steam_misc]"); // this line seems to be required in v5.3.0.0, or the config won't be read diff --git a/CreamInstaller/Selection.cs b/CreamInstaller/Selection.cs index f8b79ce..1d46f34 100644 --- a/CreamInstaller/Selection.cs +++ b/CreamInstaller/Selection.cs @@ -34,6 +34,7 @@ internal sealed class Selection : IEquatable internal readonly string RootDirectory; internal readonly TreeNode TreeNode; internal string Icon; + internal bool UseExtraProtection; internal bool UseProxy; internal string Proxy; internal string Product; diff --git a/CreamInstaller/Utility/ProgramData.cs b/CreamInstaller/Utility/ProgramData.cs index ef6c55b..7031c52 100644 --- a/CreamInstaller/Utility/ProgramData.cs +++ b/CreamInstaller/Utility/ProgramData.cs @@ -29,6 +29,7 @@ internal static class ProgramData private static readonly string ProgramChoicesPath = DirectoryPath + @"\choices.json"; private static readonly string DlcChoicesPath = DirectoryPath + @"\dlc.json"; private static readonly string KoaloaderProxyChoicesPath = DirectoryPath + @"\proxies.json"; + private static readonly string ExtraProtectionChoicesPath = DirectoryPath + @"\extraprotection.json"; internal static readonly string LogPath = DirectoryPath + @"\scan.log"; @@ -229,4 +230,37 @@ internal static class ProgramData // ignored } } + + internal static IEnumerable<(Platform platform, string id)> ReadExtraProtectionChoices() + { + if (ExtraProtectionChoicesPath.FileExists()) + try + { + if (JsonConvert.DeserializeObject(ExtraProtectionChoicesPath.ReadFile(), + typeof(IEnumerable<(Platform platform, string id)>)) is + IEnumerable<(Platform platform, string id)> choices) + return choices; + } + catch + { + // ignored + } + + return []; + } + + internal static void WriteExtraProtectionChoices(IEnumerable<(Platform platform, string id)> choices) + { + try + { + if (choices is null || !choices.Any()) + ExtraProtectionChoicesPath.DeleteFile(); + else + ExtraProtectionChoicesPath.WriteFile(JsonConvert.SerializeObject(choices)); + } + catch + { + // ignored + } + } } \ No newline at end of file