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
This commit is contained in:
Frog
2026-06-01 00:01:59 -07:00
parent 54592230c3
commit 66cf72faeb
5 changed files with 127 additions and 6 deletions
@@ -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<Selection, Rectangle> checkBoxBounds = [];
private readonly Dictionary<Selection, Rectangle> extraProtectionCheckBoxBounds = [];
private readonly Dictionary<Selection, Rectangle> comboBoxBounds = [];
private readonly Dictionary<TreeNode, Rectangle> 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<Selection, Rectangle> 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;
}
}
}
+39 -3
View File
@@ -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;
+4 -3
View File
@@ -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<string, string> dlc, InstallForm installForm = null)
SortedList<string, string> 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
+1
View File
@@ -34,6 +34,7 @@ internal sealed class Selection : IEquatable<Selection>
internal readonly string RootDirectory;
internal readonly TreeNode TreeNode;
internal string Icon;
internal bool UseExtraProtection;
internal bool UseProxy;
internal string Proxy;
internal string Product;
+34
View File
@@ -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
}
}
}