diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 00000000..c94582d9 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,66 @@ +# Contributing to CreamInstaller + +Thank you for your interest in contributing! This project is open-source and community-driven. + +--- + +## ⚠️ Before You Start + +- This project is for **educational purposes only**. +- By contributing, you agree your contributions are also educational in nature. +- Please read the [README](../README.md) and understand the project's scope. + +--- + +## πŸ”§ Development Setup + +### Requirements +- Windows 10/11 (x64) +- [.NET 9 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/9.0) or later +- Visual Studio 2022+ or VS Code with C# extension + +### Build +```bash +git clone https://github.com/ubden-community/CreamApi-CreamInstaller.git +cd CreamApi-CreamInstaller +dotnet build CreamInstaller/CreamInstaller.csproj -c Debug +``` + +--- + +## πŸ“‹ How to Contribute + +### Reporting Bugs +Use the [Bug Report](.github/ISSUE_TEMPLATE/bug-report.md) template. + +### Suggesting Enhancements +Use the [Enhancement Request](.github/ISSUE_TEMPLATE/enhancement-request.md) template. + +### Pull Requests + +1. **Fork** the repository +2. **Create a branch**: `git checkout -b feature/my-feature` +3. **Make your changes** β€” keep them focused and minimal +4. **Format your code**: `dotnet format` +5. **Build and test** locally: `dotnet build -c Release` +6. **Commit** with a descriptive message +7. **Push** and open a Pull Request against `main` + +### PR Guidelines +- Keep PRs small and focused β€” one feature or fix per PR +- Describe what changed and why in the PR description +- Ensure the CI build passes before requesting review +- Do not include binary files or DLLs in the PR + +--- + +## πŸ’¬ Community + +- [GitHub Discussions](https://github.com/ubden/CreamApi-CreamInstaller/discussions) +- [ubden Forum](https://forum.ubden.com.tr/konu/creaminstaller-auto-dlc-unlocker-installer-config-gen.1602/) + +--- + +## πŸ“œ License + +By contributing, you agree that your contributions will be licensed under the [GPL v3](../LICENSE) license. diff --git a/.github/DISCUSSION_TEMPLATE/general.yml b/.github/DISCUSSION_TEMPLATE/general.yml new file mode 100644 index 00000000..47cb292c --- /dev/null +++ b/.github/DISCUSSION_TEMPLATE/general.yml @@ -0,0 +1,30 @@ +title: "[General] " +labels: ["general"] +body: + - type: markdown + attributes: + value: | + ## πŸ’¬ General Discussion + Welcome! Use this template for general questions, ideas, and conversations. + + > ⚠️ **No official support is provided.** Community members may help, but responses are not guaranteed. + > For quicker responses, also check the [ubden Forum](https://forum.ubden.com.tr/konu/creaminstaller-auto-dlc-unlocker-installer-config-gen.1602/). + + - type: textarea + id: topic + attributes: + label: Topic + description: What would you like to discuss? + placeholder: Describe your topic... + validations: + required: true + + - type: checkboxes + id: checklist + attributes: + label: Pre-discussion checklist + options: + - label: I have read the [README](https://github.com/ubden-community/CreamApi-CreamInstaller/blob/main/README.md) + required: true + - label: I have searched existing discussions for similar topics + required: true diff --git a/.github/DISCUSSION_TEMPLATE/q-and-a.yml b/.github/DISCUSSION_TEMPLATE/q-and-a.yml new file mode 100644 index 00000000..6c8ab58e --- /dev/null +++ b/.github/DISCUSSION_TEMPLATE/q-and-a.yml @@ -0,0 +1,46 @@ +title: "[Q&A] " +labels: ["question"] +body: + - type: markdown + attributes: + value: | + ## πŸ™ Question & Answer + Ask the community for help. Please be as specific as possible. + + > ⚠️ **No official support is provided.** This is community-only support. + > You can also ask at the [ubden Forum](https://forum.ubden.com.tr/konu/creaminstaller-auto-dlc-unlocker-installer-config-gen.1602/). + + - type: input + id: version + attributes: + label: CreamInstaller Version + placeholder: "e.g., v5.0" + validations: + required: true + + - type: textarea + id: question + attributes: + label: Your Question + description: What are you trying to do? What have you already tried? + placeholder: Describe your question in detail... + validations: + required: true + + - type: textarea + id: context + attributes: + label: Additional Context + description: OS version, game platform (Steam/Epic/Ubisoft), error messages, etc. + + - type: checkboxes + id: checklist + attributes: + label: Before asking + options: + - label: I have read the [README](https://github.com/ubden-community/CreamApi-CreamInstaller/blob/main/README.md) fully + required: true + - label: I have searched existing issues and discussions + required: true + - label: I understand this software is for educational purposes only + required: true diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index a0510aac..7e2baeb3 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -1,10 +1,44 @@ --- name: Bug Report about: Report a program exception or general bug. -title: '' +title: '[Bug] ' labels: Bug -assignees: pointfeev +assignees: '' --- -###### Describe the bug and/or provide an image of the exception dialog box: +> ⚠️ **No official support is provided.** For quicker community help, visit: +> - [GitHub Discussions](https://github.com/ubden/CreamApi-CreamInstaller/discussions) +> - [ubden Forum](https://forum.ubden.com.tr/konu/creaminstaller-auto-dlc-unlocker-installer-config-gen.1602/) + +--- + +#### CreamInstaller Version + + +#### Windows Version + + +#### Game Platform + + +#### Describe the bug + + +#### Steps to reproduce +1. +2. +3. + +#### Expected behavior + + +#### Exception dialog / screenshots + + +#### Additional context + + +--- + +> πŸ›‘οΈ **Antivirus false positive?** See the [false-positives template](.github/ISSUE_TEMPLATE/false-positives.md). diff --git a/.github/ISSUE_TEMPLATE/enhancement-request.md b/.github/ISSUE_TEMPLATE/enhancement-request.md index dbaba83a..addd6e69 100644 --- a/.github/ISSUE_TEMPLATE/enhancement-request.md +++ b/.github/ISSUE_TEMPLATE/enhancement-request.md @@ -1,10 +1,30 @@ --- name: Enhancement Request -about: Request a new feature or to enhance an existing feature. -title: '' +about: Request a new feature or improvement to an existing feature. +title: '[Enhancement] ' labels: Enhancement -assignees: pointfeev +assignees: '' + +---- + +> πŸ’‘ **Have an idea?** You can also discuss it in the community first: +> - [GitHub Discussions](https://github.com/ubden/CreamApi-CreamInstaller/discussions) +> - [ubden Forum](https://forum.ubden.com.tr/konu/creaminstaller-auto-dlc-unlocker-installer-config-gen.1602/) + +--- + +#### Describe the enhancement + + +#### Use case + + +#### Proposed implementation (optional) + + +#### Additional context + --- -###### Describe the new feature and its implementation OR how the existing feature could be improved: +> ⚠️ This project is maintained by the community. Enhancement implementation is not guaranteed. diff --git a/.github/ISSUE_TEMPLATE/false-positives.md b/.github/ISSUE_TEMPLATE/false-positives.md index aca9d88e..3cdbbd68 100644 --- a/.github/ISSUE_TEMPLATE/false-positives.md +++ b/.github/ISSUE_TEMPLATE/false-positives.md @@ -1,10 +1,35 @@ --- -name: Mamson.A!ac, Phonzy.A!ml, Wacatac.H!ml, Malgent!MSR -about: These are false positives; see this template's content. -title: I promise not to post another issue about false positives. -labels: '' +name: Antivirus False Positive Report +about: Report an antivirus false positive detection (Mamson.A!ac, Phonzy.A!ml, Wacatac.H!ml, Malgent!MSR, etc.) +title: '[False Positive] ' +labels: 'false-positive' assignees: '' --- -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, and/or potentially others has already been posted and explained over 10 times now... please do not post it again; instead, refer to the explanations within [issue #40](https://github.com/pointfeev/CreamInstaller/issues/40) and its linked issues. +> πŸ›‘οΈ **Antivirus false positives are expected and well-known for this type of software.** + +This project interacts with DLL files, which causes antivirus programs to flag it as suspicious. This is a **false positive**. The entire source code is open and available for review. + +**Before posting, please note:** +- The entire project is **open source** β€” no obfuscated or encrypted code exists. +- Detections such as `Mamson.A!ac`, `Phonzy.A!ml`, `Wacatac.H!ml`, `Malgent!MSR` are common false positives for DLL-interacting tools. +- This is documented in [Springer – International Journal of Information Security (2024)](https://link.springer.com/article/10.1007/s10207-024-00836-w). + +**Community discussion:** +- [GitHub Discussions](https://github.com/ubden/CreamApi-CreamInstaller/discussions) +- [ubden Forum](https://forum.ubden.com.tr/konu/creaminstaller-auto-dlc-unlocker-installer-config-gen.1602/) + +--- + +#### Antivirus software name and version +Windows Defender and other antivirus programs + +#### Detection name(s) +Mamson.A!ac, Trojan:Win32/ and other + +#### File(s) flagged +Please review code line : "// ANTIVIRUS FALSE POSITIVE WARNING:" and folder /Resourcues + +#### VirusTotal link (optional) +Check comments : https://github.com/ubden/CreamApi-CreamInstaller/discussions diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 00000000..14267dd0 --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,42 @@ +# Security Policy + +## ⚠️ Antivirus / False Positive Notice + +This software interacts with DLL files and may be flagged by antivirus programs. This is a **known false positive**. + +- The entire project is **open source** β€” you can review every line of code. +- No encrypted, obfuscated, or hidden code is included. +- For reference: [Springer – International Journal of Information Security (2024)](https://link.springer.com/article/10.1007/s10207-024-00836-w) + +--- + +## πŸ”’ Reporting a Security Vulnerability + +If you discover a security vulnerability in this project, **do not open a public issue**. + +Instead, please report it confidentially to: + +πŸ“§ **[abuse@ubden.com](mailto:abuse@ubden.com)** + +Please include: +- A description of the vulnerability +- Steps to reproduce +- Potential impact + +We will respond as soon as possible. This project does not have a formal bug bounty program. + +--- + +## Supported Versions + +| Version | Supported | +|---------|-----------| +| 5.x | βœ… Yes | +| 4.x | ❌ No | +| < 4.0 | ❌ No | + +--- + +## Disclaimer + +This software is provided for **educational purposes only**. All responsibility for its use lies with the user. See the full [disclaimer in the README](../README.md). diff --git a/.github/SUPPORT.md b/.github/SUPPORT.md new file mode 100644 index 00000000..7d333800 --- /dev/null +++ b/.github/SUPPORT.md @@ -0,0 +1,36 @@ +# Support + +## ⚠️ No Official Support + +This project does **not** provide official support. The software is provided **"as is"** for educational purposes only, with no warranty of any kind. + +--- + +## πŸ™Œ Community Support + +For questions, troubleshooting, and discussions, please use the community channels below: + +| Channel | Link | +|---------|------| +| **GitHub Discussions** | [github.com/ubden/CreamApi-CreamInstaller/discussions](https://github.com/ubden/CreamApi-CreamInstaller/discussions) | +| **ubden Community Forum** | [forum.ubden.com.tr](https://forum.ubden.com.tr/konu/creaminstaller-auto-dlc-unlocker-installer-config-gen.1602/) | + +> Community members and contributors may be able to help, but responses are not guaranteed. + +--- + +## πŸ› Bug Reports + +If you believe you've found a bug, please open an [Issue](https://github.com/ubden-community/CreamApi-CreamInstaller/issues) using the appropriate template. + +Before reporting, please: +- Check if the issue already exists +- Search the community channels above +- Read the [README](../README.md) carefully + +--- + +## 🚨 Report Abuse + +To report abuse or misuse of this software: +πŸ“§ **[abuse@ubden.com](mailto:abuse@ubden.com)** diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..ec540243 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,21 @@ +version: 2 +updates: + - package-ecosystem: "nuget" + directory: "/CreamInstaller" + schedule: + interval: "weekly" + day: "monday" + open-pull-requests-limit: 5 + labels: + - "dependencies" + - "nuget" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + open-pull-requests-limit: 5 + labels: + - "dependencies" + - "github-actions" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..7eb91f2f --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,40 @@ +name: CI Build + +on: + push: + branches: [ main, master, develop ] + pull_request: + branches: [ main, master ] + +jobs: + build: + name: Build & Verify + runs-on: windows-latest + env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '9.x' + + - name: Restore dependencies + run: dotnet restore CreamInstaller/CreamInstaller.csproj + + - name: Build (Release) + run: dotnet build CreamInstaller/CreamInstaller.csproj -c Release --no-restore + + - name: Check code formatting + run: dotnet format CreamInstaller/CreamInstaller.csproj --verify-no-changes --severity info + continue-on-error: true + + - name: Upload build artifact + uses: actions/upload-artifact@v4 + with: + name: CreamInstaller-build + path: CreamInstaller/bin/Release/net9.0-windows/ + retention-days: 7 diff --git a/.github/workflows/delete_releases.yml b/.github/workflows/delete_releases.yml deleted file mode 100644 index 456302b5..00000000 --- a/.github/workflows/delete_releases.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Delete Releases - -on: - workflow_dispatch: - inputs: - tagPattern: - description: 'Tag pattern' - required: true - default: 'v1' - -jobs: - delete: - name: Delete - runs-on: windows-latest - if: ${{ github.event.repository.owner.id }} == ${{ github.event.sender.id }} - - permissions: write-all - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Delete - uses: dev-drprasad/delete-older-releases@v0.2.1 - with: - keep_latest: 0 - delete_tags: true - delete_tag_pattern: ${{ github.event.inputs.tagPattern }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/jekyll-gh-pages.yml b/.github/workflows/jekyll-gh-pages.yml deleted file mode 100644 index e31d81c5..00000000 --- a/.github/workflows/jekyll-gh-pages.yml +++ /dev/null @@ -1,51 +0,0 @@ -# Sample workflow for building and deploying a Jekyll site to GitHub Pages -name: Deploy Jekyll with GitHub Pages dependencies preinstalled - -on: - # Runs on pushes targeting the default branch - push: - branches: ["main"] - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages -permissions: - contents: read - pages: write - id-token: write - -# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. -# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. -concurrency: - group: "pages" - cancel-in-progress: false - -jobs: - # Build job - build: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Setup Pages - uses: actions/configure-pages@v5 - - name: Build with Jekyll - uses: actions/jekyll-build-pages@v1 - with: - source: ./ - destination: ./_site - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - - # Deployment job - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: build - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..91316d02 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,68 @@ +name: Release + +on: + push: + tags: + - 'v*' + +permissions: + contents: write + +jobs: + release: + name: Build & Publish Release + runs-on: windows-latest + env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '9.x' + + - name: Restore dependencies + run: dotnet restore CreamInstaller/CreamInstaller.csproj + + - name: Publish (self-contained single file) + run: | + dotnet publish CreamInstaller/CreamInstaller.csproj ` + -c Release ` + -r win-x64 ` + --self-contained false ` + -p:PublishSingleFile=true ` + -p:IncludeAllContentForSelfExtract=true ` + -o publish/ + + - name: Create release archive + run: | + Compress-Archive -Path publish/CreamInstaller.exe -DestinationPath CreamInstaller.zip + + - name: Extract version from tag + id: version + run: | + $tag = "${{ github.ref_name }}" + echo "tag=$tag" >> $env:GITHUB_OUTPUT + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + name: "CreamInstaller ${{ steps.version.outputs.tag }}" + files: CreamInstaller.zip + generate_release_notes: true + body: | + ## CreamInstaller ${{ steps.version.outputs.tag }} + + ### Download + Download `CreamInstaller.zip`, extract and run `CreamInstaller.exe`. + + ### Requirements + - Windows 10/11 (x64) + - [.NET 9 Desktop Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/9.0) + + --- + > ⚠️ This software is for **educational purposes only**. No official support is provided. + > For community support, visit [GitHub Discussions](https://github.com/ubden/CreamApi-CreamInstaller/discussions) or the [ubden Forum](https://forum.ubden.com.tr/konu/creaminstaller-auto-dlc-unlocker-installer-config-gen.1602/). diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index cbb0f433..00000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Test - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - test: - name: Test - runs-on: windows-latest - permissions: - actions: read - contents: read - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Setup - uses: actions/setup-dotnet@v3 - with: - dotnet-version: 7.x.x - - - name: Restore - run: dotnet restore - - - name: Build - run: dotnet build --no-restore --configuration Release /p:UseSharedCompilation=false - - - name: Test - run: dotnet test --no-build --verbosity normal --configuration Release diff --git a/.gitignore b/.gitignore index e02c3ca9..32730aea 100644 --- a/.gitignore +++ b/.gitignore @@ -335,3 +335,5 @@ ASALocalRun/ .localhistory/ *.exe *.zip +*.dll +*.dll.* \ No newline at end of file diff --git a/CreamInstaller/Components/CustomForm.cs b/CreamInstaller/Components/CustomForm.cs index 2dedb2e1..a6ad1f67 100644 --- a/CreamInstaller/Components/CustomForm.cs +++ b/CreamInstaller/Components/CustomForm.cs @@ -1,4 +1,5 @@ ο»Ώusing System; +using System.Diagnostics; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; @@ -12,80 +13,138 @@ internal class CustomForm : Form { internal const short SWP_NOACTIVATE = 0x0010; internal const short SWP_SHOWWINDOW = 0x0040; - internal const short SWP_NOMOVE = 0x0002; - internal const short SWP_NOSIZE = 0x0001; + internal const short SWP_NOMOVE = 0x0002; + internal const short SWP_NOSIZE = 0x0001; internal static readonly nint HWND_NOTOPMOST = new(-2); - internal static readonly nint HWND_TOPMOST = new(-1); + internal static readonly nint HWND_TOPMOST = new(-1); + + // ── DWM: dark title bar ─────────────────────────────────────────────────── + // ANTIVIRUS FALSE POSITIVE WARNING: + // DwmSetWindowAttribute is a Windows API used only to enable the dark mode title + // bar (DWMWA_USE_IMMERSIVE_DARK_MODE = 20) on this application's own window. + // This is standard Windows 10/11 dark-mode UI integration. + [DllImport("dwmapi.dll", SetLastError = false)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + private static extern int DwmSetWindowAttribute(nint hwnd, int attr, ref int attrValue, int attrSize); internal CustomForm() { - Icon = Properties.Resources.Icon; + Icon = Properties.Resources.Icon; KeyPreview = true; - KeyPress += OnKeyPress; + KeyPress += OnKeyPress; ResizeRedraw = true; - HelpButton = true; + HelpButton = true; HelpButtonClicked += OnHelpButtonClicked; } internal CustomForm(IWin32Window owner) : this() { - if (owner is not Form form) - return; + if (owner is not Form form) return; Owner = form; InheritLocation(form); SizeChanged += (_, _) => InheritLocation(form); form.Activated += OnActivation; - FormClosing += (_, _) => form.Activated -= OnActivation; + FormClosing += (_, _) => form.Activated -= OnActivation; TopLevel = true; } - protected override CreateParams CreateParams // Double buffering for all controls + // Double-buffering for all controls + protected override CreateParams CreateParams { get { - CreateParams handleParam = base.CreateParams; - handleParam.ExStyle |= 0x02; // WS_EX_COMPOSITED - return handleParam; + CreateParams cp = base.CreateParams; + cp.ExStyle |= 0x02; // WS_EX_COMPOSITED + return cp; } } + protected override void OnHandleCreated(EventArgs e) + { + base.OnHandleCreated(e); + + // Enable dark title bar on Windows 10 20H1+ and Windows 11 + int dark = 1; + _ = DwmSetWindowAttribute(Handle, 20 /* DWMWA_USE_IMMERSIVE_DARK_MODE */, ref dark, sizeof(int)); + + // Apply dark theme to this form and all its controls + BackColor = ThemeManager.Background; + ForeColor = ThemeManager.TextPrimary; + ThemeManager.Apply(this); + } + + // ── Help button β†’ comprehensive Help & Disclaimer dialog ───────────────── private void OnHelpButtonClicked(object sender, EventArgs args) { using DialogForm helpDialog = new(this); helpDialog.HelpButton = false; - string acidicoala = "https://github.com/acidicoala"; - string repository = $"https://github.com/{Program.RepositoryOwner}/{Program.RepositoryName}"; + + string repo = $"https://github.com/{Program.RepositoryOwner}/{Program.RepositoryName}"; + string acidicoala = "https://github.com/acidicoala"; + string discussions = Program.CommunityDiscussions; + string forum = Program.CommunityForum; + string abuse = Program.AbuseEmail; + string donate = Program.DonateUrl; + _ = helpDialog.Show(SystemIcons.Information, - "Automatically finds all installed Steam, Epic and Ubisoft games with their respective DLC-related DLL locations on the user's computer,\n" - + "parses SteamCMD, Steam Store and Epic Games Store for user-selected games' DLCs, then provides a very simple graphical interface\n" - + "utilizing the gathered information for the maintenance of DLC unlockers.\n" + "\n" - + $"The program utilizes the latest versions of [Koaloader]({acidicoala}/Koaloader), [SmokeAPI]({acidicoala}/SmokeAPI), [ScreamAPI]({acidicoala}/ScreamAPI), [Uplay R1 Unlocker]({acidicoala}/UplayR1Unlocker) and [Uplay R2 Unlocker]({acidicoala}/UplayR2Unlocker), all by\n" - + $"the wonderful [acidicoala]({acidicoala}), and all downloaded and embedded into the program itself; no further downloads necessary on your part!\n" - + "\n" + "NOTE: This program does not automatically download nor install actual DLC files for you. As the title of the program says, it's\n" - + "only a DLC Unlocker installer. Should the game you wish to unlock DLC for not already come with the DLCs installed (very many\n" - + "do not), you have to find, download, and install those yourself. Preferably, you should be referring to the proper cs.rin.ru post for\n" - + "the game(s) you're tinkering with; you'll usually find any answer to your problems there.\n" + "\n" + "USAGE:\n" - + " 1. Choose which programs and/or games the program should scan for DLC.\n" - + " The program automatically gathers all installed games from Steam, Epic and Ubisoft directories.\n" - + " 2. Wait for the program to download and install SteamCMD (if you chose a Steam game).\n" - + " 3. Wait for the program to gather and cache the chosen games' information && DLCs.\n" - + " May take some time on the first run; depends on how many DLCs the games you chose have.\n" - + " 4. CAREFULLY select which games' DLCs you wish to unlock.\n" - + " Obviously none of the DLC unlockers are tested for every single game!\n" - + " 5. Choose whether or not to install with Koaloader, and if so then also pick the proxy DLL to use.\n" - + " If the default \'version.dll\' doesn't work, then see [here](https://cs.rin.ru/forum/viewtopic.php?p=2552172#p2552172) to find one that does.\n" - + " 6. Click the \"Generate and Install\" button.\n" + " 7. Click the \"OK\" button to close the program.\n" - + " 8. If any of the DLC unlockers cause problems with any of the games you installed them on, simply go back\n" - + " to step 5 and select what games you wish you revert changes to, and instead click the \"Uninstall\" button this time.\n" + "\n" - + $"For reliable and quick assistance, all bugs, crashes and other issues should be referred to the [GitHub Issues]({repository}/issues) page!\n" - + "\n" + "SteamCMD installation and appinfo cache can be found at [C:\\ProgramData\\CreamInstaller]().\n" - + $"The program automatically and very quickly updates from [GitHub]({repository}) using [Onova](https://github.com/Tyrrrz/Onova). (updates can be ignored)\n" - + $"The program source and other information can be found on [GitHub]({repository})."); + $"CreamInstaller v{Program.Version} β€” Help & Disclaimer\n" + + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n" + + "\n" + + "⚠️ DISCLAIMER\n" + + "This software is an open-source project developed for the community\n" + + "and is NOT affiliated with any organization or institution.\n" + + "It is shared purely for EDUCATIONAL PURPOSES and software development\n" + + "testing. This software is NOT intended for production use.\n" + + "We strongly recommend purchasing and using professionally licensed\n" + + "software for your needs.\n" + + "\n" + + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n" + + "\n" + + "πŸ›‘οΈ ANTIVIRUS / FALSE POSITIVE WARNING\n" + + "All software that modifies or interacts with DLL files may be flagged\n" + + "by antivirus programs. The ENTIRE PROJECT IS OPEN SOURCE β€” no\n" + + "encrypted or obfuscated code is included. If flagged, it is a false\n" + + "positive. This software is FOR EXPERIENCED USERS ONLY.\n" + + "\n" + + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n" + + "\n" + + "βš–οΈ LEGAL RESPONSIBILITY\n" + + "By using this software, you agree that ALL RESPONSIBILITY LIES WITH\n" + + "YOU, THE USER. The platform and its contributors provide this software\n" + + "\"as is\", without any warranty of any kind, express or implied.\n" + + "USE AT YOUR OWN RISK.\n" + + "\n" + + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n" + + "\n" + + "πŸ“– ABOUT\n" + + "Automatically finds all installed Steam, Epic and Ubisoft games with\n" + + "their DLC-related DLL locations, parses SteamCMD / Steam Store /\n" + + "Epic Games Store for DLCs, then provides a graphical interface for\n" + + "maintenance of DLC unlockers.\n" + + "\n" + + $"Utilizes [Koaloader]({acidicoala}/Koaloader), [SmokeAPI]({acidicoala}/SmokeAPI),\n" + + $"[ScreamAPI]({acidicoala}/ScreamAPI), [Uplay R1 Unlocker]({acidicoala}/UplayR1Unlocker)\n" + + $"and [Uplay R2 Unlocker]({acidicoala}/UplayR2Unlocker) by [acidicoala]({acidicoala}).\n" + + "\n" + + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n" + + "\n" + + "πŸ™Œ COMMUNITY SUPPORT (NO OFFICIAL SUPPORT IS PROVIDED)\n" + + $"β€’ GitHub Discussions β†’ [{discussions}]({discussions})\n" + + $"β€’ ubden Forum β†’ [{forum}]({forum})\n" + + "\n" + + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n" + + "\n" + + $"🚨 Report abuse / misuse: [{abuse}](mailto:{abuse})\n" + + $"πŸ’™ Support the community: [{donate}]({donate})\n" + + $"πŸ“ Source code: [{repo}]({repo})\n"); } private void OnActivation(object sender, EventArgs args) => Activate(); + // ANTIVIRUS FALSE POSITIVE WARNING: + // SetWindowPos is imported from user32.dll to bring this application's own window + // to the front without stealing focus (SWP_NOACTIVATE). Standard WinForms technique. [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] private static extern void SetWindowPos(nint hWnd, nint hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags); @@ -93,34 +152,37 @@ private void OnHelpButtonClicked(object sender, EventArgs args) internal void BringToFrontWithoutActivation() { bool topMost = TopMost; - SetWindowPos(Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE); + SetWindowPos(Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE); if (!topMost) SetWindowPos(Handle, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE); } internal void InheritLocation(Form fromForm) { - if (fromForm is null) - return; - int X = fromForm.Location.X + fromForm.Size.Width / 2 - Size.Width / 2; + if (fromForm is null) return; + int X = fromForm.Location.X + fromForm.Size.Width / 2 - Size.Width / 2; int Y = fromForm.Location.Y + fromForm.Size.Height / 2 - Size.Height / 2; Location = new(X, Y); } + // ANTIVIRUS FALSE POSITIVE WARNING: + // Shift+S captures only this application's own window area via CopyFromScreen and + // copies it to the clipboard as a bitmap β€” a built-in debug screenshot helper. + // It is NOT a keylogger and reads nothing outside this window. private void OnKeyPress(object s, KeyPressEventArgs e) { - if (e.KeyChar != 'S') - return; // Shift + S + if (e.KeyChar != 'S') return; // Shift + S UpdateBounds(); Rectangle bounds = Bounds; using Bitmap bitmap = new(Size.Width - 14, Size.Height - 7); using Graphics graphics = Graphics.FromImage(bitmap); graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; - using EncoderParameters encoding = new(1); + using EncoderParameters encoding = new(1); using EncoderParameter encoderParam = new(Encoder.Quality, 100L); encoding.Param[0] = encoderParam; - graphics.CopyFromScreen(new(bounds.Left + 7, bounds.Top), Point.Empty, new(Size.Width - 14, Size.Height - 7)); + graphics.CopyFromScreen(new(bounds.Left + 7, bounds.Top), Point.Empty, + new(Size.Width - 14, Size.Height - 7)); Clipboard.SetImage(bitmap); e.Handled = true; } -} \ No newline at end of file +} diff --git a/CreamInstaller/Components/CustomTreeView.cs b/CreamInstaller/Components/CustomTreeView.cs index 5f0e95e4..eaed8844 100644 --- a/CreamInstaller/Components/CustomTreeView.cs +++ b/CreamInstaller/Components/CustomTreeView.cs @@ -189,7 +189,7 @@ protected override void OnMouseDown(MouseEventArgs e) } if (e.Button is not MouseButtons.Left) return; - if (comboBoxBounds.Any() && selectForm is not null) + if (comboBoxBounds.Count > 0 && selectForm is not null) foreach (KeyValuePair pair in comboBoxBounds) if (!ProgramSelection.All.Contains(pair.Key)) _ = comboBoxBounds.Remove(pair.Key); diff --git a/CreamInstaller/Components/DarkButton.cs b/CreamInstaller/Components/DarkButton.cs new file mode 100644 index 00000000..bdca3e6d --- /dev/null +++ b/CreamInstaller/Components/DarkButton.cs @@ -0,0 +1,88 @@ +using System; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +namespace CreamInstaller.Components; + +/// +/// A custom owner-drawn button with dark theme, rounded corners, +/// and smooth hover/pressed state transitions. +/// +internal class DarkButton : Button +{ + private bool _hovered; + private bool _pressed; + + /// When true, uses Accent fill. When false, uses Surface (outline style). + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] + internal bool IsAccent { get; set; } = true; + + internal DarkButton() + { + SetStyle(ControlStyles.UserPaint + | ControlStyles.AllPaintingInWmPaint + | ControlStyles.OptimizedDoubleBuffer + | ControlStyles.ResizeRedraw, true); + FlatStyle = FlatStyle.Flat; + FlatAppearance.BorderSize = 0; + BackColor = ThemeManager.Background; + ForeColor = ThemeManager.TextPrimary; + Font = new Font("Segoe UI", 9f, FontStyle.Regular, GraphicsUnit.Point); + Cursor = Cursors.Hand; + UseCompatibleTextRendering = false; + } + + protected override void OnMouseEnter(EventArgs e) + { + _hovered = true; + Invalidate(); + base.OnMouseEnter(e); + } + + protected override void OnMouseLeave(EventArgs e) + { + _hovered = false; + Invalidate(); + base.OnMouseLeave(e); + } + + protected override void OnMouseDown(MouseEventArgs e) + { + if (e.Button == MouseButtons.Left) { _pressed = true; Invalidate(); } + base.OnMouseDown(e); + } + + protected override void OnMouseUp(MouseEventArgs e) + { + _pressed = false; + Invalidate(); + base.OnMouseUp(e); + } + + protected override void OnEnabledChanged(EventArgs e) + { + Invalidate(); + base.OnEnabledChanged(e); + } + + protected override void OnPaint(PaintEventArgs e) + { + Graphics g = e.Graphics; + g.SmoothingMode = SmoothingMode.AntiAlias; + g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; + + Rectangle bounds = new(0, 0, Width - 1, Height - 1); + ThemeManager.DrawButton(g, bounds, Text, Font, Enabled, _hovered, _pressed, IsAccent); + + // Focus rectangle + if (Focused && ShowFocusCues) + { + Rectangle focusRect = new(2, 2, Width - 5, Height - 5); + using Pen focusPen = new(Color.FromArgb(100, ThemeManager.AccentHover), 1f) { DashStyle = DashStyle.Dot }; + using GraphicsPath fp = ThemeManager.RoundedRect(focusRect, ThemeManager.CornerRadius - 1); + g.DrawPath(focusPen, fp); + } + } +} diff --git a/CreamInstaller/Components/DarkProgressBar.cs b/CreamInstaller/Components/DarkProgressBar.cs new file mode 100644 index 00000000..f1cf6c6c --- /dev/null +++ b/CreamInstaller/Components/DarkProgressBar.cs @@ -0,0 +1,112 @@ +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +namespace CreamInstaller.Components; + +/// +/// Owner-drawn progress bar with a gradient fill, rounded corners, +/// and an optional animated shimmer effect. +/// +internal sealed class DarkProgressBar : ProgressBar +{ + private readonly Timer _shimmerTimer; + private int _shimmerOffset; + + internal DarkProgressBar() + { + SetStyle(ControlStyles.UserPaint + | ControlStyles.AllPaintingInWmPaint + | ControlStyles.OptimizedDoubleBuffer, true); + + _shimmerTimer = new Timer { Interval = 30 }; + _shimmerTimer.Tick += (_, _) => + { + _shimmerOffset = (_shimmerOffset + 4) % Math.Max(1, Width); + Invalidate(); + }; + } + + // Start shimmer when at 0 (indeterminate-like) or actively progressing + private void UpdateShimmer() + { + bool active = Value > 0 && Value < Maximum; + if (active && !_shimmerTimer.Enabled) _shimmerTimer.Start(); + if (!active && _shimmerTimer.Enabled) _shimmerTimer.Stop(); + } + + protected override void WndProc(ref Message m) + { + base.WndProc(ref m); + // PBM_SETPOS (0x402) or PBM_DELTAPOS (0x403) sent when Value changes + if (m.Msg is 0x402 or 0x403 or 0x404 or 0x406) + { + UpdateShimmer(); + Invalidate(); + } + } + + protected override void OnPaint(PaintEventArgs e) + { + Graphics g = e.Graphics; + g.SmoothingMode = SmoothingMode.AntiAlias; + + Rectangle bg = new(0, 0, Width - 1, Height - 1); + using GraphicsPath bgPath = ThemeManager.RoundedRect(bg, ThemeManager.CornerRadius); + using SolidBrush bgBrush = new(ThemeManager.Surface); + g.FillPath(bgBrush, bgPath); + + using Pen borderPen = new(ThemeManager.Border, 1f); + g.DrawPath(borderPen, bgPath); + + if (Maximum <= 0) return; + + int fillWidth = (int)((double)Value / Maximum * (Width - 2)); + if (fillWidth <= 0) return; + + Rectangle fillRect = new(1, 1, fillWidth, Height - 3); + using GraphicsPath fillPath = ThemeManager.RoundedRect(fillRect, ThemeManager.CornerRadius - 1); + + using LinearGradientBrush fillBrush = new( + fillRect.IsEmpty ? new Rectangle(0, 0, 1, 1) : fillRect, + ThemeManager.Accent, + ThemeManager.AccentHover, + LinearGradientMode.Horizontal); + g.FillPath(fillBrush, fillPath); + + // Shimmer overlay + if (_shimmerTimer.Enabled && fillWidth > 20) + { + int sw = Math.Min(60, fillWidth); + int sx = _shimmerOffset % (fillWidth + sw) - sw; + Rectangle shimmerRect = new(1 + sx, 1, sw, Height - 3); + shimmerRect.Intersect(fillRect); + if (shimmerRect.Width > 0) + { + using LinearGradientBrush shimmer = new( + new Rectangle(1 + sx, 1, sw + 1, Height - 3), + Color.FromArgb(0, Color.White), + Color.FromArgb(50, Color.White), + LinearGradientMode.Horizontal); + shimmer.SetBlendTriangularShape(0.5f); + g.FillRectangle(shimmer, shimmerRect); + } + } + + // Percentage text + if (Value > 0 && Height >= 16) + { + string pct = $"{(int)((double)Value / Maximum * 100)}%"; + using Font f = new("Segoe UI", 7.5f, FontStyle.Bold, GraphicsUnit.Point); + TextRenderer.DrawText(g, pct, f, bg, ThemeManager.TextPrimary, + TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter); + } + } + + protected override void Dispose(bool disposing) + { + if (disposing) _shimmerTimer.Dispose(); + base.Dispose(disposing); + } +} diff --git a/CreamInstaller/Components/ThemeManager.cs b/CreamInstaller/Components/ThemeManager.cs new file mode 100644 index 00000000..0c992c8b --- /dev/null +++ b/CreamInstaller/Components/ThemeManager.cs @@ -0,0 +1,186 @@ +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +namespace CreamInstaller.Components; + +internal static class ThemeManager +{ + // ── Color Palette ────────────────────────────────────────────────────────── + internal static readonly Color Background = Color.FromArgb(18, 18, 28); // #12121C + internal static readonly Color Surface = Color.FromArgb(26, 26, 40); // #1A1A28 + internal static readonly Color SurfaceHover = Color.FromArgb(34, 34, 54); // #222236 + internal static readonly Color Border = Color.FromArgb(45, 45, 70); // #2D2D46 + internal static readonly Color Accent = Color.FromArgb(99, 102, 241); // #6366F1 indigo + internal static readonly Color AccentHover = Color.FromArgb(129, 140, 248); // #818CF8 + internal static readonly Color AccentPressed = Color.FromArgb(79, 70, 229); // #4F46E5 + internal static readonly Color TextPrimary = Color.FromArgb(240, 240, 255); // #F0F0FF + internal static readonly Color TextSecondary = Color.FromArgb(148, 148, 185); // #9494B9 + internal static readonly Color TextDisabled = Color.FromArgb(80, 80, 110); // #50506E + internal static readonly Color Success = Color.FromArgb(34, 197, 94); // #22C55E + internal static readonly Color Warning = Color.FromArgb(251, 191, 36); // #FBBF24 + internal static readonly Color Error = Color.FromArgb(239, 68, 68); // #EF4444 + internal static readonly Color LogBg = Color.FromArgb(12, 12, 20); // #0C0C14 + + // ── Corner Radius ────────────────────────────────────────────────────────── + internal const int CornerRadius = 6; + + // ── Apply theme recursively to all controls ──────────────────────────────── + internal static void Apply(Control root) + { + ApplySingle(root); + foreach (Control child in root.Controls) + Apply(child); + } + + private static void ApplySingle(Control c) + { + c.BackColor = c switch + { + GroupBox => Background, + FlowLayoutPanel => Background, // must be before Panel + Panel => Background, + RichTextBox => LogBg, + TextBox => Surface, + TreeView => Background, + StatusStrip => Surface, + _ => Background + }; + + c.ForeColor = (!c.Enabled) ? TextDisabled : TextPrimary; + + switch (c) + { + case ProgressBar pb: + // DarkProgressBar handles its own painting; skip default coloring + break; + + case TreeView tv: + tv.BackColor = Background; + tv.ForeColor = TextPrimary; + tv.BorderStyle = BorderStyle.None; + break; + + case TextBox tb: + tb.BackColor = Surface; + tb.ForeColor = TextPrimary; + tb.BorderStyle = BorderStyle.FixedSingle; + break; + + case RichTextBox rtb: + rtb.BackColor = LogBg; + rtb.ForeColor = TextPrimary; + rtb.BorderStyle = BorderStyle.None; + break; + + case GroupBox gb: + gb.ForeColor = TextSecondary; + gb.Paint += PaintGroupBox; + break; + + case StatusStrip ss: + ss.BackColor = Surface; + ss.ForeColor = TextSecondary; + ss.RenderMode = ToolStripRenderMode.ManagerRenderMode; + ToolStripManager.Renderer = new DarkToolStripRenderer(); + break; + + case CheckBox cb: + cb.FlatStyle = FlatStyle.Flat; + cb.FlatAppearance.BorderColor = Border; + cb.FlatAppearance.CheckedBackColor = Accent; + cb.FlatAppearance.MouseOverBackColor = SurfaceHover; + break; + } + } + + // ── GroupBox custom paint (dark border + label) ──────────────────────────── + private static void PaintGroupBox(object sender, PaintEventArgs e) + { + if (sender is not GroupBox gb) return; + Graphics g = e.Graphics; + g.Clear(Background); + + using Pen borderPen = new(Border, 1); + int offset = string.IsNullOrEmpty(gb.Text) ? 0 : 8; + Rectangle rect = new(0, offset, gb.Width - 1, gb.Height - 1 - offset); + g.DrawRectangle(borderPen, rect); + + if (!string.IsNullOrEmpty(gb.Text)) + { + SizeF textSize = g.MeasureString(gb.Text, gb.Font); + using SolidBrush bgBrush = new(Background); + using SolidBrush textBrush = new(TextSecondary); + g.FillRectangle(bgBrush, 8, 0, textSize.Width + 4, textSize.Height); + g.DrawString(gb.Text, gb.Font, textBrush, 10, 0); + } + } + + // ── Rounded rectangle helper ─────────────────────────────────────────────── + internal static GraphicsPath RoundedRect(Rectangle bounds, int radius) + { + int d = radius * 2; + GraphicsPath path = new(); + path.AddArc(bounds.X, bounds.Y, d, d, 180, 90); + path.AddArc(bounds.Right - d, bounds.Y, d, d, 270, 90); + path.AddArc(bounds.Right - d, bounds.Bottom - d, d, d, 0, 90); + path.AddArc(bounds.X, bounds.Bottom - d, d, d, 90, 90); + path.CloseFigure(); + return path; + } + + // ── Draw accent-filled button ────────────────────────────────────────────── + internal static void DrawButton(Graphics g, Rectangle bounds, string text, Font font, + bool enabled, bool hovered, bool pressed, bool isAccent = true) + { + g.SmoothingMode = SmoothingMode.AntiAlias; + g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; + + Color fill = !enabled ? Color.FromArgb(40, 40, 60) : + pressed ? AccentPressed : + hovered ? AccentHover : + isAccent ? Accent : Surface; + + Color border = !enabled ? Border : + hovered || pressed ? AccentHover : Accent; + + Color textColor = !enabled ? TextDisabled : TextPrimary; + + using GraphicsPath path = RoundedRect(bounds, CornerRadius); + using SolidBrush fillBrush = new(fill); + g.FillPath(fillBrush, path); + + using Pen pen = new(border, 1f); + g.DrawPath(pen, path); + + TextRenderer.DrawText(g, text, font, bounds, textColor, + TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.SingleLine); + } + + // ── Dark ToolStrip renderer (for StatusStrip) ────────────────────────────── + private sealed class DarkToolStripRenderer : ToolStripProfessionalRenderer + { + public DarkToolStripRenderer() : base(new DarkColorTable()) { } + + protected override void OnRenderToolStripBackground(ToolStripRenderEventArgs e) + => e.Graphics.Clear(Surface); + + protected override void OnRenderToolStripBorder(ToolStripRenderEventArgs e) { } + + protected override void OnRenderSeparator(ToolStripSeparatorRenderEventArgs e) + { + using Pen pen = new(Border); + e.Graphics.DrawLine(pen, 0, 0, 0, e.Item.Height); + } + } + + private sealed class DarkColorTable : ProfessionalColorTable + { + public override Color ToolStripGradientBegin => Surface; + public override Color ToolStripGradientMiddle => Surface; + public override Color ToolStripGradientEnd => Surface; + public override Color StatusStripGradientBegin => Surface; + public override Color StatusStripGradientEnd => Surface; + } +} diff --git a/CreamInstaller/CreamInstaller.csproj b/CreamInstaller/CreamInstaller.csproj index 46e0b358..3ec8ee8c 100644 --- a/CreamInstaller/CreamInstaller.csproj +++ b/CreamInstaller/CreamInstaller.csproj @@ -1,11 +1,11 @@ ο»Ώ WinExe - net7.0-windows + net9.0-windows True Resources\ini.ico - 4.5.0.0 - 2021, pointfeev (https://github.com/pointfeev) + 5.0.0.0 + 2024-2026, ubden-community (https://github.com/ubden-community) CreamInstaller Automatic DLC Unlocker Installer & Configuration Generator CreamInstaller.Program @@ -143,10 +143,10 @@ - - + + - + diff --git a/CreamInstaller/Forms/DialogForm.cs b/CreamInstaller/Forms/DialogForm.cs index fe1633b6..706a1b4a 100644 --- a/CreamInstaller/Forms/DialogForm.cs +++ b/CreamInstaller/Forms/DialogForm.cs @@ -21,10 +21,10 @@ internal DialogResult Show(Icon descriptionIcon, string descriptionText, string for (int i = 0; i < descriptionText.Length; i++) if (descriptionText[i] == '[') { - int textLeft = descriptionText.IndexOf("[", i, StringComparison.Ordinal); - int textRight = descriptionText.IndexOf("]", textLeft == -1 ? i : textLeft, StringComparison.Ordinal); - int linkLeft = descriptionText.IndexOf("(", textRight == -1 ? i : textRight, StringComparison.Ordinal); - int linkRight = descriptionText.IndexOf(")", linkLeft == -1 ? i : linkLeft, StringComparison.Ordinal); + int textLeft = descriptionText.IndexOf('[', i); + int textRight = descriptionText.IndexOf(']', textLeft == -1 ? i : textLeft); + int linkLeft = descriptionText.IndexOf('(', textRight == -1 ? i : textRight); + int linkRight = descriptionText.IndexOf(')', linkLeft == -1 ? i : linkLeft); if (textLeft == -1 || textRight != linkLeft - 1 || linkRight == -1) continue; string text = descriptionText[(textLeft + 1)..textRight]; diff --git a/CreamInstaller/Forms/InstallForm.Designer.cs b/CreamInstaller/Forms/InstallForm.Designer.cs index b98794ff..cd4ede4f 100644 --- a/CreamInstaller/Forms/InstallForm.Designer.cs +++ b/CreamInstaller/Forms/InstallForm.Designer.cs @@ -1,5 +1,6 @@ ο»Ώusing System.ComponentModel; using System.Windows.Forms; +using CreamInstaller.Components; namespace CreamInstaller.Forms { @@ -15,142 +16,157 @@ protected override void Dispose(bool disposing) #region Windows Form Designer generated code - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// private void InitializeComponent() { - this.userProgressBar = new System.Windows.Forms.ProgressBar(); - this.userInfoLabel = new System.Windows.Forms.Label(); - this.acceptButton = new System.Windows.Forms.Button(); - this.retryButton = new System.Windows.Forms.Button(); - this.cancelButton = new System.Windows.Forms.Button(); - this.logTextBox = new System.Windows.Forms.RichTextBox(); - this.reselectButton = new System.Windows.Forms.Button(); + this.userProgressBar = new DarkProgressBar(); + this.userInfoLabel = new System.Windows.Forms.Label(); + this.acceptButton = new DarkButton(); + this.retryButton = new DarkButton(); + this.cancelButton = new DarkButton(); + this.logTextBox = new System.Windows.Forms.RichTextBox(); + this.reselectButton = new DarkButton(); + this.exportLogButton = new DarkButton(); this.SuspendLayout(); + // // userProgressBar // - this.userProgressBar.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.userProgressBar.Location = new System.Drawing.Point(12, 27); - this.userProgressBar.Name = "userProgressBar"; - this.userProgressBar.Size = new System.Drawing.Size(760, 23); + this.userProgressBar.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); + this.userProgressBar.Location = new System.Drawing.Point(12, 30); + this.userProgressBar.Name = "userProgressBar"; + this.userProgressBar.Size = new System.Drawing.Size(760, 22); this.userProgressBar.TabIndex = 1; + // // userInfoLabel // - this.userInfoLabel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); + this.userInfoLabel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); this.userInfoLabel.AutoEllipsis = true; - this.userInfoLabel.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.userInfoLabel.Location = new System.Drawing.Point(12, 9); - this.userInfoLabel.Name = "userInfoLabel"; - this.userInfoLabel.Size = new System.Drawing.Size(760, 15); - this.userInfoLabel.TabIndex = 2; - this.userInfoLabel.Text = "Loading . . . "; - // - // acceptButton - // - this.acceptButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.acceptButton.Enabled = false; - this.acceptButton.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.acceptButton.Location = new System.Drawing.Point(697, 526); - this.acceptButton.Name = "acceptButton"; - this.acceptButton.Size = new System.Drawing.Size(75, 23); - this.acceptButton.TabIndex = 4; - this.acceptButton.Text = "OK"; - this.acceptButton.UseVisualStyleBackColor = true; - this.acceptButton.Click += new System.EventHandler(this.OnAccept); + this.userInfoLabel.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + this.userInfoLabel.ForeColor = CreamInstaller.Components.ThemeManager.TextSecondary; + this.userInfoLabel.Location = new System.Drawing.Point(12, 10); + this.userInfoLabel.Name = "userInfoLabel"; + this.userInfoLabel.Size = new System.Drawing.Size(760, 18); + this.userInfoLabel.TabIndex = 2; + this.userInfoLabel.Text = "Loading . . . "; + // - // retryButton + // logTextBox // - this.retryButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.retryButton.Enabled = false; - this.retryButton.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.retryButton.Location = new System.Drawing.Point(616, 526); - this.retryButton.Name = "retryButton"; - this.retryButton.Size = new System.Drawing.Size(75, 23); - this.retryButton.TabIndex = 3; - this.retryButton.Text = "Retry"; - this.retryButton.UseVisualStyleBackColor = true; - this.retryButton.Click += new System.EventHandler(this.OnRetry); + this.logTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); + this.logTextBox.BackColor = CreamInstaller.Components.ThemeManager.LogBg; + this.logTextBox.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.logTextBox.Font = new System.Drawing.Font("Cascadia Mono", 8.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + this.logTextBox.ForeColor = CreamInstaller.Components.ThemeManager.TextPrimary; + this.logTextBox.HideSelection = false; + this.logTextBox.Location = new System.Drawing.Point(12, 58); + this.logTextBox.Name = "logTextBox"; + this.logTextBox.ReadOnly = true; + this.logTextBox.ScrollBars = System.Windows.Forms.RichTextBoxScrollBars.ForcedBoth; + this.logTextBox.Size = new System.Drawing.Size(760, 460); + this.logTextBox.TabIndex = 4; + this.logTextBox.TabStop = false; + this.logTextBox.Text = ""; + // // cancelButton // - this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.cancelButton.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.cancelButton.Location = new System.Drawing.Point(12, 526); - this.cancelButton.Name = "cancelButton"; - this.cancelButton.Size = new System.Drawing.Size(75, 23); - this.cancelButton.TabIndex = 1; - this.cancelButton.Text = "Cancel"; - this.cancelButton.UseVisualStyleBackColor = true; - this.cancelButton.Click += new System.EventHandler(this.OnCancel); - // - // logTextBox + this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.cancelButton.IsAccent = false; + this.cancelButton.Location = new System.Drawing.Point(12, 528); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(75, 26); + this.cancelButton.TabIndex = 1; + this.cancelButton.Text = "Cancel"; + this.cancelButton.Click += new System.EventHandler(this.OnCancel); + // - this.logTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.logTextBox.HideSelection = false; - this.logTextBox.Location = new System.Drawing.Point(12, 56); - this.logTextBox.Name = "logTextBox"; - this.logTextBox.ReadOnly = true; - this.logTextBox.ScrollBars = System.Windows.Forms.RichTextBoxScrollBars.ForcedBoth; - this.logTextBox.Size = new System.Drawing.Size(760, 464); - this.logTextBox.TabIndex = 4; - this.logTextBox.TabStop = false; - this.logTextBox.Text = ""; + // exportLogButton (NEW) + // + this.exportLogButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.exportLogButton.Enabled = false; + this.exportLogButton.IsAccent = false; + this.exportLogButton.Location = new System.Drawing.Point(93, 528); + this.exportLogButton.Name = "exportLogButton"; + this.exportLogButton.Size = new System.Drawing.Size(90, 26); + this.exportLogButton.TabIndex = 5; + this.exportLogButton.Text = "Export Log"; + this.exportLogButton.Click += new System.EventHandler(this.OnExportLog); + // // reselectButton // - this.reselectButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.reselectButton.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.reselectButton.Location = new System.Drawing.Point(410, 526); - this.reselectButton.Name = "reselectButton"; - this.reselectButton.Size = new System.Drawing.Size(200, 23); - this.reselectButton.TabIndex = 2; - this.reselectButton.Text = "Reselect Programs / Games"; - this.reselectButton.UseVisualStyleBackColor = true; - this.reselectButton.Click += new System.EventHandler(this.OnReselect); + this.reselectButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.reselectButton.IsAccent = false; + this.reselectButton.Location = new System.Drawing.Point(410, 528); + this.reselectButton.Name = "reselectButton"; + this.reselectButton.Size = new System.Drawing.Size(200, 26); + this.reselectButton.TabIndex = 2; + this.reselectButton.Text = "Reselect Programs / Games"; + this.reselectButton.Click += new System.EventHandler(this.OnReselect); + + // + // retryButton + // + this.retryButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.retryButton.Enabled = false; + this.retryButton.IsAccent = false; + this.retryButton.Location = new System.Drawing.Point(616, 528); + this.retryButton.Name = "retryButton"; + this.retryButton.Size = new System.Drawing.Size(75, 26); + this.retryButton.TabIndex = 3; + this.retryButton.Text = "Retry"; + this.retryButton.Click += new System.EventHandler(this.OnRetry); + + // + // acceptButton (accent) + // + this.acceptButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.acceptButton.Enabled = false; + this.acceptButton.IsAccent = true; + this.acceptButton.Location = new System.Drawing.Point(697, 528); + this.acceptButton.Name = "acceptButton"; + this.acceptButton.Size = new System.Drawing.Size(75, 26); + this.acceptButton.TabIndex = 4; + this.acceptButton.Text = "OK"; + this.acceptButton.Click += new System.EventHandler(this.OnAccept); + // // InstallForm // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.AutoSize = true; - this.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; - this.ClientSize = new System.Drawing.Size(784, 561); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = CreamInstaller.Components.ThemeManager.Background; + this.ClientSize = new System.Drawing.Size(784, 566); this.Controls.Add(this.reselectButton); this.Controls.Add(this.logTextBox); this.Controls.Add(this.cancelButton); + this.Controls.Add(this.exportLogButton); this.Controls.Add(this.retryButton); this.Controls.Add(this.acceptButton); this.Controls.Add(this.userProgressBar); this.Controls.Add(this.userInfoLabel); - this.DoubleBuffered = true; - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; - this.MaximizeBox = false; - this.MinimizeBox = false; - this.Name = "InstallForm"; - this.StartPosition = System.Windows.Forms.FormStartPosition.Manual; - this.Text = "InstallForm"; - this.Load += new System.EventHandler(this.OnLoad); + this.DoubleBuffered = true; + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Sizable; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.MinimumSize = new System.Drawing.Size(800, 400); + this.Name = "InstallForm"; + this.StartPosition = System.Windows.Forms.FormStartPosition.Manual; + this.Text = "InstallForm"; + this.Load += new System.EventHandler(this.OnLoad); this.ResumeLayout(false); - } #endregion - private ProgressBar userProgressBar; - private Label userInfoLabel; - private Button acceptButton; - private Button retryButton; - private Button cancelButton; - private RichTextBox logTextBox; - private Button reselectButton; + private DarkProgressBar userProgressBar; + private System.Windows.Forms.Label userInfoLabel; + private DarkButton acceptButton; + private DarkButton retryButton; + private DarkButton cancelButton; + private DarkButton exportLogButton; + private System.Windows.Forms.RichTextBox logTextBox; + private DarkButton reselectButton; } } - diff --git a/CreamInstaller/Forms/InstallForm.cs b/CreamInstaller/Forms/InstallForm.cs index 2a2013ed..03cfc0b4 100644 --- a/CreamInstaller/Forms/InstallForm.cs +++ b/CreamInstaller/Forms/InstallForm.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using System.Windows.Forms; using CreamInstaller.Components; using CreamInstaller.Resources; using CreamInstaller.Utility; @@ -28,7 +29,7 @@ internal InstallForm(bool uninstall = false) { InitializeComponent(); Text = Program.ApplicationName; - logTextBox.BackColor = LogTextBox.Background; + logTextBox.BackColor = Components.ThemeManager.LogBg; uninstalling = uninstall; } @@ -88,7 +89,7 @@ private async Task OperateFor(ProgramSelection selection) UpdateUser("Uninstalling Koaloader from " + selection.Name + $" in incorrect directory \"{directory}\" . . . ", LogTextBox.Operation); await Koaloader.Uninstall(directory, selection.RootDirectory, this); } - Thread.Sleep(1); + await Task.Delay(1); } if (uninstalling || !selection.Koaloader) foreach ((string directory, BinaryType _) in selection.ExecutableDirectories) @@ -102,7 +103,7 @@ private async Task OperateFor(ProgramSelection selection) UpdateUser("Uninstalling Koaloader from " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation); await Koaloader.Uninstall(directory, selection.RootDirectory, this); } - Thread.Sleep(1); + await Task.Delay(1); } bool uninstallProxy = uninstalling || selection.Koaloader; int count = selection.DllDirectories.Count, cur = 0; @@ -174,7 +175,7 @@ private async Task OperateFor(ProgramSelection selection) } } UpdateProgress(++cur / count * 100); - Thread.Sleep(1); + await Task.Delay(1); } if (selection.Koaloader && !uninstalling) foreach ((string directory, BinaryType binaryType) in selection.ExecutableDirectories) @@ -183,7 +184,7 @@ private async Task OperateFor(ProgramSelection selection) throw new CustomMessageException("The operation was canceled."); UpdateUser("Installing Koaloader to " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation); await Koaloader.Install(directory, binaryType, selection, selection.RootDirectory, this); - Thread.Sleep(1); + await Task.Delay(1); } UpdateProgress(100); } @@ -212,7 +213,7 @@ private async Task Operate() } Program.Cleanup(); List failedSelections = ProgramSelection.AllEnabled; - if (failedSelections.Any()) + if (failedSelections.Count > 0) if (failedSelections.Count == 1) throw new CustomMessageException($"Operation failed for {failedSelections.First().Name}."); else @@ -242,9 +243,10 @@ private async void Start() retryButton.Enabled = true; } userProgressBar.Value = userProgressBar.Maximum; - acceptButton.Enabled = true; - cancelButton.Enabled = false; + acceptButton.Enabled = true; + cancelButton.Enabled = false; reselectButton.Enabled = true; + exportLogButton.Enabled = true; } private void OnLoad(object sender, EventArgs _) @@ -287,4 +289,26 @@ private void OnReselect(object sender, EventArgs e) disabledSelections.Clear(); Close(); } + + private void OnExportLog(object sender, EventArgs e) + { + using SaveFileDialog dlg = new() + { + Title = "Export Installation Log", + Filter = "Text Files (*.txt)|*.txt|All Files (*.*)|*.*", + FileName = $"CreamInstaller_Log_{DateTime.Now:yyyyMMdd_HHmmss}.txt", + DefaultExt = "txt", + InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + }; + if (dlg.ShowDialog(this) != System.Windows.Forms.DialogResult.OK) return; + try + { + File.WriteAllText(dlg.FileName, logTextBox.Text); + UpdateUser($"Log exported to: {dlg.FileName}", LogTextBox.Success, info: false); + } + catch (Exception ex) + { + UpdateUser($"Failed to export log: {ex.Message}", LogTextBox.Error, info: false); + } + } } \ No newline at end of file diff --git a/CreamInstaller/Forms/MainForm.Designer.cs b/CreamInstaller/Forms/MainForm.Designer.cs index 946069b3..511c0d76 100644 --- a/CreamInstaller/Forms/MainForm.Designer.cs +++ b/CreamInstaller/Forms/MainForm.Designer.cs @@ -10,105 +10,124 @@ partial class MainForm #region Windows Form Designer generated code - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// private void InitializeComponent() { - this.progressLabel = new System.Windows.Forms.Label(); - this.updateButton = new System.Windows.Forms.Button(); - this.ignoreButton = new System.Windows.Forms.Button(); - this.progressBar = new System.Windows.Forms.ProgressBar(); + this.headerLabel = new System.Windows.Forms.Label(); + this.progressLabel = new System.Windows.Forms.Label(); + this.updateButton = new DarkButton(); + this.ignoreButton = new DarkButton(); + this.progressBar = new DarkProgressBar(); this.changelogTreeView = new CustomTreeView(); this.SuspendLayout(); + // - // progressLabel + // headerLabel // - this.progressLabel.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.progressLabel.Location = new System.Drawing.Point(12, 16); - this.progressLabel.Margin = new System.Windows.Forms.Padding(3, 0, 3, 12); - this.progressLabel.Name = "progressLabel"; - this.progressLabel.Size = new System.Drawing.Size(218, 15); - this.progressLabel.TabIndex = 0; - this.progressLabel.Text = "Checking for updates . . ."; + this.headerLabel.AutoSize = false; + this.headerLabel.Dock = System.Windows.Forms.DockStyle.None; + this.headerLabel.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point); + this.headerLabel.ForeColor = CreamInstaller.Components.ThemeManager.Accent; + this.headerLabel.Location = new System.Drawing.Point(12, 12); + this.headerLabel.Name = "headerLabel"; + this.headerLabel.Size = new System.Drawing.Size(380, 24); + this.headerLabel.Text = "CreamInstaller"; + this.headerLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // - // updateButton + // progressLabel // - this.updateButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.updateButton.Enabled = false; - this.updateButton.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.updateButton.Location = new System.Drawing.Point(317, 12); - this.updateButton.Margin = new System.Windows.Forms.Padding(3, 3, 3, 12); - this.updateButton.Name = "updateButton"; - this.updateButton.Size = new System.Drawing.Size(75, 23); - this.updateButton.TabIndex = 2; - this.updateButton.Text = "Update"; - this.updateButton.UseVisualStyleBackColor = true; + this.progressLabel.AutoEllipsis = true; + this.progressLabel.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + this.progressLabel.ForeColor = CreamInstaller.Components.ThemeManager.TextSecondary; + this.progressLabel.Location = new System.Drawing.Point(12, 44); + this.progressLabel.Margin = new System.Windows.Forms.Padding(3, 0, 3, 10); + this.progressLabel.Name = "progressLabel"; + this.progressLabel.Size = new System.Drawing.Size(218, 18); + this.progressLabel.Text = "Checking for updates . . ."; + // - // ignoreButton + // updateButton (accent) + // + this.updateButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.updateButton.Enabled = false; + this.updateButton.IsAccent = true; + this.updateButton.Location = new System.Drawing.Point(317, 40); + this.updateButton.Margin = new System.Windows.Forms.Padding(3, 3, 3, 10); + this.updateButton.Name = "updateButton"; + this.updateButton.Size = new System.Drawing.Size(75, 26); + this.updateButton.TabIndex = 2; + this.updateButton.Text = "Update"; + // - this.ignoreButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.ignoreButton.Enabled = false; - this.ignoreButton.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.ignoreButton.Location = new System.Drawing.Point(236, 12); - this.ignoreButton.Margin = new System.Windows.Forms.Padding(3, 3, 3, 12); - this.ignoreButton.Name = "ignoreButton"; - this.ignoreButton.Size = new System.Drawing.Size(75, 23); - this.ignoreButton.TabIndex = 1; - this.ignoreButton.Text = "Ignore"; - this.ignoreButton.UseVisualStyleBackColor = true; - this.ignoreButton.Click += new System.EventHandler(this.OnIgnore); + // ignoreButton (outline / surface) + // + this.ignoreButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.ignoreButton.Enabled = false; + this.ignoreButton.IsAccent = false; + this.ignoreButton.Location = new System.Drawing.Point(236, 40); + this.ignoreButton.Margin = new System.Windows.Forms.Padding(3, 3, 3, 10); + this.ignoreButton.Name = "ignoreButton"; + this.ignoreButton.Size = new System.Drawing.Size(75, 26); + this.ignoreButton.TabIndex = 1; + this.ignoreButton.Text = "Ignore"; + this.ignoreButton.Click += new System.EventHandler(this.OnIgnore); + // // progressBar // - this.progressBar.Location = new System.Drawing.Point(12, 41); - this.progressBar.Name = "progressBar"; - this.progressBar.Size = new System.Drawing.Size(380, 23); + this.progressBar.Location = new System.Drawing.Point(12, 72); + this.progressBar.Name = "progressBar"; + this.progressBar.Size = new System.Drawing.Size(380, 22); this.progressBar.TabIndex = 4; - this.progressBar.Visible = false; + this.progressBar.Visible = false; + // // changelogTreeView // - this.changelogTreeView.DrawMode = System.Windows.Forms.TreeViewDrawMode.OwnerDrawAll; - this.changelogTreeView.Location = new System.Drawing.Point(12, 70); - this.changelogTreeView.Margin = new System.Windows.Forms.Padding(0, 0, 0, 12); - this.changelogTreeView.Name = "changelogTreeView"; - this.changelogTreeView.Size = new System.Drawing.Size(380, 179); - this.changelogTreeView.Sorted = true; - this.changelogTreeView.TabIndex = 5; + this.changelogTreeView.BackColor = CreamInstaller.Components.ThemeManager.Background; + this.changelogTreeView.ForeColor = CreamInstaller.Components.ThemeManager.TextPrimary; + this.changelogTreeView.DrawMode = System.Windows.Forms.TreeViewDrawMode.OwnerDrawAll; + this.changelogTreeView.Location = new System.Drawing.Point(12, 102); + this.changelogTreeView.Margin = new System.Windows.Forms.Padding(0, 0, 0, 12); + this.changelogTreeView.Name = "changelogTreeView"; + this.changelogTreeView.Size = new System.Drawing.Size(380, 179); + this.changelogTreeView.Sorted = true; + this.changelogTreeView.TabIndex = 5; + this.changelogTreeView.BorderStyle = System.Windows.Forms.BorderStyle.None; + // // MainForm // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.AutoSize = true; - this.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; - this.ClientSize = new System.Drawing.Size(404, 261); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoSize = true; + this.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + this.BackColor = CreamInstaller.Components.ThemeManager.Background; + this.ClientSize = new System.Drawing.Size(404, 293); this.Controls.Add(this.changelogTreeView); this.Controls.Add(this.progressBar); this.Controls.Add(this.ignoreButton); this.Controls.Add(this.updateButton); this.Controls.Add(this.progressLabel); - this.DoubleBuffered = true; + this.Controls.Add(this.headerLabel); + this.DoubleBuffered = true; this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; - this.MaximizeBox = false; - this.MinimizeBox = false; - this.Name = "MainForm"; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; - this.Text = "MainForm"; - this.Load += new System.EventHandler(this.OnLoad); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "MainForm"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "MainForm"; + this.Load += new System.EventHandler(this.OnLoad); this.ResumeLayout(false); - } #endregion - private Label progressLabel; - private Button updateButton; - private Button ignoreButton; - private ProgressBar progressBar; - private CustomTreeView changelogTreeView; + private Label headerLabel; + private Label progressLabel; + private DarkButton updateButton; + private DarkButton ignoreButton; + private DarkProgressBar progressBar; + private CustomTreeView changelogTreeView; } } - diff --git a/CreamInstaller/Forms/MainForm.cs b/CreamInstaller/Forms/MainForm.cs index 57d2452c..725adbc9 100644 --- a/CreamInstaller/Forms/MainForm.cs +++ b/CreamInstaller/Forms/MainForm.cs @@ -28,6 +28,7 @@ internal MainForm() { InitializeComponent(); Text = Program.ApplicationNameShort; + headerLabel.Text = Program.ApplicationNameShort; } private void StartProgram() @@ -52,12 +53,14 @@ private void StartProgram() private async void OnLoad() { - progressBar.Visible = false; - ignoreButton.Visible = true; - updateButton.Text = "Update"; - updateButton.Click -= OnUpdateCancel; - progressLabel.Text = "Checking for updates . . ."; - changelogTreeView.Visible = false; + progressBar.Visible = false; + ignoreButton.Visible = true; + updateButton.Text = "Update"; + updateButton.IsAccent = true; + updateButton.Click -= OnUpdateCancel; + progressLabel.Text = "Checking for updates . . ."; + progressLabel.ForeColor = Components.ThemeManager.TextSecondary; + changelogTreeView.Visible = false; changelogTreeView.Location = progressLabel.Location with { Y = progressLabel.Location.Y + progressLabel.Size.Height + 13 }; Refresh(); #if DEBUG @@ -108,10 +111,11 @@ private async void OnLoad() } else { - progressLabel.Text = $"An update is available: v{latestVersion}"; - ignoreButton.Enabled = true; - updateButton.Enabled = true; - updateButton.Click += OnUpdate; + progressLabel.Text = $"An update is available: v{latestVersion}"; + progressLabel.ForeColor = Components.ThemeManager.Warning; + ignoreButton.Enabled = true; + updateButton.Enabled = true; + updateButton.Click += OnUpdate; changelogTreeView.Visible = true; Version currentVersion = new(Program.Version); #if DEBUG @@ -177,18 +181,20 @@ private void OnLoad(object sender, EventArgs _) private async void OnUpdate(object sender, EventArgs e) { - progressBar.Visible = true; - ignoreButton.Visible = false; - updateButton.Text = "Cancel"; - updateButton.Click -= OnUpdate; - updateButton.Click += OnUpdateCancel; + progressBar.Visible = true; + ignoreButton.Visible = false; + updateButton.Text = "Cancel"; + updateButton.IsAccent = false; + updateButton.Click -= OnUpdate; + updateButton.Click += OnUpdateCancel; changelogTreeView.Location = progressBar.Location with { Y = progressBar.Location.Y + progressBar.Size.Height + 6 }; Refresh(); Progress progress = new(); progress.ProgressChanged += delegate(object _, double _progress) { - progressLabel.Text = $"Updating . . . {(int)_progress}%"; - progressBar.Value = (int)_progress; + progressLabel.Text = $"Updating . . . {(int)_progress}%"; + progressLabel.ForeColor = Components.ThemeManager.Accent; + progressBar.Value = (int)_progress; }; progressLabel.Text = "Updating . . . "; cancellationTokenSource = new(); diff --git a/CreamInstaller/Forms/SelectDialogForm.cs b/CreamInstaller/Forms/SelectDialogForm.cs index 7802397f..d616882b 100644 --- a/CreamInstaller/Forms/SelectDialogForm.cs +++ b/CreamInstaller/Forms/SelectDialogForm.cs @@ -94,7 +94,7 @@ private void OnLoad(object sender, EventArgs e) private void OnSave(object sender, EventArgs e) { - ProgramData.WriteProgramChoices(selectionTreeView.Nodes.Cast().Where(n => n.Checked).Select(node => ((Platform)node.Tag, node.Name))); + ProgramData.WriteProgramChoices(selectionTreeView.Nodes.Cast().Where(n => n.Checked).Select(node => ((Platform)node.Tag, node.Name)).ToList()); loadButton.Enabled = ProgramData.ReadProgramChoices() is not null; } } \ No newline at end of file diff --git a/CreamInstaller/Forms/SelectForm.Designer.cs b/CreamInstaller/Forms/SelectForm.Designer.cs index bedda3b5..9d6a6c71 100644 --- a/CreamInstaller/Forms/SelectForm.Designer.cs +++ b/CreamInstaller/Forms/SelectForm.Designer.cs @@ -18,394 +18,478 @@ protected override void Dispose(bool disposing) private void InitializeComponent() { - this.installButton = new System.Windows.Forms.Button(); - this.cancelButton = new System.Windows.Forms.Button(); - this.programsGroupBox = new System.Windows.Forms.GroupBox(); - this.koaloaderFlowPanel = new System.Windows.Forms.FlowLayoutPanel(); - this.koaloaderAllCheckBox = new System.Windows.Forms.CheckBox(); - this.noneFoundLabel = new System.Windows.Forms.Label(); + this.installButton = new DarkButton(); + this.cancelButton = new DarkButton(); + this.programsGroupBox = new System.Windows.Forms.GroupBox(); + this.koaloaderFlowPanel = new System.Windows.Forms.FlowLayoutPanel(); + this.koaloaderAllCheckBox = new System.Windows.Forms.CheckBox(); + this.noneFoundLabel = new System.Windows.Forms.Label(); this.blockedGamesFlowPanel = new System.Windows.Forms.FlowLayoutPanel(); - this.blockedGamesCheckBox = new System.Windows.Forms.CheckBox(); - this.blockProtectedHelpButton = new System.Windows.Forms.Button(); - this.selectionTreeView = new CreamInstaller.Components.CustomTreeView(); + this.blockedGamesCheckBox = new System.Windows.Forms.CheckBox(); + this.blockProtectedHelpButton = new DarkButton(); + this.selectionTreeView = new CreamInstaller.Components.CustomTreeView(); this.allCheckBoxLayoutPanel = new System.Windows.Forms.FlowLayoutPanel(); - this.allCheckBox = new System.Windows.Forms.CheckBox(); - this.progressBar = new System.Windows.Forms.ProgressBar(); - this.progressLabel = new System.Windows.Forms.Label(); - this.scanButton = new System.Windows.Forms.Button(); - this.uninstallButton = new System.Windows.Forms.Button(); - this.progressLabelGames = new System.Windows.Forms.Label(); - this.progressLabelDLCs = new System.Windows.Forms.Label(); - this.sortCheckBox = new System.Windows.Forms.CheckBox(); - this.saveButton = new System.Windows.Forms.Button(); - this.loadButton = new System.Windows.Forms.Button(); - this.resetKoaloaderButton = new System.Windows.Forms.Button(); - this.resetButton = new System.Windows.Forms.Button(); - this.saveKoaloaderButton = new System.Windows.Forms.Button(); - this.loadKoaloaderButton = new System.Windows.Forms.Button(); + this.allCheckBox = new System.Windows.Forms.CheckBox(); + this.progressBar = new DarkProgressBar(); + this.progressLabel = new System.Windows.Forms.Label(); + this.searchTextBox = new System.Windows.Forms.TextBox(); + this.scanButton = new DarkButton(); + this.uninstallButton = new DarkButton(); + this.progressLabelGames = new System.Windows.Forms.Label(); + this.progressLabelDLCs = new System.Windows.Forms.Label(); + this.sortCheckBox = new System.Windows.Forms.CheckBox(); + this.saveButton = new DarkButton(); + this.loadButton = new DarkButton(); + this.resetKoaloaderButton = new DarkButton(); + this.resetButton = new DarkButton(); + this.saveKoaloaderButton = new DarkButton(); + this.loadKoaloaderButton = new DarkButton(); + this.statusStrip = new System.Windows.Forms.StatusStrip(); + this.statusLabelGames = new System.Windows.Forms.ToolStripStatusLabel(); + this.statusLabelSep = new System.Windows.Forms.ToolStripStatusLabel(); + this.statusLabelDLCs = new System.Windows.Forms.ToolStripStatusLabel(); this.programsGroupBox.SuspendLayout(); this.koaloaderFlowPanel.SuspendLayout(); this.blockedGamesFlowPanel.SuspendLayout(); this.allCheckBoxLayoutPanel.SuspendLayout(); + this.statusStrip.SuspendLayout(); this.SuspendLayout(); + // // installButton // - this.installButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.installButton.AutoSize = true; + this.installButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.installButton.AutoSize = true; this.installButton.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; - this.installButton.Enabled = false; - this.installButton.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.installButton.Location = new System.Drawing.Point(423, 325); - this.installButton.Name = "installButton"; - this.installButton.Padding = new System.Windows.Forms.Padding(12, 0, 12, 0); - this.installButton.Size = new System.Drawing.Size(149, 24); - this.installButton.TabIndex = 10000; - this.installButton.Text = "Generate and Install"; - this.installButton.UseVisualStyleBackColor = true; - this.installButton.Click += new System.EventHandler(this.OnInstall); + this.installButton.Enabled = false; + this.installButton.IsAccent = true; + this.installButton.Location = new System.Drawing.Point(418, 332); + this.installButton.Name = "installButton"; + this.installButton.Padding = new System.Windows.Forms.Padding(12, 0, 12, 0); + this.installButton.Size = new System.Drawing.Size(154, 26); + this.installButton.TabIndex = 10000; + this.installButton.Text = "Generate and Install"; + this.installButton.Click += new System.EventHandler(this.OnInstall); + // // cancelButton // - this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.cancelButton.AutoSize = true; + this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.cancelButton.AutoSize = true; this.cancelButton.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; - this.cancelButton.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.cancelButton.Location = new System.Drawing.Point(12, 325); - this.cancelButton.Name = "cancelButton"; - this.cancelButton.Padding = new System.Windows.Forms.Padding(12, 0, 12, 0); - this.cancelButton.Size = new System.Drawing.Size(81, 24); - this.cancelButton.TabIndex = 10004; - this.cancelButton.Text = "Cancel"; - this.cancelButton.UseVisualStyleBackColor = true; - this.cancelButton.Click += new System.EventHandler(this.OnCancel); + this.cancelButton.IsAccent = false; + this.cancelButton.Location = new System.Drawing.Point(12, 332); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Padding = new System.Windows.Forms.Padding(12, 0, 12, 0); + this.cancelButton.Size = new System.Drawing.Size(81, 26); + this.cancelButton.TabIndex = 10004; + this.cancelButton.Text = "Cancel"; + this.cancelButton.Click += new System.EventHandler(this.OnCancel); + // // programsGroupBox // - this.programsGroupBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) + this.programsGroupBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); + this.programsGroupBox.BackColor = CreamInstaller.Components.ThemeManager.Background; this.programsGroupBox.Controls.Add(this.koaloaderFlowPanel); this.programsGroupBox.Controls.Add(this.noneFoundLabel); this.programsGroupBox.Controls.Add(this.blockedGamesFlowPanel); this.programsGroupBox.Controls.Add(this.selectionTreeView); this.programsGroupBox.Controls.Add(this.allCheckBoxLayoutPanel); - this.programsGroupBox.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.programsGroupBox.Location = new System.Drawing.Point(12, 12); - this.programsGroupBox.Name = "programsGroupBox"; - this.programsGroupBox.Size = new System.Drawing.Size(560, 209); - this.programsGroupBox.TabIndex = 8; - this.programsGroupBox.TabStop = false; - this.programsGroupBox.Text = "Programs / Games"; + this.programsGroupBox.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.programsGroupBox.ForeColor = CreamInstaller.Components.ThemeManager.TextSecondary; + this.programsGroupBox.Location = new System.Drawing.Point(12, 44); + this.programsGroupBox.Name = "programsGroupBox"; + this.programsGroupBox.Size = new System.Drawing.Size(560, 209); + this.programsGroupBox.TabIndex = 8; + this.programsGroupBox.TabStop = false; + this.programsGroupBox.Text = "Programs / Games"; + // // koaloaderFlowPanel // - this.koaloaderFlowPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.koaloaderFlowPanel.AutoSize = true; + this.koaloaderFlowPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.koaloaderFlowPanel.AutoSize = true; this.koaloaderFlowPanel.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + this.koaloaderFlowPanel.BackColor = CreamInstaller.Components.ThemeManager.Background; this.koaloaderFlowPanel.Controls.Add(this.koaloaderAllCheckBox); - this.koaloaderFlowPanel.Location = new System.Drawing.Point(430, -1); - this.koaloaderFlowPanel.Margin = new System.Windows.Forms.Padding(0); - this.koaloaderFlowPanel.Name = "koaloaderFlowPanel"; - this.koaloaderFlowPanel.Size = new System.Drawing.Size(73, 19); - this.koaloaderFlowPanel.TabIndex = 10005; + this.koaloaderFlowPanel.Location = new System.Drawing.Point(430, -1); + this.koaloaderFlowPanel.Margin = new System.Windows.Forms.Padding(0); + this.koaloaderFlowPanel.Name = "koaloaderFlowPanel"; + this.koaloaderFlowPanel.Size = new System.Drawing.Size(73, 19); + this.koaloaderFlowPanel.TabIndex = 10005; this.koaloaderFlowPanel.WrapContents = false; + // // koaloaderAllCheckBox // - this.koaloaderAllCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.koaloaderAllCheckBox.Checked = true; + this.koaloaderAllCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.koaloaderAllCheckBox.BackColor = CreamInstaller.Components.ThemeManager.Background; + this.koaloaderAllCheckBox.Checked = true; this.koaloaderAllCheckBox.CheckState = System.Windows.Forms.CheckState.Checked; - this.koaloaderAllCheckBox.Enabled = false; - this.koaloaderAllCheckBox.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.koaloaderAllCheckBox.Location = new System.Drawing.Point(2, 0); - this.koaloaderAllCheckBox.Margin = new System.Windows.Forms.Padding(2, 0, 0, 0); - this.koaloaderAllCheckBox.Name = "koaloaderAllCheckBox"; - this.koaloaderAllCheckBox.Size = new System.Drawing.Size(71, 19); - this.koaloaderAllCheckBox.TabIndex = 4; - this.koaloaderAllCheckBox.Text = "Koaloader"; + this.koaloaderAllCheckBox.Enabled = false; + this.koaloaderAllCheckBox.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.koaloaderAllCheckBox.ForeColor = CreamInstaller.Components.ThemeManager.TextPrimary; + this.koaloaderAllCheckBox.Location = new System.Drawing.Point(2, 0); + this.koaloaderAllCheckBox.Margin = new System.Windows.Forms.Padding(2, 0, 0, 0); + this.koaloaderAllCheckBox.Name = "koaloaderAllCheckBox"; + this.koaloaderAllCheckBox.Size = new System.Drawing.Size(71, 19); + this.koaloaderAllCheckBox.TabIndex = 4; + this.koaloaderAllCheckBox.Text = "Koaloader"; this.koaloaderAllCheckBox.CheckedChanged += new System.EventHandler(this.OnKoaloaderAllCheckBoxChanged); + // // noneFoundLabel // - this.noneFoundLabel.Dock = System.Windows.Forms.DockStyle.Fill; - this.noneFoundLabel.Location = new System.Drawing.Point(3, 19); - this.noneFoundLabel.Name = "noneFoundLabel"; - this.noneFoundLabel.Size = new System.Drawing.Size(554, 187); - this.noneFoundLabel.TabIndex = 1002; - this.noneFoundLabel.Text = "No applicable programs nor games were found on your computer!"; - this.noneFoundLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - this.noneFoundLabel.Visible = false; + this.noneFoundLabel.BackColor = CreamInstaller.Components.ThemeManager.Background; + this.noneFoundLabel.Dock = System.Windows.Forms.DockStyle.Fill; + this.noneFoundLabel.ForeColor = CreamInstaller.Components.ThemeManager.TextSecondary; + this.noneFoundLabel.Location = new System.Drawing.Point(3, 19); + this.noneFoundLabel.Name = "noneFoundLabel"; + this.noneFoundLabel.Size = new System.Drawing.Size(554, 187); + this.noneFoundLabel.TabIndex = 1002; + this.noneFoundLabel.Text = "No applicable programs nor games were found on your computer!"; + this.noneFoundLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + this.noneFoundLabel.Visible = false; + // // blockedGamesFlowPanel // - this.blockedGamesFlowPanel.Anchor = System.Windows.Forms.AnchorStyles.Top; - this.blockedGamesFlowPanel.AutoSize = true; + this.blockedGamesFlowPanel.Anchor = System.Windows.Forms.AnchorStyles.Top; + this.blockedGamesFlowPanel.AutoSize = true; this.blockedGamesFlowPanel.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + this.blockedGamesFlowPanel.BackColor = CreamInstaller.Components.ThemeManager.Background; this.blockedGamesFlowPanel.Controls.Add(this.blockedGamesCheckBox); this.blockedGamesFlowPanel.Controls.Add(this.blockProtectedHelpButton); - this.blockedGamesFlowPanel.Location = new System.Drawing.Point(125, -1); - this.blockedGamesFlowPanel.Margin = new System.Windows.Forms.Padding(0); - this.blockedGamesFlowPanel.Name = "blockedGamesFlowPanel"; - this.blockedGamesFlowPanel.Size = new System.Drawing.Size(162, 20); - this.blockedGamesFlowPanel.TabIndex = 1005; + this.blockedGamesFlowPanel.Location = new System.Drawing.Point(125, -1); + this.blockedGamesFlowPanel.Margin = new System.Windows.Forms.Padding(0); + this.blockedGamesFlowPanel.Name = "blockedGamesFlowPanel"; + this.blockedGamesFlowPanel.Size = new System.Drawing.Size(162, 20); + this.blockedGamesFlowPanel.TabIndex = 1005; this.blockedGamesFlowPanel.WrapContents = false; + // // blockedGamesCheckBox // - this.blockedGamesCheckBox.Checked = true; + this.blockedGamesCheckBox.BackColor = CreamInstaller.Components.ThemeManager.Background; + this.blockedGamesCheckBox.Checked = true; this.blockedGamesCheckBox.CheckState = System.Windows.Forms.CheckState.Checked; - this.blockedGamesCheckBox.Enabled = false; - this.blockedGamesCheckBox.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.blockedGamesCheckBox.Location = new System.Drawing.Point(2, 0); - this.blockedGamesCheckBox.Margin = new System.Windows.Forms.Padding(2, 0, 0, 0); - this.blockedGamesCheckBox.Name = "blockedGamesCheckBox"; - this.blockedGamesCheckBox.Size = new System.Drawing.Size(140, 20); - this.blockedGamesCheckBox.TabIndex = 1; - this.blockedGamesCheckBox.Text = "Block Protected Games"; - this.blockedGamesCheckBox.UseVisualStyleBackColor = true; + this.blockedGamesCheckBox.Enabled = false; + this.blockedGamesCheckBox.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.blockedGamesCheckBox.ForeColor = CreamInstaller.Components.ThemeManager.TextPrimary; + this.blockedGamesCheckBox.Location = new System.Drawing.Point(2, 0); + this.blockedGamesCheckBox.Margin = new System.Windows.Forms.Padding(2, 0, 0, 0); + this.blockedGamesCheckBox.Name = "blockedGamesCheckBox"; + this.blockedGamesCheckBox.Size = new System.Drawing.Size(140, 20); + this.blockedGamesCheckBox.TabIndex = 1; + this.blockedGamesCheckBox.Text = "Block Protected Games"; this.blockedGamesCheckBox.CheckedChanged += new System.EventHandler(this.OnBlockProtectedGamesCheckBoxChanged); + // // blockProtectedHelpButton // - this.blockProtectedHelpButton.Enabled = false; - this.blockProtectedHelpButton.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.blockProtectedHelpButton.Font = new System.Drawing.Font("Segoe UI", 7F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + this.blockProtectedHelpButton.Enabled = false; + this.blockProtectedHelpButton.IsAccent = false; + this.blockProtectedHelpButton.Font = new System.Drawing.Font("Segoe UI", 7F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point); this.blockProtectedHelpButton.Location = new System.Drawing.Point(142, 0); - this.blockProtectedHelpButton.Margin = new System.Windows.Forms.Padding(0, 0, 1, 0); - this.blockProtectedHelpButton.Name = "blockProtectedHelpButton"; - this.blockProtectedHelpButton.Size = new System.Drawing.Size(19, 19); + this.blockProtectedHelpButton.Margin = new System.Windows.Forms.Padding(0, 0, 1, 0); + this.blockProtectedHelpButton.Name = "blockProtectedHelpButton"; + this.blockProtectedHelpButton.Size = new System.Drawing.Size(19, 19); this.blockProtectedHelpButton.TabIndex = 2; - this.blockProtectedHelpButton.Text = "?"; - this.blockProtectedHelpButton.UseVisualStyleBackColor = true; - this.blockProtectedHelpButton.Click += new System.EventHandler(this.OnBlockProtectedGamesHelpButtonClicked); + this.blockProtectedHelpButton.Text = "?"; + this.blockProtectedHelpButton.Click += new System.EventHandler(this.OnBlockProtectedGamesHelpButtonClicked); + // // selectionTreeView // - this.selectionTreeView.BackColor = System.Drawing.SystemColors.Control; + this.selectionTreeView.BackColor = CreamInstaller.Components.ThemeManager.Background; + this.selectionTreeView.ForeColor = CreamInstaller.Components.ThemeManager.TextPrimary; this.selectionTreeView.BorderStyle = System.Windows.Forms.BorderStyle.None; - this.selectionTreeView.CheckBoxes = true; - this.selectionTreeView.Dock = System.Windows.Forms.DockStyle.Fill; - this.selectionTreeView.DrawMode = System.Windows.Forms.TreeViewDrawMode.OwnerDrawAll; - this.selectionTreeView.Enabled = false; + this.selectionTreeView.CheckBoxes = true; + this.selectionTreeView.Dock = System.Windows.Forms.DockStyle.Fill; + this.selectionTreeView.DrawMode = System.Windows.Forms.TreeViewDrawMode.OwnerDrawAll; + this.selectionTreeView.Enabled = false; this.selectionTreeView.FullRowSelect = true; - this.selectionTreeView.Location = new System.Drawing.Point(3, 19); - this.selectionTreeView.Name = "selectionTreeView"; - this.selectionTreeView.Size = new System.Drawing.Size(554, 187); - this.selectionTreeView.Sorted = true; - this.selectionTreeView.TabIndex = 1001; + this.selectionTreeView.Location = new System.Drawing.Point(3, 19); + this.selectionTreeView.Name = "selectionTreeView"; + this.selectionTreeView.Size = new System.Drawing.Size(554, 187); + this.selectionTreeView.Sorted = true; + this.selectionTreeView.TabIndex = 1001; + // // allCheckBoxLayoutPanel // - this.allCheckBoxLayoutPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.allCheckBoxLayoutPanel.AutoSize = true; + this.allCheckBoxLayoutPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.allCheckBoxLayoutPanel.AutoSize = true; this.allCheckBoxLayoutPanel.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + this.allCheckBoxLayoutPanel.BackColor = CreamInstaller.Components.ThemeManager.Background; this.allCheckBoxLayoutPanel.Controls.Add(this.allCheckBox); - this.allCheckBoxLayoutPanel.Location = new System.Drawing.Point(520, -1); - this.allCheckBoxLayoutPanel.Margin = new System.Windows.Forms.Padding(0); - this.allCheckBoxLayoutPanel.Name = "allCheckBoxLayoutPanel"; - this.allCheckBoxLayoutPanel.Size = new System.Drawing.Size(34, 19); - this.allCheckBoxLayoutPanel.TabIndex = 1006; + this.allCheckBoxLayoutPanel.Location = new System.Drawing.Point(520, -1); + this.allCheckBoxLayoutPanel.Margin = new System.Windows.Forms.Padding(0); + this.allCheckBoxLayoutPanel.Name = "allCheckBoxLayoutPanel"; + this.allCheckBoxLayoutPanel.Size = new System.Drawing.Size(34, 19); + this.allCheckBoxLayoutPanel.TabIndex = 1006; this.allCheckBoxLayoutPanel.WrapContents = false; + // // allCheckBox // - this.allCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.allCheckBox.Checked = true; + this.allCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.allCheckBox.BackColor = CreamInstaller.Components.ThemeManager.Background; + this.allCheckBox.Checked = true; this.allCheckBox.CheckState = System.Windows.Forms.CheckState.Checked; - this.allCheckBox.Enabled = false; - this.allCheckBox.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.allCheckBox.Location = new System.Drawing.Point(2, 0); - this.allCheckBox.Margin = new System.Windows.Forms.Padding(2, 0, 0, 0); - this.allCheckBox.Name = "allCheckBox"; - this.allCheckBox.Size = new System.Drawing.Size(32, 19); - this.allCheckBox.TabIndex = 4; - this.allCheckBox.Text = "All"; + this.allCheckBox.Enabled = false; + this.allCheckBox.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.allCheckBox.ForeColor = CreamInstaller.Components.ThemeManager.TextPrimary; + this.allCheckBox.Location = new System.Drawing.Point(2, 0); + this.allCheckBox.Margin = new System.Windows.Forms.Padding(2, 0, 0, 0); + this.allCheckBox.Name = "allCheckBox"; + this.allCheckBox.Size = new System.Drawing.Size(32, 19); + this.allCheckBox.TabIndex = 4; + this.allCheckBox.Text = "All"; this.allCheckBox.CheckedChanged += new System.EventHandler(this.OnAllCheckBoxChanged); + + // + // searchTextBox (NEW - game filter) + // + this.searchTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); + this.searchTextBox.BackColor = CreamInstaller.Components.ThemeManager.Surface; + this.searchTextBox.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.searchTextBox.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + this.searchTextBox.ForeColor = CreamInstaller.Components.ThemeManager.TextPrimary; + this.searchTextBox.Location = new System.Drawing.Point(12, 16); + this.searchTextBox.Name = "searchTextBox"; + this.searchTextBox.PlaceholderText = "Search games . . ."; + this.searchTextBox.Size = new System.Drawing.Size(560, 23); + this.searchTextBox.TabIndex = 0; + this.searchTextBox.TextChanged += new System.EventHandler(this.OnSearchTextChanged); + // // progressBar // - this.progressBar.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.progressBar.Location = new System.Drawing.Point(12, 266); - this.progressBar.Name = "progressBar"; - this.progressBar.Size = new System.Drawing.Size(560, 23); + this.progressBar.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); + this.progressBar.Location = new System.Drawing.Point(12, 273); + this.progressBar.Name = "progressBar"; + this.progressBar.Size = new System.Drawing.Size(560, 22); this.progressBar.TabIndex = 9; + // // progressLabel // - this.progressLabel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.progressLabel.Location = new System.Drawing.Point(12, 224); - this.progressLabel.Name = "progressLabel"; - this.progressLabel.Size = new System.Drawing.Size(560, 15); - this.progressLabel.TabIndex = 10; - this.progressLabel.Text = "Gathering and caching your applicable games and their DLCs . . . 0%"; + this.progressLabel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); + this.progressLabel.AutoEllipsis = true; + this.progressLabel.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + this.progressLabel.ForeColor = CreamInstaller.Components.ThemeManager.TextSecondary; + this.progressLabel.Location = new System.Drawing.Point(12, 231); + this.progressLabel.Name = "progressLabel"; + this.progressLabel.Size = new System.Drawing.Size(560, 15); + this.progressLabel.Text = "Gathering and caching your applicable games and their DLCs . . . 0%"; + // // scanButton // - this.scanButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.scanButton.AutoSize = true; + this.scanButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.scanButton.AutoSize = true; this.scanButton.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; - this.scanButton.Enabled = false; - this.scanButton.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.scanButton.Location = new System.Drawing.Point(238, 325); - this.scanButton.Name = "scanButton"; - this.scanButton.Padding = new System.Windows.Forms.Padding(12, 0, 12, 0); - this.scanButton.Size = new System.Drawing.Size(82, 24); - this.scanButton.TabIndex = 10002; - this.scanButton.Text = "Rescan"; - this.scanButton.UseVisualStyleBackColor = true; - this.scanButton.Click += new System.EventHandler(this.OnScan); + this.scanButton.Enabled = false; + this.scanButton.IsAccent = false; + this.scanButton.Location = new System.Drawing.Point(238, 332); + this.scanButton.Name = "scanButton"; + this.scanButton.Padding = new System.Windows.Forms.Padding(12, 0, 12, 0); + this.scanButton.Size = new System.Drawing.Size(82, 26); + this.scanButton.TabIndex = 10002; + this.scanButton.Text = "Rescan"; + this.scanButton.Click += new System.EventHandler(this.OnScan); + // // uninstallButton // - this.uninstallButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.uninstallButton.AutoSize = true; + this.uninstallButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.uninstallButton.AutoSize = true; this.uninstallButton.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; - this.uninstallButton.Enabled = false; - this.uninstallButton.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.uninstallButton.Location = new System.Drawing.Point(326, 325); - this.uninstallButton.Name = "uninstallButton"; - this.uninstallButton.Padding = new System.Windows.Forms.Padding(12, 0, 12, 0); - this.uninstallButton.Size = new System.Drawing.Size(91, 24); - this.uninstallButton.TabIndex = 10001; - this.uninstallButton.Text = "Uninstall"; - this.uninstallButton.UseVisualStyleBackColor = true; - this.uninstallButton.Click += new System.EventHandler(this.OnUninstall); + this.uninstallButton.Enabled = false; + this.uninstallButton.IsAccent = false; + this.uninstallButton.Location = new System.Drawing.Point(326, 332); + this.uninstallButton.Name = "uninstallButton"; + this.uninstallButton.Padding = new System.Windows.Forms.Padding(12, 0, 12, 0); + this.uninstallButton.Size = new System.Drawing.Size(91, 26); + this.uninstallButton.TabIndex = 10001; + this.uninstallButton.Text = "Uninstall"; + this.uninstallButton.Click += new System.EventHandler(this.OnUninstall); + // // progressLabelGames // - this.progressLabelGames.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.progressLabelGames.Font = new System.Drawing.Font("Segoe UI", 7F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.progressLabelGames.Location = new System.Drawing.Point(12, 239); - this.progressLabelGames.Name = "progressLabelGames"; - this.progressLabelGames.Size = new System.Drawing.Size(560, 12); + this.progressLabelGames.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); + this.progressLabelGames.AutoEllipsis = true; + this.progressLabelGames.Font = new System.Drawing.Font("Segoe UI", 7F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + this.progressLabelGames.ForeColor = CreamInstaller.Components.ThemeManager.TextDisabled; + this.progressLabelGames.Location = new System.Drawing.Point(12, 246); + this.progressLabelGames.Name = "progressLabelGames"; + this.progressLabelGames.Size = new System.Drawing.Size(560, 12); this.progressLabelGames.TabIndex = 11; - this.progressLabelGames.Text = "Remaining games (2): Game 1, Game 2"; + // // progressLabelDLCs // - this.progressLabelDLCs.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.progressLabelDLCs.Font = new System.Drawing.Font("Segoe UI", 7F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); - this.progressLabelDLCs.Location = new System.Drawing.Point(12, 251); - this.progressLabelDLCs.Name = "progressLabelDLCs"; - this.progressLabelDLCs.Size = new System.Drawing.Size(560, 12); + this.progressLabelDLCs.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); + this.progressLabelDLCs.AutoEllipsis = true; + this.progressLabelDLCs.Font = new System.Drawing.Font("Segoe UI", 7F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + this.progressLabelDLCs.ForeColor = CreamInstaller.Components.ThemeManager.TextDisabled; + this.progressLabelDLCs.Location = new System.Drawing.Point(12, 258); + this.progressLabelDLCs.Name = "progressLabelDLCs"; + this.progressLabelDLCs.Size = new System.Drawing.Size(560, 12); this.progressLabelDLCs.TabIndex = 12; - this.progressLabelDLCs.Text = "Remaining DLC (2): 123456, 654321"; + // // sortCheckBox // - this.sortCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.sortCheckBox.AutoSize = true; - this.sortCheckBox.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.sortCheckBox.Location = new System.Drawing.Point(120, 328); - this.sortCheckBox.Margin = new System.Windows.Forms.Padding(3, 0, 0, 0); - this.sortCheckBox.Name = "sortCheckBox"; - this.sortCheckBox.Size = new System.Drawing.Size(104, 20); - this.sortCheckBox.TabIndex = 10003; - this.sortCheckBox.Text = "Sort By Name"; + this.sortCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.sortCheckBox.AutoSize = true; + this.sortCheckBox.BackColor = CreamInstaller.Components.ThemeManager.Background; + this.sortCheckBox.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.sortCheckBox.ForeColor = CreamInstaller.Components.ThemeManager.TextPrimary; + this.sortCheckBox.Location = new System.Drawing.Point(120, 335); + this.sortCheckBox.Margin = new System.Windows.Forms.Padding(3, 0, 0, 0); + this.sortCheckBox.Name = "sortCheckBox"; + this.sortCheckBox.Size = new System.Drawing.Size(104, 20); + this.sortCheckBox.TabIndex = 10003; + this.sortCheckBox.Text = "Sort By Name"; this.sortCheckBox.CheckedChanged += new System.EventHandler(this.OnSortCheckBoxChanged); + // // saveButton // - this.saveButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.saveButton.AutoSize = true; + this.saveButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.saveButton.AutoSize = true; this.saveButton.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; - this.saveButton.Enabled = false; - this.saveButton.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.saveButton.Location = new System.Drawing.Point(424, 295); - this.saveButton.Name = "saveButton"; - this.saveButton.Size = new System.Drawing.Size(70, 24); - this.saveButton.TabIndex = 10006; - this.saveButton.Text = "Save DLC"; - this.saveButton.UseVisualStyleBackColor = true; - this.saveButton.Click += new System.EventHandler(this.OnSaveDlc); + this.saveButton.Enabled = false; + this.saveButton.IsAccent = false; + this.saveButton.Location = new System.Drawing.Point(424, 302); + this.saveButton.Name = "saveButton"; + this.saveButton.Size = new System.Drawing.Size(70, 26); + this.saveButton.TabIndex = 10006; + this.saveButton.Text = "Save DLC"; + this.saveButton.Click += new System.EventHandler(this.OnSaveDlc); + // // loadButton // - this.loadButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.loadButton.AutoSize = true; + this.loadButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.loadButton.AutoSize = true; this.loadButton.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; - this.loadButton.Enabled = false; - this.loadButton.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.loadButton.Location = new System.Drawing.Point(500, 295); - this.loadButton.Name = "loadButton"; - this.loadButton.Size = new System.Drawing.Size(72, 24); - this.loadButton.TabIndex = 10005; - this.loadButton.Text = "Load DLC"; - this.loadButton.UseVisualStyleBackColor = true; - this.loadButton.Click += new System.EventHandler(this.OnLoadDlc); + this.loadButton.Enabled = false; + this.loadButton.IsAccent = false; + this.loadButton.Location = new System.Drawing.Point(500, 302); + this.loadButton.Name = "loadButton"; + this.loadButton.Size = new System.Drawing.Size(72, 26); + this.loadButton.TabIndex = 10005; + this.loadButton.Text = "Load DLC"; + this.loadButton.Click += new System.EventHandler(this.OnLoadDlc); + // // resetKoaloaderButton // - this.resetKoaloaderButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.resetKoaloaderButton.AutoSize = true; + this.resetKoaloaderButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.resetKoaloaderButton.AutoSize = true; this.resetKoaloaderButton.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; - this.resetKoaloaderButton.Enabled = false; - this.resetKoaloaderButton.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.resetKoaloaderButton.Location = new System.Drawing.Point(12, 295); - this.resetKoaloaderButton.Name = "resetKoaloaderButton"; - this.resetKoaloaderButton.Size = new System.Drawing.Size(105, 24); - this.resetKoaloaderButton.TabIndex = 10010; - this.resetKoaloaderButton.Text = "Reset Koaloader"; - this.resetKoaloaderButton.UseVisualStyleBackColor = true; - this.resetKoaloaderButton.Click += new System.EventHandler(this.OnResetKoaloader); + this.resetKoaloaderButton.Enabled = false; + this.resetKoaloaderButton.IsAccent = false; + this.resetKoaloaderButton.Location = new System.Drawing.Point(12, 302); + this.resetKoaloaderButton.Name = "resetKoaloaderButton"; + this.resetKoaloaderButton.Size = new System.Drawing.Size(105, 26); + this.resetKoaloaderButton.TabIndex = 10010; + this.resetKoaloaderButton.Text = "Reset Koaloader"; + this.resetKoaloaderButton.Click += new System.EventHandler(this.OnResetKoaloader); + // // resetButton // - this.resetButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.resetButton.AutoSize = true; + this.resetButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.resetButton.AutoSize = true; this.resetButton.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; - this.resetButton.Enabled = false; - this.resetButton.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.resetButton.Location = new System.Drawing.Point(344, 295); - this.resetButton.Name = "resetButton"; - this.resetButton.Size = new System.Drawing.Size(74, 24); - this.resetButton.TabIndex = 10007; - this.resetButton.Text = "Reset DLC"; - this.resetButton.UseVisualStyleBackColor = true; - this.resetButton.Click += new System.EventHandler(this.OnResetDlc); + this.resetButton.Enabled = false; + this.resetButton.IsAccent = false; + this.resetButton.Location = new System.Drawing.Point(344, 302); + this.resetButton.Name = "resetButton"; + this.resetButton.Size = new System.Drawing.Size(74, 26); + this.resetButton.TabIndex = 10007; + this.resetButton.Text = "Reset DLC"; + this.resetButton.Click += new System.EventHandler(this.OnResetDlc); + // // saveKoaloaderButton // - this.saveKoaloaderButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.saveKoaloaderButton.AutoSize = true; + this.saveKoaloaderButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.saveKoaloaderButton.AutoSize = true; this.saveKoaloaderButton.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; - this.saveKoaloaderButton.Enabled = false; - this.saveKoaloaderButton.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.saveKoaloaderButton.Location = new System.Drawing.Point(123, 295); - this.saveKoaloaderButton.Name = "saveKoaloaderButton"; - this.saveKoaloaderButton.Size = new System.Drawing.Size(101, 24); - this.saveKoaloaderButton.TabIndex = 10009; - this.saveKoaloaderButton.Text = "Save Koaloader"; - this.saveKoaloaderButton.UseVisualStyleBackColor = true; - this.saveKoaloaderButton.Click += new System.EventHandler(this.OnSaveKoaloader); + this.saveKoaloaderButton.Enabled = false; + this.saveKoaloaderButton.IsAccent = false; + this.saveKoaloaderButton.Location = new System.Drawing.Point(123, 302); + this.saveKoaloaderButton.Name = "saveKoaloaderButton"; + this.saveKoaloaderButton.Size = new System.Drawing.Size(101, 26); + this.saveKoaloaderButton.TabIndex = 10009; + this.saveKoaloaderButton.Text = "Save Koaloader"; + this.saveKoaloaderButton.Click += new System.EventHandler(this.OnSaveKoaloader); + // // loadKoaloaderButton // - this.loadKoaloaderButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.loadKoaloaderButton.AutoSize = true; + this.loadKoaloaderButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.loadKoaloaderButton.AutoSize = true; this.loadKoaloaderButton.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; - this.loadKoaloaderButton.Enabled = false; - this.loadKoaloaderButton.FlatStyle = System.Windows.Forms.FlatStyle.System; - this.loadKoaloaderButton.Location = new System.Drawing.Point(230, 295); - this.loadKoaloaderButton.Name = "loadKoaloaderButton"; - this.loadKoaloaderButton.Size = new System.Drawing.Size(103, 24); - this.loadKoaloaderButton.TabIndex = 10008; - this.loadKoaloaderButton.Text = "Load Koaloader"; - this.loadKoaloaderButton.UseVisualStyleBackColor = true; - this.loadKoaloaderButton.Click += new System.EventHandler(this.OnLoadKoaloader); + this.loadKoaloaderButton.Enabled = false; + this.loadKoaloaderButton.IsAccent = false; + this.loadKoaloaderButton.Location = new System.Drawing.Point(230, 302); + this.loadKoaloaderButton.Name = "loadKoaloaderButton"; + this.loadKoaloaderButton.Size = new System.Drawing.Size(103, 26); + this.loadKoaloaderButton.TabIndex = 10008; + this.loadKoaloaderButton.Text = "Load Koaloader"; + this.loadKoaloaderButton.Click += new System.EventHandler(this.OnLoadKoaloader); + + // + // statusStrip + // + this.statusStrip.BackColor = CreamInstaller.Components.ThemeManager.Surface; + this.statusStrip.Dock = System.Windows.Forms.DockStyle.Bottom; + this.statusStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.statusLabelGames, + this.statusLabelSep, + this.statusLabelDLCs }); + this.statusStrip.Name = "statusStrip"; + this.statusStrip.Size = new System.Drawing.Size(584, 22); + this.statusStrip.TabIndex = 20000; + + // + // statusLabelGames + // + this.statusLabelGames.ForeColor = CreamInstaller.Components.ThemeManager.TextSecondary; + this.statusLabelGames.Name = "statusLabelGames"; + this.statusLabelGames.Text = "Ready"; + + // + // statusLabelSep + // + this.statusLabelSep.ForeColor = CreamInstaller.Components.ThemeManager.Border; + this.statusLabelSep.Name = "statusLabelSep"; + this.statusLabelSep.Text = " | "; + + // + // statusLabelDLCs + // + this.statusLabelDLCs.ForeColor = CreamInstaller.Components.ThemeManager.TextSecondary; + this.statusLabelDLCs.Name = "statusLabelDLCs"; + this.statusLabelDLCs.Text = ""; + // // SelectForm // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.AutoSize = true; - this.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; - this.ClientSize = new System.Drawing.Size(584, 361); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = CreamInstaller.Components.ThemeManager.Background; + this.ClientSize = new System.Drawing.Size(584, 385); this.Controls.Add(this.loadKoaloaderButton); this.Controls.Add(this.saveKoaloaderButton); this.Controls.Add(this.resetButton); @@ -422,51 +506,60 @@ private void InitializeComponent() this.Controls.Add(this.cancelButton); this.Controls.Add(this.installButton); this.Controls.Add(this.progressLabel); - this.DoubleBuffered = true; - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; - this.MaximizeBox = false; - this.MinimizeBox = false; - this.Name = "SelectForm"; - this.StartPosition = System.Windows.Forms.FormStartPosition.Manual; - this.Text = "SelectForm"; - this.Load += new System.EventHandler(this.OnLoad); + this.Controls.Add(this.searchTextBox); + this.Controls.Add(this.statusStrip); + this.DoubleBuffered = true; + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Sizable; + this.MaximizeBox = false; + this.MinimizeBox = true; + this.MinimumSize = new System.Drawing.Size(600, 420); + this.Name = "SelectForm"; + this.StartPosition = System.Windows.Forms.FormStartPosition.Manual; + this.Text = "SelectForm"; + this.Load += new System.EventHandler(this.OnLoad); + this.ResizeEnd += new System.EventHandler(this.OnResizeEnd); this.programsGroupBox.ResumeLayout(false); this.programsGroupBox.PerformLayout(); this.koaloaderFlowPanel.ResumeLayout(false); this.blockedGamesFlowPanel.ResumeLayout(false); this.allCheckBoxLayoutPanel.ResumeLayout(false); + this.statusStrip.ResumeLayout(false); + this.statusStrip.PerformLayout(); this.ResumeLayout(false); this.PerformLayout(); - } #endregion - private Button installButton; - private Button cancelButton; - private GroupBox programsGroupBox; - private ProgressBar progressBar; - private Label progressLabel; - private CheckBox allCheckBox; - private Button scanButton; - private Label noneFoundLabel; - private CustomTreeView selectionTreeView; - private CheckBox blockedGamesCheckBox; - private Button blockProtectedHelpButton; - private FlowLayoutPanel blockedGamesFlowPanel; - private FlowLayoutPanel allCheckBoxLayoutPanel; - private Button uninstallButton; - private Label progressLabelGames; - private Label progressLabelDLCs; - private CheckBox sortCheckBox; - private FlowLayoutPanel koaloaderFlowPanel; - private CheckBox koaloaderAllCheckBox; - private Button saveButton; - private Button loadButton; - private Button resetKoaloaderButton; - private Button resetButton; - private Button saveKoaloaderButton; - private Button loadKoaloaderButton; + private DarkButton installButton; + private DarkButton cancelButton; + private System.Windows.Forms.GroupBox programsGroupBox; + private DarkProgressBar progressBar; + private System.Windows.Forms.Label progressLabel; + private System.Windows.Forms.CheckBox allCheckBox; + private DarkButton scanButton; + private System.Windows.Forms.Label noneFoundLabel; + private CustomTreeView selectionTreeView; + private System.Windows.Forms.CheckBox blockedGamesCheckBox; + private DarkButton blockProtectedHelpButton; + private System.Windows.Forms.FlowLayoutPanel blockedGamesFlowPanel; + private System.Windows.Forms.FlowLayoutPanel allCheckBoxLayoutPanel; + private DarkButton uninstallButton; + private System.Windows.Forms.Label progressLabelGames; + private System.Windows.Forms.Label progressLabelDLCs; + private System.Windows.Forms.CheckBox sortCheckBox; + private System.Windows.Forms.FlowLayoutPanel koaloaderFlowPanel; + private System.Windows.Forms.CheckBox koaloaderAllCheckBox; + private DarkButton saveButton; + private DarkButton loadButton; + private DarkButton resetKoaloaderButton; + private DarkButton resetButton; + private DarkButton saveKoaloaderButton; + private DarkButton loadKoaloaderButton; + private System.Windows.Forms.TextBox searchTextBox; + private System.Windows.Forms.StatusStrip statusStrip; + private System.Windows.Forms.ToolStripStatusLabel statusLabelGames; + private System.Windows.Forms.ToolStripStatusLabel statusLabelSep; + private System.Windows.Forms.ToolStripStatusLabel statusLabelDLCs; } } - diff --git a/CreamInstaller/Forms/SelectForm.cs b/CreamInstaller/Forms/SelectForm.cs index eea50276..917e05e2 100644 --- a/CreamInstaller/Forms/SelectForm.cs +++ b/CreamInstaller/Forms/SelectForm.cs @@ -41,7 +41,7 @@ internal SelectForm() private List TreeNodes => GatherTreeNodes(selectionTreeView.Nodes); private static void UpdateRemaining(Label label, SynchronizedCollection list, string descriptor) - => label.Text = list.Any() ? $"Remaining {descriptor} ({list.Count}): " + string.Join(", ", list).Replace("&", "&&") : ""; + => label.Text = list.Count > 0 ? $"Remaining {descriptor} ({list.Count}): " + string.Join(", ", list).Replace("&", "&&") : ""; private void UpdateRemainingGames() => UpdateRemaining(progressLabelGames, remainingGames, "games"); @@ -204,7 +204,7 @@ void RemoveFromRemainingGames(string gameName) if (Program.Canceled) return; do // give games steam store api limit priority - Thread.Sleep(200); + await Task.Delay(200); while (!Program.Canceled && steamGamesToCheck > 0); if (Program.Canceled) return; @@ -610,6 +610,9 @@ private async void OnLoad(bool forceScan = false, bool forceProvideChoices = fal scanButton.Enabled = true; blockedGamesCheckBox.Enabled = true; blockProtectedHelpButton.Enabled = true; + AppSettings.Current.LastScanTime = DateTime.Now; + AppSettings.Current.Save(); + UpdateStatus(); } private void OnTreeViewNodeCheckedChanged(object sender, TreeViewEventArgs e) @@ -895,6 +898,10 @@ private void OnLoad(object sender, EventArgs _) { HideProgressBar(); selectionTreeView.AfterCheck += OnTreeViewNodeCheckedChanged; + // Restore window position/size from settings + AppSettings.Current.RestoreFormState(this, restoreSize: true); + AppSettings.Current.SortByName = sortCheckBox.Checked; + sortCheckBox.Checked = AppSettings.Current.SortByName; OnLoad(forceProvideChoices: true); } catch (Exception e) @@ -1126,4 +1133,96 @@ private void OnBlockProtectedGamesHelpButtonClicked(object sender, EventArgs e) private void OnSortCheckBoxChanged(object sender, EventArgs e) => selectionTreeView.TreeViewNodeSorter = sortCheckBox.Checked ? PlatformIdComparer.NodeText : PlatformIdComparer.NodeName; + + // ── Search / Filter ──────────────────────────────────────────────────────── + private void OnSearchTextChanged(object sender, EventArgs e) + { + string query = searchTextBox.Text.Trim(); + FilterTreeNodes(query); + } + + private void FilterTreeNodes(string query) + { + selectionTreeView.BeginUpdate(); + try + { + if (string.IsNullOrWhiteSpace(query)) + { + // Restore all nodes visibility by re-expanding roots + foreach (TreeNode root in selectionTreeView.Nodes) + { + root.BackColor = ThemeManager.Background; + root.ForeColor = Components.ThemeManager.TextPrimary; + } + selectionTreeView.CollapseAll(); + return; + } + string q = query.ToLowerInvariant(); + foreach (TreeNode root in selectionTreeView.Nodes) + { + bool matchRoot = root.Text.Contains(q, StringComparison.OrdinalIgnoreCase); + bool anyChildMatch = root.Nodes.Cast() + .Any(child => child.Text.Contains(q, StringComparison.OrdinalIgnoreCase)); + if (matchRoot || anyChildMatch) + { + root.BackColor = matchRoot ? Components.ThemeManager.SurfaceHover : Components.ThemeManager.Background; + root.ForeColor = Components.ThemeManager.TextPrimary; + root.Expand(); + } + else + { + root.BackColor = Components.ThemeManager.Background; + root.ForeColor = Components.ThemeManager.TextDisabled; + root.Collapse(); + } + } + } + finally + { + selectionTreeView.EndUpdate(); + } + } + + // ── StatusStrip ──────────────────────────────────────────────────────────── + private void UpdateStatus() + { + int gameCount = selectionTreeView.Nodes.Count; + int dlcCount = TreeNodes.Count(n => n.Parent is not null); + int selectedGames = selectionTreeView.Nodes.Cast().Count(n => n.Checked); + + statusLabelGames.Text = gameCount > 0 + ? $"{selectedGames} / {gameCount} games selected" + : "No games found"; + + statusLabelDLCs.Text = dlcCount > 0 + ? $"{dlcCount} DLC entries" + : ""; + + if (AppSettings.Current.LastScanTime.HasValue) + { + TimeSpan ago = DateTime.Now - AppSettings.Current.LastScanTime.Value; + string agoStr = ago.TotalMinutes < 1 ? "just now" + : ago.TotalHours < 1 ? $"{(int)ago.TotalMinutes}m ago" + : ago.TotalDays < 1 ? $"{(int)ago.TotalHours}h ago" + : $"{(int)ago.TotalDays}d ago"; + statusLabelDLCs.Text += (statusLabelDLCs.Text.Length > 0 ? " β€” " : "") + $"Last scan: {agoStr}"; + } + } + + private void OnResizeEnd(object sender, EventArgs e) + { + AppSettings.Current.SaveFormState(this, saveSize: true); + AdjustLayoutForSize(); + } + + private void AdjustLayoutForSize() + { + // Keep programsGroupBox anchored properly on resize + int bottom = ClientSize.Height - statusStrip.Height; + programsGroupBox.Size = programsGroupBox.Size with + { + Height = bottom - programsGroupBox.Top - 170 + }; + } + } \ No newline at end of file diff --git a/CreamInstaller/Platforms/Epic/EpicStore.cs b/CreamInstaller/Platforms/Epic/EpicStore.cs index 61cb9e98..59095ad4 100644 --- a/CreamInstaller/Platforms/Epic/EpicStore.cs +++ b/CreamInstaller/Platforms/Epic/EpicStore.cs @@ -121,7 +121,7 @@ private static async Task QueryGraphQL(string categoryNamespace) HttpClient client = HttpClientManager.HttpClient; if (client is null) return null; - HttpResponseMessage httpResponse = await client.PostAsync(new Uri("https://graphql.epicgames.com/graphql"), content); + using HttpResponseMessage httpResponse = await client.PostAsync(new Uri("https://graphql.epicgames.com/graphql"), content); _ = httpResponse.EnsureSuccessStatusCode(); string response = await httpResponse.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject(response); diff --git a/CreamInstaller/Platforms/Steam/SteamCMD.cs b/CreamInstaller/Platforms/Steam/SteamCMD.cs index ece31b98..9607d2b8 100644 --- a/CreamInstaller/Platforms/Steam/SteamCMD.cs +++ b/CreamInstaller/Platforms/Steam/SteamCMD.cs @@ -62,6 +62,10 @@ private static async Task Run(string appId) } if (Program.Canceled) return ""; + // ANTIVIRUS FALSE POSITIVE WARNING: + // Launches steamcmd.exe with +login anonymous +app_info_print +quit to + // retrieve game metadata from Steam. The process output is captured and parsed. + // SteamCMD is an official Valve tool; launching it does not indicate malicious intent. ProcessStartInfo processStartInfo = new() { FileName = FilePath, RedirectStandardOutput = true, RedirectStandardInput = true, RedirectStandardError = true, @@ -117,6 +121,12 @@ private static async Task Run(string appId) goto wait_for_lock; }); + // ANTIVIRUS FALSE POSITIVE WARNING: + // Setup downloads steamcmd.zip from Valve's official CDN (steamcdn-a.akamaihd.net), + // extracts the ZIP archive, and runs steamcmd.exe once with +quit to initialise it. + // Downloading and extracting an executable is flagged by some AV heuristics as a dropper; + // the source is Valve's own content delivery network and is used only when steamcmd.exe + // is not already present in the CreamInstaller data directory. internal static async Task Setup(IProgress progress) { await Cleanup(); @@ -200,8 +210,8 @@ internal static async Task GetAppInfo(string appId, string branch = " else { output = await Run(appId) ?? ""; - int openBracket = output.IndexOf("{", StringComparison.Ordinal); - int closeBracket = output.LastIndexOf("}", StringComparison.Ordinal); + int openBracket = output.IndexOf('{'); + int closeBracket = output.LastIndexOf('}'); if (openBracket != -1 && closeBracket != -1 && closeBracket > openBracket) { output = $"\"{appId}\"\n" + output[openBracket..(1 + closeBracket)]; @@ -271,6 +281,10 @@ internal static async Task> ParseDlcAppIds(VProperty appInfo) return dlcIds; }); + // ANTIVIRUS FALSE POSITIVE WARNING: + // Kill enumerates running processes by name ("steamcmd") and terminates them. + // Process-enumeration and process-kill APIs are used here only to clean up child + // steamcmd.exe instances that were started by this application. private static async Task Kill() { List tasks = Process.GetProcessesByName("steamcmd").Select(process => Task.Run(() => @@ -292,7 +306,7 @@ private static async Task Kill() internal static void Dispose() { - Kill().Wait(); + Kill().GetAwaiter().GetResult(); if (Directory.Exists(DirectoryPath)) Directory.Delete(DirectoryPath, true); } diff --git a/CreamInstaller/Platforms/Steam/SteamStore.cs b/CreamInstaller/Platforms/Steam/SteamStore.cs index e4f0810c..7ce542ab 100644 --- a/CreamInstaller/Platforms/Steam/SteamStore.cs +++ b/CreamInstaller/Platforms/Steam/SteamStore.cs @@ -133,7 +133,7 @@ internal static async Task QueryStoreAPI(string appId, bool isDlc = fal } if (isDlc || attempts >= 10) return null; - Thread.Sleep(1000); + await Task.Delay(1000); attempts = ++attempts; } } diff --git a/CreamInstaller/Program.cs b/CreamInstaller/Program.cs index 7c3d8b7d..0d55e2d0 100644 --- a/CreamInstaller/Program.cs +++ b/CreamInstaller/Program.cs @@ -18,9 +18,14 @@ internal static class Program private static readonly string Description = Application.ProductName; internal static readonly string Version = Application.ProductVersion; - internal const string RepositoryOwner = "pointfeev"; - internal static readonly string RepositoryName = Name; + internal const string RepositoryOwner = "ubden-community"; + internal static readonly string RepositoryName = "CreamApi-CreamInstaller"; internal static readonly string RepositoryPackage = Name + ".zip"; + + internal const string CommunityDiscussions = "https://github.com/ubden/CreamApi-CreamInstaller/discussions"; + internal const string CommunityForum = "https://forum.ubden.com.tr/konu/creaminstaller-auto-dlc-unlocker-installer-config-gen.1602/"; + internal const string AbuseEmail = "abuse@ubden.com"; + internal const string DonateUrl = "https://ubd.one/donate"; #if DEBUG internal static readonly string ApplicationName = Name + " v" + Version + "-debug: " + Description; internal static readonly string ApplicationNameShort = Name + " v" + Version + "-debug"; @@ -75,13 +80,14 @@ private static void Main() using Mutex mutex = new(true, Name, out bool createdNew); if (createdNew) { - _ = Application.SetHighDpiMode(HighDpiMode.SystemAware); + _ = Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.ApplicationExit += OnApplicationExit; Application.ThreadException += (_, e) => e.Exception.HandleFatalException(); Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); AppDomain.CurrentDomain.UnhandledException += (_, e) => (e.ExceptionObject as Exception)?.HandleFatalException(); + _ = Utility.AppSettings.Current; // Initialize settings on startup retry: try { diff --git a/CreamInstaller/Properties/Resources.resx b/CreamInstaller/Properties/Resources.resx index 56c0201b..cee2dae6 100644 --- a/CreamInstaller/Properties/Resources.resx +++ b/CreamInstaller/Properties/Resources.resx @@ -119,6 +119,6 @@ - ..\resources\ini.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\Resources\ini.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a \ No newline at end of file diff --git a/CreamInstaller/Resources/Koaloader.cs b/CreamInstaller/Resources/Koaloader.cs index c4fc6a63..17c17375 100644 --- a/CreamInstaller/Resources/Koaloader.cs +++ b/CreamInstaller/Resources/Koaloader.cs @@ -35,6 +35,12 @@ internal static void GetKoaloaderComponents(this string directory, out string ol config = directory + @"\Koaloader.config.json"; } + // ANTIVIRUS FALSE POSITIVE WARNING: + // WriteProxy writes a Koaloader DLL (a DLL proxy / shim) named after a well-known Windows + // system DLL (e.g. version.dll, d3d9.dll) into the game's installation directory. + // This is a standard DLL-proxying technique used by the Koaloader project to load + // additional modules. The DLL is extracted from embedded resources and validated via MD5. + // This pattern is commonly misidentified as DLL-hijacking malware by antivirus engines. private static void WriteProxy(this string path, string proxyName, BinaryType binaryType) { foreach (string resourceIdentifier in EmbeddedResources.FindAll(r => r.StartsWith("Koaloader"))) @@ -74,15 +80,15 @@ private static void CheckConfig(string directory, InstallForm installForm = null } SortedList targets = new(PlatformIdComparer.String); SortedList modules = new(PlatformIdComparer.String); + // NOTE: targets and modules are intentionally left empty here; the block below is + // preserved for future use when per-game target/module configuration is implemented. if (targets.Any() || modules.Any()) { /*if (installForm is not null) installForm.UpdateUser("Generating Koaloader configuration for " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation);*/ File.Create(config).Close(); - StreamWriter writer = new(config, true, Encoding.UTF8); + using StreamWriter writer = new(config, true, Encoding.UTF8); WriteConfig(writer, targets, modules, installForm); - writer.Flush(); - writer.Close(); } else if (File.Exists(config)) { @@ -165,6 +171,12 @@ internal static async Task Uninstall(string directory, string rootDirectory = nu await Uninstall(rootDirectory, null, installForm, deleteConfig); }); + // ANTIVIRUS FALSE POSITIVE WARNING: + // The Install method writes Koaloader proxy DLLs and the appropriate unlocker DLLs + // (SmokeAPI32/64.dll, ScreamAPI32/64.dll, UplayR1/R2Unlocker32/64.dll) into the game + // directory. It also deletes outdated proxy DLLs from previous installations. + // Writing named DLLs to application directories is the intended installation procedure β€” + // not malware dropper behavior. All written binaries are MD5-verified. internal static async Task Install(string directory, BinaryType binaryType, ProgramSelection selection, string rootDirectory = null, InstallForm installForm = null, bool generateConfig = true) => await Task.Run(() => diff --git a/CreamInstaller/Resources/Resources.cs b/CreamInstaller/Resources/Resources.cs index 1d625284..481a3b79 100644 --- a/CreamInstaller/Resources/Resources.cs +++ b/CreamInstaller/Resources/Resources.cs @@ -1,4 +1,5 @@ ο»Ώusing System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; @@ -15,6 +16,11 @@ internal static class Resources { private static List embeddedResources; + // Performance: cache computed MD5 hashes keyed by file path to avoid re-hashing the same + // file on every call to IsResourceFile(). The cache is invalidated only when the file is + // written (Write() clears the entry after writing) so stale hashes are never returned. + private static readonly ConcurrentDictionary md5Cache = new(StringComparer.OrdinalIgnoreCase); + private static readonly Dictionary> ResourceMD5s = new() { { @@ -432,17 +438,24 @@ internal static List EmbeddedResources } } + // ANTIVIRUS FALSE POSITIVE WARNING: + // The following two Write methods extract DLL binaries that are embedded in this assembly's + // resources and write them to the target game directory. This is the core installation step + // of the DLC unlockers (SmokeAPI, ScreamAPI, Koaloader, UplayR1/R2 Unlocker). + // These files are verified against a hardcoded MD5 whitelist before and after writing. internal static void Write(this string resourceIdentifier, string filePath) { using Stream resource = Assembly.GetExecutingAssembly().GetManifestResourceStream("CreamInstaller.Resources." + resourceIdentifier); using FileStream file = new(filePath, FileMode.Create, FileAccess.Write); resource?.CopyTo(file); + md5Cache.TryRemove(filePath, out _); // invalidate cached hash after file is written } internal static void Write(this byte[] resource, string filePath) { using FileStream fileStream = new(filePath, FileMode.Create, FileAccess.Write); fileStream.Write(resource); + md5Cache.TryRemove(filePath, out _); // invalidate cached hash after file is written } internal static bool IsFilePathLocked(this string filePath) @@ -518,6 +531,11 @@ private static bool IsCommonIncorrectExecutable(this string rootDirectory, strin || subPath.Contains("ANTICHEAT"); } + // ANTIVIRUS FALSE POSITIVE WARNING: + // Scans the game's installation directory tree looking for platform-specific DLL files + // (steam_api.dll, EOSSDK-*.dll, uplay_r1_loader*.dll, upc_r2_loader*.dll) to determine + // which subdirectories contain unlocker targets. This recursive directory + file scan + // is purely read-only detection, not malicious file enumeration. internal static async Task> GetDllDirectoriesFromGameDirectory(this string gameDirectory, Platform platform) => await Task.Run(() => { @@ -582,12 +600,17 @@ private static string ComputeMD5(this string filePath) { if (!File.Exists(filePath)) return null; + // Performance: return cached hash if the file has already been hashed this session. + if (md5Cache.TryGetValue(filePath, out string cached)) + return cached; #pragma warning disable CA5351 using MD5 md5 = MD5.Create(); #pragma warning restore CA5351 using FileStream stream = File.OpenRead(filePath); byte[] hash = md5.ComputeHash(stream); - return BitConverter.ToString(hash).Replace("-", "").ToUpperInvariant(); + string result = BitConverter.ToString(hash).Replace("-", "").ToUpperInvariant(); + md5Cache[filePath] = result; + return result; } internal static bool IsResourceFile(this string filePath, ResourceIdentifier identifier) diff --git a/CreamInstaller/Resources/ScreamAPI.cs b/CreamInstaller/Resources/ScreamAPI.cs index fbc039f3..5f7e311c 100644 --- a/CreamInstaller/Resources/ScreamAPI.cs +++ b/CreamInstaller/Resources/ScreamAPI.cs @@ -40,11 +40,9 @@ internal static void CheckConfig(string directory, ProgramSelection selection, I /*if (installForm is not null) installForm.UpdateUser("Generating ScreamAPI configuration for " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation);*/ File.Create(config).Close(); - StreamWriter writer = new(config, true, Encoding.UTF8); + using StreamWriter writer = new(config, true, Encoding.UTF8); WriteConfig(writer, new(overrideCatalogItems.ToDictionary(pair => pair.Key, pair => pair.Value), PlatformIdComparer.String), new(entitlements.ToDictionary(pair => pair.Key, pair => pair.Value), PlatformIdComparer.String), installForm); - writer.Flush(); - writer.Close(); } else if (File.Exists(config)) { @@ -101,6 +99,9 @@ private static void WriteConfig(StreamWriter writer, SortedList await Task.Run(() => { @@ -139,6 +140,9 @@ internal static async Task Uninstall(string directory, InstallForm installForm = } }); + // ANTIVIRUS FALSE POSITIVE WARNING: + // Install renames EOSSDK-Win32/64-Shipping.dll to *_o backups and writes ScreamAPI in + // their place. Replacing Epic Online Services SDK DLLs is the intended installation method. internal static async Task Install(string directory, ProgramSelection selection, InstallForm installForm = null, bool generateConfig = true) => await Task.Run(() => { diff --git a/CreamInstaller/Resources/SmokeAPI.cs b/CreamInstaller/Resources/SmokeAPI.cs index 6462e3ec..9fd27d0c 100644 --- a/CreamInstaller/Resources/SmokeAPI.cs +++ b/CreamInstaller/Resources/SmokeAPI.cs @@ -61,12 +61,10 @@ internal static void CheckConfig(string directory, ProgramSelection selection, I /*if (installForm is not null) installForm.UpdateUser("Generating SmokeAPI configuration for " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation);*/ File.Create(config).Close(); - StreamWriter writer = new(config, true, Encoding.UTF8); + using StreamWriter writer = new(config, true, Encoding.UTF8); WriteConfig(writer, selection.Id, new(extraApps.ToDictionary(pair => pair.Key, pair => pair.Value), PlatformIdComparer.String), new(overrideDlc.ToDictionary(pair => pair.Key, pair => pair.Value), PlatformIdComparer.String), new(injectDlc.ToDictionary(pair => pair.Key, pair => pair.Value), PlatformIdComparer.String), installForm); - writer.Flush(); - writer.Close(); } else if (File.Exists(config)) { @@ -102,7 +100,7 @@ private static void WriteConfig(StreamWriter writer, string appId, else writer.WriteLine(" \"override_dlc_status\": {},"); writer.WriteLine(" \"auto_inject_inventory\": true,"); - writer.WriteLine(" \"extra_inventory_items\": {},"); + writer.WriteLine(" \"extra_inventory_items\": [],"); if (injectDlc.Count > 0 || extraApps.Count > 0) { writer.WriteLine(" \"extra_dlcs\": {"); @@ -151,6 +149,10 @@ private static void WriteConfig(StreamWriter writer, string appId, writer.WriteLine("}"); } + // ANTIVIRUS FALSE POSITIVE WARNING: + // Uninstall deletes the SmokeAPI DLL and moves the original steam_api.dll / steam_api64.dll + // backups back to their original names. Renaming and deleting DLLs in Steam game directories + // may be flagged as malicious file manipulation; this is the intended restore procedure. internal static async Task Uninstall(string directory, InstallForm installForm = null, bool deleteOthers = true) => await Task.Run(() => { @@ -211,6 +213,11 @@ internal static async Task Uninstall(string directory, InstallForm installForm = } }); + // ANTIVIRUS FALSE POSITIVE WARNING: + // Install renames the original steam_api.dll / steam_api64.dll to *_o.dll backups and + // writes the SmokeAPI replacement DLL in their place. Replacing Steamworks DLLs is the + // intended SmokeAPI installation method. This pattern is commonly flagged as a trojan + // dropper or DLL hijack by heuristic antivirus scanners. internal static async Task Install(string directory, ProgramSelection selection, InstallForm installForm = null, bool generateConfig = true) => await Task.Run(() => { diff --git a/CreamInstaller/Resources/UplayR1.cs b/CreamInstaller/Resources/UplayR1.cs index c0fce841..bea89a58 100644 --- a/CreamInstaller/Resources/UplayR1.cs +++ b/CreamInstaller/Resources/UplayR1.cs @@ -34,10 +34,8 @@ internal static void CheckConfig(string directory, ProgramSelection selection, I /*if (installForm is not null) installForm.UpdateUser("Generating Uplay R1 Unlocker configuration for " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation);*/ File.Create(config).Close(); - StreamWriter writer = new(config, true, Encoding.UTF8); + using StreamWriter writer = new(config, true, Encoding.UTF8); WriteConfig(writer, new(blacklistDlc.ToDictionary(pair => pair.Key, pair => pair.Value), PlatformIdComparer.String), installForm); - writer.Flush(); - writer.Close(); } else if (File.Exists(config)) { @@ -71,6 +69,8 @@ private static void WriteConfig(StreamWriter writer, SortedList await Task.Run(() => { @@ -109,6 +109,9 @@ internal static async Task Uninstall(string directory, InstallForm installForm = } }); + // ANTIVIRUS FALSE POSITIVE WARNING: + // Install renames uplay_r1_loader*.dll to *_o backups and writes the Uplay R1 Unlocker DLL. + // Replacing Uplay/Ubisoft Connect loader DLLs is the intended installation method. internal static async Task Install(string directory, ProgramSelection selection, InstallForm installForm = null, bool generateConfig = true) => await Task.Run(() => { diff --git a/CreamInstaller/Resources/UplayR2.cs b/CreamInstaller/Resources/UplayR2.cs index 1f2c8ced..8e21e5a7 100644 --- a/CreamInstaller/Resources/UplayR2.cs +++ b/CreamInstaller/Resources/UplayR2.cs @@ -36,10 +36,8 @@ internal static void CheckConfig(string directory, ProgramSelection selection, I /*if (installForm is not null) installForm.UpdateUser("Generating Uplay R2 Unlocker configuration for " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation);*/ File.Create(config).Close(); - StreamWriter writer = new(config, true, Encoding.UTF8); + using StreamWriter writer = new(config, true, Encoding.UTF8); WriteConfig(writer, new(blacklistDlc.ToDictionary(pair => pair.Key, pair => pair.Value), PlatformIdComparer.String), installForm); - writer.Flush(); - writer.Close(); } else if (File.Exists(config)) { @@ -75,6 +73,8 @@ private static void WriteConfig(StreamWriter writer, SortedList await Task.Run(() => { @@ -116,6 +116,9 @@ internal static async Task Uninstall(string directory, InstallForm installForm = } }); + // ANTIVIRUS FALSE POSITIVE WARNING: + // Install renames upc_r2_loader*.dll to *_o backups and writes the Uplay R2 Unlocker DLL. + // Replacing Ubisoft Connect R2 loader DLLs is the intended installation method. internal static async Task Install(string directory, ProgramSelection selection, InstallForm installForm = null, bool generateConfig = true) => await Task.Run(() => { diff --git a/CreamInstaller/Utility/AppSettings.cs b/CreamInstaller/Utility/AppSettings.cs new file mode 100644 index 00000000..2c3298b9 --- /dev/null +++ b/CreamInstaller/Utility/AppSettings.cs @@ -0,0 +1,135 @@ +using System; +using System.IO; +using System.Windows.Forms; +using Newtonsoft.Json; + +namespace CreamInstaller.Utility; + +/// +/// Persistent application settings stored as JSON in %AppData%\CreamInstaller\settings.json. +/// +internal sealed class AppSettings +{ + private static readonly string SettingsPath = + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + "CreamInstaller", "settings.json"); + + private static AppSettings _current; + internal static AppSettings Current => _current ??= Load(); + + // ── Settings properties ──────────────────────────────────────────────────── + [JsonProperty] + internal bool SortByName { get; set; } = false; + + [JsonProperty] + internal bool RememberWindowPositions { get; set; } = true; + + [JsonProperty] + internal int MainWindowX { get; set; } = -1; + + [JsonProperty] + internal int MainWindowY { get; set; } = -1; + + [JsonProperty] + internal int SelectWindowX { get; set; } = -1; + + [JsonProperty] + internal int SelectWindowY { get; set; } = -1; + + [JsonProperty] + internal int SelectWindowWidth { get; set; } = 600; + + [JsonProperty] + internal int SelectWindowHeight { get; set; } = 400; + + [JsonProperty] + internal DateTime? LastScanTime { get; set; } = null; + + [JsonProperty] + internal bool DisclaimerShown { get; set; } = false; + + // ── Load / Save ──────────────────────────────────────────────────────────── + internal static AppSettings Load() + { + try + { + if (File.Exists(SettingsPath)) + { + string json = File.ReadAllText(SettingsPath); + AppSettings loaded = JsonConvert.DeserializeObject(json); + if (loaded is not null) + return loaded; + } + } + catch + { + // ignored β€” fall through to defaults + } + return new AppSettings(); + } + + internal void Save() + { + try + { + string dir = Path.GetDirectoryName(SettingsPath); + if (dir is not null && !Directory.Exists(dir)) + Directory.CreateDirectory(dir); + File.WriteAllText(SettingsPath, JsonConvert.SerializeObject(this, Formatting.Indented)); + } + catch + { + // ignored + } + } + + /// Saves the position and size of a form if RememberWindowPositions is enabled. + internal void SaveFormState(Form form, bool saveSize = false) + { + if (!RememberWindowPositions || form is null) return; + if (form.WindowState != FormWindowState.Normal) return; + + switch (form) + { + case Forms.MainForm: + MainWindowX = form.Location.X; + MainWindowY = form.Location.Y; + break; + case Forms.SelectForm: + SelectWindowX = form.Location.X; + SelectWindowY = form.Location.Y; + if (saveSize) + { + SelectWindowWidth = form.Size.Width; + SelectWindowHeight = form.Size.Height; + } + break; + } + Save(); + } + + /// Restores the position (and optionally size) of a form. + internal void RestoreFormState(Form form, bool restoreSize = false) + { + if (!RememberWindowPositions || form is null) return; + + int x = -1, y = -1, w = 0, h = 0; + switch (form) + { + case Forms.MainForm: + x = MainWindowX; y = MainWindowY; + break; + case Forms.SelectForm: + x = SelectWindowX; y = SelectWindowY; + w = SelectWindowWidth; h = SelectWindowHeight; + break; + } + + if (x < 0 || y < 0) return; + + form.StartPosition = FormStartPosition.Manual; + form.Location = new System.Drawing.Point(x, y); + if (restoreSize && w > 200 && h > 100) + form.Size = new System.Drawing.Size(w, h); + } +} diff --git a/CreamInstaller/Utility/Diagnostics.cs b/CreamInstaller/Utility/Diagnostics.cs index d44317a1..ad48eba1 100644 --- a/CreamInstaller/Utility/Diagnostics.cs +++ b/CreamInstaller/Utility/Diagnostics.cs @@ -9,6 +9,9 @@ internal static class Diagnostics { private static string notepadPlusPlusPath; + // ANTIVIRUS FALSE POSITIVE WARNING: + // Reading registry keys from HKEY_LOCAL_MACHINE to detect installed applications. + // This is standard application-detection behavior, not malicious registry enumeration. internal static string NotepadPlusPlusPath { get @@ -21,26 +24,60 @@ internal static string NotepadPlusPlusPath internal static string GetNotepadPath() { - string npp = NotepadPlusPlusPath + @"\notepad++.exe"; - return File.Exists(npp) ? npp : Environment.GetFolderPath(Environment.SpecialFolder.Windows) + @"\notepad.exe"; + // Bug fix: NotepadPlusPlusPath may be null; guard before string concatenation. + string nppBase = NotepadPlusPlusPath; + string npp = nppBase is not null ? nppBase + @"\notepad++.exe" : null; + return npp is not null && File.Exists(npp) + ? npp + : Environment.GetFolderPath(Environment.SpecialFolder.Windows) + @"\notepad.exe"; } internal static void OpenFileInNotepad(string path) { - string npp = NotepadPlusPlusPath + @"\notepad++.exe"; - if (File.Exists(npp)) + // Bug fix: NotepadPlusPlusPath may be null; guard before string concatenation. + string nppBase = NotepadPlusPlusPath; + string npp = nppBase is not null ? nppBase + @"\notepad++.exe" : null; + if (npp is not null && File.Exists(npp)) OpenFileInNotepadPlusPlus(npp, path); else OpenFileInWindowsNotepad(path); } - private static void OpenFileInNotepadPlusPlus(string npp, string path) => Process.Start(new ProcessStartInfo { FileName = npp, Arguments = path }); + // ANTIVIRUS FALSE POSITIVE WARNING: + // The following three methods launch external processes (Notepad++, notepad.exe, explorer.exe). + // They are invoked only on explicit user request to view a configuration file or directory. + // ArgumentList is used instead of the Arguments string to prevent argument-injection vulnerabilities. + private static void OpenFileInNotepadPlusPlus(string npp, string path) + { + if (!File.Exists(path)) + return; + ProcessStartInfo psi = new() { FileName = npp, UseShellExecute = false }; + psi.ArgumentList.Add(path); + _ = Process.Start(psi); + } - private static void OpenFileInWindowsNotepad(string path) => Process.Start(new ProcessStartInfo { FileName = "notepad.exe", Arguments = path }); + private static void OpenFileInWindowsNotepad(string path) + { + if (!File.Exists(path)) + return; + ProcessStartInfo psi = new() { FileName = "notepad.exe", UseShellExecute = false }; + psi.ArgumentList.Add(path); + _ = Process.Start(psi); + } - internal static void OpenDirectoryInFileExplorer(string path) => Process.Start(new ProcessStartInfo { FileName = "explorer.exe", Arguments = path }); + internal static void OpenDirectoryInFileExplorer(string path) + { + if (!Directory.Exists(path)) + return; + ProcessStartInfo psi = new() { FileName = "explorer.exe", UseShellExecute = false }; + psi.ArgumentList.Add(path); + _ = Process.Start(psi); + } - internal static void OpenUrlInInternetBrowser(string url) => Process.Start(new ProcessStartInfo { FileName = url, UseShellExecute = true }); + // ANTIVIRUS FALSE POSITIVE WARNING: + // Opens a URL in the user's default browser via ShellExecute. This is standard behavior + // for linking to the project's GitHub page from the Help button β€” not drive-by download. + internal static void OpenUrlInInternetBrowser(string url) => _ = Process.Start(new ProcessStartInfo { FileName = url, UseShellExecute = true }); internal static string BeautifyPath(this string path) => path is null ? null : Path.TrimEndingDirectorySeparator(Path.GetFullPath(path)); } \ No newline at end of file diff --git a/CreamInstaller/Utility/HttpClientManager.cs b/CreamInstaller/Utility/HttpClientManager.cs index 2fddcad6..26fc1556 100644 --- a/CreamInstaller/Utility/HttpClientManager.cs +++ b/CreamInstaller/Utility/HttpClientManager.cs @@ -12,10 +12,19 @@ internal static class HttpClientManager internal static void Setup() { - HttpClient = new(); + HttpClient = new() + { + // Performance: limit request duration so the UI does not hang indefinitely + // on slow or unresponsive endpoints (Steam API, Epic API, image CDNs). + Timeout = TimeSpan.FromSeconds(60) + }; HttpClient.DefaultRequestHeaders.Add("User-Agent", $"CI{Program.Version.Replace(".", "")}"); } + // ANTIVIRUS FALSE POSITIVE WARNING: + // This method makes outbound HTTPS GET requests to Steam Store / Epic Games Store APIs + // to retrieve game and DLC metadata. All requests are read-only and user-initiated. + // No data is sent to any third-party server beyond the query string. internal static async Task EnsureGet(string url) { try @@ -43,6 +52,9 @@ internal static async Task GetDocumentNodes(string url, stri internal static HtmlNodeCollection GetDocumentNodes(this HtmlDocument htmlDocument, string xpath) => htmlDocument.DocumentNode?.SelectNodes(xpath); + // ANTIVIRUS FALSE POSITIVE WARNING: + // Downloads game cover art images from Steam / Epic CDNs for display in the UI. + // This is a standard image-fetch pattern, not a silent payload download. internal static async Task GetImageFromUrl(string url) { try diff --git a/CreamInstaller/Utility/IconGrabber.cs b/CreamInstaller/Utility/IconGrabber.cs index 7e59c38d..49ec473f 100644 --- a/CreamInstaller/Utility/IconGrabber.cs +++ b/CreamInstaller/Utility/IconGrabber.cs @@ -1,6 +1,7 @@ ο»Ώusing System; using System.Drawing; using System.IO; +using System.Runtime.InteropServices; namespace CreamInstaller.Utility; @@ -10,10 +11,21 @@ internal static class IconGrabber internal const string GoogleFaviconsApiUrl = "https://www.google.com/s2/favicons"; + [DllImport("user32.dll", SetLastError = true)] + private static extern bool DestroyIcon(IntPtr hIcon); + internal static Icon ToIcon(this Image image) { using Bitmap dialogIconBitmap = new(image, new(image.Width, image.Height)); - return Icon.FromHandle(dialogIconBitmap.GetHicon()); + IntPtr hIcon = dialogIconBitmap.GetHicon(); + try + { + return (Icon)Icon.FromHandle(hIcon).Clone(); + } + finally + { + DestroyIcon(hIcon); + } } internal static string GetDomainFaviconUrl(string domain, int size = 16) => GoogleFaviconsApiUrl + $"?domain={domain}&sz={size}"; diff --git a/CreamInstaller/Utility/ProgramData.cs b/CreamInstaller/Utility/ProgramData.cs index 344c26f0..37243aad 100644 --- a/CreamInstaller/Utility/ProgramData.cs +++ b/CreamInstaller/Utility/ProgramData.cs @@ -102,8 +102,7 @@ private static void SetCooldown(string identifier, DateTime time) return Enumerable.Empty<(Platform platform, string id)>(); try { - return JsonConvert.DeserializeObject(File.ReadAllText(ProgramChoicesPath), typeof(List<(Platform platform, string id)>)) as - List<(Platform platform, string id)>; + return JsonConvert.DeserializeObject>(File.ReadAllText(ProgramChoicesPath)); } catch { @@ -111,11 +110,11 @@ private static void SetCooldown(string identifier, DateTime time) } } - internal static void WriteProgramChoices(IEnumerable<(Platform platform, string id)> choices) + internal static void WriteProgramChoices(List<(Platform platform, string id)> choices) { try { - if (choices is null || !choices.Any()) + if (choices is null || choices.Count == 0) File.Delete(ProgramChoicesPath); else File.WriteAllText(ProgramChoicesPath, JsonConvert.SerializeObject(choices)); @@ -132,8 +131,7 @@ internal static void WriteProgramChoices(IEnumerable<(Platform platform, string return Enumerable.Empty<(Platform platform, string gameId, string dlcId)>(); try { - return JsonConvert.DeserializeObject(File.ReadAllText(DlcChoicesPath), typeof(IEnumerable<(Platform platform, string gameId, string dlcId)>)) as - IEnumerable<(Platform platform, string gameId, string dlcId)>; + return JsonConvert.DeserializeObject>(File.ReadAllText(DlcChoicesPath)); } catch { @@ -145,7 +143,7 @@ internal static void WriteDlcChoices(List<(Platform platform, string gameId, str { try { - if (choices is null || !choices.Any()) + if (choices is null || choices.Count == 0) File.Delete(DlcChoicesPath); else File.WriteAllText(DlcChoicesPath, JsonConvert.SerializeObject(choices)); @@ -162,9 +160,7 @@ internal static void WriteDlcChoices(List<(Platform platform, string gameId, str return Enumerable.Empty<(Platform platform, string id, string proxy, bool enabled)>(); try { - return JsonConvert.DeserializeObject(File.ReadAllText(KoaloaderProxyChoicesPath), - typeof(IEnumerable<(Platform platform, string id, string proxy, bool enabled)>)) as - IEnumerable<(Platform platform, string id, string proxy, bool enabled)>; + return JsonConvert.DeserializeObject>(File.ReadAllText(KoaloaderProxyChoicesPath)); } catch { @@ -172,11 +168,11 @@ internal static void WriteDlcChoices(List<(Platform platform, string gameId, str } } - internal static void WriteKoaloaderProxyChoices(IEnumerable<(Platform platform, string id, string proxy, bool enabled)> choices) + internal static void WriteKoaloaderProxyChoices(List<(Platform platform, string id, string proxy, bool enabled)> choices) { try { - if (choices is null || !choices.Any()) + if (choices is null || choices.Count == 0) File.Delete(KoaloaderProxyChoicesPath); else File.WriteAllText(KoaloaderProxyChoicesPath, JsonConvert.SerializeObject(choices)); diff --git a/README.md b/README.md index f512104f..64e7dfd3 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,64 @@ -### CreamInstaller: Automatic DLC Unlocker Installer & Configuration Generator +### CreamInstaller v5.0: Automatic DLC Unlocker Installer & Configuration Generator +--- + +# ⚠️ Disclaimer +## (Read before installation and Follow Us on Github !) + +> **This software is an open-source project developed for the community and is not affiliated with any organization or institution.** +> It is shared purely for **educational purposes**, software development testing, and to contribute to the growth of the open-source community. + +--- + +### πŸ›‘οΈ Antivirus / False Positive Warning + +> ⚠️ **As is widely known, all software that modifies or interacts with DLL files may be flagged as a virus by antivirus programs.** + +VirusTotal scan results and antivirus software **may detect this project as malicious**. However: + +- The **entire project is open source** β€” no encrypted or obfuscated code is included. +- It is intended solely for **educational and development purposes**. +- This software is **for experienced users only**. If you are not comfortable reviewing source code yourself, **downloading and using this software is not recommended**. + +For further reading on antivirus false positives related to DLL-interacting tools, see: +πŸ“„ [Springer – *International Journal of Information Security* (2024)](https://link.springer.com/article/10.1007/s10207-024-00836-w) +πŸ“– [Wikipedia – Antivirus Software](https://en.wikipedia.org/wiki/Antivirus_software) + +--- + +### βš–οΈ Legal Responsibility +By using this software, you agree that: +- **All responsibility lies with you, the user.** +- The platform and its contributors provide this software **"as is"**, without any warranty of any kind, express or implied. + This includes, but is not limited to, the warranties of **merchantability**, **fitness for a particular purpose**, or **non-infringement**. + +> ⚠️ **Use it at your own risk.** + +--- -##DONATE +### 🎯 Intended Use +The primary purpose of this project is to: +- Educate the community by sharing open-source code. +- Facilitate learning and encourage innovation through open collaboration. + +❌ **This software is not intended for production use.** +We strongly recommend purchasing and using professionally licensed software for your needs. + +--- + +### πŸ™Œ Support the Community +If you would like to support the community and this project, consider making a donation: +[![Donate](https://img.shields.io/badge/Donate-Click%20Here-orange?style=for-the-badge&logo=paypal)](https://ubd.one/donate) + +--- + +### 🚨 Report Abuse +If you encounter any abuse or misuse of this software, please report it to: +πŸ“§ **[abuse@ubden.com](mailto:abuse@ubden.com)** + +--- + +> Thank you for being a part of the open-source community! 🌟 -# Https://ubd.one/donate ###### The program utilizes the latest versions of [Koaloader](https://github.com/acidicoala/Koaloader), [SmokeAPI](https://github.com/acidicoala/SmokeAPI), [ScreamAPI](https://github.com/acidicoala/ScreamAPI), [Uplay R1 Unlocker](https://github.com/acidicoala/UplayR1Unlocker) and [Uplay R2 Unlocker](https://github.com/acidicoala/UplayR2Unlocker), all by the wonderful [acidicoala](https://github.com/acidicoala), and all downloaded from the posts above and embedded into the program itself; no further downloads necessary on your part! --- @@ -29,10 +85,18 @@ games and DLCs the user selects; however, through the use of **right-click conte --- #### Installation: -1. Click [here]([https://github.com/pointfeev/CreamInstaller/releases/latest/download/CreamInstaller.zip](https://github.com/ubden/CreamApi/releases)) to download the latest release from [GitHub]([https://github.com/pointfeev/CreamInstaller](https://github.com/ubden/CreamApi/releases)). -2. Extract the executable to anywhere on your computer you want. *It's completely self-contained.* +1. Download the latest release from [GitHub Releases](https://github.com/ubden-community/CreamApi-CreamInstaller/releases/latest). +2. Extract `CreamInstaller.exe` to anywhere on your computer. *It's completely self-contained.* + +If the program doesn't launch, install the [.NET 9 Desktop Runtime (x64)](https://dotnet.microsoft.com/en-us/download/dotnet/9.0). + +--- +#### Building from Source: +To build the project from source code, you need: +1. [.NET 9 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/9.0) or later (SDK 10.x is also supported) +2. Visual Studio 2022 (or later) with .NET desktop development workload, or Visual Studio Code with C# extension -If the program doesn't seem to launch, try downloading and installing [.NET 7 Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-7.0.2-windows-x64-installer). +**Note:** The application targets .NET 9. The `global.json` file uses `rollForward: latestMajor` for maximum SDK compatibility. --- #### **NOTE:** This program does not automatically download nor install actual DLC files for you. As the title of the program says, it's only a DLC Unlocker installer. Should the game you wish to unlock DLC for not already come with the DLCs installed (very many do not), you have to find, download, and install those yourself. Preferably, you should be referring to the proper cs.rin.ru post for the game(s) you're tinkering with; you'll usually find any answer to your problems there. @@ -51,6 +115,12 @@ If the program doesn't seem to launch, try downloading and installing [.NET 7 Ru --- ##### Bugs/Crashes/Issues: -For reliable and quick assistance, all bugs, crashes and other issues should be referred to the [GitHub Issues]([https://github.com/](https://github.com/ubden/CreamApi/issues)) page! +For reliable and quick assistance, all bugs, crashes and other issues should be referred to the [GitHub Issues](https://github.com/ubden-community/CreamApi-CreamInstaller/issues) page! + +> ⚠️ **No official support is provided.** For community help visit: +> - [GitHub Discussions](https://github.com/ubden/CreamApi-CreamInstaller/discussions) +> - [ubden Forum](https://forum.ubden.com.tr/konu/creaminstaller-auto-dlc-unlocker-installer-config-gen.1602/) --- + + diff --git a/global.json b/global.json new file mode 100644 index 00000000..3c5cf51d --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "9.0.0", + "rollForward": "latestMajor", + "allowPrerelease": false + } +}