53 Commits

Author SHA1 Message Date
Frog ee19990b5b Logging Infrastructure / Normalize Paths
- Added base logging infrastructure
- Create scan log during library/game scan in %ProgramData%\CreamInstaller\scan.log
- Normalize Steam library paths (libraryfolders.vdf) using Path.GetFullPath + ResolvePath to handle slashes, casing, and drive changes
- Diagnostics.ResolvePath: wrap GetFileSystemInfos in try/catch and guard against empty results to prevent IndexOutOfRangeException (May assist with issues on slow or intermittently accessible external drives)
2026-03-24 00:23:08 -07:00
Frog 39097c27ef Add Dev CI Builds
- Added CI action workflow for dev branch
2026-03-23 23:55:06 -07:00
Frog 3ba4747be3 Increment Version 5.0.2.0
- Increment version number to 5.0.2.0
2026-03-17 11:24:03 -07:00
Frog 322490d0b2 Additional Changes to Correct Null Exception for #12
- Additional changes to prevent Null Exception when catalog mapping property is null.
2026-03-16 22:58:00 -07:00
Frog 3dae7508f0 Merge branch 'main' of https://github.com/FroggMaster/CreamInstaller 2026-03-15 03:15:25 -07:00
Frog 8f8e893e84 Fix NullReferenceException in EpicStore See #12
- Fix a NullReferenceException that could occur when the Epic GraphQL API returns a partial response with missing fields (Data, Catalog, SearchStore, or CatalogOffers).
2026-03-15 03:15:17 -07:00
Frog 0cec730c1e Update README.md
- Additional clarification about DLC files because people apparently can't fucking read.
2026-02-10 12:15:52 -08:00
Frog df7dc0e019 Update create-release.yml
- Added generate_release_notes > Should automatically add the full change-log in the release description.
2026-01-31 03:09:58 -08:00
Frog 455a290051 Increment Application Version
- Version increased to 5.0.1.9
2026-01-31 03:03:13 -08:00
Frog 6e8326b84f Merge branch 'main' of https://github.com/FroggMaster/CreamInstaller 2026-01-31 03:00:44 -08:00
Frog f20ca0d833 Migrate Legacy Theme Code into Theme Manager / Shitty Fix for Selection in Dark Mode / Shitty Comments
- Moved as much of the legacy theme code as I could find into ThemeManager
- Some shitty comments added VIA AI (probably better than I'd write anyways)
- Some adjustments to how the highlight is being rendered, for some fucking reason the system highlight refuses to match on the left/right (probably some dumb shit I'm doing.) > This version makes things clearer/easier to read in dark mode.
2026-01-31 03:00:38 -08:00
Frog 788e7f5293 Update FAQ
- Updated FAQ so particular questions have headings which allows them to be directly linked to.
2026-01-31 00:40:37 -08:00
Frog ce566cfa47 Update README.md 2026-01-30 21:59:39 -08:00
Frog d2a5549878 Dark Mode Additions / Proxy Combo Box
- Moves more color handling to ThemeManager
- Adds dark mode for the proxy combo box
- Adjusts the combo box highlight so its no fucking impossible to read in dark mode.
2026-01-30 01:49:01 -08:00
Frog e79aecc023 Fixes bug with toggling dark mode
- Fixes issue with toggling dark mode after having added a game to the list, the store identifier / proxy toggle would not properly change colors.
2026-01-30 01:04:09 -08:00
Frog 4b9897bde2 Dark Mode For Right Click Context Menu
- Adds dark mode for the right click context menu. (Overlooked this and a few other items when adding dark mode.)
2026-01-30 00:39:19 -08:00
Frog 034951e4d2 Right Click Context Fix
- Should fix the issue with the right click menu displaying incorrectly at times when clicking on it a little too quickly.
2026-01-30 00:15:42 -08:00
Frog 4075078790 Updated ReadMe
- Updated common false positives and include a rough description of what they mean. 
- Removed link to build instructions (spoilers, I don't have any written.)
2026-01-28 16:50:23 -08:00
Frog 46df791c19 ReadMe Updates
- Clarified FAQ
- Added additional information about false positives, if you ask about this you can RTFM. 
- Added unnecessary legal disclaimer.
2026-01-26 17:04:53 -08:00
Frog b26a5aec48 Adjusted bug template
Adjusted organization of bug template
2026-01-26 16:31:40 -08:00
Frog e824ebd713 Bug report template changes
Updated bug report template to improve clarity and structure.
2026-01-26 16:26:29 -08:00
Frog d723d1c0c7 Increment Application Version 2026-01-26 01:41:42 -08:00
Frog 956b6d0c1c Fixes Incorrect Config Syntax / Fix #7
- Fixes incorrect comma that was added at the end of a configuration if Extra DLCs were added
2026-01-25 23:33:16 -08:00
Frog 028dd1586b Increment Application Version
- Increment app version to 5.0.1.7
2026-01-18 13:04:40 -08:00
Frog f5d6007404 Sory By Name By Default
- Adjusts the Sort By Name setting to be enabled by default for a more user friendly experience.
2026-01-18 13:02:44 -08:00
Frog 1c7ffb215d BugFix for DLC Query Failure - Fixes #5
- Fixes #5 Corrects null pointer failure. This ensures publishers exist AND that it contains at least one object before we access it.
2026-01-18 12:47:03 -08:00
Frog 1bd5501869 Update SmokeAPI to v4.1.3
- Updated SmokeAPI to v4.1.3
- Included MD5 hash for older versions (V4.1.0, V4.1.1, v4.1.2)
2026-01-18 12:33:25 -08:00
Frog 1db70541f9 Increment App Version 2025-11-21 03:01:44 -08:00
Frog ae08e990cc Enable Dark Mode by Default
- Enabled dark mode by default (Looks decent enough)
2025-11-21 02:52:01 -08:00
Frog 12c7c9a9d2 Change Dark Mode Toggle to Checkbox
- Changed dark mode toggle to Checkbox instead of button > better aligns with other settings in the top panel. (Gee, sure is getting crowded up there)
2025-11-21 02:48:19 -08:00
Frog 701ca5627d Merge branch 'main' of https://github.com/FroggMaster/CreamInstaller 2025-11-21 02:19:40 -08:00
Frog c0af3b85bb Dark Mode / Hyperlink Color Fix
- Fixes hard to read hyperlinks in Dark Mode
2025-11-21 02:19:18 -08:00
Frog 8577e6df7f Update README.md
- Correct typo/extra word.
2025-11-20 23:03:26 -08:00
Frog 961b7153f4 Added Info about CI Builds to ReadMe / Added Status Badge for CI Builds / Added Status Badge for Latest Release
-  Added Info about CI Builds to ReadMe 
- Added Status Badge for CI Builds 
- Added Status Badge for Latest Release
2025-11-20 02:55:35 -08:00
Frog e640b8b15d Dark mode WIP
- The start of a dark mode theme
- Adds a button to toggle dark mode in the top options panel. (The buttons kind of ugly though and I may change this to a checkbox to align with the other existing options)
- Added library uxtheme.dll for handling scrollbar themeing (Dealing with scrollbars sucks)
- Added ThemeManager for handling of theme colors (Allows for potential expandability I'll probably never implement)
2025-11-20 02:35:51 -08:00
Frog bcf3ff84fe Merge branch 'main' of https://github.com/FroggMaster/CreamInstaller 2025-11-19 22:58:40 -08:00
Frog eb1fee38f3 Slight cleaner preview image, that also shows an enabled proxy dll. 2025-11-19 22:58:25 -08:00
Frog cffc4cce07 Update ci-builds.yml
Bug Fix: I forgot to adjust the Rename-Item PS command to rename the EXE before packaging.
2025-11-19 22:04:24 -08:00
Frog b7a9505599 Adjusted CI Build Workflow
- Adjusted artifact name to include CI-Release
- Adjusted EXE to include CI
2025-11-19 03:36:36 -08:00
Frog 094d60b003 Merge branch 'main' of https://github.com/FroggMaster/CreamInstaller 2025-11-19 03:32:11 -08:00
Frog 09cafa27fb Update ci-builds.yml
- Fix artifact becoming two ZIPs (LOL woops)
2025-11-19 03:30:34 -08:00
Frog e6fa7b4a39 Fixed unreachable logging segment for CreamAPI
- Fixed unreachable code that provides information about deleted config file
- Adjusted old comment from pointfeev that indiciated SmokeAPI might always be false (it's now toggleable)
2025-11-19 03:27:28 -08:00
Frog 8a24bdad81 Update ci-builds.yml
- GH Action fix
2025-11-19 03:17:26 -08:00
Frog 21bcfae688 Update README.md
- Minor change to ReadMe changing "extract" to "move"
2025-11-19 03:15:02 -08:00
Frog 4063e482dd Add zip release
- Add zip release for internal application updates
- Left the EXE for ease of access for those that don't want to extract a ZIP on first install.
2025-11-19 03:00:47 -08:00
Frog bdb1d9ffd2 Update ci-builds.yml
- Fix to include git commit in final CI build
2025-11-19 02:59:44 -08:00
Frog 800cb2b9f6 Rename CI-Builds / Adjust so CI Builds include commit in ZIP file
- Updated name of CI builds for clarity to include -
- Updated so the final ZIP file includes the commit it was built from
- Adjusted artifact release name.
2025-11-19 02:53:29 -08:00
Frog 8b6013e1c0 Update and rename autobuild.yml to cibuilds.yml
- Adjusted old AutoBuild script for CI builds
- Renamed from AutoBuild to CIBuilds
- Configured to run on every commit to main branch
2025-11-19 02:22:53 -08:00
Frog 668f367838 Update preview image 2025-11-19 02:16:32 -08:00
Frog 6613b777a7 Increment App version / Adjust Copyright Info
- Increment App Version
- Adjust Copyright Info / Repository
2025-11-19 02:01:17 -08:00
Frog 1036f8a8b4 Apply SelectForm.cs from commit 6fa5503 2025-11-18 02:03:11 -08:00
Frog c5a3a98827 Update README.md
- Adjusted ReadMe instructions to reflect EXE download rather than ZIP
- Updated link to Issue 40 > Now points to Web Archived version of the original issue before being hit by DCMA
2025-11-18 01:51:03 -08:00
Frog 4751a3bf76 Update README.md
- Fixed ReadMe change from previous merge (I really should pay a little more attention when pulling from a fork, I guess that's what I get for doing things at 2AM)
2025-11-18 01:42:44 -08:00
26 changed files with 1006 additions and 175 deletions
+34 -4
View File
@@ -1,10 +1,40 @@
---
name: Bug Report
about: Report a program exception or general bug, not including those explained within the FAQ and/or template issues.
title: ''
about: Report a program exception or general bug (not covered in FAQ or existing issues)
title: '[Bug] - '
labels: Bug
assignees: pointfeev
assignees: FroggMaster
---
###### Describe the bug and/or provide an image of the exception dialog box:
## Bug Description
<!-- Provide a clear and concise description of what the bug is -->
## Steps to Reproduce
<!-- How can the issue be reproduced? -->
1.
2.
3.
## Exception Details
<!-- If you received an error dialog, provide a screenshot or paste the full error message below -->
<details>
<summary>Click to expand error message/screenshot</summary>
```
Paste error text here, or drag and drop screenshot below
```
</details>
## Generated Config
<!-- If a configuration was generated, please provide the configuration file -->
## Affected Version
<!-- Please specify the version of the application you experience the issue with -->
- **CreamInstaller Version:** [e.g. v5.0.1.7, CI build #21]
@@ -1,12 +1,10 @@
name: Autobuild
name: CI Builds
on:
push:
tags:
- '*'
branches:
- main
workflow_dispatch:
pull_request:
types: [ main ]
jobs:
build:
@@ -30,8 +28,20 @@ jobs:
- name: Publish single-file
run: dotnet publish CreamInstaller.sln -c Release -r win-x64 -p:PublishSingleFile=true --self-contained true --output ./publish
- name: Set short commit SHA
id: vars
run: |
$shortSha = $env:GITHUB_SHA.Substring(0,7)
Write-Output "shortSha=$shortSha" >> $env:GITHUB_ENV
shell: pwsh
- name: Rename EXE with short commit SHA
run: |
Rename-Item -Path ./publish/CreamInstaller.exe -NewName "CreamInstaller-CI-$env:shortSha.exe"
shell: pwsh
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: CreamInstaller-release
path: ./publish/CreamInstaller.exe
name: CreamInstaller-CI-Release-${{ env.shortSha }}
path: ./publish/CreamInstaller-CI-${{ env.shortSha }}.exe
+8 -1
View File
@@ -52,12 +52,19 @@ jobs:
- name: Publish single-file
run: dotnet publish CreamInstaller.sln -c Release -r win-x64 -p:PublishSingleFile=true --self-contained true --output ./publish
- name: Zip Release
run: |
Compress-Archive -Path ./publish/* -DestinationPath ./publish/CreamInstaller.zip
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: v${{ inputs.version }}
name: ${{ inputs.title || format('Release v{0}', inputs.version) }}
body: ${{ inputs.notes }}
files: ./publish/CreamInstaller.exe
generate_release_notes: true
files: |
./publish/CreamInstaller.exe
./publish/CreamInstaller.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+49
View File
@@ -0,0 +1,49 @@
name: Dev CI Builds
on:
push:
branches:
- dev
workflow_dispatch:
jobs:
build:
runs-on: windows-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Restore dependencies
run: dotnet restore CreamInstaller.sln
- name: Build Release
run: dotnet build CreamInstaller.sln --configuration Release --no-restore
- name: Publish single-file
run: dotnet publish CreamInstaller.sln -c Release -r win-x64 -p:PublishSingleFile=true --self-contained true --output ./publish
- name: Set short commit SHA and branch name
id: vars
run: |
$shortSha = $env:GITHUB_SHA.Substring(0,7)
$branch = $env:GITHUB_REF_NAME
Write-Output "shortSha=$shortSha" >> $env:GITHUB_ENV
Write-Output "branch=$branch" >> $env:GITHUB_ENV
shell: pwsh
- name: Rename EXE with branch and short commit SHA
run: |
Rename-Item -Path ./publish/CreamInstaller.exe -NewName "CreamInstaller-CI-$env:branch-$env:shortSha.exe"
shell: pwsh
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: CreamInstaller-CI-${{ env.branch }}-${{ env.shortSha }}
path: ./publish/CreamInstaller-CI-${{ env.branch }}-${{ env.shortSha }}.exe
+67 -77
View File
@@ -44,95 +44,85 @@ internal sealed class ContextMenuItem : ToolStripMenuItem
}
private static async Task TryImageIdentifier(ContextMenuItem item, string imageIdentifier)
=> await Task.Run(async () =>
{
if (Images.TryGetValue(imageIdentifier, out Image image) && image is not null)
{
if (Images.TryGetValue(imageIdentifier, out Image image) && image is not null)
item.Image = image;
else
item.Image = image;
return;
}
image = await Task.Run(async () =>
{
switch (imageIdentifier)
{
switch (imageIdentifier)
{
case "Paradox Launcher":
if (ParadoxLauncher.InstallPath.DirectoryExists())
foreach (string file in ParadoxLauncher.InstallPath.EnumerateDirectory("*.exe"))
{
image = file.GetFileIconImage();
break;
}
break;
case "Notepad":
image = IconGrabber.GetNotepadImage();
break;
case "Command Prompt":
image = IconGrabber.GetCommandPromptImage();
break;
case "File Explorer":
image = IconGrabber.GetFileExplorerImage();
break;
case "SteamDB":
image = await HttpClientManager.GetImageFromUrl(
IconGrabber.GetDomainFaviconUrl("steamdb.info"));
break;
case "Steam Store":
image = await HttpClientManager.GetImageFromUrl(
IconGrabber.GetDomainFaviconUrl("store.steampowered.com"));
break;
case "Steam Community":
image = await HttpClientManager.GetImageFromUrl(
IconGrabber.GetDomainFaviconUrl("steamcommunity.com"));
break;
case "ScreamDB":
image = await HttpClientManager.GetImageFromUrl(
IconGrabber.GetDomainFaviconUrl("scream-db.web.app"));
break;
case "Epic Games":
image = await HttpClientManager.GetImageFromUrl(
IconGrabber.GetDomainFaviconUrl("epicgames.com"));
break;
case "Ubisoft Store":
image = await HttpClientManager.GetImageFromUrl(
IconGrabber.GetDomainFaviconUrl("store.ubi.com"));
break;
default:
return;
}
if (image is not null)
{
Images[imageIdentifier] = image;
item.Image = image;
}
case "Paradox Launcher":
if (ParadoxLauncher.InstallPath.DirectoryExists())
foreach (string file in ParadoxLauncher.InstallPath.EnumerateDirectory("*.exe"))
return file.GetFileIconImage();
break;
case "Notepad":
return IconGrabber.GetNotepadImage();
case "Command Prompt":
return IconGrabber.GetCommandPromptImage();
case "File Explorer":
return IconGrabber.GetFileExplorerImage();
case "SteamDB":
return await HttpClientManager.GetImageFromUrl(
IconGrabber.GetDomainFaviconUrl("steamdb.info"));
case "Steam Store":
return await HttpClientManager.GetImageFromUrl(
IconGrabber.GetDomainFaviconUrl("store.steampowered.com"));
case "Steam Community":
return await HttpClientManager.GetImageFromUrl(
IconGrabber.GetDomainFaviconUrl("steamcommunity.com"));
case "ScreamDB":
return await HttpClientManager.GetImageFromUrl(
IconGrabber.GetDomainFaviconUrl("scream-db.web.app"));
case "Epic Games":
return await HttpClientManager.GetImageFromUrl(
IconGrabber.GetDomainFaviconUrl("epicgames.com"));
case "Ubisoft Store":
return await HttpClientManager.GetImageFromUrl(
IconGrabber.GetDomainFaviconUrl("store.ubi.com"));
}
return null;
});
if (image is not null)
{
Images[imageIdentifier] = image;
item.Image = image;
}
}
private static async Task TryImageIdentifierInfo(ContextMenuItem item,
(string id, string iconUrl) imageIdentifierInfo, Action onFail = null)
=> await Task.Run(async () =>
{
try
{
try
(string id, string iconUrl) = imageIdentifierInfo;
string imageIdentifier = "Icon_" + id;
if (Images.TryGetValue(imageIdentifier, out Image image) && image is not null)
{
(string id, string iconUrl) = imageIdentifierInfo;
string imageIdentifier = "Icon_" + id;
if (Images.TryGetValue(imageIdentifier, out Image image) && image is not null)
item.Image = image;
else
{
image = await HttpClientManager.GetImageFromUrl(iconUrl);
if (image is not null)
{
Images[imageIdentifier] = image;
item.Image = image;
}
else
onFail?.Invoke();
}
item.Image = image;
return;
}
catch
image = await HttpClientManager.GetImageFromUrl(iconUrl);
if (image is not null)
{
// ignored
Images[imageIdentifier] = image;
item.Image = image;
}
});
else
onFail?.Invoke();
}
catch
{
// ignored
}
}
protected override void OnClick(EventArgs e)
{
+12
View File
@@ -42,6 +42,18 @@ internal class CustomForm : Form
}
}
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
ThemeManager.Apply(this); // apply current theme (initial or toggled)
}
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
ThemeManager.Apply(this); // ensure late-added controls also themed
}
private void OnHelpButtonClicked(object sender, EventArgs args)
{
using DialogForm helpDialog = new(this);
+69 -38
View File
@@ -15,20 +15,17 @@ internal sealed class CustomTreeView : TreeView
{
private const string ProxyToggleString = "Proxy";
private static readonly Color C1 = ColorTranslator.FromHtml("#FFFF99");
private static readonly Color C2 = ColorTranslator.FromHtml("#696900");
private static readonly Color C3 = ColorTranslator.FromHtml("#AAAA69");
private static readonly Color C4 = ColorTranslator.FromHtml("#99FFFF");
private static readonly Color C5 = ColorTranslator.FromHtml("#006969");
private static readonly Color C6 = ColorTranslator.FromHtml("#69AAAA");
private static readonly Color C7 = ColorTranslator.FromHtml("#006900");
private static readonly Color C8 = ColorTranslator.FromHtml("#69AA69");
private readonly Dictionary<Selection, Rectangle> checkBoxBounds = [];
private readonly Dictionary<Selection, Rectangle> comboBoxBounds = [];
private readonly Dictionary<TreeNode, Rectangle> selectionBounds = [];
private SolidBrush backBrush;
private Color lastBackColor; // Tracks the last background color
// Selection background brush (used instead of SystemBrushes.Highlight to support dark mode)
private SolidBrush selectionBrush;
private Color lastSelectionBackColor;
private ToolStripDropDown comboBoxDropDown;
private Font comboBoxFont;
private Form form;
@@ -54,6 +51,8 @@ internal sealed class CustomTreeView : TreeView
{
backBrush?.Dispose();
backBrush = null;
selectionBrush?.Dispose();
selectionBrush = null;
comboBoxFont?.Dispose();
comboBoxFont = null;
comboBoxDropDown?.Dispose();
@@ -65,6 +64,13 @@ internal sealed class CustomTreeView : TreeView
checkBoxBounds.Clear();
comboBoxBounds.Clear();
selectionBounds.Clear();
backBrush?.Dispose();
backBrush = null;
lastBackColor = Color.Empty;
selectionBrush?.Dispose();
selectionBrush = null;
lastSelectionBackColor = Color.Empty;
}
private void DrawTreeNode(object sender, DrawTreeNodeEventArgs e)
@@ -76,9 +82,29 @@ internal sealed class CustomTreeView : TreeView
bool highlighted = (e.State & TreeNodeStates.Selected) == TreeNodeStates.Selected && Focused;
Graphics graphics = e.Graphics;
backBrush ??= new(BackColor);
// Recreate back brush if background color changed
if (backBrush == null || lastBackColor != BackColor)
{
backBrush?.Dispose();
backBrush = new(BackColor);
lastBackColor = BackColor;
}
// If highlighted, prepare a selection brush that respects the theme
if (highlighted)
{
Color selColor = ThemeManager.CustomTreeViewSelectionBackColor;
if (selectionBrush == null || lastSelectionBackColor != selColor)
{
selectionBrush?.Dispose();
selectionBrush = new(selColor);
lastSelectionBackColor = selColor;
}
}
Font font = node.NodeFont ?? Font;
Brush brush = highlighted ? SystemBrushes.Highlight : backBrush;
Brush brush = highlighted ? (Brush)selectionBrush : backBrush;
Rectangle bounds = node.Bounds;
Rectangle selectionBounds = bounds;
@@ -93,10 +119,10 @@ internal sealed class CustomTreeView : TreeView
return;
Color color = highlighted
? C1
? ThemeManager.CustomTreeViewHighlightPlatformColor
: Enabled
? C2
: C3;
? ThemeManager.CustomTreeViewPlatformColor
: ThemeManager.CustomTreeViewDisabledPlatformColor;
string text;
if (dlcType is not DLCType.None)
{
@@ -115,10 +141,10 @@ internal sealed class CustomTreeView : TreeView
if (platform is not Platform.Paradox)
{
color = highlighted
? C4
? ThemeManager.CustomTreeViewHighlightIdColor
: Enabled
? C5
: C6;
? ThemeManager.CustomTreeViewIdColor
: ThemeManager.CustomTreeViewDisabledIdColor;
text = id;
size = TextRenderer.MeasureText(graphics, text, font);
const int left = -4;
@@ -163,7 +189,9 @@ internal sealed class CustomTreeView : TreeView
checkBoxBounds = new(checkBoxBounds.Location, checkBoxBounds.Size + bounds.Size with { Height = 0 });
graphics.FillRectangle(backBrush, bounds);
point = new(bounds.Location.X - 1 + left, bounds.Location.Y + 1);
TextRenderer.DrawText(graphics, text, font, point, Enabled ? C7 : C8, TextFormatFlags.Default);
TextRenderer.DrawText(graphics, text, font, point,
Enabled ? ThemeManager.CustomTreeViewProxyColor : ThemeManager.CustomTreeViewDisabledProxyColor,
TextFormatFlags.Default);
this.checkBoxBounds[selection] = RectangleToClient(checkBoxBounds);
@@ -171,8 +199,11 @@ internal sealed class CustomTreeView : TreeView
{
comboBoxFont ??= new(font.FontFamily, 6, font.Style, font.Unit, font.GdiCharSet,
font.GdiVerticalFont);
ComboBoxState comboBoxState = Enabled ? ComboBoxState.Normal : ComboBoxState.Disabled;
ButtonState buttonState = Enabled ? ButtonState.Normal : ButtonState.Inactive;
bool darkMode = Program.DarkModeEnabled;
Color comboBackColor = ThemeManager.CustomTreeViewComboBackColor;
Color comboBorderColor = ThemeManager.CustomTreeViewComboBorderColor;
Color comboTextColor = ThemeManager.CustomTreeViewComboTextColor;
text = (selection.Proxy ?? Selection.DefaultProxy) + ".dll";
size = TextRenderer.MeasureText(graphics, text, comboBoxFont) + new Size(6, 0);
@@ -181,18 +212,9 @@ internal sealed class CustomTreeView : TreeView
selectionBounds = new(selectionBounds.Location,
selectionBounds.Size + bounds.Size with { Height = 0 });
Rectangle comboBoxBounds = bounds;
graphics.FillRectangle(backBrush, bounds);
if (ComboBoxRenderer.IsSupported)
ComboBoxRenderer.DrawTextBox(graphics, bounds, text, comboBoxFont, comboBoxState);
else
{
graphics.FillRectangle(SystemBrushes.ControlText, bounds);
ControlPaint.DrawButton(graphics, bounds, buttonState);
point = new(bounds.Location.X + 3 + bounds.Width / 2 - size.Width / 2,
bounds.Location.Y + bounds.Height / 2 - size.Height / 2);
TextRenderer.DrawText(graphics, text, comboBoxFont, point,
Enabled ? SystemColors.ControlText : SystemColors.GrayText, TextFormatFlags.Default);
}
// Themed combobox background + text (centralized in ThemeManager)
ThemeManager.DrawCustomComboBox(graphics, bounds, comboBoxFont, text);
size = new(14, 0);
left = -1;
@@ -201,10 +223,9 @@ internal sealed class CustomTreeView : TreeView
selectionBounds.Size + new Size(bounds.Size.Width + left, 0));
comboBoxBounds = new(comboBoxBounds.Location,
comboBoxBounds.Size + new Size(bounds.Size.Width + left, 0));
if (ComboBoxRenderer.IsSupported)
ComboBoxRenderer.DrawDropDownButton(graphics, bounds, comboBoxState);
else
ControlPaint.DrawComboButton(graphics, bounds, buttonState);
// Themed combobox dropdown button (centralized in ThemeManager)
ThemeManager.DrawCustomComboBoxButton(graphics, bounds);
this.comboBoxBounds[selection] = RectangleToClient(comboBoxBounds);
}
@@ -246,6 +267,7 @@ internal sealed class CustomTreeView : TreeView
comboBoxDropDown ??= new();
comboBoxDropDown.ShowItemToolTips = false;
comboBoxDropDown.Items.Clear();
foreach (string proxy in proxies)
{
bool canUse = true;
@@ -261,13 +283,22 @@ internal sealed class CustomTreeView : TreeView
}
if (canUse)
_ = comboBoxDropDown.Items.Add(new ToolStripButton(proxy + ".dll", null, (_, _) =>
{
ToolStripMenuItem menuItem = new(proxy + ".dll", null, (_, _) =>
{
pair.Key.Proxy = proxy == Selection.DefaultProxy ? null : proxy;
selectForm.OnProxyChanged();
}) { Font = comboBoxFont });
})
{
Font = comboBoxFont
};
_ = comboBoxDropDown.Items.Add(menuItem);
}
}
// Apply theme using ThemeManager
ThemeManager.ApplyToolStripDropDown(comboBoxDropDown);
comboBoxDropDown.Show(this, PointToScreen(new(pair.Value.Left, pair.Value.Bottom - 1)));
break;
}
+3 -3
View File
@@ -4,8 +4,8 @@
<TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>
<UseWindowsForms>True</UseWindowsForms>
<ApplicationIcon>Resources\program.ico</ApplicationIcon>
<Version>5.0.1.4</Version>
<Copyright>2021, pointfeev (https://github.com/pointfeev)</Copyright>
<Version>5.0.2.0</Version>
<Copyright>2025, FroggMaster (https://github.com/FroggMaster)</Copyright>
<Company>CreamInstaller</Company>
<Product>Automatic DLC Unlocker Installer &amp; Configuration Generator</Product>
<StartupObject>CreamInstaller.Program</StartupObject>
@@ -208,4 +208,4 @@
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>
</Project>
+1
View File
@@ -106,6 +106,7 @@ namespace CreamInstaller.Forms
//
sortCheckBox.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
sortCheckBox.AutoSize = true;
sortCheckBox.Checked = true; // Enable Sort By Name by default
sortCheckBox.Location = new System.Drawing.Point(220, 247);
sortCheckBox.Margin = new Padding(3, 0, 0, 0);
sortCheckBox.Name = "sortCheckBox";
+1 -1
View File
@@ -14,7 +14,7 @@ internal sealed partial class SelectDialogForm : CustomForm
internal SelectDialogForm(IWin32Window owner) : base(owner)
{
InitializeComponent();
selectionTreeView.TreeViewNodeSorter = PlatformIdComparer.NodeName;
selectionTreeView.TreeViewNodeSorter = sortCheckBox.Checked ? PlatformIdComparer.NodeText : PlatformIdComparer.NodeName;
}
internal DialogResult QueryUser(string groupBoxText,
+38 -3
View File
@@ -31,6 +31,8 @@ namespace CreamInstaller.Forms
useSmokeAPILayoutPanel = new FlowLayoutPanel();
useSmokeAPICheckBox = new CheckBox();
useSmokeAPIHelpButton = new Button();
darkModeFlowPanel = new FlowLayoutPanel();
darkModeCheckBox = new CheckBox();
allCheckBoxLayoutPanel = new FlowLayoutPanel();
allCheckBox = new CheckBox();
progressBar = new ProgressBar();
@@ -50,6 +52,7 @@ namespace CreamInstaller.Forms
proxyFlowPanel.SuspendLayout();
blockedGamesFlowPanel.SuspendLayout();
useSmokeAPILayoutPanel.SuspendLayout();
darkModeFlowPanel.SuspendLayout();
allCheckBoxLayoutPanel.SuspendLayout();
saveFlowPanel.SuspendLayout();
SuspendLayout();
@@ -209,6 +212,30 @@ namespace CreamInstaller.Forms
useSmokeAPIHelpButton.UseVisualStyleBackColor = true;
useSmokeAPIHelpButton.Click += OnUseSmokeAPIHelpButtonClicked;
//
// darkModeFlowPanel
//
darkModeFlowPanel.AutoSize = true;
darkModeFlowPanel.AutoSizeMode = AutoSizeMode.GrowAndShrink;
darkModeFlowPanel.Margin = new Padding(12, 0, 0, 0);
darkModeFlowPanel.Name = "darkModeFlowPanel";
darkModeFlowPanel.Size = new System.Drawing.Size(98, 19);
darkModeFlowPanel.TabIndex = 10011;
darkModeFlowPanel.WrapContents = false;
//
// darkModeCheckBox
//
darkModeCheckBox.AutoSize = true;
darkModeCheckBox.Enabled = true;
darkModeCheckBox.Location = new System.Drawing.Point(2, 0);
darkModeCheckBox.Margin = new Padding(2, 0, 0, 0);
darkModeCheckBox.Name = "darkModeCheckBox";
darkModeCheckBox.Size = new System.Drawing.Size(96, 19);
darkModeCheckBox.TabIndex = 1;
darkModeCheckBox.Text = "Enable Dark Mode";
darkModeCheckBox.UseVisualStyleBackColor = true;
darkModeCheckBox.CheckedChanged += OnDarkModeCheckBoxChanged;
darkModeFlowPanel.Controls.Add(darkModeCheckBox);
//
// allCheckBoxLayoutPanel
//
allCheckBoxLayoutPanel.AutoSize = true;
@@ -319,6 +346,7 @@ namespace CreamInstaller.Forms
//
sortCheckBox.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
sortCheckBox.AutoSize = true;
sortCheckBox.Checked = true; // Enable Sort By Name by default
sortCheckBox.Location = new System.Drawing.Point(84, 380);
sortCheckBox.Margin = new Padding(3, 0, 0, 0);
sortCheckBox.Name = "sortCheckBox";
@@ -391,7 +419,8 @@ namespace CreamInstaller.Forms
topOptionsTable.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
topOptionsTable.AutoSize = true;
topOptionsTable.AutoSizeMode = AutoSizeMode.GrowAndShrink;
topOptionsTable.ColumnCount = 5;
topOptionsTable.ColumnCount = 6;
topOptionsTable.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
topOptionsTable.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
topOptionsTable.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
topOptionsTable.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F)); // spacer
@@ -404,10 +433,12 @@ namespace CreamInstaller.Forms
topOptionsTable.RowStyles.Add(new RowStyle(SizeType.AutoSize));
topOptionsTable.Size = new System.Drawing.Size(610, 25);
topOptionsTable.TabIndex = 10009;
topOptionsTable.Controls.Clear();
topOptionsTable.Controls.Add(blockedGamesFlowPanel, 0, 0);
topOptionsTable.Controls.Add(useSmokeAPILayoutPanel, 1, 0);
topOptionsTable.Controls.Add(proxyFlowPanel, 3, 0);
topOptionsTable.Controls.Add(allCheckBoxLayoutPanel, 4, 0);
topOptionsTable.Controls.Add(darkModeFlowPanel, 2, 0);
topOptionsTable.Controls.Add(proxyFlowPanel, 4, 0);
topOptionsTable.Controls.Add(allCheckBoxLayoutPanel, 5, 0);
//
// SelectForm
//
@@ -442,6 +473,8 @@ namespace CreamInstaller.Forms
blockedGamesFlowPanel.PerformLayout();
useSmokeAPILayoutPanel.ResumeLayout(false);
useSmokeAPILayoutPanel.PerformLayout();
darkModeFlowPanel.ResumeLayout(false);
darkModeFlowPanel.PerformLayout();
allCheckBoxLayoutPanel.ResumeLayout(false);
allCheckBoxLayoutPanel.PerformLayout();
saveFlowPanel.ResumeLayout(false);
@@ -467,6 +500,7 @@ namespace CreamInstaller.Forms
private Button useSmokeAPIHelpButton;
private FlowLayoutPanel blockedGamesFlowPanel;
private FlowLayoutPanel useSmokeAPILayoutPanel;
private FlowLayoutPanel darkModeFlowPanel;
private FlowLayoutPanel allCheckBoxLayoutPanel;
private Button uninstallButton;
private Label progressLabelGames;
@@ -479,6 +513,7 @@ namespace CreamInstaller.Forms
private Button resetButton;
private FlowLayoutPanel saveFlowPanel;
private TableLayoutPanel topOptionsTable;
private CheckBox darkModeCheckBox;
}
}
+21 -4
View File
@@ -35,7 +35,7 @@ internal sealed partial class SelectForm : CustomForm
private SelectForm()
{
InitializeComponent();
selectionTreeView.TreeViewNodeSorter = PlatformIdComparer.NodeName;
selectionTreeView.TreeViewNodeSorter = sortCheckBox.Checked ? PlatformIdComparer.NodeText : PlatformIdComparer.NodeName;
Text = Program.ApplicationName;
}
@@ -204,7 +204,7 @@ internal sealed partial class SelectForm : CustomForm
return;
StoreAppData storeAppData = await SteamStore.QueryStoreAPI(appId);
_ = Interlocked.Decrement(ref steamGamesToCheck);
CmdAppData cmdAppData = await WithTimeout(SteamCMD.GetAppInfo(appId, branch, buildId), 20000);
CmdAppData cmdAppData = await WithTimeout(SteamCMD.GetAppInfo(appId, branch, buildId), 16000);
if (storeAppData is null && cmdAppData is null)
{
RemoveFromRemainingGames(name);
@@ -251,7 +251,7 @@ internal sealed partial class SelectForm : CustomForm
}
else
{
CmdAppData dlcCmdAppData = await SteamCMD.GetAppInfo(dlcAppId);
CmdAppData dlcCmdAppData = await WithTimeout(SteamCMD.GetAppInfo(dlcAppId), 16000);
if (dlcCmdAppData is not null)
{
dlcName = dlcCmdAppData.Common?.Name;
@@ -346,7 +346,7 @@ internal sealed partial class SelectForm : CustomForm
selection.Icon = IconGrabber.SteamAppImagesPath + @$"\{appId}\{cmdAppData?.Common?.Icon}.jpg";
selection.SubIcon = storeAppData?.HeaderImage ?? IconGrabber.SteamAppImagesPath
+ @$"\{appId}\{cmdAppData?.Common?.ClientIcon}.ico";
selection.Publisher = storeAppData?.Publishers[0] ?? cmdAppData?.Extended?.Publisher;
selection.Publisher = storeAppData?.Publishers?.FirstOrDefault() ?? cmdAppData?.Extended?.Publisher;
selection.Website = storeAppData?.Website;
if (Program.Canceled)
return;
@@ -569,6 +569,8 @@ internal sealed partial class SelectForm : CustomForm
progressLabel.Text = "Waiting for user to select which programs/games to scan . . .";
ShowProgressBar();
await ProgramData.Setup(this);
ProgramData.ClearLog();
ProgramData.Log($"[Scan] CreamInstaller {Program.Version} — scan started at {DateTime.UtcNow:yyyy-MM-dd HH:mm:ss} UTC");
bool scan = forceScan;
if (!scan && (programsToScan is null || programsToScan.Count < 1 || forceProvideChoices))
{
@@ -791,6 +793,7 @@ internal sealed partial class SelectForm : CustomForm
=> Invoke(() =>
{
ContextMenuStrip contextMenuStrip = new();
ThemeManager.ApplyContextMenu(contextMenuStrip);
ToolStripItemCollection items = contextMenuStrip.Items;
string id = node.Name;
Platform platform = (Platform)node.Tag;
@@ -1239,4 +1242,18 @@ internal sealed partial class SelectForm : CustomForm
{
}
private void OnDarkModeCheckBoxChanged(object sender, EventArgs e)
{
Program.DarkModeEnabled = darkModeCheckBox.Checked;
ThemeManager.ApplyToAllOpenForms();
}
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
ThemeManager.Apply(this);
if (darkModeCheckBox is not null)
darkModeCheckBox.Checked = Program.DarkModeEnabled;
}
}
+1
View File
@@ -40,6 +40,7 @@ internal sealed partial class UpdateForm : CustomForm
#if DEBUG
DebugForm.Current.Attach(form);
#endif
ThemeManager.Apply(form); // apply current theme when transitioning
}
private async void OnLoad()
+6 -5
View File
@@ -58,13 +58,13 @@ internal static class EpicStore
cacheFile.DeleteFile();
}
if (response is null)
if (response is null || response.Data?.Catalog is null)
return dlcIds;
List<Element> searchStore = [..response.Data.Catalog.SearchStore.Elements];
List<Element> searchStore = [..response.Data.Catalog.SearchStore?.Elements ?? []];
foreach (Element element in searchStore)
{
string title = element.Title;
string product = element.CatalogNs is not null && element.CatalogNs.Mappings.Length > 0
string product = element.CatalogNs?.Mappings is { Length: > 0 }
? element.CatalogNs.Mappings.First().PageSlug
: null;
string icon = null;
@@ -81,11 +81,11 @@ internal static class EpicStore
dlcIds.Populate(item.Id, title, product, icon, null, element.Items.Length == 1);
}
List<Element> catalogOffers = [..response.Data.Catalog.CatalogOffers.Elements];
List<Element> catalogOffers = [..response.Data.Catalog.CatalogOffers?.Elements ?? []];
foreach (Element element in catalogOffers)
{
string title = element.Title;
string product = element.CatalogNs is not null && element.CatalogNs.Mappings.Length > 0
string product = element.CatalogNs?.Mappings is { Length: > 0 }
? element.CatalogNs.Mappings.First().PageSlug
: null;
string icon = null;
@@ -118,6 +118,7 @@ internal static class EpicStore
(string id, string name, string product, string icon, string developer) app = dlcIds[i];
if (app.id != id)
continue;
found = true;
dlcIds[i] = canOverwrite
? (app.id, title ?? app.name, product ?? app.product, icon ?? app.icon, developer ?? app.developer)
+59 -8
View File
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using CreamInstaller.Utility;
@@ -28,16 +29,19 @@ internal static class SteamLibrary
{
List<(string appId, string name, string branch, int buildId, string gameDirectory)> games = new();
HashSet<string> gameLibraryDirectories = await GetLibraryDirectories();
ProgramData.Log($"[Steam] Found {gameLibraryDirectories.Count} library folder(s).");
foreach (string libraryDirectory in gameLibraryDirectories)
{
if (Program.Canceled)
return games;
ProgramData.Log($"[Steam] Scanning library: {libraryDirectory}");
foreach ((string appId, string name, string branch, int buildId, string gameDirectory) game in (await
GetGamesFromLibraryDirectory(
libraryDirectory)).Where(game => games.All(_game => _game.appId != game.appId)))
games.Add(game);
}
ProgramData.Log($"[Steam] Total games detected: {games.Count}");
return games;
});
@@ -47,13 +51,21 @@ internal static class SteamLibrary
{
List<(string appId, string name, string branch, int buildId, string gameDirectory)> games = new();
if (Program.Canceled || !libraryDirectory.DirectoryExists())
{
ProgramData.Log($"[Steam] Skipping library (not found or canceled): {libraryDirectory}");
return games;
}
foreach (string file in libraryDirectory.EnumerateDirectory("*.acf"))
{
if (Program.Canceled)
return games;
if (!ValveDataFile.TryDeserialize(file.ReadFile(), out VProperty result))
{
ProgramData.Log($"[Steam] Failed to deserialize ACF: {file}");
continue;
}
string appId = result.Value.GetChild("appid")?.ToString();
string installdir = result.Value.GetChild("installdir")?.ToString();
string name = result.Value.GetChild("name")?.ToString();
@@ -61,11 +73,23 @@ internal static class SteamLibrary
if (string.IsNullOrWhiteSpace(appId) || string.IsNullOrWhiteSpace(installdir) ||
string.IsNullOrWhiteSpace(name)
|| string.IsNullOrWhiteSpace(buildId))
{
ProgramData.Log($"[Steam] Skipping ACF with missing fields: {file}");
continue;
string gameDirectory = (libraryDirectory + @"\common\" + installdir).ResolvePath();
if (gameDirectory is null || !int.TryParse(appId, out int _) ||
!int.TryParse(buildId, out int buildIdInt) || games.Any(g => g.appId == appId))
}
string rawGameDirectory = libraryDirectory + @"\common\" + installdir;
string gameDirectory = rawGameDirectory.ResolvePath();
if (gameDirectory is null)
{
ProgramData.Log($"[Steam] Game directory not found (drive may be slow or disconnected): {rawGameDirectory} | App: {name} ({appId})");
continue;
}
if (!int.TryParse(appId, out int _) || !int.TryParse(buildId, out int buildIdInt) ||
games.Any(g => g.appId == appId))
continue;
VToken userConfig = result.Value.GetChild("UserConfig");
string branch = userConfig?.GetChild("BetaKey")?.ToString();
branch ??= userConfig?.GetChild("betakey")?.ToString();
@@ -78,6 +102,8 @@ internal static class SteamLibrary
if (string.IsNullOrWhiteSpace(branch))
branch = "public";
ProgramData.Log($"[Steam] Detected game: {name} ({appId}) | Branch: {branch} | Dir: {gameDirectory}");
games.Add((appId, name, branch, buildIdInt, gameDirectory));
}
@@ -92,25 +118,50 @@ internal static class SteamLibrary
return libraryDirectories;
string steamInstallPath = InstallPath;
if (steamInstallPath == null || !steamInstallPath.DirectoryExists())
{
ProgramData.Log($"[Steam] Steam install path not found or inaccessible: {steamInstallPath ?? "(null)"}");
return libraryDirectories;
}
string libraryFolder = steamInstallPath + @"\steamapps";
if (!libraryFolder.DirectoryExists())
{
ProgramData.Log($"[Steam] Default steamapps folder not found: {libraryFolder}");
return libraryDirectories;
}
_ = libraryDirectories.Add(libraryFolder);
ProgramData.Log($"[Steam] Default library folder: {libraryFolder}");
string libraryFolders = libraryFolder + @"\libraryfolders.vdf";
if (!libraryFolders.FileExists() ||
!ValveDataFile.TryDeserialize(libraryFolders.ReadFile(), out VProperty result))
{
ProgramData.Log($"[Steam] libraryfolders.vdf not found or failed to parse: {libraryFolders}");
return libraryDirectories;
}
foreach (VToken vToken in result.Value.Where(p =>
p is VProperty property && int.TryParse(property.Key, out int _)))
{
VProperty property = (VProperty)vToken;
string path = property.Value.GetChild("path")?.ToString();
if (string.IsNullOrWhiteSpace(path))
string rawPath = property.Value.GetChild("path")?.ToString();
if (string.IsNullOrWhiteSpace(rawPath))
continue;
path += @"\steamapps";
if (path.DirectoryExists())
_ = libraryDirectories.Add(path);
// Normalize the path from VDF (may use forward slashes or wrong casing)
string normalizedPath = Path.GetFullPath(rawPath);
string steamappsPath = normalizedPath + @"\steamapps";
string resolvedPath = steamappsPath.ResolvePath();
if (resolvedPath is null)
{
ProgramData.Log($"[Steam] External library not accessible (drive may be disconnected or letter changed): {steamappsPath}");
continue;
}
if (libraryDirectories.Add(resolvedPath))
ProgramData.Log($"[Steam] Additional library folder found: {resolvedPath}");
}
return libraryDirectories;
+6 -1
View File
@@ -35,7 +35,7 @@ internal static class Program
internal static readonly string CurrentProcessFilePath = CurrentProcess.MainModule?.FileName;
internal static readonly int CurrentProcessId = CurrentProcess.Id;
// this may forever be false, but who knows, maybe acidicoala makes it once again better than CreamAPI some day
// Setting is now toggleable. Huzzah!
internal static bool UseSmokeAPI = true;
internal static bool BlockProtectedGames = true;
@@ -43,6 +43,9 @@ internal static class Program
internal static readonly string[] ProtectedGameDirectories = [@"\EasyAntiCheat", @"\BattlEye"];
internal static readonly string[] ProtectedGameDirectoryExceptions = [];
// Dark mode enabled by default
internal static bool DarkModeEnabled = true;
internal static bool IsGameBlocked(string name, string directory = null)
=> BlockProtectedGames && (ProtectedGames.Contains(name) || directory is not null &&
!ProtectedGameDirectoryExceptions.Contains(name)
@@ -70,6 +73,8 @@ internal static class Program
#if DEBUG
DebugForm.Current.Attach(form);
#endif
// Apply initial theme (dark by default)
Utility.ThemeManager.Apply(form);
Application.Run(form);
}
catch (Exception e)
+1 -3
View File
@@ -37,6 +37,7 @@ internal static class CreamAPI
_ = dlc.Add(extraDlc);
config.DeleteFile();
installForm?.UpdateUser($"Deleted unnecessary configuration: {Path.GetFileName(config)}", LogTextBox.Action, false);
config.CreateFile(true, installForm)?.Close();
StreamWriter writer = new(config, true, Encoding.Default);
WriteConfig(writer, selection.Name, !int.TryParse(selection.Id, out _) ? "0" : selection.Id,
@@ -44,9 +45,6 @@ internal static class CreamAPI
writer.Flush();
writer.Close();
return;
installForm?.UpdateUser($"Deleted unnecessary configuration: {Path.GetFileName(config)}", LogTextBox.Action,
false);
}
private static void WriteConfig(StreamWriter writer, string name, string appId,
+11 -3
View File
@@ -167,7 +167,7 @@ internal static class SmokeAPI
}
}
writer.WriteLine(" },");
writer.WriteLine(" }");
}
else
writer.WriteLine(" \"extra_dlcs\": {}");
@@ -367,7 +367,11 @@ internal static class SmokeAPI
"A3873569DECAD08962C46E88352E6DB1", // SmokeAPI v2.0.4
"4A1A823E5CF4FB861DD6BA94539D29C4", // SmokeAPI v2.0.5
"EC153C0CCE476AFFB2458575930F11E6", // SmokeAPI v3.1.5
"E833ACE855245D5939EE36FF25D8B4A4" // SmokeAPI v4.0.0
"E833ACE855245D5939EE36FF25D8B4A4", // SmokeAPI v4.0.0
"A2728FC65BFF3305F43F87CB6E3AE448", // SmokeAPI v4.1.0
"CA6B8DE96022A70C45E11FF6D0B55857", // SmokeAPI v4.1.1
"0438477117293DF1EAE1B4D87E8CE084", // SmokeAPI v4.1.2
"2B2413E3CCDA93C3821711D089129D34" // SmokeAPI v4.1.3
],
[ResourceIdentifier.Steamworks64] =
[
@@ -384,7 +388,11 @@ internal static class SmokeAPI
"C0DDB49C9BFD3E05CBC1C61D117E93F9", // SmokeAPI v2.0.4
"F7C3064D5E3C892B168F504C21AC4923", // SmokeAPI v2.0.5
"5A6712770EC7CE589252706245E62C72", // SmokeAPI v3.1.5
"22DD39B16D3C10FDB044FDCB1BAE63B8" // SmokeAPI v4.0.0
"22DD39B16D3C10FDB044FDCB1BAE63B8", // SmokeAPI v4.0.0
"997656BEB55D1D87918D0BF96BD5312F", // SmokeAPI v4.1.0
"CD628177EC5D6303043E35DB6A83AB30", // SmokeAPI v4.1.1
"3AC05641AA561C11BE706782B5D3C49D", // SmokeAPI v4.1.2
"B87E96F9A52D98A957B252CDAB61CBE8" // SmokeAPI v4.1.3
]
};
}
Binary file not shown.
Binary file not shown.
+11 -2
View File
@@ -54,7 +54,16 @@ internal static class Diagnostics
if (info.Parent is null)
return info.Name.ToUpperInvariant();
string parent = ResolvePath(info.Parent.FullName);
string name = info.Parent.GetFileSystemInfos(info.Name)[0].Name;
return parent is null ? name : Path.Combine(parent, name);
try
{
FileSystemInfo[] infos = info.Parent.GetFileSystemInfos(info.Name);
string name = infos.Length > 0 ? infos[0].Name : info.Name;
return parent is null ? name : Path.Combine(parent, name);
}
catch
{
// Fall back to the raw name if the filesystem call fails (e.g. on a slow external drive)
return parent is null ? info.Name : Path.Combine(parent, info.Name);
}
}
}
+5
View File
@@ -21,4 +21,9 @@ internal static partial class NativeImports
[LibraryImport("user32.dll", SetLastError = true), DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
internal static partial void SetWindowPos(nint hWnd, nint hWndInsertAfter, int x, int y, int cx, int cy,
uint uFlags);
// Windows theming (scrollbars / dark mode for some controls)
[LibraryImport("uxtheme.dll", SetLastError = true)]
internal static partial int SetWindowTheme(nint hWnd, [MarshalAs(UnmanagedType.LPWStr)] string pszSubAppName,
[MarshalAs(UnmanagedType.LPWStr)] string pszSubIdList);
}
+34
View File
@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Newtonsoft.Json;
@@ -28,6 +30,38 @@ internal static class ProgramData
private static readonly string DlcChoicesPath = DirectoryPath + @"\dlc.json";
private static readonly string KoaloaderProxyChoicesPath = DirectoryPath + @"\proxies.json";
internal static readonly string LogPath = DirectoryPath + @"\scan.log";
private static readonly object LogLock = new();
internal static void Log(string message)
{
try
{
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);
}
catch
{
// ignored — logging must never crash the application
}
}
internal static void ClearLog()
{
try
{
if (File.Exists(LogPath))
File.Delete(LogPath);
}
catch
{
// ignored
}
}
internal static async Task Setup(Form form = null)
=> await Task.Run(() =>
{
+464
View File
@@ -0,0 +1,464 @@
using System;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
namespace CreamInstaller.Utility;
internal static class ThemeManager
{
// -----------------------------------------------------------------
// Color definitions (do not change values)
// -----------------------------------------------------------------
// ----------------------------
// Dark mode colors
// ----------------------------
private static readonly Color DarkBack = ColorTranslator.FromHtml("#1E1E1E");
private static readonly Color DarkBackAlt = ColorTranslator.FromHtml("#252525");
private static readonly Color DarkBorder = ColorTranslator.FromHtml("#3F3F46");
private static readonly Color DarkFore = ColorTranslator.FromHtml("#D4D4D4");
private static readonly Color DarkForeDim = ColorTranslator.FromHtml("#9CA3AF");
private static readonly Color Accent = ColorTranslator.FromHtml("#0E639C");
private static readonly Color DarkLink = ColorTranslator.FromHtml("#64B5F6");
// CustomTreeView dark-mode specific colors
private static readonly Color DarkPlatform = ColorTranslator.FromHtml("#FFFF99");
private static readonly Color DarkId = ColorTranslator.FromHtml("#99FFFF");
private static readonly Color DarkProxy = ColorTranslator.FromHtml("#99FF99");
private static readonly Color DarkSelectionBack = ColorTranslator.FromHtml("#2A2D2E");
private static readonly Color DarkComboBack = DarkBackAlt; // #252525
private static readonly Color DarkComboBorder = DarkBorder; // #3F3F46
private static readonly Color DarkComboText = DarkFore; // #D4D4D4
// ----------------------------
// Light mode colors (system defaults)
// ----------------------------
private static readonly Color LightBack = SystemColors.Control;
private static readonly Color LightBackAlt = SystemColors.ControlLightLight;
private static readonly Color LightFore = SystemColors.ControlText;
private static readonly Color LightBorder = SystemColors.ControlDark;
// CustomTreeView light-mode specific colors
private static readonly Color LightPlatform = ColorTranslator.FromHtml("#696900");
private static readonly Color LightId = ColorTranslator.FromHtml("#006969");
private static readonly Color LightProxy = ColorTranslator.FromHtml("#006900");
private static readonly Color LightSelectionBack = SystemColors.Highlight;
private static readonly Color LightComboBack = SystemColors.Control;
private static readonly Color LightComboBorder = SystemColors.ControlDark;
private static readonly Color LightComboText = SystemColors.ControlText;
// -----------------------------------------------------------------
// Theme-aware properties used by other components (CustomTreeView etc.)
// -----------------------------------------------------------------
internal static bool IsDark => Program.DarkModeEnabled;
internal static Color CustomTreeViewPlatformColor => IsDark ? DarkPlatform : LightPlatform;
internal static Color CustomTreeViewIdColor => IsDark ? DarkId : LightId;
internal static Color CustomTreeViewProxyColor => IsDark ? DarkProxy : LightProxy;
internal static Color CustomTreeViewHighlightPlatformColor => DarkPlatform; // C1 (uses same color for highlight)
internal static Color CustomTreeViewDisabledPlatformColor => ColorTranslator.FromHtml("#AAAA69"); // C3
internal static Color CustomTreeViewHighlightIdColor => DarkId; // C4
internal static Color CustomTreeViewDisabledIdColor => ColorTranslator.FromHtml("#69AAAA"); // C6
internal static Color CustomTreeViewDisabledProxyColor => ColorTranslator.FromHtml("#69AA69"); // C8
// Background color used when a tree node is selected.
// Keeps light-mode behavior using the system highlight, but supplies a custom dark color for dark mode
internal static Color CustomTreeViewSelectionBackColor => IsDark ? DarkSelectionBack : LightSelectionBack;
internal static Color CustomTreeViewComboBackColor => IsDark ? DarkComboBack : LightComboBack;
internal static Color CustomTreeViewComboBorderColor => IsDark ? DarkComboBorder : LightComboBorder;
internal static Color CustomTreeViewComboTextColor => IsDark ? DarkComboText : LightComboText;
// -----------------------------------------------------------------
// Public / Internal API
// -----------------------------------------------------------------
/// <summary>
/// Toggle dark mode and re-apply theming to all open forms.
/// </summary>
internal static void ToggleDarkMode(Form anyForm)
{
Program.DarkModeEnabled = !Program.DarkModeEnabled;
ApplyToAllOpenForms();
}
/// <summary>
/// Apply current theme to a single form and its child controls.
/// </summary>
internal static void Apply(Form form)
{
if (form is null) return;
if (!IsDark)
{
Reset(form);
return;
}
form.SuspendLayout();
form.BackColor = DarkBack;
form.ForeColor = DarkFore;
ApplyTitleBar(form);
foreach (Control c in form.Controls)
ApplyControlTheme(c, true);
form.ResumeLayout(true);
}
/// <summary>
/// Apply the theme to all currently open forms.
/// </summary>
internal static void ApplyToAllOpenForms()
{
foreach (Form openForm in Application.OpenForms.Cast<Form>())
Apply(openForm);
}
// -----------------------------------------------------------------
// Control theming helpers
// -----------------------------------------------------------------
/// <summary>
/// Apply theming to a control tree. Entry point which recurses children
/// then applies either the dark or light styling logic.
/// </summary>
private static void ApplyControlTheme(Control control, bool dark)
{
if (control is null) return;
// Recurse first so parent layering still works correctly
foreach (Control child in control.Controls)
ApplyControlTheme(child, dark);
if (dark)
ApplyDarkControl(control);
else
ApplyLightControl(control);
// Try to apply themed scrollbars where applicable
TryApplyScrollbarTheme(control, dark);
}
// Separated dark/light cases to make the intent clearer and reduce duplication
private static void ApplyDarkControl(Control control)
{
switch (control)
{
// Group box background/foreground
case GroupBox gb:
gb.ForeColor = DarkFore;
gb.BackColor = DarkBackAlt;
break;
// Buttons: flat appearance, border and foreground
case Button b:
b.FlatStyle = FlatStyle.Flat;
b.FlatAppearance.BorderColor = DarkBorder;
b.BackColor = DarkBackAlt;
b.ForeColor = DarkFore;
break;
// Checkboxes: match form background and foreground
case CheckBox cb:
cb.BackColor = DarkBack;
cb.ForeColor = DarkFore;
break;
// LinkLabel: color and active/visited styling
case LinkLabel ll:
ll.BackColor = DarkBack;
ll.ForeColor = DarkFore;
ll.LinkColor = DarkLink;
ll.ActiveLinkColor = Color.White;
ll.VisitedLinkColor = DarkLink;
break;
// Labels: dark background, light foreground
case Label lbl:
lbl.BackColor = DarkBack;
lbl.ForeColor = DarkFore;
break;
// ProgressBar uses accent color for foreground
case ProgressBar pb:
pb.ForeColor = Accent;
pb.BackColor = DarkBackAlt;
break;
// TreeView: darker alternate background, light text, darker lines
case TreeView tv:
tv.BackColor = DarkBackAlt;
tv.ForeColor = DarkFore;
tv.LineColor = DarkBorder;
tv.Invalidate(); // Forces a redraw
break;
// RichTextBox follows alternate dark background
case RichTextBox rtb:
rtb.BackColor = DarkBackAlt;
rtb.ForeColor = DarkFore;
break;
// Layout panels set a consistent background
case TableLayoutPanel tlp:
tlp.BackColor = DarkBack;
break;
case FlowLayoutPanel flp:
flp.BackColor = DarkBack;
break;
}
}
private static void ApplyLightControl(Control control)
{
switch (control)
{
case GroupBox gb:
gb.BackColor = LightBack;
gb.ForeColor = LightFore;
break;
case Button b:
b.FlatStyle = FlatStyle.Standard;
b.BackColor = LightBack;
b.ForeColor = LightFore;
break;
case CheckBox cb:
cb.BackColor = LightBack;
cb.ForeColor = LightFore;
break;
case LinkLabel ll:
ll.BackColor = LightBack;
ll.ForeColor = LightFore;
ll.LinkColor = SystemColors.HotTrack;
ll.ActiveLinkColor = SystemColors.Highlight;
ll.VisitedLinkColor = SystemColors.HotTrack;
break;
case Label lbl:
lbl.BackColor = LightBack;
lbl.ForeColor = LightFore;
break;
case ProgressBar pb:
pb.BackColor = LightBack;
pb.ForeColor = LightFore;
break;
case TreeView tv:
tv.BackColor = LightBack;
tv.ForeColor = LightFore;
tv.LineColor = LightBorder;
tv.Invalidate(); // Forces a redraw
break;
case RichTextBox rtb:
rtb.BackColor = LightBack;
rtb.ForeColor = LightFore;
break;
case TableLayoutPanel tlp:
tlp.BackColor = LightBack;
break;
case FlowLayoutPanel flp:
flp.BackColor = LightBack;
break;
}
}
private static void Reset(Form form)
{
form.SuspendLayout();
form.BackColor = LightBack;
form.ForeColor = LightFore;
ApplyTitleBar(form);
foreach (Control c in form.Controls)
ApplyControlTheme(c, false);
form.ResumeLayout(true);
}
// -----------------------------------------------------------------
// Titlebar / platform-specific helpers
// -----------------------------------------------------------------
private static void ApplyTitleBar(Form form)
{
try
{
int useDark = IsDark ? 1 : 0;
NativeMethods.EnableDarkTitleBar(form.Handle, useDark);
}
catch { }
}
private static void TryApplyScrollbarTheme(Control control, bool dark)
{
try
{
string theme = dark ? "DarkMode_Explorer" : null;
NativeImports.SetWindowTheme(control.Handle, theme, null);
}
catch { }
}
// -----------------------------------------------------------------
// Context menu / ToolStrip theming
// -----------------------------------------------------------------
/// <summary>
/// Apply theme to a context menu (ContextMenuStrip).
/// </summary>
internal static void ApplyContextMenu(ContextMenuStrip contextMenu)
{
if (contextMenu is null) return;
bool dark = IsDark;
contextMenu.BackColor = dark ? DarkBackAlt : SystemColors.Menu;
contextMenu.ForeColor = dark ? DarkFore : SystemColors.MenuText;
contextMenu.Renderer = dark ? new DarkContextMenuRenderer() : new ToolStripProfessionalRenderer();
foreach (ToolStripItem item in contextMenu.Items)
ApplyContextMenuItem(item, dark);
}
private static void ApplyContextMenuItem(ToolStripItem item, bool dark)
{
if (item is null) return;
item.BackColor = dark ? DarkBackAlt : SystemColors.Menu;
item.ForeColor = dark ? DarkFore : SystemColors.MenuText;
if (item is ToolStripMenuItem menuItem)
foreach (ToolStripItem subItem in menuItem.DropDownItems)
ApplyContextMenuItem(subItem, dark);
}
internal static void ApplyToolStripDropDown(ToolStripDropDown dropDown)
{
if (dropDown is null) return;
bool dark = IsDark;
dropDown.BackColor = dark ? DarkBackAlt : SystemColors.Menu;
dropDown.ForeColor = dark ? DarkFore : SystemColors.MenuText;
dropDown.Renderer = dark ? new DarkDropDownRenderer() : new ToolStripProfessionalRenderer();
foreach (ToolStripItem item in dropDown.Items)
ApplyToolStripItem(item, dark);
}
private static void ApplyToolStripItem(ToolStripItem item, bool dark)
{
if (item is null) return;
item.BackColor = dark ? DarkBackAlt : SystemColors.Menu;
item.ForeColor = dark ? DarkFore : SystemColors.MenuText;
}
// -----------------------------------------------------------------
// Themed renderers for menus
// -----------------------------------------------------------------
private class DarkContextMenuRenderer : ToolStripProfessionalRenderer
{
public DarkContextMenuRenderer() : base(new DarkMenuColorTable()) { }
protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e)
{
if (e.Item.Selected)
e.TextColor = DarkFore;
base.OnRenderItemText(e);
}
}
private class DarkDropDownRenderer : ToolStripProfessionalRenderer
{
public DarkDropDownRenderer() : base(new DarkMenuColorTable()) { }
protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e)
{
// Force text color to stay light even when selected
e.TextColor = DarkFore;
base.OnRenderItemText(e);
}
}
private class DarkMenuColorTable : ProfessionalColorTable
{
public override Color MenuItemSelected => ColorTranslator.FromHtml("#2A2D2E");
public override Color MenuItemSelectedGradientBegin => ColorTranslator.FromHtml("#2A2D2E");
public override Color MenuItemSelectedGradientEnd => ColorTranslator.FromHtml("#2A2D2E");
public override Color MenuItemBorder => ColorTranslator.FromHtml("#3F3F46");
public override Color MenuBorder => ColorTranslator.FromHtml("#3F3F46");
public override Color MenuItemPressedGradientBegin => ColorTranslator.FromHtml("#252525");
public override Color MenuItemPressedGradientEnd => ColorTranslator.FromHtml("#252525");
public override Color ImageMarginGradientBegin => ColorTranslator.FromHtml("#1E1E1E");
public override Color ImageMarginGradientMiddle => ColorTranslator.FromHtml("#1E1E1E");
public override Color ImageMarginGradientEnd => ColorTranslator.FromHtml("#1E1E1E");
public override Color ToolStripDropDownBackground => ColorTranslator.FromHtml("#252525");
public override Color SeparatorDark => ColorTranslator.FromHtml("#3F3F46");
public override Color SeparatorLight => ColorTranslator.FromHtml("#3F3F46");
}
// -----------------------------------------------------------------
// Theming helpers for CustomTreeView
// All rendering logic for the CustomTreeView's proxy combo box and dropdown
// button is centralized here so theming resides in ThemeManager.
// -----------------------------------------------------------------
/// <summary>
/// Draws the themed combobox area (background, border and text) used in CustomTreeView.
/// This centralizes colors and rendering for light/dark modes.
/// </summary>
internal static void DrawCustomComboBox(Graphics graphics, Rectangle rect, Font font, string text)
{
if (graphics is null) return;
using SolidBrush comboBrush = new(CustomTreeViewComboBackColor);
using Pen borderPen = new(CustomTreeViewComboBorderColor);
graphics.FillRectangle(comboBrush, rect);
graphics.DrawRectangle(borderPen, rect);
// Draw text inside the combobox
Size textSize = TextRenderer.MeasureText(graphics, text, font);
Point textPoint = new(rect.Left +3, rect.Top + rect.Height /2 - textSize.Height /2);
TextRenderer.DrawText(graphics, text, font, textPoint, CustomTreeViewComboTextColor, TextFormatFlags.Default);
}
/// <summary>
/// Draws the themed dropdown button (right-side arrow) used in CustomTreeView comboboxes.
/// </summary>
internal static void DrawCustomComboBoxButton(Graphics graphics, Rectangle rect)
{
if (graphics is null) return;
using SolidBrush comboBrush = new(CustomTreeViewComboBackColor);
using Pen borderPen = new(CustomTreeViewComboBorderColor);
graphics.FillRectangle(comboBrush, rect);
graphics.DrawRectangle(borderPen, rect);
// Draw the arrow glyph centered in the rect
int arrowSize =3;
Point arrowTop = new(rect.X + rect.Width /2, rect.Y + rect.Height /2 -1);
Point[] arrowPoints = new[]
{
arrowTop,
new Point(arrowTop.X - arrowSize, arrowTop.Y - arrowSize),
new Point(arrowTop.X + arrowSize, arrowTop.Y - arrowSize)
};
using SolidBrush arrowBrush = new(CustomTreeViewComboTextColor);
graphics.FillPolygon(arrowBrush, arrowPoints);
}
}
internal static class NativeMethods
{
private const int DWMWA_USE_IMMERSIVE_DARK_MODE = 20;
[System.Runtime.InteropServices.DllImport("dwmapi.dll")]
private static extern int DwmSetWindowAttribute(System.IntPtr hwnd, int attr, ref int attrValue, int attrSize);
internal static void EnableDarkTitleBar(System.IntPtr handle, int useDark)
{
_ = DwmSetWindowAttribute(handle, DWMWA_USE_IMMERSIVE_DARK_MODE, ref useDark, sizeof(int));
}
}
+88 -15
View File
@@ -1,6 +1,7 @@
### [Forked] CreamInstaller: Automatic DLC Unlocker Installer & Configuration Generator
### [Revived] CreamInstaller: Automatic DLC Unlocker Installer & Configuration Generator
[![Latest Release](https://img.shields.io/github/v/release/FroggMaster/CreamInstaller?label=latest%20release)](https://github.com/FroggMaster/CreamInstaller/releases/latest) [![CI Build](https://github.com/FroggMaster/CreamInstaller/actions/workflows/ci-builds.yml/badge.svg)](https://github.com/FroggMaster/CreamInstaller/actions/workflows/ci-builds.yml)
![Program Preview Image](https://raw.githubusercontent.com/HvTcCore/CreamInstaller/main/preview.png)
![Program Preview Image](https://raw.githubusercontent.com/FroggMaster/CreamInstaller/main/preview.png)
###### **NOTE:** This is simply a preview image; this is not a list of supported games nor configurations!
@@ -26,11 +27,18 @@ games and DLCs the user selects; however, through the use of **right-click conte
* Automatic DLL installation and configuration generation for CreamAPI, Koaloader, ScreamAPI, Uplay R1 Unlocker and Uplay R2 Unlocker.
* Automatic uninstallation of DLLs and configurations for CreamAPI, Koaloader, SmokeAPI, ScreamAPI, Uplay R1 Unlocker and Uplay R2 Unlocker.
* Automatic reparation of the Paradox Launcher (and manually via the right-click context menu "Repair" option). *For when the launcher updates whilst you have CreamAPI, SmokeAPI or ScreamAPI installed to it.*
---
<details>
<summary><strong>Continuous Integration (CI) Builds</strong></summary>
- CreamInstaller is automatically built and tested using GitHub Actions on every push to the **main** branch. You can view all recent CI build runs by clicking the status badge at the top or here: [![CI Build](https://github.com/FroggMaster/CreamInstaller/actions/workflows/ci-builds.yml/badge.svg)](https://github.com/FroggMaster/CreamInstaller/actions/workflows/ci-builds.yml)
</details>
---
#### Installation:
1. Click [here](https://github.com/HvTcCore/CreamInstaller/releases/latest/download/CreamInstaller.zip) to download the latest release from [GitHub](https://github.com/HvtcCore/CreamInstaller).
2. Extract the executable from the ZIP file to anywhere on your computer you want. *It's completely self-contained.*
1. Click [here](https://github.com/FroggMaster/CreamInstaller/releases/latest/download/CreamInstaller.exe) to download the latest release from [GitHub](https://github.com/FroggMaster/CreamInstaller).
2. Move the executable to anywhere on your computer you want. *It's completely self-contained.*
If the program doesn't seem to launch, try downloading and installing [.NET Desktop Runtime 8.0.7](https://download.visualstudio.microsoft.com/download/pr/bb581716-4cca-466e-9857-512e2371734b/5fe261422a7305171866fd7812d0976f/windowsdesktop-runtime-8.0.7-win-x64.exe) and restarting your computer. Note that the program currently only supports Windows 10+ 64-bit machines as seen [here](https://github.com/dotnet/core/blob/main/release-notes/8.0/supported-os.md).
@@ -49,26 +57,91 @@ If the program doesn't seem to launch, try downloading and installing [.NET Desk
##### **NOTE:** This program does not automatically download nor install actual DLC files for you; as the title of the program states, this program is only a *DLC Unlocker* installer. Should the game you wish to unlock DLC for not already come with the DLCs installed, as is the case with a good majority of games, you must find, download and install those to the game yourself. This process includes manually installing new DLCs and manually updating the previously manually installed DLCs after game updates.
---
#### FAQ / Common Issues:
# FAQ / Common Issues
**Q:** The program is not launching.
**A:** First and foremost, note that the program currently only supports Windows 10+ 64-bit machines as seen [here](https://github.com/dotnet/core/blob/main/release-notes/8.0/supported-os.md). If that does not apply to you, then make sure you've extracted the executable from the ZIP file before you've launched it, resolved your anti-virus, and have tried downloading the .NET Desktop Runtime mentioned under [installation instructions](https://github.com/HvTcCore/CreamInstaller#installation) above and restarting your computer. If none of the above work, then I simply cannot do anything about it, I do not control .NET. Either your system is not supported by the current version of .NET, or something is wrong/corrupted with your system.
### The program won't launch
**Q:** The game I installed the unlocker(s) to is not working/the DLCs are not unlocked.
**A:** Make sure you've read the note under [Usage](https://github.com/HvTcCore/CreamInstaller#usage) above! Assuming the program functioned as it was supposed to by properly installing DLC unlockers to your chosen games, this is not an issue I can do anything about and it's entirely up to you to seek the appropriate resources to fix it yourself (hint: https://cs.rin.ru/forum/viewforum.php?f=10).
Check the following in order:
1. **System requirements**: Windows 10+ 64-bit only ([.NET 8 Supported OS List](https://github.com/dotnet/core/blob/main/release-notes/8.0/supported-os.md))
2. **Extract before running**: Ensure you've extracted the executable from the ZIP file
3. **Antivirus**: Add an exception for CreamInstaller (see [False Positives](#false-positive-antivirus-detections) below)
4. **Runtime**: Install [.NET 8 Desktop Runtime](https://github.com/FroggMaster/CreamInstaller#installation) and restart your computer
If none of these work, your system may not support .NET 8 or may have underlying system issues.
---
### DLCs aren't unlocking in my game
CreamInstaller only installs DLC **unlockers** — it does **not** guarantee they will work for every game.
If the program successfully installs the unlockers but DLCs still arent unlocking, this is **not an issue with CreamInstaller itself** and isnt something I can directly fix. DLC Unlocker compatibility and behavior vary from game to game.
**DLC Files:** _This program does **not** automatically download or install actual DLC files for you. As the name implies, it is only a *DLC unlocker installer*. If the game you wish to unlock DLC for does not already include the DLC files (which is the case for many games), you must manually obtain and install those files yourself. This includes manually installing new DLCs and manually updating or reinstalling previously installed DLCs after game updates._
If youre having trouble, try the following:
- Review the [Usage section](https://github.com/FroggMaster/CreamInstaller#usage) for proper setup
- Visit the [CS.RIN.RU forum](https://cs.rin.ru/forum/viewforum.php?f=10) for game-specific troubleshooting and compatibility info
---
### My antivirus detects CreamInstaller as a virus (False Positives)
**These are false positives.** See the detailed explanation below:
<details>
<summary>Click to expand for information about false positives</summary>
## Why Antivirus Software Flags CreamInstaller
CreamInstaller is **not a virus**, but it's commonly flagged because of its functionality:
| Reason | Explanation |
|--------|-------------|
| **DLL modification** | Replaces game DLLs to unlock content — behavior similar to some malware |
| **Process hooking** | Embedded DLC unlockers interact with Steam/Epic/Ubisoft/game processes |
| **Compressed executable** | Single-file executables are often associated with packed malware |
| **Not code-signed** | No Extended Validation certificate ($300500/year) means lower AV reputation (**I will not be paying for this**) |
| **Misc** | Game modding tools frequently trigger heuristic detections regardless of intent |
## Common False Positive Names
| Detection Name | What It Usually Means / Why Its a False Positive |
|----------------------------------------|---------------------------------------------------|
| Mamson.A!ac | Generic heuristic detection; often triggered by packed or obfuscated executables |
| Phonzy.A!ml | Machine-learning detection; flags unusual behavior patterns |
| Wacatac.H!ml | Extremely common false positive; triggered by compressed or self-updating programs |
| Malgent!MSR | Generic Microsoft label for “suspicious behavior,” not confirmed malware |
| Tiggre!rfn | Heuristic runtime detection often seen with tools that hook processes |
| UDS:DangerousObject.Multi.Generic | Reputation-based detection for tools that *can* be abused |
| Trojan.Win64.Agent | Very broad category; common false positive for unsigned binaries |
| Trojan.Win64.Agent.oa!s1 | Cloud/AI heuristic variant of the above |
**See also:** [Archived issue #40](https://web.archive.org/web/20240604162435/https://github.com/pointfeev/CreamInstaller/issues/40)
## Verify Safety Yourself
CreamInstaller is **100% open source**:
1. **Review the source code** in this repository
2. **Build it yourself**
3. **Compare hashes** of your build with the official release
</details>
**Q:** The program and/or files installed by the program are detected as a virus/trojan/malware.
**A:** The "issue" of the program's outputted Koaloader DLLs being detected as false positives such as Mamson.A!ac, Phonzy.A!ml, Wacatac.H!ml, Malgent!MSR, Tiggre!rfn, and many many others, has already been posted and explained dozens of times now in many different manners... please do not post it again, you will just be ignored; instead, refer to the explanations within issue #40 and its linked issues: https://github.com/HvTcCore/CreamInstaller/issues/40.
---
##### Bugs/Crashes/Issues:
For reliable and quick assistance, all bugs, crashes and other issues should be referred to the [GitHub Issues](https://github.com/HvTcCore/CreamInstaller/issues) page!
For reliable and quick assistance, all bugs, crashes and other issues should be referred to the [GitHub Issues](https://github.com/FroggMaster/CreamInstaller/issues) page!
##### **HOWEVER**: Please read the [FAQ entry](https://github.com/HvTcCore/CreamInstaller#faq--common-issues) above and/or [template issue](https://github.com/HvTcCore/CreamInstaller/issues/new/choose) corresponding to your problem should one exist! Also, note that the [GitHub Issues](https://github.com/HvTcCore/CreamInstaller/issues) page is not your personal assistance hotline, rather it is for genuine bugs/crashes/issues with the program itself. If you post an issue which is off-topic or has already been explained within the FAQ, template issues, and/or within this text in general, I will just close it and you will be ignored.
##### **HOWEVER**: Please read the [FAQ entry](https://github.com/FroggMaster/CreamInstaller#faq--common-issues) above and/or [template issue](https://github.com/FroggMaster/CreamInstaller/issues/new/choose) corresponding to your problem should one exist! Also, note that the [GitHub Issues](https://github.com/FroggMaster/CreamInstaller/issues) page is not your personal assistance hotline, rather it is for genuine bugs/crashes/issues with the program itself. If you post an issue which is off-topic or has already been explained within the FAQ, template issues, and/or within this text in general, I will just close it and you will be ignored.
---
##### More Information:
* SteamCMD installation and appinfo cache can be found at **C:\ProgramData\CreamInstaller**.
* The program automatically and very quickly updates from [GitHub](https://github.com/HvTcCore/CreamInstaller) by choice of the user through a dialog on startup.
* The program source and other information can be found on [GitHub](https://github.com/HvTcCore/CreamInstaller).
* The program automatically and very quickly updates from [GitHub](https://github.com/FroggMaster/CreamInstaller) by choice of the user through a dialog on startup.
* The program source and other information can be found on [GitHub](https://github.com/FroggMaster/CreamInstaller).
* Credit to [Mattahan](https://www.mattahan.com) for the program icon.
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 36 KiB