├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug-report.md │ ├── enhancement-request.md │ └── false-positives.md └── workflows │ ├── delete_releases.yml │ ├── jekyll-gh-pages.yml │ └── test.yml ├── .gitignore ├── CreamInstaller.sln ├── CreamInstaller ├── Components │ ├── ContextMenuItem.cs │ ├── CustomForm.cs │ ├── CustomTreeView.cs │ └── PlatformIdComparer.cs ├── CreamInstaller.csproj ├── Forms │ ├── DebugForm.Designer.cs │ ├── DebugForm.cs │ ├── DebugForm.resx │ ├── DialogForm.Designer.cs │ ├── DialogForm.cs │ ├── DialogForm.resx │ ├── InstallForm.Designer.cs │ ├── InstallForm.cs │ ├── InstallForm.resx │ ├── MainForm.Designer.cs │ ├── MainForm.cs │ ├── MainForm.resx │ ├── SelectDialogForm.Designer.cs │ ├── SelectDialogForm.cs │ ├── SelectDialogForm.resx │ ├── SelectForm.Designer.cs │ ├── SelectForm.cs │ └── SelectForm.resx ├── GlobalSuppressions.cs ├── Platforms │ ├── Epic │ │ ├── EpicLibrary.cs │ │ ├── EpicStore.cs │ │ ├── GraphQL │ │ │ ├── Request.cs │ │ │ └── Response.cs │ │ └── Manifest.cs │ ├── Paradox │ │ └── ParadoxLauncher.cs │ ├── Steam │ │ ├── AppDetails.cs │ │ ├── SteamCMD.cs │ │ ├── SteamLibrary.cs │ │ ├── SteamStore.cs │ │ └── ValveDataFile.cs │ └── Ubisoft │ │ └── UbisoftLibrary.cs ├── Program.cs ├── ProgramSelection.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ └── launchSettings.json ├── Resources │ ├── Koaloader.cs │ ├── Koaloader │ │ ├── audioses-32 │ │ │ └── audioses.dll │ │ ├── audioses-64 │ │ │ └── audioses.dll │ │ ├── d3d10-32 │ │ │ └── d3d10.dll │ │ ├── d3d10-64 │ │ │ └── d3d10.dll │ │ ├── d3d11-32 │ │ │ └── d3d11.dll │ │ ├── d3d11-64 │ │ │ └── d3d11.dll │ │ ├── d3d9-32 │ │ │ └── d3d9.dll │ │ ├── d3d9-64 │ │ │ └── d3d9.dll │ │ ├── dinput8-32 │ │ │ └── dinput8.dll │ │ ├── dinput8-64 │ │ │ └── dinput8.dll │ │ ├── dwmapi-32 │ │ │ └── dwmapi.dll │ │ ├── dwmapi-64 │ │ │ └── dwmapi.dll │ │ ├── dxgi-32 │ │ │ └── dxgi.dll │ │ ├── dxgi-64 │ │ │ └── dxgi.dll │ │ ├── glu32-32 │ │ │ └── glu32.dll │ │ ├── glu32-64 │ │ │ └── glu32.dll │ │ ├── hid-32 │ │ │ └── hid.dll │ │ ├── hid-64 │ │ │ └── hid.dll │ │ ├── iphlpapi-32 │ │ │ └── iphlpapi.dll │ │ ├── iphlpapi-64 │ │ │ └── iphlpapi.dll │ │ ├── msasn1-32 │ │ │ └── msasn1.dll │ │ ├── msasn1-64 │ │ │ └── msasn1.dll │ │ ├── msimg32-32 │ │ │ └── msimg32.dll │ │ ├── msimg32-64 │ │ │ └── msimg32.dll │ │ ├── mswsock-32 │ │ │ └── mswsock.dll │ │ ├── mswsock-64 │ │ │ └── mswsock.dll │ │ ├── opengl32-32 │ │ │ └── opengl32.dll │ │ ├── opengl32-64 │ │ │ └── opengl32.dll │ │ ├── profapi-32 │ │ │ └── profapi.dll │ │ ├── profapi-64 │ │ │ └── profapi.dll │ │ ├── propsys-32 │ │ │ └── propsys.dll │ │ ├── propsys-64 │ │ │ └── propsys.dll │ │ ├── textshaping-32 │ │ │ └── textshaping.dll │ │ ├── textshaping-64 │ │ │ └── textshaping.dll │ │ ├── version-32 │ │ │ └── version.dll │ │ ├── version-64 │ │ │ └── version.dll │ │ ├── winhttp-32 │ │ │ └── winhttp.dll │ │ ├── winhttp-64 │ │ │ └── winhttp.dll │ │ ├── winmm-32 │ │ │ └── winmm.dll │ │ ├── winmm-64 │ │ │ └── winmm.dll │ │ ├── wldp-32 │ │ │ └── wldp.dll │ │ ├── wldp-64 │ │ │ └── wldp.dll │ │ ├── xinput9_1_0-32 │ │ │ └── xinput9_1_0.dll │ │ └── xinput9_1_0-64 │ │ │ └── xinput9_1_0.dll │ ├── Resources.cs │ ├── ScreamAPI.cs │ ├── ScreamAPI │ │ ├── EOSSDK-Win32-Shipping.dll │ │ └── EOSSDK-Win64-Shipping.dll │ ├── SmokeAPI.cs │ ├── SmokeAPI │ │ ├── steam_api.dll │ │ └── steam_api64.dll │ ├── UplayR1.cs │ ├── UplayR1 │ │ ├── uplay_r1_loader.dll │ │ └── uplay_r1_loader64.dll │ ├── UplayR2.cs │ ├── UplayR2 │ │ ├── upc_r2_loader.dll │ │ └── upc_r2_loader64.dll │ └── ini.ico └── Utility │ ├── Diagnostics.cs │ ├── ExceptionHandler.cs │ ├── HttpClientManager.cs │ ├── IconGrabber.cs │ ├── LogTextBox.cs │ └── ProgramData.cs ├── LICENSE ├── README.md └── preview.png /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: ubden 4 | patreon: ubden 5 | open_collective: ubden 6 | ko_fi: ubden 7 | liberapay: ubden 8 | issuehunt: ubden 9 | buy_me_a_coffee: ubden 10 | thanks_dev: ubden 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Report a program exception or general bug. 4 | title: '' 5 | labels: Bug 6 | assignees: pointfeev 7 | 8 | --- 9 | 10 | ###### Describe the bug and/or provide an image of the exception dialog box: 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enhancement Request 3 | about: Request a new feature or to enhance an existing feature. 4 | title: '' 5 | labels: Enhancement 6 | assignees: pointfeev 7 | 8 | --- 9 | 10 | ###### Describe the new feature and its implementation OR how the existing feature could be improved: 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/false-positives.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Mamson.A!ac, Phonzy.A!ml, Wacatac.H!ml, Malgent!MSR 3 | about: These are false positives; see this template's content. 4 | title: I promise not to post another issue about false positives. 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 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. 11 | -------------------------------------------------------------------------------- /.github/workflows/delete_releases.yml: -------------------------------------------------------------------------------- 1 | name: Delete Releases 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | tagPattern: 7 | description: 'Tag pattern' 8 | required: true 9 | default: 'v1' 10 | 11 | jobs: 12 | delete: 13 | name: Delete 14 | runs-on: windows-latest 15 | if: ${{ github.event.repository.owner.id }} == ${{ github.event.sender.id }} 16 | 17 | permissions: write-all 18 | 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v3 22 | 23 | - name: Delete 24 | uses: dev-drprasad/delete-older-releases@v0.2.1 25 | with: 26 | keep_latest: 0 27 | delete_tags: true 28 | delete_tag_pattern: ${{ github.event.inputs.tagPattern }} 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | -------------------------------------------------------------------------------- /.github/workflows/jekyll-gh-pages.yml: -------------------------------------------------------------------------------- 1 | # Sample workflow for building and deploying a Jekyll site to GitHub Pages 2 | name: Deploy Jekyll with GitHub Pages dependencies preinstalled 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["main"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 20 | concurrency: 21 | group: "pages" 22 | cancel-in-progress: false 23 | 24 | jobs: 25 | # Build job 26 | build: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Checkout 30 | uses: actions/checkout@v4 31 | - name: Setup Pages 32 | uses: actions/configure-pages@v5 33 | - name: Build with Jekyll 34 | uses: actions/jekyll-build-pages@v1 35 | with: 36 | source: ./ 37 | destination: ./_site 38 | - name: Upload artifact 39 | uses: actions/upload-pages-artifact@v3 40 | 41 | # Deployment job 42 | deploy: 43 | environment: 44 | name: github-pages 45 | url: ${{ steps.deployment.outputs.page_url }} 46 | runs-on: ubuntu-latest 47 | needs: build 48 | steps: 49 | - name: Deploy to GitHub Pages 50 | id: deployment 51 | uses: actions/deploy-pages@v4 52 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | test: 11 | name: Test 12 | runs-on: windows-latest 13 | permissions: 14 | actions: read 15 | contents: read 16 | 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v3 20 | 21 | - name: Setup 22 | uses: actions/setup-dotnet@v3 23 | with: 24 | dotnet-version: 7.x.x 25 | 26 | - name: Restore 27 | run: dotnet restore 28 | 29 | - name: Build 30 | run: dotnet build --no-restore --configuration Release /p:UseSharedCompilation=false 31 | 32 | - name: Test 33 | run: dotnet test --no-build --verbosity normal --configuration Release 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.lnk 8 | *.rsuser 9 | *.suo 10 | *.user 11 | *.userosscache 12 | *.sln.docstates 13 | 14 | # User-specific files (MonoDevelop/Xamarin Studio) 15 | *.userprefs 16 | 17 | # Build results 18 | [Dd]ebug/ 19 | [Dd]ebugPublic/ 20 | [Rr]elease/ 21 | [Rr]eleases/ 22 | x64/ 23 | x86/ 24 | bld/ 25 | [Bb]in/ 26 | [Oo]bj/ 27 | [Ll]og/ 28 | 29 | # Visual Studio 2015/2017 cache/options directory 30 | .vs/ 31 | # Uncomment if you have tasks that create the project's static files in wwwroot 32 | #wwwroot/ 33 | 34 | # Visual Studio 2017 auto generated files 35 | Generated\ Files/ 36 | 37 | # MSTest test Results 38 | [Tt]est[Rr]esult*/ 39 | [Bb]uild[Ll]og.* 40 | 41 | # NUNIT 42 | *.VisualState.xml 43 | TestResult.xml 44 | 45 | # Build Results of an ATL Project 46 | [Dd]ebugPS/ 47 | [Rr]eleasePS/ 48 | dlldata.c 49 | 50 | # Benchmark Results 51 | BenchmarkDotNet.Artifacts/ 52 | 53 | # .NET Core 54 | project.lock.json 55 | project.fragment.lock.json 56 | artifacts/ 57 | 58 | # StyleCop 59 | StyleCopReport.xml 60 | 61 | # Files built by Visual Studio 62 | *_i.c 63 | *_p.c 64 | *_h.h 65 | *.ilk 66 | *.meta 67 | *.obj 68 | *.iobj 69 | *.pch 70 | *.pdb 71 | *.ipdb 72 | *.pgc 73 | *.pgd 74 | *.rsp 75 | *.sbr 76 | *.tlb 77 | *.tli 78 | *.tlh 79 | *.tmp 80 | *.tmp_proj 81 | *_wpftmp.csproj 82 | *.log 83 | *.vspscc 84 | *.vssscc 85 | .builds 86 | *.pidb 87 | *.svclog 88 | *.scc 89 | 90 | # Chutzpah Test files 91 | _Chutzpah* 92 | 93 | # Visual C++ cache files 94 | ipch/ 95 | *.aps 96 | *.ncb 97 | *.opendb 98 | *.opensdf 99 | *.sdf 100 | *.cachefile 101 | *.VC.db 102 | *.VC.VC.opendb 103 | 104 | # Visual Studio profiler 105 | *.psess 106 | *.vsp 107 | *.vspx 108 | *.sap 109 | 110 | # Visual Studio Trace Files 111 | *.e2e 112 | 113 | # TFS 2012 Local Workspace 114 | $tf/ 115 | 116 | # Guidance Automation Toolkit 117 | *.gpState 118 | 119 | # ReSharper is a .NET coding add-in 120 | _ReSharper*/ 121 | *.[Rr]e[Ss]harper 122 | *.DotSettings.user 123 | 124 | # JustCode is a .NET coding add-in 125 | .JustCode 126 | 127 | # TeamCity is a build add-in 128 | _TeamCity* 129 | 130 | # DotCover is a Code Coverage Tool 131 | *.dotCover 132 | 133 | # AxoCover is a Code Coverage Tool 134 | .axoCover/* 135 | !.axoCover/settings.json 136 | 137 | # Visual Studio code coverage results 138 | *.coverage 139 | *.coveragexml 140 | 141 | # NCrunch 142 | _NCrunch_* 143 | .*crunch*.local.xml 144 | nCrunchTemp_* 145 | 146 | # MightyMoose 147 | *.mm.* 148 | AutoTest.Net/ 149 | 150 | # Web workbench (sass) 151 | .sass-cache/ 152 | 153 | # Installshield output folder 154 | [Ee]xpress/ 155 | 156 | # DocProject is a documentation generator add-in 157 | DocProject/buildhelp/ 158 | DocProject/Help/*.HxT 159 | DocProject/Help/*.HxC 160 | DocProject/Help/*.hhc 161 | DocProject/Help/*.hhk 162 | DocProject/Help/*.hhp 163 | DocProject/Help/Html2 164 | DocProject/Help/html 165 | 166 | # Click-Once directory 167 | publish/ 168 | 169 | # Publish Web Output 170 | *.[Pp]ublish.xml 171 | *.azurePubxml 172 | # Note: Comment the next line if you want to checkin your web deploy settings, 173 | # but database connection strings (with potential passwords) will be unencrypted 174 | *.pubxml 175 | *.publishproj 176 | 177 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 178 | # checkin your Azure Web App publish settings, but sensitive information contained 179 | # in these scripts will be unencrypted 180 | PublishScripts/ 181 | 182 | # NuGet Packages 183 | *.nupkg 184 | # The packages folder can be ignored because of Package Restore 185 | **/[Pp]ackages/* 186 | # except build/, which is used as an MSBuild target. 187 | !**/[Pp]ackages/build/ 188 | # Uncomment if necessary however generally it will be regenerated when needed 189 | #!**/[Pp]ackages/repositories.config 190 | # NuGet v3's project.json files produces more ignorable files 191 | *.nuget.props 192 | *.nuget.targets 193 | 194 | # Microsoft Azure Build Output 195 | csx/ 196 | *.build.csdef 197 | 198 | # Microsoft Azure Emulator 199 | ecf/ 200 | rcf/ 201 | 202 | # Windows Store app package directories and files 203 | AppPackages/ 204 | BundleArtifacts/ 205 | Package.StoreAssociation.xml 206 | _pkginfo.txt 207 | *.appx 208 | 209 | # Visual Studio cache files 210 | # files ending in .cache can be ignored 211 | *.[Cc]ache 212 | # but keep track of directories ending in .cache 213 | !*.[Cc]ache/ 214 | 215 | # Others 216 | ClientBin/ 217 | ~$* 218 | *~ 219 | *.dbmdl 220 | *.dbproj.schemaview 221 | *.jfm 222 | *.pfx 223 | *.publishsettings 224 | orleans.codegen.cs 225 | 226 | # Including strong name files can present a security risk 227 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 228 | #*.snk 229 | 230 | # Since there are multiple workflows, uncomment next line to ignore bower_components 231 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 232 | #bower_components/ 233 | 234 | # RIA/Silverlight projects 235 | Generated_Code/ 236 | 237 | # Backup & report files from converting an old project file 238 | # to a newer Visual Studio version. Backup files are not needed, 239 | # because we have git ;-) 240 | _UpgradeReport_Files/ 241 | Backup*/ 242 | UpgradeLog*.XML 243 | UpgradeLog*.htm 244 | ServiceFabricBackup/ 245 | *.rptproj.bak 246 | 247 | # SQL Server files 248 | *.mdf 249 | *.ldf 250 | *.ndf 251 | 252 | # Business Intelligence projects 253 | *.rdl.data 254 | *.bim.layout 255 | *.bim_*.settings 256 | *.rptproj.rsuser 257 | 258 | # Microsoft Fakes 259 | FakesAssemblies/ 260 | 261 | # GhostDoc plugin setting file 262 | *.GhostDoc.xml 263 | 264 | # Node.js Tools for Visual Studio 265 | .ntvs_analysis.dat 266 | node_modules/ 267 | 268 | # Visual Studio 6 build log 269 | *.plg 270 | 271 | # Visual Studio 6 workspace options file 272 | *.opt 273 | 274 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 275 | *.vbw 276 | 277 | # Visual Studio LightSwitch build output 278 | **/*.HTMLClient/GeneratedArtifacts 279 | **/*.DesktopClient/GeneratedArtifacts 280 | **/*.DesktopClient/ModelManifest.xml 281 | **/*.Server/GeneratedArtifacts 282 | **/*.Server/ModelManifest.xml 283 | _Pvt_Extensions 284 | 285 | # Paket dependency manager 286 | .paket/paket.exe 287 | paket-files/ 288 | 289 | # FAKE - F# Make 290 | .fake/ 291 | 292 | # JetBrains Rider 293 | .idea/ 294 | *.sln.iml 295 | 296 | # CodeRush personal settings 297 | .cr/personal 298 | 299 | # Python Tools for Visual Studio (PTVS) 300 | __pycache__/ 301 | *.pyc 302 | 303 | # Cake - Uncomment if you are using it 304 | # tools/** 305 | # !tools/packages.config 306 | 307 | # Tabs Studio 308 | *.tss 309 | 310 | # Telerik's JustMock configuration file 311 | *.jmconfig 312 | 313 | # BizTalk build output 314 | *.btp.cs 315 | *.btm.cs 316 | *.odx.cs 317 | *.xsd.cs 318 | 319 | # OpenCover UI analysis results 320 | OpenCover/ 321 | 322 | # Azure Stream Analytics local run output 323 | ASALocalRun/ 324 | 325 | # MSBuild Binary and Structured Log 326 | *.binlog 327 | 328 | # NVidia Nsight GPU debugger configuration file 329 | *.nvuser 330 | 331 | # MFractors (Xamarin productivity tool) working folder 332 | .mfractor/ 333 | 334 | # Local History for Visual Studio 335 | .localhistory/ 336 | *.exe 337 | *.zip 338 | -------------------------------------------------------------------------------- /CreamInstaller.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.2.32516.85 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CreamInstaller", "CreamInstaller\CreamInstaller.csproj", "{6C94C882-7168-435E-B9E3-B4B9222BBF68}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {6C94C882-7168-435E-B9E3-B4B9222BBF68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {6C94C882-7168-435E-B9E3-B4B9222BBF68}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {6C94C882-7168-435E-B9E3-B4B9222BBF68}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {6C94C882-7168-435E-B9E3-B4B9222BBF68}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {5373FD20-3B35-4077-8E2A-EC976AFF05BB} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /CreamInstaller/Components/ContextMenuItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Drawing; 4 | using System.IO; 5 | using System.Threading.Tasks; 6 | using System.Windows.Forms; 7 | using CreamInstaller.Platforms.Paradox; 8 | using CreamInstaller.Utility; 9 | 10 | namespace CreamInstaller.Components; 11 | 12 | internal sealed class ContextMenuItem : ToolStripMenuItem 13 | { 14 | private static readonly ConcurrentDictionary Images = new(); 15 | 16 | private readonly EventHandler onClickEvent; 17 | 18 | internal ContextMenuItem(string text, EventHandler onClick = null) 19 | { 20 | Text = text; 21 | onClickEvent = onClick; 22 | } 23 | 24 | internal ContextMenuItem(string text, string imageIdentifier, EventHandler onClick = null) : this(text, onClick) 25 | => _ = TryImageIdentifier(this, imageIdentifier); 26 | 27 | internal ContextMenuItem(string text, (string id, string iconUrl) imageIdentifierInfo, EventHandler onClick = null) : this(text, onClick) 28 | => _ = TryImageIdentifierInfo(this, imageIdentifierInfo); 29 | 30 | internal ContextMenuItem(string text, (string id, string iconUrl) imageIdentifierInfo, string imageIdentifierFallback, EventHandler onClick = null) : 31 | this(text, onClick) 32 | { 33 | async void OnFail() => await TryImageIdentifier(this, imageIdentifierFallback); 34 | _ = TryImageIdentifierInfo(this, imageIdentifierInfo, OnFail); 35 | } 36 | 37 | internal ContextMenuItem(string text, (string id, string iconUrl) imageIdentifierInfo, (string id, string iconUrl) imageIdentifierInfoFallback, 38 | EventHandler onClick = null) : this(text, onClick) 39 | { 40 | async void OnFail() => await TryImageIdentifierInfo(this, imageIdentifierInfoFallback); 41 | _ = TryImageIdentifierInfo(this, imageIdentifierInfo, OnFail); 42 | } 43 | 44 | private static async Task TryImageIdentifier(ContextMenuItem item, string imageIdentifier) 45 | => await Task.Run(async () => 46 | { 47 | if (Images.TryGetValue(imageIdentifier, out Image image) && image is not null) 48 | item.Image = image; 49 | else 50 | { 51 | switch (imageIdentifier) 52 | { 53 | case "Paradox Launcher": 54 | if (Directory.Exists(ParadoxLauncher.InstallPath)) 55 | foreach (string file in Directory.EnumerateFiles(ParadoxLauncher.InstallPath, "*.exe")) 56 | { 57 | image = file.GetFileIconImage(); 58 | break; 59 | } 60 | break; 61 | case "Notepad": 62 | image = IconGrabber.GetNotepadImage(); 63 | break; 64 | case "Command Prompt": 65 | image = IconGrabber.GetCommandPromptImage(); 66 | break; 67 | case "File Explorer": 68 | image = IconGrabber.GetFileExplorerImage(); 69 | break; 70 | case "SteamDB": 71 | image = await HttpClientManager.GetImageFromUrl(IconGrabber.GetDomainFaviconUrl("steamdb.info")); 72 | break; 73 | case "Steam Store": 74 | image = await HttpClientManager.GetImageFromUrl(IconGrabber.GetDomainFaviconUrl("store.steampowered.com")); 75 | break; 76 | case "Steam Community": 77 | image = await HttpClientManager.GetImageFromUrl(IconGrabber.GetDomainFaviconUrl("steamcommunity.com")); 78 | break; 79 | case "ScreamDB": 80 | image = await HttpClientManager.GetImageFromUrl(IconGrabber.GetDomainFaviconUrl("scream-db.web.app")); 81 | break; 82 | case "Epic Games": 83 | image = await HttpClientManager.GetImageFromUrl(IconGrabber.GetDomainFaviconUrl("epicgames.com")); 84 | break; 85 | case "Ubisoft Store": 86 | image = await HttpClientManager.GetImageFromUrl(IconGrabber.GetDomainFaviconUrl("store.ubi.com")); 87 | break; 88 | default: 89 | return; 90 | } 91 | if (image is not null) 92 | { 93 | Images[imageIdentifier] = image; 94 | item.Image = image; 95 | } 96 | } 97 | }); 98 | 99 | private static async Task TryImageIdentifierInfo(ContextMenuItem item, (string id, string iconUrl) imageIdentifierInfo, Action onFail = null) 100 | => await Task.Run(async () => 101 | { 102 | (string id, string iconUrl) = imageIdentifierInfo; 103 | string imageIdentifier = "Icon_" + id; 104 | if (Images.TryGetValue(imageIdentifier, out Image image) && image is not null) 105 | item.Image = image; 106 | else 107 | { 108 | image = await HttpClientManager.GetImageFromUrl(iconUrl); 109 | if (image is not null) 110 | { 111 | Images[imageIdentifier] = image; 112 | item.Image = image; 113 | } 114 | else 115 | onFail?.Invoke(); 116 | } 117 | }); 118 | 119 | protected override void OnClick(EventArgs e) 120 | { 121 | base.OnClick(e); 122 | onClickEvent?.Invoke(this, e); 123 | } 124 | } -------------------------------------------------------------------------------- /CreamInstaller/Components/CustomForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Drawing.Drawing2D; 4 | using System.Drawing.Imaging; 5 | using System.Runtime.InteropServices; 6 | using System.Windows.Forms; 7 | using CreamInstaller.Forms; 8 | 9 | namespace CreamInstaller.Components; 10 | 11 | internal class CustomForm : Form 12 | { 13 | internal const short SWP_NOACTIVATE = 0x0010; 14 | internal const short SWP_SHOWWINDOW = 0x0040; 15 | internal const short SWP_NOMOVE = 0x0002; 16 | internal const short SWP_NOSIZE = 0x0001; 17 | 18 | internal static readonly nint HWND_NOTOPMOST = new(-2); 19 | internal static readonly nint HWND_TOPMOST = new(-1); 20 | 21 | internal CustomForm() 22 | { 23 | Icon = Properties.Resources.Icon; 24 | KeyPreview = true; 25 | KeyPress += OnKeyPress; 26 | ResizeRedraw = true; 27 | HelpButton = true; 28 | HelpButtonClicked += OnHelpButtonClicked; 29 | } 30 | 31 | internal CustomForm(IWin32Window owner) : this() 32 | { 33 | if (owner is not Form form) 34 | return; 35 | Owner = form; 36 | InheritLocation(form); 37 | SizeChanged += (_, _) => InheritLocation(form); 38 | form.Activated += OnActivation; 39 | FormClosing += (_, _) => form.Activated -= OnActivation; 40 | TopLevel = true; 41 | } 42 | 43 | protected override CreateParams CreateParams // Double buffering for all controls 44 | { 45 | get 46 | { 47 | CreateParams handleParam = base.CreateParams; 48 | handleParam.ExStyle |= 0x02; // WS_EX_COMPOSITED 49 | return handleParam; 50 | } 51 | } 52 | 53 | private void OnHelpButtonClicked(object sender, EventArgs args) 54 | { 55 | using DialogForm helpDialog = new(this); 56 | helpDialog.HelpButton = false; 57 | string acidicoala = "https://github.com/acidicoala"; 58 | string repository = $"https://github.com/{Program.RepositoryOwner}/{Program.RepositoryName}"; 59 | _ = helpDialog.Show(SystemIcons.Information, 60 | "Automatically finds all installed Steam, Epic and Ubisoft games with their respective DLC-related DLL locations on the user's computer,\n" 61 | + "parses SteamCMD, Steam Store and Epic Games Store for user-selected games' DLCs, then provides a very simple graphical interface\n" 62 | + "utilizing the gathered information for the maintenance of DLC unlockers.\n" + "\n" 63 | + $"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" 64 | + $"the wonderful [acidicoala]({acidicoala}), and all downloaded and embedded into the program itself; no further downloads necessary on your part!\n" 65 | + "\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" 66 | + "only a DLC Unlocker installer. Should the game you wish to unlock DLC for not already come with the DLCs installed (very many\n" 67 | + "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" 68 | + "the game(s) you're tinkering with; you'll usually find any answer to your problems there.\n" + "\n" + "USAGE:\n" 69 | + " 1. Choose which programs and/or games the program should scan for DLC.\n" 70 | + " The program automatically gathers all installed games from Steam, Epic and Ubisoft directories.\n" 71 | + " 2. Wait for the program to download and install SteamCMD (if you chose a Steam game).\n" 72 | + " 3. Wait for the program to gather and cache the chosen games' information && DLCs.\n" 73 | + " May take some time on the first run; depends on how many DLCs the games you chose have.\n" 74 | + " 4. CAREFULLY select which games' DLCs you wish to unlock.\n" 75 | + " Obviously none of the DLC unlockers are tested for every single game!\n" 76 | + " 5. Choose whether or not to install with Koaloader, and if so then also pick the proxy DLL to use.\n" 77 | + " 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" 78 | + " 6. Click the \"Generate and Install\" button.\n" + " 7. Click the \"OK\" button to close the program.\n" 79 | + " 8. If any of the DLC unlockers cause problems with any of the games you installed them on, simply go back\n" 80 | + " to step 5 and select what games you wish you revert changes to, and instead click the \"Uninstall\" button this time.\n" + "\n" 81 | + $"For reliable and quick assistance, all bugs, crashes and other issues should be referred to the [GitHub Issues]({repository}/issues) page!\n" 82 | + "\n" + "SteamCMD installation and appinfo cache can be found at [C:\\ProgramData\\CreamInstaller]().\n" 83 | + $"The program automatically and very quickly updates from [GitHub]({repository}) using [Onova](https://github.com/Tyrrrz/Onova). (updates can be ignored)\n" 84 | + $"The program source and other information can be found on [GitHub]({repository})."); 85 | } 86 | 87 | private void OnActivation(object sender, EventArgs args) => Activate(); 88 | 89 | [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] 90 | [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] 91 | private static extern void SetWindowPos(nint hWnd, nint hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags); 92 | 93 | internal void BringToFrontWithoutActivation() 94 | { 95 | bool topMost = TopMost; 96 | SetWindowPos(Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE); 97 | if (!topMost) 98 | SetWindowPos(Handle, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE); 99 | } 100 | 101 | internal void InheritLocation(Form fromForm) 102 | { 103 | if (fromForm is null) 104 | return; 105 | int X = fromForm.Location.X + fromForm.Size.Width / 2 - Size.Width / 2; 106 | int Y = fromForm.Location.Y + fromForm.Size.Height / 2 - Size.Height / 2; 107 | Location = new(X, Y); 108 | } 109 | 110 | private void OnKeyPress(object s, KeyPressEventArgs e) 111 | { 112 | if (e.KeyChar != 'S') 113 | return; // Shift + S 114 | UpdateBounds(); 115 | Rectangle bounds = Bounds; 116 | using Bitmap bitmap = new(Size.Width - 14, Size.Height - 7); 117 | using Graphics graphics = Graphics.FromImage(bitmap); 118 | graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; 119 | using EncoderParameters encoding = new(1); 120 | using EncoderParameter encoderParam = new(Encoder.Quality, 100L); 121 | encoding.Param[0] = encoderParam; 122 | graphics.CopyFromScreen(new(bounds.Left + 7, bounds.Top), Point.Empty, new(Size.Width - 14, Size.Height - 7)); 123 | Clipboard.SetImage(bitmap); 124 | e.Handled = true; 125 | } 126 | } -------------------------------------------------------------------------------- /CreamInstaller/Components/PlatformIdComparer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Windows.Forms; 5 | 6 | namespace CreamInstaller.Components; 7 | 8 | internal static class PlatformIdComparer 9 | { 10 | private static StringComparer stringComparer; 11 | 12 | private static NodeComparer nodeComparer; 13 | 14 | private static NodeNameComparer nodeNameComparer; 15 | 16 | private static NodeTextComparer nodeTextComparer; 17 | internal static StringComparer String => stringComparer ??= new(); 18 | internal static NodeComparer Node => nodeComparer ??= new(); 19 | internal static NodeNameComparer NodeName => nodeNameComparer ??= new(); 20 | internal static NodeTextComparer NodeText => nodeTextComparer ??= new(); 21 | } 22 | 23 | internal sealed class StringComparer : IComparer 24 | { 25 | public int Compare(string a, string b) 26 | => !int.TryParse(a, out _) && !int.TryParse(b, out _) 27 | ? string.Compare(a, b, StringComparison.Ordinal) 28 | : !int.TryParse(a, out int A) 29 | ? 1 30 | : !int.TryParse(b, out int B) 31 | ? -1 32 | : A > B 33 | ? 1 34 | : A < B 35 | ? -1 36 | : 0; 37 | } 38 | 39 | internal sealed class NodeComparer : IComparer 40 | { 41 | public int Compare(TreeNode a, TreeNode b) 42 | => a?.Tag is not Platform A 43 | ? 1 44 | : b?.Tag is not Platform B 45 | ? -1 46 | : A > B 47 | ? 1 48 | : A < B 49 | ? -1 50 | : 0; 51 | } 52 | 53 | internal sealed class NodeNameComparer : IComparer 54 | { 55 | public int Compare(object a, object b) 56 | => a is not TreeNode A 57 | ? 1 58 | : b is not TreeNode B 59 | ? -1 60 | : PlatformIdComparer.Node.Compare(A, B) is var c && c != 0 61 | ? c 62 | : PlatformIdComparer.String.Compare(A.Name, B.Name); 63 | } 64 | 65 | internal sealed class NodeTextComparer : IComparer 66 | { 67 | public int Compare(object a, object b) 68 | => a is not TreeNode A 69 | ? 1 70 | : b is not TreeNode B 71 | ? -1 72 | : PlatformIdComparer.Node.Compare(A, B) is var c && c != 0 73 | ? c 74 | : PlatformIdComparer.String.Compare(A.Text, B.Text); 75 | } -------------------------------------------------------------------------------- /CreamInstaller/CreamInstaller.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | net7.0-windows 5 | True 6 | Resources\ini.ico 7 | 4.5.0.0 8 | 2021, pointfeev (https://github.com/pointfeev) 9 | CreamInstaller 10 | Automatic DLC Unlocker Installer & Configuration Generator 11 | CreamInstaller.Program 12 | True 13 | latest-recommended 14 | AnyCPU 15 | true 16 | true 17 | true 18 | True 19 | embedded 20 | true 21 | $(DefineConstants) 22 | False 23 | 24 | 25 | $(Company) 26 | 27 | 28 | $(Company)-debug 29 | 30 | 31 | False 32 | 33 | 34 | False 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | Form 155 | 156 | 157 | True 158 | True 159 | Resources.resx 160 | 161 | 162 | 163 | 164 | Designer 165 | 166 | 167 | ResXFileCodeGenerator 168 | Resources.Designer.cs 169 | 170 | 171 | -------------------------------------------------------------------------------- /CreamInstaller/Forms/DebugForm.Designer.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Windows.Forms; 3 | 4 | namespace CreamInstaller.Forms; 5 | 6 | partial class DebugForm 7 | { 8 | /// 9 | /// Required designer variable. 10 | /// 11 | private IContainer components = null; 12 | 13 | /// 14 | /// Clean up any resources being used. 15 | /// 16 | /// true if managed resources should be disposed; otherwise, false. 17 | protected override void Dispose(bool disposing) 18 | { 19 | if (disposing && (components != null)) 20 | { 21 | components.Dispose(); 22 | } 23 | base.Dispose(disposing); 24 | } 25 | 26 | #region Windows Form Designer generated code 27 | 28 | /// 29 | /// Required method for Designer support - do not modify 30 | /// the contents of this method with the code editor. 31 | /// 32 | private void InitializeComponent() 33 | { 34 | this.debugTextBox = new System.Windows.Forms.RichTextBox(); 35 | this.SuspendLayout(); 36 | // 37 | // debugTextBox 38 | // 39 | this.debugTextBox.Dock = System.Windows.Forms.DockStyle.Fill; 40 | this.debugTextBox.Location = new System.Drawing.Point(10, 10); 41 | this.debugTextBox.Name = "debugTextBox"; 42 | this.debugTextBox.ReadOnly = true; 43 | this.debugTextBox.ScrollBars = System.Windows.Forms.RichTextBoxScrollBars.ForcedBoth; 44 | this.debugTextBox.Size = new System.Drawing.Size(544, 321); 45 | this.debugTextBox.TabIndex = 0; 46 | this.debugTextBox.TabStop = false; 47 | this.debugTextBox.Text = ""; 48 | // 49 | // DebugForm 50 | // 51 | this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); 52 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 53 | this.ClientSize = new System.Drawing.Size(564, 341); 54 | this.ControlBox = false; 55 | this.Controls.Add(this.debugTextBox); 56 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; 57 | this.MaximizeBox = false; 58 | this.MinimizeBox = false; 59 | this.Name = "DebugForm"; 60 | this.Padding = new System.Windows.Forms.Padding(10); 61 | this.ShowIcon = false; 62 | this.ShowInTaskbar = false; 63 | this.StartPosition = System.Windows.Forms.FormStartPosition.Manual; 64 | this.Text = "Debug"; 65 | this.ResumeLayout(false); 66 | 67 | } 68 | 69 | #endregion 70 | 71 | private RichTextBox debugTextBox; 72 | } -------------------------------------------------------------------------------- /CreamInstaller/Forms/DebugForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Windows.Forms; 4 | using CreamInstaller.Components; 5 | using CreamInstaller.Utility; 6 | 7 | namespace CreamInstaller.Forms; 8 | 9 | internal sealed partial class DebugForm : CustomForm 10 | { 11 | private static DebugForm current; 12 | 13 | private Form attachedForm; 14 | 15 | private DebugForm() 16 | { 17 | InitializeComponent(); 18 | debugTextBox.BackColor = LogTextBox.Background; 19 | } 20 | 21 | internal static DebugForm Current 22 | { 23 | get 24 | { 25 | if (current is not null && (current.Disposing || current.IsDisposed)) 26 | current = null; 27 | return current ??= new(); 28 | } 29 | } 30 | 31 | protected override void WndProc(ref Message message) // make form immovable by user 32 | { 33 | if (message.Msg == 0x0112) // WM_SYSCOMMAND 34 | { 35 | int command = message.WParam.ToInt32() & 0xFFF0; 36 | if (command == 0xF010) // SC_MOVE 37 | return; 38 | } 39 | base.WndProc(ref message); 40 | } 41 | 42 | internal void Attach(Form form) 43 | { 44 | if (attachedForm is not null) 45 | { 46 | attachedForm.Activated -= OnChange; 47 | attachedForm.LocationChanged -= OnChange; 48 | attachedForm.SizeChanged -= OnChange; 49 | attachedForm.VisibleChanged -= OnChange; 50 | } 51 | attachedForm = form; 52 | attachedForm.Activated += OnChange; 53 | attachedForm.LocationChanged += OnChange; 54 | attachedForm.SizeChanged += OnChange; 55 | attachedForm.VisibleChanged += OnChange; 56 | UpdateAttachment(); 57 | } 58 | 59 | private void OnChange(object sender, EventArgs args) => UpdateAttachment(); 60 | 61 | private void UpdateAttachment() 62 | { 63 | if (attachedForm is null || !attachedForm.Visible) 64 | return; 65 | //Size = new(Size.Width, attachedForm.Size.Height); 66 | Location = new(attachedForm.Right, attachedForm.Top); 67 | BringToFrontWithoutActivation(); 68 | } 69 | 70 | internal void Log(string text) => Log(text, LogTextBox.Error); 71 | 72 | internal void Log(string text, Color color) 73 | { 74 | if (!debugTextBox.Disposing && !debugTextBox.IsDisposed) 75 | debugTextBox.Invoke(() => 76 | { 77 | if (debugTextBox.Text.Length > 0) 78 | debugTextBox.AppendText(Environment.NewLine, color, true); 79 | debugTextBox.AppendText(text, color, true); 80 | }); 81 | } 82 | } -------------------------------------------------------------------------------- /CreamInstaller/Forms/DebugForm.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | text/microsoft-resx 50 | 51 | 52 | 2.0 53 | 54 | 55 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 56 | 57 | 58 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 59 | 60 | 61 | True 62 | 63 | -------------------------------------------------------------------------------- /CreamInstaller/Forms/DialogForm.Designer.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Windows.Forms; 3 | 4 | namespace CreamInstaller.Forms 5 | { 6 | partial class DialogForm 7 | { 8 | private IContainer components = null; 9 | protected override void Dispose(bool disposing) 10 | { 11 | if (disposing && components is not null) 12 | components.Dispose(); 13 | base.Dispose(disposing); 14 | } 15 | 16 | #region Windows Form Designer generated code 17 | 18 | /// 19 | /// Required method for Designer support - do not modify 20 | /// the contents of this method with the code editor. 21 | /// 22 | private void InitializeComponent() 23 | { 24 | this.cancelButton = new System.Windows.Forms.Button(); 25 | this.acceptButton = new System.Windows.Forms.Button(); 26 | this.descriptionLabel = new System.Windows.Forms.LinkLabel(); 27 | this.icon = new System.Windows.Forms.PictureBox(); 28 | this.descriptionPanel = new System.Windows.Forms.FlowLayoutPanel(); 29 | this.buttonPanel = new System.Windows.Forms.FlowLayoutPanel(); 30 | ((System.ComponentModel.ISupportInitialize)(this.icon)).BeginInit(); 31 | this.descriptionPanel.SuspendLayout(); 32 | this.buttonPanel.SuspendLayout(); 33 | this.SuspendLayout(); 34 | // 35 | // cancelButton 36 | // 37 | this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); 38 | this.cancelButton.AutoSize = true; 39 | this.cancelButton.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; 40 | this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; 41 | this.cancelButton.FlatStyle = System.Windows.Forms.FlatStyle.System; 42 | this.cancelButton.Location = new System.Drawing.Point(32, 9); 43 | this.cancelButton.Name = "cancelButton"; 44 | this.cancelButton.Padding = new System.Windows.Forms.Padding(12, 0, 12, 0); 45 | this.cancelButton.Size = new System.Drawing.Size(115, 24); 46 | this.cancelButton.TabIndex = 1; 47 | this.cancelButton.Text = "cancelButton"; 48 | this.cancelButton.UseVisualStyleBackColor = true; 49 | // 50 | // acceptButton 51 | // 52 | this.acceptButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 53 | this.acceptButton.AutoSize = true; 54 | this.acceptButton.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; 55 | this.acceptButton.DialogResult = System.Windows.Forms.DialogResult.OK; 56 | this.acceptButton.FlatStyle = System.Windows.Forms.FlatStyle.System; 57 | this.acceptButton.Location = new System.Drawing.Point(153, 9); 58 | this.acceptButton.Name = "acceptButton"; 59 | this.acceptButton.Padding = new System.Windows.Forms.Padding(12, 0, 12, 0); 60 | this.acceptButton.Size = new System.Drawing.Size(116, 24); 61 | this.acceptButton.TabIndex = 0; 62 | this.acceptButton.Text = "acceptButton"; 63 | this.acceptButton.UseVisualStyleBackColor = true; 64 | // 65 | // descriptionLabel 66 | // 67 | this.descriptionLabel.AutoSize = true; 68 | this.descriptionLabel.FlatStyle = System.Windows.Forms.FlatStyle.System; 69 | this.descriptionLabel.LinkArea = new System.Windows.Forms.LinkArea(0, 0); 70 | this.descriptionLabel.Location = new System.Drawing.Point(75, 12); 71 | this.descriptionLabel.Margin = new System.Windows.Forms.Padding(9, 0, 3, 0); 72 | this.descriptionLabel.Name = "descriptionLabel"; 73 | this.descriptionLabel.Size = new System.Drawing.Size(94, 15); 74 | this.descriptionLabel.TabIndex = 2; 75 | this.descriptionLabel.Text = "descriptionLabel"; 76 | // 77 | // icon 78 | // 79 | this.icon.Location = new System.Drawing.Point(15, 15); 80 | this.icon.Name = "icon"; 81 | this.icon.Size = new System.Drawing.Size(48, 48); 82 | this.icon.SizeMode = System.Windows.Forms.PictureBoxSizeMode.AutoSize; 83 | this.icon.TabIndex = 4; 84 | this.icon.TabStop = false; 85 | // 86 | // descriptionPanel 87 | // 88 | this.descriptionPanel.AutoSize = true; 89 | this.descriptionPanel.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; 90 | this.descriptionPanel.Controls.Add(this.icon); 91 | this.descriptionPanel.Controls.Add(this.descriptionLabel); 92 | this.descriptionPanel.Dock = System.Windows.Forms.DockStyle.Fill; 93 | this.descriptionPanel.Location = new System.Drawing.Point(0, 0); 94 | this.descriptionPanel.Margin = new System.Windows.Forms.Padding(0); 95 | this.descriptionPanel.Name = "descriptionPanel"; 96 | this.descriptionPanel.Padding = new System.Windows.Forms.Padding(12, 12, 12, 6); 97 | this.descriptionPanel.Size = new System.Drawing.Size(284, 73); 98 | this.descriptionPanel.TabIndex = 5; 99 | // 100 | // buttonPanel 101 | // 102 | this.buttonPanel.AutoSize = true; 103 | this.buttonPanel.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; 104 | this.buttonPanel.Controls.Add(this.acceptButton); 105 | this.buttonPanel.Controls.Add(this.cancelButton); 106 | this.buttonPanel.Dock = System.Windows.Forms.DockStyle.Bottom; 107 | this.buttonPanel.FlowDirection = System.Windows.Forms.FlowDirection.RightToLeft; 108 | this.buttonPanel.Location = new System.Drawing.Point(0, 73); 109 | this.buttonPanel.Name = "buttonPanel"; 110 | this.buttonPanel.Padding = new System.Windows.Forms.Padding(12, 6, 0, 12); 111 | this.buttonPanel.Size = new System.Drawing.Size(284, 48); 112 | this.buttonPanel.TabIndex = 6; 113 | // 114 | // DialogForm 115 | // 116 | this.AcceptButton = this.acceptButton; 117 | this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); 118 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 119 | this.AutoSize = true; 120 | this.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; 121 | this.CancelButton = this.cancelButton; 122 | this.ClientSize = new System.Drawing.Size(284, 121); 123 | this.Controls.Add(this.descriptionPanel); 124 | this.Controls.Add(this.buttonPanel); 125 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; 126 | this.MaximizeBox = false; 127 | this.MinimizeBox = false; 128 | this.Name = "DialogForm"; 129 | this.ShowInTaskbar = false; 130 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 131 | this.Text = "DialogForm"; 132 | ((System.ComponentModel.ISupportInitialize)(this.icon)).EndInit(); 133 | this.descriptionPanel.ResumeLayout(false); 134 | this.descriptionPanel.PerformLayout(); 135 | this.buttonPanel.ResumeLayout(false); 136 | this.buttonPanel.PerformLayout(); 137 | this.ResumeLayout(false); 138 | this.PerformLayout(); 139 | 140 | } 141 | 142 | #endregion 143 | 144 | private Button cancelButton; 145 | private Button acceptButton; 146 | private PictureBox icon; 147 | private FlowLayoutPanel descriptionPanel; 148 | private FlowLayoutPanel buttonPanel; 149 | private LinkLabel descriptionLabel; 150 | } 151 | } -------------------------------------------------------------------------------- /CreamInstaller/Forms/DialogForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Drawing; 5 | using System.Linq; 6 | using System.Windows.Forms; 7 | using CreamInstaller.Components; 8 | 9 | namespace CreamInstaller.Forms; 10 | 11 | internal sealed partial class DialogForm : CustomForm 12 | { 13 | internal DialogForm(IWin32Window owner) : base(owner) => InitializeComponent(); 14 | 15 | internal DialogResult Show(Icon descriptionIcon, string descriptionText, string acceptButtonText = "OK", string cancelButtonText = null, 16 | string customFormText = null, Icon customFormIcon = null) 17 | { 18 | descriptionIcon ??= Icon; 19 | icon.Image = descriptionIcon?.ToBitmap(); 20 | List links = new(); 21 | for (int i = 0; i < descriptionText.Length; i++) 22 | if (descriptionText[i] == '[') 23 | { 24 | int textLeft = descriptionText.IndexOf("[", i, StringComparison.Ordinal); 25 | int textRight = descriptionText.IndexOf("]", textLeft == -1 ? i : textLeft, StringComparison.Ordinal); 26 | int linkLeft = descriptionText.IndexOf("(", textRight == -1 ? i : textRight, StringComparison.Ordinal); 27 | int linkRight = descriptionText.IndexOf(")", linkLeft == -1 ? i : linkLeft, StringComparison.Ordinal); 28 | if (textLeft == -1 || textRight != linkLeft - 1 || linkRight == -1) 29 | continue; 30 | string text = descriptionText[(textLeft + 1)..textRight]; 31 | string link = descriptionText[(linkLeft + 1)..linkRight]; 32 | if (string.IsNullOrWhiteSpace(link)) 33 | link = text; 34 | descriptionText = descriptionText.Remove(i, linkRight + 1 - i).Insert(i, text); 35 | links.Add(new(i, text.Length, link)); 36 | } 37 | descriptionLabel.Text = descriptionText; 38 | acceptButton.Text = acceptButtonText; 39 | if (cancelButtonText is null) 40 | { 41 | cancelButton.Enabled = false; 42 | cancelButton.Visible = false; 43 | } 44 | else 45 | cancelButton.Text = cancelButtonText; 46 | if (customFormText is not null) 47 | Text = customFormText; 48 | else 49 | { 50 | OnResize(null, null); 51 | Resize += OnResize; 52 | } 53 | if (customFormIcon is not null) 54 | Icon = customFormIcon; 55 | if (!links.Any()) 56 | return ShowDialog(); 57 | foreach (LinkLabel.Link link in links) 58 | _ = descriptionLabel.Links.Add(link); 59 | descriptionLabel.LinkClicked += (_, e) => 60 | { 61 | if (e.Link != null) 62 | Process.Start(new ProcessStartInfo((string)e.Link.LinkData) { UseShellExecute = true }); 63 | }; 64 | return ShowDialog(); 65 | } 66 | 67 | private void OnResize(object s, EventArgs e) 68 | => Text = TextRenderer.MeasureText(Program.ApplicationName, Font).Width > Size.Width - 100 69 | ? TextRenderer.MeasureText(Program.ApplicationNameShort, Font).Width > Size.Width - 100 ? Program.Name : Program.ApplicationNameShort 70 | : Program.ApplicationName; 71 | } -------------------------------------------------------------------------------- /CreamInstaller/Forms/DialogForm.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | text/microsoft-resx 50 | 51 | 52 | 2.0 53 | 54 | 55 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 56 | 57 | 58 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 59 | 60 | -------------------------------------------------------------------------------- /CreamInstaller/Forms/InstallForm.Designer.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Windows.Forms; 3 | 4 | namespace CreamInstaller.Forms 5 | { 6 | sealed partial class InstallForm 7 | { 8 | private IContainer components = null; 9 | protected override void Dispose(bool disposing) 10 | { 11 | if (disposing && components is not null) 12 | components.Dispose(); 13 | base.Dispose(disposing); 14 | } 15 | 16 | #region Windows Form Designer generated code 17 | 18 | /// 19 | /// Required method for Designer support - do not modify 20 | /// the contents of this method with the code editor. 21 | /// 22 | private void InitializeComponent() 23 | { 24 | this.userProgressBar = new System.Windows.Forms.ProgressBar(); 25 | this.userInfoLabel = new System.Windows.Forms.Label(); 26 | this.acceptButton = new System.Windows.Forms.Button(); 27 | this.retryButton = new System.Windows.Forms.Button(); 28 | this.cancelButton = new System.Windows.Forms.Button(); 29 | this.logTextBox = new System.Windows.Forms.RichTextBox(); 30 | this.reselectButton = new System.Windows.Forms.Button(); 31 | this.SuspendLayout(); 32 | // 33 | // userProgressBar 34 | // 35 | this.userProgressBar.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 36 | | System.Windows.Forms.AnchorStyles.Right))); 37 | this.userProgressBar.Location = new System.Drawing.Point(12, 27); 38 | this.userProgressBar.Name = "userProgressBar"; 39 | this.userProgressBar.Size = new System.Drawing.Size(760, 23); 40 | this.userProgressBar.TabIndex = 1; 41 | // 42 | // userInfoLabel 43 | // 44 | this.userInfoLabel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 45 | | System.Windows.Forms.AnchorStyles.Right))); 46 | this.userInfoLabel.AutoEllipsis = true; 47 | this.userInfoLabel.FlatStyle = System.Windows.Forms.FlatStyle.System; 48 | this.userInfoLabel.Location = new System.Drawing.Point(12, 9); 49 | this.userInfoLabel.Name = "userInfoLabel"; 50 | this.userInfoLabel.Size = new System.Drawing.Size(760, 15); 51 | this.userInfoLabel.TabIndex = 2; 52 | this.userInfoLabel.Text = "Loading . . . "; 53 | // 54 | // acceptButton 55 | // 56 | this.acceptButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 57 | this.acceptButton.Enabled = false; 58 | this.acceptButton.FlatStyle = System.Windows.Forms.FlatStyle.System; 59 | this.acceptButton.Location = new System.Drawing.Point(697, 526); 60 | this.acceptButton.Name = "acceptButton"; 61 | this.acceptButton.Size = new System.Drawing.Size(75, 23); 62 | this.acceptButton.TabIndex = 4; 63 | this.acceptButton.Text = "OK"; 64 | this.acceptButton.UseVisualStyleBackColor = true; 65 | this.acceptButton.Click += new System.EventHandler(this.OnAccept); 66 | // 67 | // retryButton 68 | // 69 | this.retryButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 70 | this.retryButton.Enabled = false; 71 | this.retryButton.FlatStyle = System.Windows.Forms.FlatStyle.System; 72 | this.retryButton.Location = new System.Drawing.Point(616, 526); 73 | this.retryButton.Name = "retryButton"; 74 | this.retryButton.Size = new System.Drawing.Size(75, 23); 75 | this.retryButton.TabIndex = 3; 76 | this.retryButton.Text = "Retry"; 77 | this.retryButton.UseVisualStyleBackColor = true; 78 | this.retryButton.Click += new System.EventHandler(this.OnRetry); 79 | // 80 | // cancelButton 81 | // 82 | this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); 83 | this.cancelButton.FlatStyle = System.Windows.Forms.FlatStyle.System; 84 | this.cancelButton.Location = new System.Drawing.Point(12, 526); 85 | this.cancelButton.Name = "cancelButton"; 86 | this.cancelButton.Size = new System.Drawing.Size(75, 23); 87 | this.cancelButton.TabIndex = 1; 88 | this.cancelButton.Text = "Cancel"; 89 | this.cancelButton.UseVisualStyleBackColor = true; 90 | this.cancelButton.Click += new System.EventHandler(this.OnCancel); 91 | // 92 | // logTextBox 93 | // 94 | this.logTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 95 | | System.Windows.Forms.AnchorStyles.Left) 96 | | System.Windows.Forms.AnchorStyles.Right))); 97 | this.logTextBox.HideSelection = false; 98 | this.logTextBox.Location = new System.Drawing.Point(12, 56); 99 | this.logTextBox.Name = "logTextBox"; 100 | this.logTextBox.ReadOnly = true; 101 | this.logTextBox.ScrollBars = System.Windows.Forms.RichTextBoxScrollBars.ForcedBoth; 102 | this.logTextBox.Size = new System.Drawing.Size(760, 464); 103 | this.logTextBox.TabIndex = 4; 104 | this.logTextBox.TabStop = false; 105 | this.logTextBox.Text = ""; 106 | // 107 | // reselectButton 108 | // 109 | this.reselectButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 110 | this.reselectButton.FlatStyle = System.Windows.Forms.FlatStyle.System; 111 | this.reselectButton.Location = new System.Drawing.Point(410, 526); 112 | this.reselectButton.Name = "reselectButton"; 113 | this.reselectButton.Size = new System.Drawing.Size(200, 23); 114 | this.reselectButton.TabIndex = 2; 115 | this.reselectButton.Text = "Reselect Programs / Games"; 116 | this.reselectButton.UseVisualStyleBackColor = true; 117 | this.reselectButton.Click += new System.EventHandler(this.OnReselect); 118 | // 119 | // InstallForm 120 | // 121 | this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); 122 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 123 | this.AutoSize = true; 124 | this.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; 125 | this.ClientSize = new System.Drawing.Size(784, 561); 126 | this.Controls.Add(this.reselectButton); 127 | this.Controls.Add(this.logTextBox); 128 | this.Controls.Add(this.cancelButton); 129 | this.Controls.Add(this.retryButton); 130 | this.Controls.Add(this.acceptButton); 131 | this.Controls.Add(this.userProgressBar); 132 | this.Controls.Add(this.userInfoLabel); 133 | this.DoubleBuffered = true; 134 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; 135 | this.MaximizeBox = false; 136 | this.MinimizeBox = false; 137 | this.Name = "InstallForm"; 138 | this.StartPosition = System.Windows.Forms.FormStartPosition.Manual; 139 | this.Text = "InstallForm"; 140 | this.Load += new System.EventHandler(this.OnLoad); 141 | this.ResumeLayout(false); 142 | 143 | } 144 | 145 | #endregion 146 | 147 | private ProgressBar userProgressBar; 148 | private Label userInfoLabel; 149 | private Button acceptButton; 150 | private Button retryButton; 151 | private Button cancelButton; 152 | private RichTextBox logTextBox; 153 | private Button reselectButton; 154 | } 155 | } 156 | 157 | -------------------------------------------------------------------------------- /CreamInstaller/Forms/InstallForm.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | text/microsoft-resx 50 | 51 | 52 | 2.0 53 | 54 | 55 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 56 | 57 | 58 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 59 | 60 | -------------------------------------------------------------------------------- /CreamInstaller/Forms/MainForm.Designer.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Windows.Forms; 3 | using CreamInstaller.Components; 4 | 5 | namespace CreamInstaller.Forms 6 | { 7 | partial class MainForm 8 | { 9 | private IContainer components = null; 10 | 11 | #region Windows Form Designer generated code 12 | 13 | /// 14 | /// Required method for Designer support - do not modify 15 | /// the contents of this method with the code editor. 16 | /// 17 | private void InitializeComponent() 18 | { 19 | this.progressLabel = new System.Windows.Forms.Label(); 20 | this.updateButton = new System.Windows.Forms.Button(); 21 | this.ignoreButton = new System.Windows.Forms.Button(); 22 | this.progressBar = new System.Windows.Forms.ProgressBar(); 23 | this.changelogTreeView = new CustomTreeView(); 24 | this.SuspendLayout(); 25 | // 26 | // progressLabel 27 | // 28 | this.progressLabel.FlatStyle = System.Windows.Forms.FlatStyle.System; 29 | this.progressLabel.Location = new System.Drawing.Point(12, 16); 30 | this.progressLabel.Margin = new System.Windows.Forms.Padding(3, 0, 3, 12); 31 | this.progressLabel.Name = "progressLabel"; 32 | this.progressLabel.Size = new System.Drawing.Size(218, 15); 33 | this.progressLabel.TabIndex = 0; 34 | this.progressLabel.Text = "Checking for updates . . ."; 35 | // 36 | // updateButton 37 | // 38 | this.updateButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); 39 | this.updateButton.Enabled = false; 40 | this.updateButton.FlatStyle = System.Windows.Forms.FlatStyle.System; 41 | this.updateButton.Location = new System.Drawing.Point(317, 12); 42 | this.updateButton.Margin = new System.Windows.Forms.Padding(3, 3, 3, 12); 43 | this.updateButton.Name = "updateButton"; 44 | this.updateButton.Size = new System.Drawing.Size(75, 23); 45 | this.updateButton.TabIndex = 2; 46 | this.updateButton.Text = "Update"; 47 | this.updateButton.UseVisualStyleBackColor = true; 48 | // 49 | // ignoreButton 50 | // 51 | this.ignoreButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); 52 | this.ignoreButton.Enabled = false; 53 | this.ignoreButton.FlatStyle = System.Windows.Forms.FlatStyle.System; 54 | this.ignoreButton.Location = new System.Drawing.Point(236, 12); 55 | this.ignoreButton.Margin = new System.Windows.Forms.Padding(3, 3, 3, 12); 56 | this.ignoreButton.Name = "ignoreButton"; 57 | this.ignoreButton.Size = new System.Drawing.Size(75, 23); 58 | this.ignoreButton.TabIndex = 1; 59 | this.ignoreButton.Text = "Ignore"; 60 | this.ignoreButton.UseVisualStyleBackColor = true; 61 | this.ignoreButton.Click += new System.EventHandler(this.OnIgnore); 62 | // 63 | // progressBar 64 | // 65 | this.progressBar.Location = new System.Drawing.Point(12, 41); 66 | this.progressBar.Name = "progressBar"; 67 | this.progressBar.Size = new System.Drawing.Size(380, 23); 68 | this.progressBar.TabIndex = 4; 69 | this.progressBar.Visible = false; 70 | // 71 | // changelogTreeView 72 | // 73 | this.changelogTreeView.DrawMode = System.Windows.Forms.TreeViewDrawMode.OwnerDrawAll; 74 | this.changelogTreeView.Location = new System.Drawing.Point(12, 70); 75 | this.changelogTreeView.Margin = new System.Windows.Forms.Padding(0, 0, 0, 12); 76 | this.changelogTreeView.Name = "changelogTreeView"; 77 | this.changelogTreeView.Size = new System.Drawing.Size(380, 179); 78 | this.changelogTreeView.Sorted = true; 79 | this.changelogTreeView.TabIndex = 5; 80 | // 81 | // MainForm 82 | // 83 | this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); 84 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 85 | this.AutoSize = true; 86 | this.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; 87 | this.ClientSize = new System.Drawing.Size(404, 261); 88 | this.Controls.Add(this.changelogTreeView); 89 | this.Controls.Add(this.progressBar); 90 | this.Controls.Add(this.ignoreButton); 91 | this.Controls.Add(this.updateButton); 92 | this.Controls.Add(this.progressLabel); 93 | this.DoubleBuffered = true; 94 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; 95 | this.MaximizeBox = false; 96 | this.MinimizeBox = false; 97 | this.Name = "MainForm"; 98 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; 99 | this.Text = "MainForm"; 100 | this.Load += new System.EventHandler(this.OnLoad); 101 | this.ResumeLayout(false); 102 | 103 | } 104 | 105 | #endregion 106 | 107 | private Label progressLabel; 108 | private Button updateButton; 109 | private Button ignoreButton; 110 | private ProgressBar progressBar; 111 | private CustomTreeView changelogTreeView; 112 | } 113 | } 114 | 115 | -------------------------------------------------------------------------------- /CreamInstaller/Forms/MainForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using System.Web; 9 | using System.Windows.Forms; 10 | using CreamInstaller.Components; 11 | using CreamInstaller.Utility; 12 | using HtmlAgilityPack; 13 | using Onova; 14 | using Onova.Models; 15 | using Onova.Services; 16 | 17 | namespace CreamInstaller.Forms; 18 | 19 | internal sealed partial class MainForm : CustomForm 20 | { 21 | private CancellationTokenSource cancellationTokenSource; 22 | private Version latestVersion; 23 | 24 | private UpdateManager updateManager; 25 | private IReadOnlyList versions; 26 | 27 | internal MainForm() 28 | { 29 | InitializeComponent(); 30 | Text = Program.ApplicationNameShort; 31 | } 32 | 33 | private void StartProgram() 34 | { 35 | if (cancellationTokenSource is not null) 36 | { 37 | cancellationTokenSource.Cancel(); 38 | cancellationTokenSource.Dispose(); 39 | cancellationTokenSource = null; 40 | } 41 | #pragma warning disable CA2000 // Dispose objects before losing scope 42 | SelectForm form = new(); 43 | #pragma warning restore CA2000 // Dispose objects before losing scope 44 | form.InheritLocation(this); 45 | form.FormClosing += (_, _) => Close(); 46 | form.Show(); 47 | Hide(); 48 | #if DEBUG 49 | DebugForm.Current.Attach(form); 50 | #endif 51 | } 52 | 53 | private async void OnLoad() 54 | { 55 | progressBar.Visible = false; 56 | ignoreButton.Visible = true; 57 | updateButton.Text = "Update"; 58 | updateButton.Click -= OnUpdateCancel; 59 | progressLabel.Text = "Checking for updates . . ."; 60 | changelogTreeView.Visible = false; 61 | changelogTreeView.Location = progressLabel.Location with { Y = progressLabel.Location.Y + progressLabel.Size.Height + 13 }; 62 | Refresh(); 63 | #if DEBUG 64 | DebugForm.Current.Attach(this); 65 | #endif 66 | GithubPackageResolver resolver = new(Program.RepositoryOwner, Program.RepositoryName, Program.RepositoryPackage); 67 | ZipPackageExtractor extractor = new(); 68 | updateManager = new(AssemblyMetadata.FromAssembly(Program.EntryAssembly, Program.CurrentProcessFilePath), resolver, extractor); 69 | if (latestVersion is null) 70 | { 71 | cancellationTokenSource = new(); 72 | try 73 | { 74 | CheckForUpdatesResult checkForUpdatesResult = await updateManager.CheckForUpdatesAsync(cancellationTokenSource.Token); 75 | #if !DEBUG 76 | if (checkForUpdatesResult.CanUpdate) 77 | { 78 | #endif 79 | latestVersion = checkForUpdatesResult.LastVersion; 80 | versions = checkForUpdatesResult.Versions; 81 | #if !DEBUG 82 | } 83 | #endif 84 | } 85 | #if DEBUG 86 | catch (TaskCanceledException) { } 87 | catch (Exception e) 88 | { 89 | DebugForm.Current.Log($"Exception while checking for updates: {e.GetType()} ({e.Message})", LogTextBox.Warning); 90 | } 91 | #else 92 | catch 93 | { 94 | // ignored 95 | } 96 | #endif 97 | finally 98 | { 99 | cancellationTokenSource.Dispose(); 100 | cancellationTokenSource = null; 101 | } 102 | } 103 | if (latestVersion is null) 104 | { 105 | updateManager.Dispose(); 106 | updateManager = null; 107 | StartProgram(); 108 | } 109 | else 110 | { 111 | progressLabel.Text = $"An update is available: v{latestVersion}"; 112 | ignoreButton.Enabled = true; 113 | updateButton.Enabled = true; 114 | updateButton.Click += OnUpdate; 115 | changelogTreeView.Visible = true; 116 | Version currentVersion = new(Program.Version); 117 | #if DEBUG 118 | foreach (Version version in versions.Where(v => (v > currentVersion || v == latestVersion) && !changelogTreeView.Nodes.ContainsKey(v.ToString()))) 119 | #else 120 | foreach (Version version in versions.Where(v => v > currentVersion && !changelogTreeView.Nodes.ContainsKey(v.ToString()))) 121 | #endif 122 | { 123 | TreeNode root = new($"v{version}") { Name = version.ToString() }; 124 | changelogTreeView.Nodes.Add(root); 125 | if (changelogTreeView.Nodes.Count > 0) 126 | changelogTreeView.Nodes[0].EnsureVisible(); 127 | _ = Task.Run(async () => 128 | { 129 | HtmlNodeCollection nodes = await HttpClientManager.GetDocumentNodes( 130 | $"https://github.com/{Program.RepositoryOwner}/{Program.RepositoryName}/releases/tag/v{version}", 131 | "//div[@data-test-selector='body-content']/ul/li"); 132 | if (nodes is null) 133 | changelogTreeView.Nodes.Remove(root); 134 | else 135 | foreach (HtmlNode node in nodes) 136 | changelogTreeView.Invoke(delegate 137 | { 138 | TreeNode change = new() { Text = HttpUtility.HtmlDecode(node.InnerText) ?? string.Empty }; 139 | root.Nodes.Add(change); 140 | root.Expand(); 141 | if (changelogTreeView.Nodes.Count > 0) 142 | changelogTreeView.Nodes[0].EnsureVisible(); 143 | }); 144 | }); 145 | } 146 | } 147 | } 148 | 149 | private void OnLoad(object sender, EventArgs _) 150 | { 151 | retry: 152 | try 153 | { 154 | string fileName = Path.GetFileName(Program.CurrentProcessFilePath); 155 | if (fileName != Program.ApplicationExecutable) 156 | { 157 | using DialogForm form = new(this); 158 | if (form.Show(SystemIcons.Warning, 159 | "WARNING: " + Program.ApplicationExecutable + " was renamed!" + "\n\nThis will cause undesirable behavior when updating the program!", 160 | "Ignore", "Abort") == DialogResult.Cancel) 161 | { 162 | Application.Exit(); 163 | return; 164 | } 165 | } 166 | OnLoad(); 167 | } 168 | catch (Exception e) 169 | { 170 | if (e.HandleException(this)) 171 | goto retry; 172 | Close(); 173 | } 174 | } 175 | 176 | private void OnIgnore(object sender, EventArgs e) => StartProgram(); 177 | 178 | private async void OnUpdate(object sender, EventArgs e) 179 | { 180 | progressBar.Visible = true; 181 | ignoreButton.Visible = false; 182 | updateButton.Text = "Cancel"; 183 | updateButton.Click -= OnUpdate; 184 | updateButton.Click += OnUpdateCancel; 185 | changelogTreeView.Location = progressBar.Location with { Y = progressBar.Location.Y + progressBar.Size.Height + 6 }; 186 | Refresh(); 187 | Progress progress = new(); 188 | progress.ProgressChanged += delegate(object _, double _progress) 189 | { 190 | progressLabel.Text = $"Updating . . . {(int)_progress}%"; 191 | progressBar.Value = (int)_progress; 192 | }; 193 | progressLabel.Text = "Updating . . . "; 194 | cancellationTokenSource = new(); 195 | try 196 | { 197 | await updateManager.PrepareUpdateAsync(latestVersion, progress, cancellationTokenSource.Token); 198 | } 199 | #if DEBUG 200 | catch (TaskCanceledException) { } 201 | catch (Exception ex) 202 | { 203 | DebugForm.Current.Log($"Exception while preparing update: {ex.GetType()} ({ex.Message})", LogTextBox.Warning); 204 | } 205 | #else 206 | catch 207 | { 208 | // ignored 209 | } 210 | #endif 211 | finally 212 | { 213 | cancellationTokenSource.Dispose(); 214 | cancellationTokenSource = null; 215 | } 216 | if (updateManager is not null && updateManager.IsUpdatePrepared(latestVersion)) 217 | { 218 | updateManager.LaunchUpdater(latestVersion); 219 | Application.Exit(); 220 | return; 221 | } 222 | OnLoad(); 223 | } 224 | 225 | private void OnUpdateCancel(object sender, EventArgs e) 226 | { 227 | cancellationTokenSource?.Cancel(); 228 | updateManager?.Dispose(); 229 | updateManager = null; 230 | } 231 | 232 | protected override void Dispose(bool disposing) 233 | { 234 | if (disposing) 235 | components?.Dispose(); 236 | base.Dispose(disposing); 237 | cancellationTokenSource?.Dispose(); 238 | updateManager?.Dispose(); 239 | } 240 | } -------------------------------------------------------------------------------- /CreamInstaller/Forms/MainForm.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | text/microsoft-resx 50 | 51 | 52 | 2.0 53 | 54 | 55 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 56 | 57 | 58 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 59 | 60 | -------------------------------------------------------------------------------- /CreamInstaller/Forms/SelectDialogForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Windows.Forms; 5 | using CreamInstaller.Components; 6 | using CreamInstaller.Utility; 7 | 8 | namespace CreamInstaller.Forms; 9 | 10 | internal sealed partial class SelectDialogForm : CustomForm 11 | { 12 | private readonly List<(Platform platform, string id, string name)> selected = new(); 13 | internal SelectDialogForm(IWin32Window owner) : base(owner) => InitializeComponent(); 14 | 15 | internal List<(Platform platform, string id, string name)> QueryUser(string groupBoxText, 16 | List<(Platform platform, string id, string name, bool alreadySelected)> choices) 17 | { 18 | if (!choices.Any()) 19 | return null; 20 | groupBox.Text = groupBoxText; 21 | allCheckBox.Enabled = false; 22 | acceptButton.Enabled = false; 23 | selectionTreeView.AfterCheck += OnTreeNodeChecked; 24 | foreach ((Platform platform, string id, string name, bool alreadySelected) in choices) 25 | { 26 | TreeNode node = new() { Tag = platform, Name = id, Text = name, Checked = alreadySelected }; 27 | OnTreeNodeChecked(node); 28 | _ = selectionTreeView.Nodes.Add(node); 29 | } 30 | if (!selected.Any()) 31 | OnLoad(null, null); 32 | allCheckBox.CheckedChanged -= OnAllCheckBoxChanged; 33 | allCheckBox.Checked = selectionTreeView.Nodes.Cast().All(n => n.Checked); 34 | allCheckBox.CheckedChanged += OnAllCheckBoxChanged; 35 | allCheckBox.Enabled = true; 36 | acceptButton.Enabled = selected.Any(); 37 | saveButton.Enabled = acceptButton.Enabled; 38 | loadButton.Enabled = ProgramData.ReadProgramChoices() is not null; 39 | OnResize(null, null); 40 | Resize += OnResize; 41 | return ShowDialog() == DialogResult.OK ? selected : null; 42 | } 43 | 44 | private void OnTreeNodeChecked(object sender, TreeViewEventArgs e) 45 | { 46 | OnTreeNodeChecked(e.Node); 47 | acceptButton.Enabled = selected.Any(); 48 | saveButton.Enabled = acceptButton.Enabled; 49 | } 50 | 51 | private void OnTreeNodeChecked(TreeNode node) 52 | { 53 | string id = node.Name; 54 | Platform platform = (Platform)node.Tag; 55 | if (node.Checked) 56 | selected.Add((platform, id, node.Text)); 57 | else 58 | _ = selected.RemoveAll(s => s.platform == platform && s.id == id); 59 | allCheckBox.CheckedChanged -= OnAllCheckBoxChanged; 60 | allCheckBox.Checked = selectionTreeView.Nodes.Cast().All(n => n.Checked); 61 | allCheckBox.CheckedChanged += OnAllCheckBoxChanged; 62 | } 63 | 64 | private void OnResize(object s, EventArgs e) 65 | => Text = TextRenderer.MeasureText(Program.ApplicationName, Font).Width > Size.Width - 100 ? Program.ApplicationNameShort : Program.ApplicationName; 66 | 67 | private void OnSortCheckBoxChanged(object sender, EventArgs e) 68 | => selectionTreeView.TreeViewNodeSorter = sortCheckBox.Checked ? PlatformIdComparer.NodeText : PlatformIdComparer.NodeName; 69 | 70 | private void OnAllCheckBoxChanged(object sender, EventArgs e) 71 | { 72 | bool shouldCheck = selectionTreeView.Nodes.Cast().Any(n => !n.Checked); 73 | foreach (TreeNode node in selectionTreeView.Nodes) 74 | { 75 | node.Checked = shouldCheck; 76 | OnTreeNodeChecked(node); 77 | } 78 | allCheckBox.CheckedChanged -= OnAllCheckBoxChanged; 79 | allCheckBox.Checked = shouldCheck; 80 | allCheckBox.CheckedChanged += OnAllCheckBoxChanged; 81 | } 82 | 83 | private void OnLoad(object sender, EventArgs e) 84 | { 85 | List<(Platform platform, string id)> choices = ProgramData.ReadProgramChoices().ToList(); 86 | if (!choices.Any()) 87 | return; 88 | foreach (TreeNode node in selectionTreeView.Nodes) 89 | { 90 | node.Checked = choices.Any(n => n.platform == (Platform)node.Tag && n.id == node.Name); 91 | OnTreeNodeChecked(node); 92 | } 93 | } 94 | 95 | private void OnSave(object sender, EventArgs e) 96 | { 97 | ProgramData.WriteProgramChoices(selectionTreeView.Nodes.Cast().Where(n => n.Checked).Select(node => ((Platform)node.Tag, node.Name))); 98 | loadButton.Enabled = ProgramData.ReadProgramChoices() is not null; 99 | } 100 | } -------------------------------------------------------------------------------- /CreamInstaller/Forms/SelectDialogForm.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | text/microsoft-resx 50 | 51 | 52 | 2.0 53 | 54 | 55 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 56 | 57 | 58 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 59 | 60 | -------------------------------------------------------------------------------- /CreamInstaller/Forms/SelectForm.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | text/microsoft-resx 50 | 51 | 52 | 2.0 53 | 54 | 55 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 56 | 57 | 58 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 59 | 60 | -------------------------------------------------------------------------------- /CreamInstaller/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | [assembly: SuppressMessage("CodeQuality", "IDE0076:Invalid global 'SuppressMessageAttribute'")] 4 | [assembly: SuppressMessage("CodeQuality", "IDE0079:Remove unnecessary suppression")] 5 | 6 | [assembly: SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters")] 7 | [assembly: SuppressMessage("Globalization", "CA1305:Specify IFormatProvider")] 8 | [assembly: SuppressMessage("Globalization", "CA1307:Specify StringComparison for clarity")] 9 | [assembly: SuppressMessage("Globalization", "CA1310:Specify StringComparison for correctness")] 10 | 11 | [assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types")] 12 | 13 | [assembly: SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task")] -------------------------------------------------------------------------------- /CreamInstaller/Platforms/Epic/EpicLibrary.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Text.Json; 5 | using System.Threading.Tasks; 6 | using CreamInstaller.Utility; 7 | using Microsoft.Win32; 8 | using static CreamInstaller.Resources.Resources; 9 | 10 | namespace CreamInstaller.Platforms.Epic; 11 | 12 | internal static class EpicLibrary 13 | { 14 | private static string epicManifestsPath; 15 | 16 | internal static string EpicManifestsPath 17 | { 18 | get 19 | { 20 | epicManifestsPath ??= Registry.GetValue(@"HKEY_CURRENT_USER\Software\Epic Games\EOS", "ModSdkMetadataDir", null) as string; 21 | epicManifestsPath ??= Registry.GetValue(@"HKEY_CURRENT_USER\Software\Wow6432Node\Epic Games\EOS", "ModSdkMetadataDir", null) as string; 22 | epicManifestsPath ??= Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Epic Games\EpicGamesLauncher", "AppDataPath", null) as string; 23 | epicManifestsPath ??= Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Epic Games\EpicGamesLauncher", "AppDataPath", null) as string; 24 | if (epicManifestsPath is not null && epicManifestsPath.EndsWith(@"\Data")) 25 | epicManifestsPath += @"\Manifests"; 26 | return epicManifestsPath.BeautifyPath(); 27 | } 28 | } 29 | 30 | internal static async Task> GetExecutableDirectories(string gameDirectory) 31 | => await Task.Run(async () => await gameDirectory.GetExecutableDirectories(true)); 32 | 33 | internal static async Task> GetGames() 34 | => await Task.Run(() => 35 | { 36 | List games = new(); 37 | string manifests = EpicManifestsPath; 38 | if (!Directory.Exists(manifests)) 39 | return games; 40 | foreach (string file in Directory.EnumerateFiles(manifests, "*.item")) 41 | { 42 | if (Program.Canceled) 43 | return games; 44 | string json = File.ReadAllText(file); 45 | try 46 | { 47 | Manifest manifest = JsonSerializer.Deserialize(json); 48 | if (manifest is not null && manifest.CatalogItemId == manifest.MainGameCatalogItemId && !games.Any(g 49 | => g.CatalogItemId == manifest.CatalogItemId && g.InstallLocation == manifest.InstallLocation)) 50 | games.Add(manifest); 51 | } 52 | catch 53 | { 54 | // ignored 55 | } 56 | } 57 | return games; 58 | }); 59 | } -------------------------------------------------------------------------------- /CreamInstaller/Platforms/Epic/EpicStore.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net.Http; 6 | using System.Threading.Tasks; 7 | using System.Web; 8 | using CreamInstaller.Platforms.Epic.GraphQL; 9 | using CreamInstaller.Utility; 10 | using Newtonsoft.Json; 11 | 12 | namespace CreamInstaller.Platforms.Epic; 13 | 14 | internal static class EpicStore 15 | { 16 | //private const int CooldownCatalogItem = 600; 17 | 18 | /* need a method to query catalog items 19 | internal static async Task QueryCatalogItems(Manifest manifest) 20 | { 21 | }*/ 22 | 23 | private const int CooldownEntitlement = 600; 24 | 25 | internal static async Task> QueryEntitlements(string categoryNamespace) 26 | { 27 | List<(string id, string name, string product, string icon, string developer)> dlcIds = new(); 28 | string cacheFile = ProgramData.AppInfoPath + @$"\{categoryNamespace}.json"; 29 | bool cachedExists = File.Exists(cacheFile); 30 | Response response = null; 31 | if (!cachedExists || ProgramData.CheckCooldown(categoryNamespace, CooldownEntitlement)) 32 | { 33 | response = await QueryGraphQL(categoryNamespace); 34 | try 35 | { 36 | await File.WriteAllTextAsync(cacheFile, JsonConvert.SerializeObject(response, Formatting.Indented)); 37 | } 38 | catch 39 | { 40 | // ignored 41 | } 42 | } 43 | else 44 | try 45 | { 46 | response = JsonConvert.DeserializeObject(await File.ReadAllTextAsync(cacheFile)); 47 | } 48 | catch 49 | { 50 | File.Delete(cacheFile); 51 | } 52 | if (response is null) 53 | return dlcIds; 54 | List searchStore = new(response.Data.Catalog.SearchStore.Elements); 55 | foreach (Element element in searchStore) 56 | { 57 | string title = element.Title; 58 | string product = element.CatalogNs is not null && element.CatalogNs.Mappings.Any() ? element.CatalogNs.Mappings.First().PageSlug : null; 59 | string icon = null; 60 | for (int i = 0; i < element.KeyImages?.Length; i++) 61 | { 62 | KeyImage keyImage = element.KeyImages[i]; 63 | if (keyImage.Type != "DieselStoreFront") 64 | continue; 65 | icon = keyImage.Url.ToString(); 66 | break; 67 | } 68 | foreach (Item item in element.Items) 69 | dlcIds.Populate(item.Id, title, product, icon, null, element.Items.Length == 1); 70 | } 71 | List catalogOffers = new(response.Data.Catalog.CatalogOffers.Elements); 72 | foreach (Element element in catalogOffers) 73 | { 74 | string title = element.Title; 75 | string product = element.CatalogNs is not null && element.CatalogNs.Mappings.Any() ? element.CatalogNs.Mappings.First().PageSlug : null; 76 | string icon = null; 77 | for (int i = 0; i < element.KeyImages?.Length; i++) 78 | { 79 | KeyImage keyImage = element.KeyImages[i]; 80 | if (keyImage.Type != "Thumbnail") 81 | continue; 82 | icon = keyImage.Url.ToString(); 83 | break; 84 | } 85 | foreach (Item item in element.Items) 86 | dlcIds.Populate(item.Id, title, product, icon, item.Developer, element.Items.Length == 1); 87 | } 88 | return dlcIds; 89 | } 90 | 91 | private static void Populate(this List<(string id, string name, string product, string icon, string developer)> dlcIds, string id, string title, 92 | string product, string icon, string developer, bool canOverwrite = false) 93 | { 94 | if (id == null) 95 | return; 96 | bool found = false; 97 | for (int i = 0; i < dlcIds.Count; i++) 98 | { 99 | (string id, string name, string product, string icon, string developer) app = dlcIds[i]; 100 | if (app.id != id) 101 | continue; 102 | found = true; 103 | dlcIds[i] = canOverwrite 104 | ? (app.id, title ?? app.name, product ?? app.product, icon ?? app.icon, developer ?? app.developer) 105 | : (app.id, app.name ?? title, app.product ?? product, app.icon ?? icon, app.developer ?? developer); 106 | break; 107 | } 108 | if (!found) 109 | dlcIds.Add((id, title, product, icon, developer)); 110 | } 111 | 112 | private static async Task QueryGraphQL(string categoryNamespace) 113 | { 114 | try 115 | { 116 | string encoded = HttpUtility.UrlEncode(categoryNamespace); 117 | Request request = new(encoded); 118 | string payload = JsonConvert.SerializeObject(request); 119 | using HttpContent content = new StringContent(payload); 120 | content.Headers.ContentType = new("application/json"); 121 | HttpClient client = HttpClientManager.HttpClient; 122 | if (client is null) 123 | return null; 124 | HttpResponseMessage httpResponse = await client.PostAsync(new Uri("https://graphql.epicgames.com/graphql"), content); 125 | _ = httpResponse.EnsureSuccessStatusCode(); 126 | string response = await httpResponse.Content.ReadAsStringAsync(); 127 | return JsonConvert.DeserializeObject(response); 128 | } 129 | catch 130 | { 131 | return null; 132 | } 133 | } 134 | } -------------------------------------------------------------------------------- /CreamInstaller/Platforms/Epic/GraphQL/Request.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE0051 // Remove unused private members 2 | #pragma warning disable IDE0052 // Remove unread private members 3 | #pragma warning disable CA1812 // Avoid uninstantiated internal classes 4 | #pragma warning disable CA1822 // Mark members as static 5 | 6 | using Newtonsoft.Json; 7 | 8 | namespace CreamInstaller.Platforms.Epic.GraphQL; 9 | 10 | internal sealed class Request 11 | { 12 | internal Request(string @namespace) => Vars = new(@namespace); 13 | 14 | [JsonProperty(PropertyName = "query")] 15 | private string Query 16 | => @"query searchOffers($namespace: String!) { 17 | Catalog { 18 | searchStore(category: ""*"", namespace: $namespace){ 19 | elements { 20 | id 21 | title 22 | developer 23 | items { 24 | id 25 | } 26 | catalogNs { 27 | mappings(pageType: ""productHome"") { 28 | pageSlug 29 | } 30 | } 31 | } 32 | } 33 | catalogOffers( 34 | namespace: $namespace 35 | params: { 36 | count: 1000, 37 | } 38 | ) { 39 | elements { 40 | id 41 | title 42 | keyImages { 43 | type 44 | url 45 | } 46 | items { 47 | id 48 | title 49 | developer 50 | } 51 | catalogNs { 52 | mappings(pageType: ""productHome"") { 53 | pageSlug 54 | } 55 | } 56 | } 57 | } 58 | } 59 | }"; 60 | 61 | [JsonProperty(PropertyName = "variables")] 62 | private Variables Vars { get; set; } 63 | 64 | private sealed class Headers 65 | { 66 | [JsonProperty(PropertyName = "Content-Type")] 67 | private string ContentType => "application/graphql"; 68 | } 69 | 70 | private sealed class Variables 71 | { 72 | internal Variables(string @namespace) => Namespace = @namespace; 73 | 74 | [JsonProperty(PropertyName = "namespace")] 75 | private string Namespace { get; set; } 76 | } 77 | } -------------------------------------------------------------------------------- /CreamInstaller/Platforms/Epic/GraphQL/Response.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable CA1819 // Properties should not return arrays 2 | 3 | using System; 4 | using Newtonsoft.Json; 5 | 6 | namespace CreamInstaller.Platforms.Epic.GraphQL; 7 | 8 | public class Response 9 | { 10 | [JsonProperty(PropertyName = "data")] public ResponseData Data { get; protected set; } 11 | } 12 | 13 | public class ResponseData 14 | { 15 | [JsonProperty(PropertyName = "Catalog")] 16 | public Catalog Catalog { get; protected set; } 17 | } 18 | 19 | public class Catalog 20 | { 21 | [JsonProperty(PropertyName = "searchStore")] 22 | public ElementContainer SearchStore { get; protected set; } 23 | 24 | [JsonProperty(PropertyName = "catalogOffers")] 25 | public ElementContainer CatalogOffers { get; protected set; } 26 | } 27 | 28 | public class ElementContainer 29 | { 30 | [JsonProperty(PropertyName = "elements")] 31 | public Element[] Elements { get; protected set; } 32 | } 33 | 34 | public class Element 35 | { 36 | [JsonProperty(PropertyName = "id")] public string Id { get; protected set; } 37 | 38 | [JsonProperty(PropertyName = "title")] public string Title { get; protected set; } 39 | 40 | [JsonProperty(PropertyName = "keyImages")] 41 | public KeyImage[] KeyImages { get; protected set; } 42 | 43 | [JsonProperty(PropertyName = "items")] public Item[] Items { get; protected set; } 44 | 45 | [JsonProperty(PropertyName = "catalogNs")] 46 | public CatalogNs CatalogNs { get; protected set; } 47 | } 48 | 49 | public class Item 50 | { 51 | [JsonProperty(PropertyName = "id")] public string Id { get; protected set; } 52 | 53 | [JsonProperty(PropertyName = "title")] public string Title { get; protected set; } 54 | 55 | [JsonProperty(PropertyName = "developer")] 56 | public string Developer { get; protected set; } 57 | } 58 | 59 | public class KeyImage 60 | { 61 | [JsonProperty(PropertyName = "type")] public string Type { get; protected set; } 62 | 63 | [JsonProperty(PropertyName = "url")] public Uri Url { get; protected set; } 64 | } 65 | 66 | public class CatalogNs 67 | { 68 | [JsonProperty(PropertyName = "mappings")] 69 | public Mapping[] Mappings { get; protected set; } 70 | } 71 | 72 | public class Mapping 73 | { 74 | [JsonProperty(PropertyName = "pageSlug")] 75 | public string PageSlug { get; protected set; } 76 | } -------------------------------------------------------------------------------- /CreamInstaller/Platforms/Epic/Manifest.cs: -------------------------------------------------------------------------------- 1 | namespace CreamInstaller.Platforms.Epic; 2 | 3 | public class Manifest 4 | { 5 | public string DisplayName { get; set; } 6 | 7 | public string InstallLocation { get; set; } 8 | 9 | public string LaunchExecutable { get; set; } 10 | 11 | public string CatalogNamespace { get; set; } 12 | 13 | public string CatalogItemId { get; set; } 14 | 15 | public string MainGameCatalogNamespace { get; set; } 16 | 17 | public string MainGameCatalogItemId { get; set; } 18 | } -------------------------------------------------------------------------------- /CreamInstaller/Platforms/Steam/AppDetails.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE1006 // Naming Styles 2 | #pragma warning disable CA1002 // Do not expose generic lists 3 | #pragma warning disable CA1707 // Identifiers should not contain underscores 4 | #pragma warning disable CA2227 // Collection properties should be read only 5 | 6 | using System.Collections.Generic; 7 | 8 | namespace CreamInstaller.Platforms.Steam; 9 | 10 | public class AppData 11 | { 12 | public string type { get; set; } 13 | 14 | public string name { get; set; } 15 | 16 | public int steam_appid { get; set; } 17 | 18 | public List dlc { get; set; } 19 | 20 | public string header_image { get; set; } 21 | 22 | public string website { get; set; } 23 | 24 | public List developers { get; set; } 25 | 26 | public List publishers { get; set; } 27 | 28 | public List packages { get; set; } 29 | } 30 | 31 | public class AppDetails 32 | { 33 | public bool success { get; set; } 34 | 35 | public AppData data { get; set; } 36 | } -------------------------------------------------------------------------------- /CreamInstaller/Platforms/Steam/SteamLibrary.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using CreamInstaller.Utility; 7 | using Gameloop.Vdf.Linq; 8 | using Microsoft.Win32; 9 | using static CreamInstaller.Resources.Resources; 10 | 11 | namespace CreamInstaller.Platforms.Steam; 12 | 13 | internal static class SteamLibrary 14 | { 15 | private static string installPath; 16 | 17 | internal static string InstallPath 18 | { 19 | get 20 | { 21 | installPath ??= Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Valve\Steam", "InstallPath", null) as string; 22 | installPath ??= Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Valve\Steam", "InstallPath", null) as string; 23 | return installPath.BeautifyPath(); 24 | } 25 | } 26 | 27 | internal static async Task> GetExecutableDirectories(string gameDirectory) 28 | => await Task.Run(async () => await gameDirectory.GetExecutableDirectories(true)); 29 | 30 | internal static async Task> GetGames() 31 | => await Task.Run(async () => 32 | { 33 | List<(string appId, string name, string branch, int buildId, string gameDirectory)> games = new(); 34 | List gameLibraryDirectories = await GetLibraryDirectories(); 35 | foreach (string libraryDirectory in gameLibraryDirectories) 36 | { 37 | if (Program.Canceled) 38 | return games; 39 | foreach ((string appId, string name, string branch, int buildId, string gameDirectory) game in 40 | (await GetGamesFromLibraryDirectory(libraryDirectory)).Where(game 41 | => !games.Any(_game => _game.appId == game.appId && _game.gameDirectory == game.gameDirectory))) 42 | games.Add(game); 43 | } 44 | return games; 45 | }); 46 | 47 | private static async Task> 48 | GetGamesFromLibraryDirectory(string libraryDirectory) 49 | => await Task.Run(() => 50 | { 51 | List<(string appId, string name, string branch, int buildId, string gameDirectory)> games = new(); 52 | if (Program.Canceled || !Directory.Exists(libraryDirectory)) 53 | return games; 54 | foreach (string file in Directory.EnumerateFiles(libraryDirectory, "*.acf")) 55 | { 56 | if (Program.Canceled) 57 | return games; 58 | if (!ValveDataFile.TryDeserialize(File.ReadAllText(file, Encoding.UTF8), out VProperty result)) 59 | continue; 60 | string appId = result.Value.GetChild("appid")?.ToString(); 61 | string installdir = result.Value.GetChild("installdir")?.ToString(); 62 | string name = result.Value.GetChild("name")?.ToString(); 63 | string buildId = result.Value.GetChild("buildid")?.ToString(); 64 | if (string.IsNullOrWhiteSpace(appId) || string.IsNullOrWhiteSpace(installdir) || string.IsNullOrWhiteSpace(name) 65 | || string.IsNullOrWhiteSpace(buildId)) 66 | continue; 67 | string gameDirectory = (libraryDirectory + @"\common\" + installdir).BeautifyPath(); 68 | if (games.Any(g => g.appId == appId && g.gameDirectory == gameDirectory)) 69 | continue; 70 | if (!int.TryParse(appId, out int _)) 71 | continue; 72 | if (!int.TryParse(buildId, out int buildIdInt)) 73 | continue; 74 | string branch = result.Value.GetChild("UserConfig")?.GetChild("betakey")?.ToString(); 75 | if (string.IsNullOrWhiteSpace(branch)) 76 | branch = "public"; 77 | games.Add((appId, name, branch, buildIdInt, gameDirectory)); 78 | } 79 | return games; 80 | }); 81 | 82 | private static async Task> GetLibraryDirectories() 83 | => await Task.Run(() => 84 | { 85 | List gameDirectories = new(); 86 | if (Program.Canceled) 87 | return gameDirectories; 88 | string steamInstallPath = InstallPath; 89 | if (steamInstallPath == null || !Directory.Exists(steamInstallPath)) 90 | return gameDirectories; 91 | string libraryFolder = steamInstallPath + @"\steamapps"; 92 | if (!Directory.Exists(libraryFolder)) 93 | return gameDirectories; 94 | gameDirectories.Add(libraryFolder); 95 | string libraryFolders = libraryFolder + @"\libraryfolders.vdf"; 96 | if (!File.Exists(libraryFolders) || !ValveDataFile.TryDeserialize(File.ReadAllText(libraryFolders, Encoding.UTF8), out VProperty result)) 97 | return gameDirectories; 98 | foreach (VToken vToken in result.Value.Where(p => p is VProperty property && int.TryParse(property.Key, out int _))) 99 | { 100 | VProperty property = (VProperty)vToken; 101 | string path = property.Value.GetChild("path")?.ToString(); 102 | if (string.IsNullOrWhiteSpace(path)) 103 | continue; 104 | path += @"\steamapps"; 105 | if (Directory.Exists(path) && !gameDirectories.Contains(path)) 106 | gameDirectories.Add(path); 107 | } 108 | return gameDirectories; 109 | }); 110 | } -------------------------------------------------------------------------------- /CreamInstaller/Platforms/Steam/SteamStore.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using CreamInstaller.Utility; 7 | using Newtonsoft.Json; 8 | using Newtonsoft.Json.Linq; 9 | #if DEBUG 10 | using System; 11 | using CreamInstaller.Forms; 12 | #endif 13 | 14 | namespace CreamInstaller.Platforms.Steam; 15 | 16 | internal static class SteamStore 17 | { 18 | private const int CooldownGame = 600; 19 | private const int CooldownDlc = 1200; 20 | 21 | internal static async Task> ParseDlcAppIds(AppData appData) 22 | => await Task.Run(() => 23 | { 24 | List dlcIds = new(); 25 | if (appData.dlc is null) 26 | return dlcIds; 27 | dlcIds.AddRange(from appId in appData.dlc where appId > 0 select appId.ToString()); 28 | return dlcIds; 29 | }); 30 | 31 | internal static async Task QueryStoreAPI(string appId, bool isDlc = false, int attempts = 0) 32 | { 33 | while (true) 34 | { 35 | if (Program.Canceled) 36 | return null; 37 | string cacheFile = ProgramData.AppInfoPath + @$"\{appId}.json"; 38 | bool cachedExists = File.Exists(cacheFile); 39 | if (!cachedExists || ProgramData.CheckCooldown(appId, isDlc ? CooldownDlc : CooldownGame)) 40 | { 41 | string response = await HttpClientManager.EnsureGet($"https://store.steampowered.com/api/appdetails?appids={appId}"); 42 | if (response is not null) 43 | { 44 | IDictionary apps = (IDictionary)JsonConvert.DeserializeObject(response); 45 | if (apps is not null) 46 | foreach (KeyValuePair app in apps) 47 | try 48 | { 49 | AppDetails appDetails = JsonConvert.DeserializeObject(app.Value.ToString()); 50 | if (appDetails is not null) 51 | { 52 | AppData data = appDetails.data; 53 | if (!appDetails.success) 54 | { 55 | #if DEBUG 56 | DebugForm.Current.Log( 57 | $"Query unsuccessful for appid {appId}{(isDlc ? " (DLC)" : "")}: {app.Value.ToString(Formatting.None)}", 58 | LogTextBox.Warning); 59 | #endif 60 | if (data is null) 61 | return null; 62 | } 63 | if (data is not null) 64 | { 65 | try 66 | { 67 | await File.WriteAllTextAsync(cacheFile, JsonConvert.SerializeObject(data, Formatting.Indented)); 68 | } 69 | catch 70 | #if DEBUG 71 | (Exception e) 72 | { 73 | DebugForm.Current.Log( 74 | $"Unsuccessful serialization of query for appid {appId}{(isDlc ? " (DLC)" : "")}: {e.GetType()} ({e.Message})"); 75 | } 76 | #else 77 | { 78 | // ignored 79 | } 80 | #endif 81 | return data; 82 | } 83 | #if DEBUG 84 | DebugForm.Current.Log( 85 | $"Response data null for appid {appId}{(isDlc ? " (DLC)" : "")}: {app.Value.ToString(Formatting.None)}"); 86 | #endif 87 | } 88 | #if DEBUG 89 | else 90 | DebugForm.Current.Log( 91 | $"Response details null for appid {appId}{(isDlc ? " (DLC)" : "")}: {app.Value.ToString(Formatting.None)}"); 92 | #endif 93 | } 94 | catch 95 | #if DEBUG 96 | (Exception e) 97 | { 98 | DebugForm.Current.Log( 99 | $"Unsuccessful deserialization of query for appid {appId}{(isDlc ? " (DLC)" : "")}: {e.GetType()} ({e.Message})"); 100 | } 101 | #else 102 | { 103 | // ignored 104 | } 105 | #endif 106 | #if DEBUG 107 | else 108 | DebugForm.Current.Log("Response deserialization null for appid " + appId); 109 | #endif 110 | } 111 | else 112 | { 113 | #if DEBUG 114 | DebugForm.Current.Log("Response null for appid " + appId, LogTextBox.Warning); 115 | #endif 116 | } 117 | } 118 | if (cachedExists) 119 | try 120 | { 121 | return JsonConvert.DeserializeObject(await File.ReadAllTextAsync(cacheFile)); 122 | } 123 | catch 124 | { 125 | try 126 | { 127 | File.Delete(cacheFile); 128 | } 129 | catch 130 | { 131 | // ignored 132 | } 133 | } 134 | if (isDlc || attempts >= 10) 135 | return null; 136 | Thread.Sleep(1000); 137 | attempts = ++attempts; 138 | } 139 | } 140 | } -------------------------------------------------------------------------------- /CreamInstaller/Platforms/Steam/ValveDataFile.cs: -------------------------------------------------------------------------------- 1 | using Gameloop.Vdf; 2 | using Gameloop.Vdf.Linq; 3 | 4 | namespace CreamInstaller.Platforms.Steam; 5 | 6 | internal static class ValveDataFile 7 | { 8 | internal static bool TryDeserialize(string value, out VProperty result) 9 | { 10 | result = null; 11 | try 12 | { 13 | result = VdfConvert.Deserialize(value); 14 | return true; 15 | } 16 | catch 17 | { 18 | // ignored 19 | } 20 | return false; 21 | } 22 | 23 | internal static VToken GetChild(this VToken token, string index) 24 | { 25 | try 26 | { 27 | return token[index]; 28 | } 29 | catch 30 | { 31 | // ignored 32 | } 33 | return null; 34 | } 35 | } -------------------------------------------------------------------------------- /CreamInstaller/Platforms/Ubisoft/UbisoftLibrary.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using CreamInstaller.Utility; 6 | using Microsoft.Win32; 7 | using static CreamInstaller.Resources.Resources; 8 | 9 | namespace CreamInstaller.Platforms.Ubisoft; 10 | 11 | internal static class UbisoftLibrary 12 | { 13 | private static RegistryKey installsKey; 14 | 15 | internal static RegistryKey InstallsKey 16 | { 17 | get 18 | { 19 | installsKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Ubisoft\Launcher\Installs"); 20 | installsKey ??= Registry.LocalMachine.OpenSubKey(@"SOFTWARE\WOW6432Node\Ubisoft\Launcher\Installs"); 21 | return installsKey; 22 | } 23 | } 24 | 25 | internal static async Task> GetExecutableDirectories(string gameDirectory) 26 | => await Task.Run(async () => await gameDirectory.GetExecutableDirectories(true)); 27 | 28 | internal static async Task> GetGames() 29 | => await Task.Run(() => 30 | { 31 | List<(string gameId, string name, string gameDirectory)> games = new(); 32 | RegistryKey installsKey = InstallsKey; 33 | if (installsKey is null) 34 | return games; 35 | foreach (string gameId in installsKey.GetSubKeyNames()) 36 | { 37 | RegistryKey installKey = installsKey.OpenSubKey(gameId); 38 | string installDir = installKey?.GetValue("InstallDir")?.ToString()?.BeautifyPath(); 39 | if (installDir is not null && !games.Any(g => g.gameId == gameId && g.gameDirectory == installDir)) 40 | games.Add((gameId, new DirectoryInfo(installDir).Name, installDir)); 41 | } 42 | return games; 43 | }); 44 | } -------------------------------------------------------------------------------- /CreamInstaller/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Drawing; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Threading; 8 | using System.Windows.Forms; 9 | using CreamInstaller.Forms; 10 | using CreamInstaller.Platforms.Steam; 11 | using CreamInstaller.Utility; 12 | 13 | namespace CreamInstaller; 14 | 15 | internal static class Program 16 | { 17 | internal static readonly string Name = Application.CompanyName; 18 | private static readonly string Description = Application.ProductName; 19 | internal static readonly string Version = Application.ProductVersion; 20 | 21 | internal const string RepositoryOwner = "pointfeev"; 22 | internal static readonly string RepositoryName = Name; 23 | internal static readonly string RepositoryPackage = Name + ".zip"; 24 | #if DEBUG 25 | internal static readonly string ApplicationName = Name + " v" + Version + "-debug: " + Description; 26 | internal static readonly string ApplicationNameShort = Name + " v" + Version + "-debug"; 27 | internal static readonly string ApplicationExecutable = Name + "-debug.exe"; // should be the same as in .csproj 28 | #else 29 | internal static readonly string ApplicationName = Name + " v" + Version + ": " + Description; 30 | internal static readonly string ApplicationNameShort = Name + " v" + Version; 31 | internal static readonly string ApplicationExecutable = Name + ".exe"; // should be the same as in .csproj 32 | #endif 33 | 34 | internal static readonly Assembly EntryAssembly = Assembly.GetEntryAssembly(); 35 | private static readonly Process CurrentProcess = Process.GetCurrentProcess(); 36 | internal static readonly string CurrentProcessFilePath = CurrentProcess.MainModule?.FileName; 37 | 38 | internal static bool BlockProtectedGames = true; 39 | internal static readonly string[] ProtectedGames = { "PAYDAY 2" }; 40 | internal static readonly string[] ProtectedGameDirectories = { @"\EasyAntiCheat", @"\BattlEye" }; 41 | internal static readonly string[] ProtectedGameDirectoryExceptions = Array.Empty(); 42 | 43 | internal static bool IsGameBlocked(string name, string directory = null) 44 | { 45 | if (!BlockProtectedGames) 46 | return false; 47 | if (ProtectedGames.Contains(name)) 48 | return true; 49 | if (directory is null || ProtectedGameDirectoryExceptions.Contains(name)) 50 | return false; 51 | return ProtectedGameDirectories.Any(path => Directory.Exists(directory + path)); 52 | } 53 | 54 | internal static bool IsProgramRunningDialog(Form form, ProgramSelection selection) 55 | { 56 | while (true) 57 | { 58 | if (selection.AreDllsLocked) 59 | { 60 | using DialogForm dialogForm = new(form); 61 | if (dialogForm.Show(SystemIcons.Error, 62 | $"ERROR: {selection.Name} is currently running!" + "\n\nPlease close the program/game to continue . . . ", "Retry", "Cancel") 63 | == DialogResult.OK) 64 | continue; 65 | } 66 | else 67 | return true; 68 | return false; 69 | } 70 | } 71 | 72 | [STAThread] 73 | private static void Main() 74 | { 75 | using Mutex mutex = new(true, Name, out bool createdNew); 76 | if (createdNew) 77 | { 78 | _ = Application.SetHighDpiMode(HighDpiMode.SystemAware); 79 | Application.EnableVisualStyles(); 80 | Application.SetCompatibleTextRenderingDefault(false); 81 | Application.ApplicationExit += OnApplicationExit; 82 | Application.ThreadException += (_, e) => e.Exception.HandleFatalException(); 83 | Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); 84 | AppDomain.CurrentDomain.UnhandledException += (_, e) => (e.ExceptionObject as Exception)?.HandleFatalException(); 85 | retry: 86 | try 87 | { 88 | HttpClientManager.Setup(); 89 | using MainForm form = new(); 90 | #if DEBUG 91 | DebugForm.Current.Attach(form); 92 | #endif 93 | Application.Run(form); 94 | } 95 | catch (Exception e) 96 | { 97 | if (e.HandleException()) 98 | goto retry; 99 | Application.Exit(); 100 | return; 101 | } 102 | } 103 | mutex.Close(); 104 | } 105 | 106 | internal static bool Canceled; 107 | 108 | internal static async void Cleanup(bool cancel = true) 109 | { 110 | Canceled = cancel; 111 | await SteamCMD.Cleanup(); 112 | } 113 | 114 | private static void OnApplicationExit(object s, EventArgs e) 115 | { 116 | Cleanup(); 117 | HttpClientManager.Dispose(); 118 | } 119 | } -------------------------------------------------------------------------------- /CreamInstaller/ProgramSelection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using CreamInstaller.Components; 5 | using CreamInstaller.Resources; 6 | using static CreamInstaller.Resources.Resources; 7 | 8 | namespace CreamInstaller; 9 | 10 | public enum Platform 11 | { 12 | None = 0, Paradox, Steam, 13 | Epic, Ubisoft 14 | } 15 | 16 | public enum DlcType 17 | { 18 | Steam, SteamHidden, EpicCatalogItem, 19 | EpicEntitlement 20 | } 21 | 22 | internal sealed class ProgramSelection 23 | { 24 | internal const string DefaultKoaloaderProxy = "version"; 25 | 26 | internal static readonly List All = new(); 27 | 28 | internal readonly SortedList AllDlc = new(PlatformIdComparer.String); 29 | 30 | internal readonly SortedList dlc)> ExtraDlc = new(); 31 | 32 | internal readonly SortedList dlc)> ExtraSelectedDlc = new(); 33 | 34 | internal readonly SortedList SelectedDlc = new(PlatformIdComparer.String); 35 | 36 | internal List DllDirectories; 37 | internal bool Enabled; 38 | internal List<(string directory, BinaryType binaryType)> ExecutableDirectories; 39 | internal string IconUrl; 40 | internal string Id = "0"; 41 | internal bool Koaloader; 42 | internal string KoaloaderProxy; 43 | internal string Name = "Program"; 44 | 45 | internal Platform Platform; 46 | 47 | internal string ProductUrl; 48 | 49 | internal string Publisher; 50 | 51 | internal string RootDirectory; 52 | internal string SubIconUrl; 53 | 54 | internal string WebsiteUrl; 55 | 56 | internal ProgramSelection() => All.Add(this); 57 | 58 | internal bool AreDllsLocked 59 | { 60 | get 61 | { 62 | foreach (string directory in DllDirectories) 63 | { 64 | if (Platform is Platform.Steam or Platform.Paradox) 65 | { 66 | directory.GetCreamApiComponents(out string api32, out string api32_o, out string api64, out string api64_o, out string config); 67 | if (api32.IsFilePathLocked() || api32_o.IsFilePathLocked() || api64.IsFilePathLocked() || api64_o.IsFilePathLocked() 68 | || config.IsFilePathLocked()) 69 | return true; 70 | directory.GetSmokeApiComponents(out api32, out api32_o, out api64, out api64_o, out string old_config, out config, out string old_log, 71 | out string log, out string cache); 72 | if (api32.IsFilePathLocked() || api32_o.IsFilePathLocked() || api64.IsFilePathLocked() || api64_o.IsFilePathLocked() 73 | || old_config.IsFilePathLocked() || config.IsFilePathLocked() || old_log.IsFilePathLocked() || log.IsFilePathLocked() 74 | || cache.IsFilePathLocked()) 75 | return true; 76 | } 77 | if (Platform is Platform.Epic or Platform.Paradox) 78 | { 79 | directory.GetScreamApiComponents(out string api32, out string api32_o, out string api64, out string api64_o, out string config, 80 | out string log); 81 | if (api32.IsFilePathLocked() || api32_o.IsFilePathLocked() || api64.IsFilePathLocked() || api64_o.IsFilePathLocked() 82 | || config.IsFilePathLocked() || log.IsFilePathLocked()) 83 | return true; 84 | } 85 | if (Platform is Platform.Ubisoft) 86 | { 87 | directory.GetUplayR1Components(out string api32, out string api32_o, out string api64, out string api64_o, out string config, 88 | out string log); 89 | if (api32.IsFilePathLocked() || api32_o.IsFilePathLocked() || api64.IsFilePathLocked() || api64_o.IsFilePathLocked() 90 | || config.IsFilePathLocked() || log.IsFilePathLocked()) 91 | return true; 92 | directory.GetUplayR2Components(out string old_api32, out string old_api64, out api32, out api32_o, out api64, out api64_o, out config, 93 | out log); 94 | if (old_api32.IsFilePathLocked() || old_api64.IsFilePathLocked() || api32.IsFilePathLocked() || api32_o.IsFilePathLocked() 95 | || api64.IsFilePathLocked() || api64_o.IsFilePathLocked() || config.IsFilePathLocked() || log.IsFilePathLocked()) 96 | return true; 97 | } 98 | } 99 | return false; 100 | } 101 | } 102 | 103 | internal static List AllSafe => All.ToList(); 104 | 105 | internal static List AllEnabled => AllSafe.FindAll(s => s.Enabled); 106 | 107 | private void Toggle(string dlcAppId, (DlcType type, string name, string icon) dlcApp, bool enabled) 108 | { 109 | if (enabled) 110 | SelectedDlc[dlcAppId] = dlcApp; 111 | else 112 | _ = SelectedDlc.Remove(dlcAppId); 113 | } 114 | 115 | internal void ToggleDlc(string dlcId, bool enabled) 116 | { 117 | foreach (KeyValuePair pair in AllDlc) 118 | { 119 | string appId = pair.Key; 120 | (DlcType type, string name, string icon) dlcApp = pair.Value; 121 | if (appId != dlcId) 122 | continue; 123 | Toggle(appId, dlcApp, enabled); 124 | break; 125 | } 126 | Enabled = SelectedDlc.Any() || ExtraSelectedDlc.Any(); 127 | } 128 | 129 | private void Validate() 130 | { 131 | if (Program.IsGameBlocked(Name, RootDirectory)) 132 | { 133 | _ = All.Remove(this); 134 | return; 135 | } 136 | if (!Directory.Exists(RootDirectory)) 137 | { 138 | _ = All.Remove(this); 139 | return; 140 | } 141 | _ = DllDirectories.RemoveAll(directory => !Directory.Exists(directory)); 142 | if (!DllDirectories.Any()) 143 | _ = All.Remove(this); 144 | } 145 | 146 | private void Validate(List<(Platform platform, string id, string name)> programsToScan) 147 | { 148 | if (programsToScan is null || !programsToScan.Any(p => p.platform == Platform && p.id == Id)) 149 | { 150 | _ = All.Remove(this); 151 | return; 152 | } 153 | Validate(); 154 | } 155 | 156 | internal static void ValidateAll() => AllSafe.ForEach(selection => selection.Validate()); 157 | 158 | internal static void ValidateAll(List<(Platform platform, string id, string name)> programsToScan) 159 | => AllSafe.ForEach(selection => selection.Validate(programsToScan)); 160 | 161 | internal static ProgramSelection FromPlatformId(Platform platform, string gameId) => AllSafe.Find(s => s.Platform == platform && s.Id == gameId); 162 | 163 | internal static (string gameId, (DlcType type, string name, string icon) app)? GetDlcFromPlatformId(Platform platform, string dlcId) 164 | { 165 | foreach (ProgramSelection selection in AllSafe.Where(s => s.Platform == platform)) 166 | foreach (KeyValuePair pair in selection.AllDlc.Where(p => p.Key == dlcId)) 167 | return (selection.Id, pair.Value); 168 | return null; 169 | } 170 | } -------------------------------------------------------------------------------- /CreamInstaller/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Resources; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: ComVisible(false)] 6 | [assembly: CLSCompliant(true)] 7 | [assembly: NeutralResourcesLanguage("en")] -------------------------------------------------------------------------------- /CreamInstaller/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | using System.CodeDom.Compiler; 12 | using System.ComponentModel; 13 | using System.Diagnostics; 14 | using System.Diagnostics.CodeAnalysis; 15 | using System.Drawing; 16 | using System.Globalization; 17 | using System.Resources; 18 | using System.Runtime.CompilerServices; 19 | 20 | namespace CreamInstaller.Properties { 21 | /// 22 | /// A strongly-typed resource class, for looking up localized strings, etc. 23 | /// 24 | // This class was auto-generated by the StronglyTypedResourceBuilder 25 | // class via a tool like ResGen or Visual Studio. 26 | // To add or remove a member, edit your .ResX file then rerun ResGen 27 | // with the /str option, or rebuild your VS project. 28 | [GeneratedCode("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] 29 | [DebuggerNonUserCode()] 30 | [CompilerGenerated()] 31 | internal class Resources { 32 | 33 | private static ResourceManager resourceMan; 34 | 35 | private static CultureInfo resourceCulture; 36 | 37 | [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 38 | internal Resources() { 39 | } 40 | 41 | /// 42 | /// Returns the cached ResourceManager instance used by this class. 43 | /// 44 | [EditorBrowsable(EditorBrowsableState.Advanced)] 45 | internal static ResourceManager ResourceManager { 46 | get { 47 | if (ReferenceEquals(resourceMan, null)) { 48 | ResourceManager temp = new ResourceManager("CreamInstaller.Properties.Resources", typeof(Resources).Assembly); 49 | resourceMan = temp; 50 | } 51 | return resourceMan; 52 | } 53 | } 54 | 55 | /// 56 | /// Overrides the current thread's CurrentUICulture property for all 57 | /// resource lookups using this strongly typed resource class. 58 | /// 59 | [EditorBrowsable(EditorBrowsableState.Advanced)] 60 | internal static CultureInfo Culture { 61 | get { 62 | return resourceCulture; 63 | } 64 | set { 65 | resourceCulture = value; 66 | } 67 | } 68 | 69 | /// 70 | /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). 71 | /// 72 | internal static Icon Icon { 73 | get { 74 | object obj = ResourceManager.GetObject("Icon", resourceCulture); 75 | return ((Icon)(obj)); 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /CreamInstaller/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | ..\resources\ini.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 123 | 124 | -------------------------------------------------------------------------------- /CreamInstaller/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "CreamInstaller": { 4 | "commandName": "Project", 5 | "hotReloadEnabled": false, 6 | "nativeDebugging": false 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/audioses-32/audioses.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/audioses-32/audioses.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/audioses-64/audioses.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/audioses-64/audioses.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/d3d10-32/d3d10.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/d3d10-32/d3d10.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/d3d10-64/d3d10.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/d3d10-64/d3d10.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/d3d11-32/d3d11.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/d3d11-32/d3d11.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/d3d11-64/d3d11.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/d3d11-64/d3d11.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/d3d9-32/d3d9.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/d3d9-32/d3d9.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/d3d9-64/d3d9.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/d3d9-64/d3d9.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/dinput8-32/dinput8.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/dinput8-32/dinput8.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/dinput8-64/dinput8.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/dinput8-64/dinput8.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/dwmapi-32/dwmapi.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/dwmapi-32/dwmapi.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/dwmapi-64/dwmapi.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/dwmapi-64/dwmapi.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/dxgi-32/dxgi.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/dxgi-32/dxgi.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/dxgi-64/dxgi.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/dxgi-64/dxgi.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/glu32-32/glu32.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/glu32-32/glu32.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/glu32-64/glu32.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/glu32-64/glu32.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/hid-32/hid.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/hid-32/hid.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/hid-64/hid.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/hid-64/hid.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/iphlpapi-32/iphlpapi.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/iphlpapi-32/iphlpapi.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/iphlpapi-64/iphlpapi.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/iphlpapi-64/iphlpapi.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/msasn1-32/msasn1.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/msasn1-32/msasn1.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/msasn1-64/msasn1.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/msasn1-64/msasn1.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/msimg32-32/msimg32.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/msimg32-32/msimg32.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/msimg32-64/msimg32.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/msimg32-64/msimg32.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/mswsock-32/mswsock.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/mswsock-32/mswsock.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/mswsock-64/mswsock.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/mswsock-64/mswsock.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/opengl32-32/opengl32.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/opengl32-32/opengl32.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/opengl32-64/opengl32.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/opengl32-64/opengl32.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/profapi-32/profapi.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/profapi-32/profapi.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/profapi-64/profapi.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/profapi-64/profapi.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/propsys-32/propsys.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/propsys-32/propsys.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/propsys-64/propsys.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/propsys-64/propsys.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/textshaping-32/textshaping.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/textshaping-32/textshaping.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/textshaping-64/textshaping.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/textshaping-64/textshaping.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/version-32/version.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/version-32/version.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/version-64/version.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/version-64/version.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/winhttp-32/winhttp.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/winhttp-32/winhttp.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/winhttp-64/winhttp.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/winhttp-64/winhttp.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/winmm-32/winmm.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/winmm-32/winmm.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/winmm-64/winmm.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/winmm-64/winmm.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/wldp-32/wldp.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/wldp-32/wldp.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/wldp-64/wldp.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/wldp-64/wldp.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/xinput9_1_0-32/xinput9_1_0.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/xinput9_1_0-32/xinput9_1_0.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/Koaloader/xinput9_1_0-64/xinput9_1_0.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/Koaloader/xinput9_1_0-64/xinput9_1_0.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/ScreamAPI.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using CreamInstaller.Components; 7 | using CreamInstaller.Forms; 8 | using CreamInstaller.Utility; 9 | 10 | namespace CreamInstaller.Resources; 11 | 12 | internal static class ScreamAPI 13 | { 14 | internal static void GetScreamApiComponents(this string directory, out string api32, out string api32_o, out string api64, out string api64_o, 15 | out string config, out string log) 16 | { 17 | api32 = directory + @"\EOSSDK-Win32-Shipping.dll"; 18 | api32_o = directory + @"\EOSSDK-Win32-Shipping_o.dll"; 19 | api64 = directory + @"\EOSSDK-Win64-Shipping.dll"; 20 | api64_o = directory + @"\EOSSDK-Win64-Shipping_o.dll"; 21 | config = directory + @"\ScreamAPI.json"; 22 | log = directory + @"\ScreamAPI.log"; 23 | } 24 | 25 | internal static void CheckConfig(string directory, ProgramSelection selection, InstallForm installForm = null) 26 | { 27 | directory.GetScreamApiComponents(out _, out _, out _, out _, out string config, out _); 28 | IEnumerable> overrideCatalogItems 29 | = selection.AllDlc.Where(pair => pair.Value.type is DlcType.EpicCatalogItem).Except(selection.SelectedDlc); 30 | foreach (KeyValuePair extraDlc)> pair in selection.ExtraSelectedDlc) 31 | overrideCatalogItems = overrideCatalogItems.Except(pair.Value.extraDlc); 32 | IEnumerable> entitlements 33 | = selection.SelectedDlc.Where(pair => pair.Value.type == DlcType.EpicEntitlement); 34 | foreach (KeyValuePair dlc)> pair in selection.ExtraSelectedDlc) 35 | entitlements = entitlements.Concat(pair.Value.dlc.Where(pair => pair.Value.type == DlcType.EpicEntitlement)); 36 | overrideCatalogItems = overrideCatalogItems.ToList(); 37 | entitlements = entitlements.ToList(); 38 | if (overrideCatalogItems.Any() || entitlements.Any()) 39 | { 40 | /*if (installForm is not null) 41 | installForm.UpdateUser("Generating ScreamAPI configuration for " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation);*/ 42 | File.Create(config).Close(); 43 | StreamWriter writer = new(config, true, Encoding.UTF8); 44 | WriteConfig(writer, new(overrideCatalogItems.ToDictionary(pair => pair.Key, pair => pair.Value), PlatformIdComparer.String), 45 | new(entitlements.ToDictionary(pair => pair.Key, pair => pair.Value), PlatformIdComparer.String), installForm); 46 | writer.Flush(); 47 | writer.Close(); 48 | } 49 | else if (File.Exists(config)) 50 | { 51 | File.Delete(config); 52 | installForm?.UpdateUser($"Deleted unnecessary configuration: {Path.GetFileName(config)}", LogTextBox.Action, false); 53 | } 54 | } 55 | 56 | private static void WriteConfig(StreamWriter writer, SortedList overrideCatalogItems, 57 | SortedList entitlements, InstallForm installForm = null) 58 | { 59 | writer.WriteLine("{"); 60 | writer.WriteLine(" \"version\": 2,"); 61 | writer.WriteLine(" \"logging\": false,"); 62 | writer.WriteLine(" \"eos_logging\": false,"); 63 | writer.WriteLine(" \"block_metrics\": false,"); 64 | writer.WriteLine(" \"catalog_items\": {"); 65 | writer.WriteLine(" \"unlock_all\": true,"); 66 | if (overrideCatalogItems.Any()) 67 | { 68 | writer.WriteLine(" \"override\": ["); 69 | KeyValuePair lastOverrideCatalogItem = overrideCatalogItems.Last(); 70 | foreach (KeyValuePair pair in overrideCatalogItems) 71 | { 72 | string id = pair.Key; 73 | (_, string name, _) = pair.Value; 74 | writer.WriteLine($" \"{id}\"{(pair.Equals(lastOverrideCatalogItem) ? "" : ",")}"); 75 | installForm?.UpdateUser($"Added override catalog item to ScreamAPI.json with id {id} ({name})", LogTextBox.Action, false); 76 | } 77 | writer.WriteLine(" ]"); 78 | } 79 | else 80 | writer.WriteLine(" \"override\": []"); 81 | writer.WriteLine(" },"); 82 | writer.WriteLine(" \"entitlements\": {"); 83 | writer.WriteLine(" \"unlock_all\": true,"); 84 | writer.WriteLine(" \"auto_inject\": true,"); 85 | if (entitlements.Any()) 86 | { 87 | writer.WriteLine(" \"inject\": ["); 88 | KeyValuePair lastEntitlement = entitlements.Last(); 89 | foreach (KeyValuePair pair in entitlements) 90 | { 91 | string id = pair.Key; 92 | (_, string name, _) = pair.Value; 93 | writer.WriteLine($" \"{id}\"{(pair.Equals(lastEntitlement) ? "" : ",")}"); 94 | installForm?.UpdateUser($"Added entitlement to ScreamAPI.json with id {id} ({name})", LogTextBox.Action, false); 95 | } 96 | writer.WriteLine(" ]"); 97 | } 98 | else 99 | writer.WriteLine(" \"inject\": []"); 100 | writer.WriteLine(" }"); 101 | writer.WriteLine("}"); 102 | } 103 | 104 | internal static async Task Uninstall(string directory, InstallForm installForm = null, bool deleteOthers = true) 105 | => await Task.Run(() => 106 | { 107 | directory.GetScreamApiComponents(out string api32, out string api32_o, out string api64, out string api64_o, out string config, out string log); 108 | if (File.Exists(api32_o)) 109 | { 110 | if (File.Exists(api32)) 111 | { 112 | File.Delete(api32); 113 | installForm?.UpdateUser($"Deleted ScreamAPI: {Path.GetFileName(api32)}", LogTextBox.Action, false); 114 | } 115 | File.Move(api32_o, api32!); 116 | installForm?.UpdateUser($"Restored EOS: {Path.GetFileName(api32_o)} -> {Path.GetFileName(api32)}", LogTextBox.Action, false); 117 | } 118 | if (File.Exists(api64_o)) 119 | { 120 | if (File.Exists(api64)) 121 | { 122 | File.Delete(api64); 123 | installForm?.UpdateUser($"Deleted ScreamAPI: {Path.GetFileName(api64)}", LogTextBox.Action, false); 124 | } 125 | File.Move(api64_o, api64!); 126 | installForm?.UpdateUser($"Restored EOS: {Path.GetFileName(api64_o)} -> {Path.GetFileName(api64)}", LogTextBox.Action, false); 127 | } 128 | if (!deleteOthers) 129 | return; 130 | if (File.Exists(config)) 131 | { 132 | File.Delete(config); 133 | installForm?.UpdateUser($"Deleted configuration: {Path.GetFileName(config)}", LogTextBox.Action, false); 134 | } 135 | if (File.Exists(log)) 136 | { 137 | File.Delete(log); 138 | installForm?.UpdateUser($"Deleted log: {Path.GetFileName(log)}", LogTextBox.Action, false); 139 | } 140 | }); 141 | 142 | internal static async Task Install(string directory, ProgramSelection selection, InstallForm installForm = null, bool generateConfig = true) 143 | => await Task.Run(() => 144 | { 145 | directory.GetScreamApiComponents(out string api32, out string api32_o, out string api64, out string api64_o, out _, out _); 146 | if (File.Exists(api32) && !File.Exists(api32_o)) 147 | { 148 | File.Move(api32, api32_o!); 149 | installForm?.UpdateUser($"Renamed EOS: {Path.GetFileName(api32)} -> {Path.GetFileName(api32_o)}", LogTextBox.Action, false); 150 | } 151 | if (File.Exists(api32_o)) 152 | { 153 | "ScreamAPI.EOSSDK-Win32-Shipping.dll".Write(api32); 154 | installForm?.UpdateUser($"Wrote ScreamAPI: {Path.GetFileName(api32)}", LogTextBox.Action, false); 155 | } 156 | if (File.Exists(api64) && !File.Exists(api64_o)) 157 | { 158 | File.Move(api64, api64_o!); 159 | installForm?.UpdateUser($"Renamed EOS: {Path.GetFileName(api64)} -> {Path.GetFileName(api64_o)}", LogTextBox.Action, false); 160 | } 161 | if (File.Exists(api64_o)) 162 | { 163 | "ScreamAPI.EOSSDK-Win64-Shipping.dll".Write(api64); 164 | installForm?.UpdateUser($"Wrote ScreamAPI: {Path.GetFileName(api64)}", LogTextBox.Action, false); 165 | } 166 | if (generateConfig) 167 | CheckConfig(directory, selection, installForm); 168 | }); 169 | } -------------------------------------------------------------------------------- /CreamInstaller/Resources/ScreamAPI/EOSSDK-Win32-Shipping.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/ScreamAPI/EOSSDK-Win32-Shipping.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/ScreamAPI/EOSSDK-Win64-Shipping.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/ScreamAPI/EOSSDK-Win64-Shipping.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/SmokeAPI/steam_api.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/SmokeAPI/steam_api.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/SmokeAPI/steam_api64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/SmokeAPI/steam_api64.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/UplayR1.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using CreamInstaller.Components; 7 | using CreamInstaller.Forms; 8 | using CreamInstaller.Utility; 9 | 10 | namespace CreamInstaller.Resources; 11 | 12 | internal static class UplayR1 13 | { 14 | internal static void GetUplayR1Components(this string directory, out string api32, out string api32_o, out string api64, out string api64_o, 15 | out string config, out string log) 16 | { 17 | api32 = directory + @"\uplay_r1_loader.dll"; 18 | api32_o = directory + @"\uplay_r1_loader_o.dll"; 19 | api64 = directory + @"\uplay_r1_loader64.dll"; 20 | api64_o = directory + @"\uplay_r1_loader64_o.dll"; 21 | config = directory + @"\UplayR1Unlocker.jsonc"; 22 | log = directory + @"\UplayR1Unlocker.log"; 23 | } 24 | 25 | internal static void CheckConfig(string directory, ProgramSelection selection, InstallForm installForm = null) 26 | { 27 | directory.GetUplayR1Components(out _, out _, out _, out _, out string config, out _); 28 | IEnumerable> blacklistDlc = selection.AllDlc.Except(selection.SelectedDlc); 29 | foreach (KeyValuePair extraDlc)> pair in selection.ExtraSelectedDlc) 30 | blacklistDlc = blacklistDlc.Except(pair.Value.extraDlc); 31 | blacklistDlc = blacklistDlc.ToList(); 32 | if (blacklistDlc.Any()) 33 | { 34 | /*if (installForm is not null) 35 | installForm.UpdateUser("Generating Uplay R1 Unlocker configuration for " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation);*/ 36 | File.Create(config).Close(); 37 | StreamWriter writer = new(config, true, Encoding.UTF8); 38 | WriteConfig(writer, new(blacklistDlc.ToDictionary(pair => pair.Key, pair => pair.Value), PlatformIdComparer.String), installForm); 39 | writer.Flush(); 40 | writer.Close(); 41 | } 42 | else if (File.Exists(config)) 43 | { 44 | File.Delete(config); 45 | installForm?.UpdateUser($"Deleted unnecessary configuration: {Path.GetFileName(config)}", LogTextBox.Action, false); 46 | } 47 | } 48 | 49 | private static void WriteConfig(StreamWriter writer, SortedList blacklistDlc, 50 | InstallForm installForm = null) 51 | { 52 | writer.WriteLine("{"); 53 | writer.WriteLine(" \"logging\": false,"); 54 | writer.WriteLine(" \"lang\": \"default\","); 55 | writer.WriteLine(" \"hook_loader\": false,"); 56 | if (blacklistDlc.Count > 0) 57 | { 58 | writer.WriteLine(" \"blacklist\": ["); 59 | KeyValuePair lastBlacklistDlc = blacklistDlc.Last(); 60 | foreach (KeyValuePair pair in blacklistDlc) 61 | { 62 | string dlcId = pair.Key; 63 | (_, string dlcName, _) = pair.Value; 64 | writer.WriteLine($" {dlcId}{(pair.Equals(lastBlacklistDlc) ? "" : ",")}"); 65 | installForm?.UpdateUser($"Added blacklist DLC to UplayR1Unlocker.jsonc with appid {dlcId} ({dlcName})", LogTextBox.Action, false); 66 | } 67 | writer.WriteLine(" ],"); 68 | } 69 | else 70 | writer.WriteLine(" \"blacklist\": [],"); 71 | writer.WriteLine("}"); 72 | } 73 | 74 | internal static async Task Uninstall(string directory, InstallForm installForm = null, bool deleteOthers = true) 75 | => await Task.Run(() => 76 | { 77 | directory.GetUplayR1Components(out string api32, out string api32_o, out string api64, out string api64_o, out string config, out string log); 78 | if (File.Exists(api32_o)) 79 | { 80 | if (File.Exists(api32)) 81 | { 82 | File.Delete(api32); 83 | installForm?.UpdateUser($"Deleted Uplay R1 Unlocker: {Path.GetFileName(api32)}", LogTextBox.Action, false); 84 | } 85 | File.Move(api32_o, api32!); 86 | installForm?.UpdateUser($"Restored Uplay R1: {Path.GetFileName(api32_o)} -> {Path.GetFileName(api32)}", LogTextBox.Action, false); 87 | } 88 | if (File.Exists(api64_o)) 89 | { 90 | if (File.Exists(api64)) 91 | { 92 | File.Delete(api64); 93 | installForm?.UpdateUser($"Deleted Uplay R1 Unlocker: {Path.GetFileName(api64)}", LogTextBox.Action, false); 94 | } 95 | File.Move(api64_o, api64!); 96 | installForm?.UpdateUser($"Restored Uplay R1: {Path.GetFileName(api64_o)} -> {Path.GetFileName(api64)}", LogTextBox.Action, false); 97 | } 98 | if (!deleteOthers) 99 | return; 100 | if (File.Exists(config)) 101 | { 102 | File.Delete(config); 103 | installForm?.UpdateUser($"Deleted configuration: {Path.GetFileName(config)}", LogTextBox.Action, false); 104 | } 105 | if (File.Exists(log)) 106 | { 107 | File.Delete(log); 108 | installForm?.UpdateUser($"Deleted log: {Path.GetFileName(log)}", LogTextBox.Action, false); 109 | } 110 | }); 111 | 112 | internal static async Task Install(string directory, ProgramSelection selection, InstallForm installForm = null, bool generateConfig = true) 113 | => await Task.Run(() => 114 | { 115 | directory.GetUplayR1Components(out string api32, out string api32_o, out string api64, out string api64_o, out _, out _); 116 | if (File.Exists(api32) && !File.Exists(api32_o)) 117 | { 118 | File.Move(api32, api32_o!); 119 | installForm?.UpdateUser($"Renamed Uplay R1: {Path.GetFileName(api32)} -> {Path.GetFileName(api32_o)}", LogTextBox.Action, false); 120 | } 121 | if (File.Exists(api32_o)) 122 | { 123 | "UplayR1.uplay_r1_loader.dll".Write(api32); 124 | installForm?.UpdateUser($"Wrote Uplay R1 Unlocker: {Path.GetFileName(api32)}", LogTextBox.Action, false); 125 | } 126 | if (File.Exists(api64) && !File.Exists(api64_o)) 127 | { 128 | File.Move(api64, api64_o!); 129 | installForm?.UpdateUser($"Renamed Uplay R1: {Path.GetFileName(api64)} -> {Path.GetFileName(api64_o)}", LogTextBox.Action, false); 130 | } 131 | if (File.Exists(api64_o)) 132 | { 133 | "UplayR1.uplay_r1_loader64.dll".Write(api64); 134 | installForm?.UpdateUser($"Wrote Uplay R1 Unlocker: {Path.GetFileName(api64)}", LogTextBox.Action, false); 135 | } 136 | if (generateConfig) 137 | CheckConfig(directory, selection, installForm); 138 | }); 139 | } -------------------------------------------------------------------------------- /CreamInstaller/Resources/UplayR1/uplay_r1_loader.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/UplayR1/uplay_r1_loader.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/UplayR1/uplay_r1_loader64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/UplayR1/uplay_r1_loader64.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/UplayR2.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using CreamInstaller.Components; 7 | using CreamInstaller.Forms; 8 | using CreamInstaller.Utility; 9 | 10 | namespace CreamInstaller.Resources; 11 | 12 | internal static class UplayR2 13 | { 14 | internal static void GetUplayR2Components(this string directory, out string old_api32, out string old_api64, out string api32, out string api32_o, 15 | out string api64, out string api64_o, out string config, out string log) 16 | { 17 | old_api32 = directory + @"\uplay_r2_loader.dll"; 18 | old_api64 = directory + @"\uplay_r2_loader64.dll"; 19 | api32 = directory + @"\upc_r2_loader.dll"; 20 | api32_o = directory + @"\upc_r2_loader_o.dll"; 21 | api64 = directory + @"\upc_r2_loader64.dll"; 22 | api64_o = directory + @"\upc_r2_loader64_o.dll"; 23 | config = directory + @"\UplayR2Unlocker.jsonc"; 24 | log = directory + @"\UplayR2Unlocker.log"; 25 | } 26 | 27 | internal static void CheckConfig(string directory, ProgramSelection selection, InstallForm installForm = null) 28 | { 29 | directory.GetUplayR2Components(out _, out _, out _, out _, out _, out _, out string config, out _); 30 | IEnumerable> blacklistDlc = selection.AllDlc.Except(selection.SelectedDlc); 31 | foreach (KeyValuePair extraDlc)> pair in selection.ExtraSelectedDlc) 32 | blacklistDlc = blacklistDlc.Except(pair.Value.extraDlc); 33 | blacklistDlc = blacklistDlc.ToList(); 34 | if (blacklistDlc.Any()) 35 | { 36 | /*if (installForm is not null) 37 | installForm.UpdateUser("Generating Uplay R2 Unlocker configuration for " + selection.Name + $" in directory \"{directory}\" . . . ", LogTextBox.Operation);*/ 38 | File.Create(config).Close(); 39 | StreamWriter writer = new(config, true, Encoding.UTF8); 40 | WriteConfig(writer, new(blacklistDlc.ToDictionary(pair => pair.Key, pair => pair.Value), PlatformIdComparer.String), installForm); 41 | writer.Flush(); 42 | writer.Close(); 43 | } 44 | else if (File.Exists(config)) 45 | { 46 | File.Delete(config); 47 | installForm?.UpdateUser($"Deleted unnecessary configuration: {Path.GetFileName(config)}", LogTextBox.Action, false); 48 | } 49 | } 50 | 51 | private static void WriteConfig(StreamWriter writer, SortedList blacklistDlc, 52 | InstallForm installForm = null) 53 | { 54 | writer.WriteLine("{"); 55 | writer.WriteLine(" \"logging\": false,"); 56 | writer.WriteLine(" \"lang\": \"default\","); 57 | writer.WriteLine(" \"auto_fetch\": true,"); 58 | writer.WriteLine(" \"dlcs\": [],"); 59 | writer.WriteLine(" \"items\": [],"); 60 | if (blacklistDlc.Count > 0) 61 | { 62 | writer.WriteLine(" \"blacklist\": ["); 63 | KeyValuePair lastBlacklistDlc = blacklistDlc.Last(); 64 | foreach (KeyValuePair pair in blacklistDlc) 65 | { 66 | string dlcId = pair.Key; 67 | (_, string dlcName, _) = pair.Value; 68 | writer.WriteLine($" {dlcId}{(pair.Equals(lastBlacklistDlc) ? "" : ",")}"); 69 | installForm?.UpdateUser($"Added blacklist DLC to UplayR2Unlocker.jsonc with appid {dlcId} ({dlcName})", LogTextBox.Action, false); 70 | } 71 | writer.WriteLine(" ],"); 72 | } 73 | else 74 | writer.WriteLine(" \"blacklist\": [],"); 75 | writer.WriteLine("}"); 76 | } 77 | 78 | internal static async Task Uninstall(string directory, InstallForm installForm = null, bool deleteOthers = true) 79 | => await Task.Run(() => 80 | { 81 | directory.GetUplayR2Components(out string old_api32, out string old_api64, out string api32, out string api32_o, out string api64, 82 | out string api64_o, out string config, out string log); 83 | if (File.Exists(api32_o)) 84 | { 85 | string api = File.Exists(old_api32) ? old_api32 : api32; 86 | if (File.Exists(api)) 87 | { 88 | File.Delete(api); 89 | installForm?.UpdateUser($"Deleted Uplay R2 Unlocker: {Path.GetFileName(api)}", LogTextBox.Action, false); 90 | } 91 | File.Move(api32_o, api!); 92 | installForm?.UpdateUser($"Restored Uplay R2: {Path.GetFileName(api32_o)} -> {Path.GetFileName(api)}", LogTextBox.Action, false); 93 | } 94 | if (File.Exists(api64_o)) 95 | { 96 | string api = File.Exists(old_api64) ? old_api64 : api64; 97 | if (File.Exists(api)) 98 | { 99 | File.Delete(api); 100 | installForm?.UpdateUser($"Deleted Uplay R2 Unlocker: {Path.GetFileName(api)}", LogTextBox.Action, false); 101 | } 102 | File.Move(api64_o, api!); 103 | installForm?.UpdateUser($"Restored Uplay R2: {Path.GetFileName(api64_o)} -> {Path.GetFileName(api)}", LogTextBox.Action, false); 104 | } 105 | if (!deleteOthers) 106 | return; 107 | if (File.Exists(config)) 108 | { 109 | File.Delete(config); 110 | installForm?.UpdateUser($"Deleted configuration: {Path.GetFileName(config)}", LogTextBox.Action, false); 111 | } 112 | if (File.Exists(log)) 113 | { 114 | File.Delete(log); 115 | installForm?.UpdateUser($"Deleted log: {Path.GetFileName(log)}", LogTextBox.Action, false); 116 | } 117 | }); 118 | 119 | internal static async Task Install(string directory, ProgramSelection selection, InstallForm installForm = null, bool generateConfig = true) 120 | => await Task.Run(() => 121 | { 122 | directory.GetUplayR2Components(out string old_api32, out string old_api64, out string api32, out string api32_o, out string api64, 123 | out string api64_o, out _, out _); 124 | string api = File.Exists(old_api32) ? old_api32 : api32; 125 | if (File.Exists(api) && !File.Exists(api32_o)) 126 | { 127 | File.Move(api, api32_o!); 128 | installForm?.UpdateUser($"Renamed Uplay R2: {Path.GetFileName(api)} -> {Path.GetFileName(api32_o)}", LogTextBox.Action, false); 129 | } 130 | if (File.Exists(api32_o)) 131 | { 132 | "UplayR2.upc_r2_loader.dll".Write(api); 133 | installForm?.UpdateUser($"Wrote Uplay R2 Unlocker: {Path.GetFileName(api)}", LogTextBox.Action, false); 134 | } 135 | api = File.Exists(old_api64) ? old_api64 : api64; 136 | if (File.Exists(api) && !File.Exists(api64_o)) 137 | { 138 | File.Move(api, api64_o!); 139 | installForm?.UpdateUser($"Renamed Uplay R2: {Path.GetFileName(api)} -> {Path.GetFileName(api64_o)}", LogTextBox.Action, false); 140 | } 141 | if (File.Exists(api64_o)) 142 | { 143 | "UplayR2.upc_r2_loader64.dll".Write(api); 144 | installForm?.UpdateUser($"Wrote Uplay R2 Unlocker: {Path.GetFileName(api)}", LogTextBox.Action, false); 145 | } 146 | if (generateConfig) 147 | CheckConfig(directory, selection, installForm); 148 | }); 149 | } -------------------------------------------------------------------------------- /CreamInstaller/Resources/UplayR2/upc_r2_loader.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/UplayR2/upc_r2_loader.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/UplayR2/upc_r2_loader64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/UplayR2/upc_r2_loader64.dll -------------------------------------------------------------------------------- /CreamInstaller/Resources/ini.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/CreamInstaller/Resources/ini.ico -------------------------------------------------------------------------------- /CreamInstaller/Utility/Diagnostics.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using Microsoft.Win32; 5 | 6 | namespace CreamInstaller.Utility; 7 | 8 | internal static class Diagnostics 9 | { 10 | private static string notepadPlusPlusPath; 11 | 12 | internal static string NotepadPlusPlusPath 13 | { 14 | get 15 | { 16 | notepadPlusPlusPath ??= Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Notepad++", "", null) as string; 17 | notepadPlusPlusPath ??= Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432NODE\Notepad++", "", null) as string; 18 | return notepadPlusPlusPath; 19 | } 20 | } 21 | 22 | internal static string GetNotepadPath() 23 | { 24 | string npp = NotepadPlusPlusPath + @"\notepad++.exe"; 25 | return File.Exists(npp) ? npp : Environment.GetFolderPath(Environment.SpecialFolder.Windows) + @"\notepad.exe"; 26 | } 27 | 28 | internal static void OpenFileInNotepad(string path) 29 | { 30 | string npp = NotepadPlusPlusPath + @"\notepad++.exe"; 31 | if (File.Exists(npp)) 32 | OpenFileInNotepadPlusPlus(npp, path); 33 | else 34 | OpenFileInWindowsNotepad(path); 35 | } 36 | 37 | private static void OpenFileInNotepadPlusPlus(string npp, string path) => Process.Start(new ProcessStartInfo { FileName = npp, Arguments = path }); 38 | 39 | private static void OpenFileInWindowsNotepad(string path) => Process.Start(new ProcessStartInfo { FileName = "notepad.exe", Arguments = path }); 40 | 41 | internal static void OpenDirectoryInFileExplorer(string path) => Process.Start(new ProcessStartInfo { FileName = "explorer.exe", Arguments = path }); 42 | 43 | internal static void OpenUrlInInternetBrowser(string url) => Process.Start(new ProcessStartInfo { FileName = url, UseShellExecute = true }); 44 | 45 | internal static string BeautifyPath(this string path) => path is null ? null : Path.TrimEndingDirectorySeparator(Path.GetFullPath(path)); 46 | } -------------------------------------------------------------------------------- /CreamInstaller/Utility/ExceptionHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Text; 4 | using System.Windows.Forms; 5 | using CreamInstaller.Forms; 6 | 7 | namespace CreamInstaller.Utility; 8 | 9 | internal static class ExceptionHandler 10 | { 11 | internal static bool HandleException(this Exception e, Form form = null, string caption = null, string acceptButtonText = "Retry", 12 | string cancelButtonText = "Cancel") 13 | { 14 | caption ??= Program.Name + " encountered an exception"; 15 | StringBuilder output = new(); 16 | int stackDepth = 0; 17 | while (e is not null) 18 | { 19 | if (stackDepth > 10) 20 | break; 21 | if (output.Length > 0) 22 | _ = output.Append("\n\n"); 23 | string[] stackTrace = e.StackTrace?.Split('\n'); 24 | if (stackTrace is not null && stackTrace.Length > 0) 25 | { 26 | _ = output.Append(e.GetType() + (": " + e.Message)); 27 | for (int i = 0; i < stackTrace.Length; i++) 28 | { 29 | string line = stackTrace[i]; 30 | int atNum = line.IndexOf("at ", StringComparison.Ordinal); 31 | int inNum = line.IndexOf("in ", StringComparison.Ordinal); 32 | int ciNum = line.LastIndexOf(@"CreamInstaller\", StringComparison.Ordinal); 33 | int lineNum = line.LastIndexOf(":line ", StringComparison.Ordinal); 34 | if (atNum != -1) 35 | _ = output.Append("\n " + (inNum != -1 ? line[atNum..(inNum - 1)] : line[atNum..]) + (inNum != -1 36 | ? "\n " + (ciNum != -1 37 | ? "in " + (lineNum != -1 ? line[ciNum..lineNum] + "\n on " + line[(lineNum + 1)..] : line[ciNum..]) 38 | : line[inNum..]) 39 | : null)); 40 | } 41 | } 42 | e = e.InnerException; 43 | stackDepth++; 44 | } 45 | using DialogForm dialogForm = new(form ?? Form.ActiveForm); 46 | return dialogForm.Show(SystemIcons.Error, output.ToString(), acceptButtonText, cancelButtonText, caption) == DialogResult.OK; 47 | } 48 | 49 | internal static void HandleFatalException(this Exception e) 50 | { 51 | bool? restart = e?.HandleException(caption: Program.Name + " encountered a fatal exception", acceptButtonText: "Restart"); 52 | if (restart.HasValue && restart.Value) 53 | Application.Restart(); 54 | Application.Exit(); 55 | } 56 | } 57 | 58 | public class CustomMessageException : Exception 59 | { 60 | public CustomMessageException() => Message = "CustomMessageException"; 61 | 62 | public CustomMessageException(string message) : base(message) => Message = message; 63 | 64 | public CustomMessageException(string message, Exception e) : base(message, e) => Message = message; 65 | public override string Message { get; } 66 | 67 | public override string ToString() => Message; 68 | } -------------------------------------------------------------------------------- /CreamInstaller/Utility/HttpClientManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Net.Http; 4 | using System.Threading.Tasks; 5 | using HtmlAgilityPack; 6 | 7 | namespace CreamInstaller.Utility; 8 | 9 | internal static class HttpClientManager 10 | { 11 | internal static HttpClient HttpClient; 12 | 13 | internal static void Setup() 14 | { 15 | HttpClient = new(); 16 | HttpClient.DefaultRequestHeaders.Add("User-Agent", $"CI{Program.Version.Replace(".", "")}"); 17 | } 18 | 19 | internal static async Task EnsureGet(string url) 20 | { 21 | try 22 | { 23 | using HttpRequestMessage request = new(HttpMethod.Get, url); 24 | using HttpResponseMessage response = await HttpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); 25 | _ = response.EnsureSuccessStatusCode(); 26 | return await response.Content.ReadAsStringAsync(); 27 | } 28 | catch 29 | { 30 | return null; 31 | } 32 | } 33 | 34 | internal static HtmlDocument ToHtmlDocument(this string html) 35 | { 36 | HtmlDocument document = new(); 37 | document.LoadHtml(html); 38 | return document; 39 | } 40 | 41 | internal static async Task GetDocumentNodes(string url, string xpath) 42 | => (await EnsureGet(url))?.ToHtmlDocument()?.DocumentNode?.SelectNodes(xpath); 43 | 44 | internal static HtmlNodeCollection GetDocumentNodes(this HtmlDocument htmlDocument, string xpath) => htmlDocument.DocumentNode?.SelectNodes(xpath); 45 | 46 | internal static async Task GetImageFromUrl(string url) 47 | { 48 | try 49 | { 50 | return new Bitmap(await HttpClient.GetStreamAsync(new Uri(url))); 51 | } 52 | catch 53 | { 54 | return null; 55 | } 56 | } 57 | 58 | internal static void Dispose() => HttpClient?.Dispose(); 59 | } -------------------------------------------------------------------------------- /CreamInstaller/Utility/IconGrabber.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.IO; 4 | 5 | namespace CreamInstaller.Utility; 6 | 7 | internal static class IconGrabber 8 | { 9 | internal const string SteamAppImagesPath = "https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/apps/"; 10 | 11 | internal const string GoogleFaviconsApiUrl = "https://www.google.com/s2/favicons"; 12 | 13 | internal static Icon ToIcon(this Image image) 14 | { 15 | using Bitmap dialogIconBitmap = new(image, new(image.Width, image.Height)); 16 | return Icon.FromHandle(dialogIconBitmap.GetHicon()); 17 | } 18 | 19 | internal static string GetDomainFaviconUrl(string domain, int size = 16) => GoogleFaviconsApiUrl + $"?domain={domain}&sz={size}"; 20 | 21 | internal static Image GetFileIconImage(this string path) => File.Exists(path) ? Icon.ExtractAssociatedIcon(path)?.ToBitmap() : null; 22 | 23 | internal static Image GetNotepadImage() => GetFileIconImage(Diagnostics.GetNotepadPath()); 24 | 25 | internal static Image GetCommandPromptImage() => GetFileIconImage(Environment.SystemDirectory + @"\cmd.exe"); 26 | 27 | internal static Image GetFileExplorerImage() => GetFileIconImage(Environment.GetFolderPath(Environment.SpecialFolder.Windows) + @"\explorer.exe"); 28 | } -------------------------------------------------------------------------------- /CreamInstaller/Utility/LogTextBox.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using System.Windows.Forms; 3 | 4 | namespace CreamInstaller.Utility; 5 | 6 | internal static class LogTextBox 7 | { 8 | internal static readonly Color Background = Color.DarkSlateGray; 9 | internal static readonly Color Operation = Color.LightGray; 10 | internal static readonly Color Action = Color.LightBlue; 11 | internal static readonly Color Success = Color.LightGreen; 12 | internal static readonly Color Cleanup = Color.YellowGreen; 13 | internal static readonly Color Warning = Color.Yellow; 14 | internal static readonly Color Error = Color.DarkOrange; 15 | 16 | internal static void AppendText(this RichTextBox textBox, string text, Color color, bool scroll = false) 17 | { 18 | textBox.SelectionStart = textBox.TextLength; 19 | textBox.SelectionLength = 0; 20 | textBox.SelectionColor = color; 21 | if (scroll) 22 | textBox.ScrollToCaret(); 23 | textBox.AppendText(text); 24 | if (scroll) 25 | textBox.ScrollToCaret(); 26 | textBox.SelectionColor = textBox.ForeColor; 27 | textBox.Invalidate(); 28 | } 29 | } -------------------------------------------------------------------------------- /CreamInstaller/Utility/ProgramData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using Newtonsoft.Json; 9 | 10 | namespace CreamInstaller.Utility; 11 | 12 | internal static class ProgramData 13 | { 14 | private static readonly string DirectoryPathOld = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\CreamInstaller"; 15 | 16 | internal static readonly string DirectoryPath = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + @"\CreamInstaller"; 17 | 18 | internal static readonly string AppInfoPath = DirectoryPath + @"\appinfo"; 19 | private static readonly string AppInfoVersionPath = AppInfoPath + @"\version.txt"; 20 | 21 | private static readonly Version MinimumAppInfoVersion = Version.Parse("3.2.0.0"); 22 | 23 | internal static readonly string CooldownPath = DirectoryPath + @"\cooldown"; 24 | 25 | private static readonly string OldProgramChoicesPath = DirectoryPath + @"\choices.txt"; 26 | private static readonly string ProgramChoicesPath = DirectoryPath + @"\choices.json"; 27 | private static readonly string DlcChoicesPath = DirectoryPath + @"\dlc.json"; 28 | private static readonly string KoaloaderProxyChoicesPath = DirectoryPath + @"\proxies.json"; 29 | 30 | internal static async Task Setup() 31 | => await Task.Run(() => 32 | { 33 | if (Directory.Exists(DirectoryPathOld)) 34 | { 35 | if (Directory.Exists(DirectoryPath)) 36 | Directory.Delete(DirectoryPath, true); 37 | Directory.Move(DirectoryPathOld, DirectoryPath); 38 | } 39 | if (!Directory.Exists(DirectoryPath)) 40 | _ = Directory.CreateDirectory(DirectoryPath); 41 | if (!File.Exists(AppInfoVersionPath) || !Version.TryParse(File.ReadAllText(AppInfoVersionPath, Encoding.UTF8), out Version version) 42 | || version < MinimumAppInfoVersion) 43 | { 44 | if (Directory.Exists(AppInfoPath)) 45 | Directory.Delete(AppInfoPath, true); 46 | _ = Directory.CreateDirectory(AppInfoPath); 47 | File.WriteAllText(AppInfoVersionPath, Program.Version, Encoding.UTF8); 48 | } 49 | if (!Directory.Exists(CooldownPath)) 50 | _ = Directory.CreateDirectory(CooldownPath); 51 | if (File.Exists(OldProgramChoicesPath)) 52 | File.Delete(OldProgramChoicesPath); 53 | }); 54 | 55 | internal static bool CheckCooldown(string identifier, int cooldown) 56 | { 57 | DateTime now = DateTime.UtcNow; 58 | DateTime lastCheck = GetCooldown(identifier) ?? now; 59 | bool cooldownOver = (now - lastCheck).TotalSeconds > cooldown; 60 | if (cooldownOver || now == lastCheck) 61 | SetCooldown(identifier, now); 62 | return cooldownOver; 63 | } 64 | 65 | private static DateTime? GetCooldown(string identifier) 66 | { 67 | if (!Directory.Exists(CooldownPath)) 68 | return null; 69 | string cooldownFile = CooldownPath + @$"\{identifier}.txt"; 70 | if (!File.Exists(cooldownFile)) 71 | return null; 72 | try 73 | { 74 | if (DateTime.TryParse(File.ReadAllText(cooldownFile), out DateTime cooldown)) 75 | return cooldown; 76 | } 77 | catch 78 | { 79 | // ignored 80 | } 81 | return null; 82 | } 83 | 84 | private static void SetCooldown(string identifier, DateTime time) 85 | { 86 | if (!Directory.Exists(CooldownPath)) 87 | _ = Directory.CreateDirectory(CooldownPath); 88 | string cooldownFile = CooldownPath + @$"\{identifier}.txt"; 89 | try 90 | { 91 | File.WriteAllText(cooldownFile, time.ToString(CultureInfo.InvariantCulture)); 92 | } 93 | catch 94 | { 95 | // ignored 96 | } 97 | } 98 | 99 | internal static IEnumerable<(Platform platform, string id)> ReadProgramChoices() 100 | { 101 | if (!File.Exists(ProgramChoicesPath)) 102 | return Enumerable.Empty<(Platform platform, string id)>(); 103 | try 104 | { 105 | return JsonConvert.DeserializeObject(File.ReadAllText(ProgramChoicesPath), typeof(List<(Platform platform, string id)>)) as 106 | List<(Platform platform, string id)>; 107 | } 108 | catch 109 | { 110 | return Enumerable.Empty<(Platform platform, string id)>(); 111 | } 112 | } 113 | 114 | internal static void WriteProgramChoices(IEnumerable<(Platform platform, string id)> choices) 115 | { 116 | try 117 | { 118 | if (choices is null || !choices.Any()) 119 | File.Delete(ProgramChoicesPath); 120 | else 121 | File.WriteAllText(ProgramChoicesPath, JsonConvert.SerializeObject(choices)); 122 | } 123 | catch 124 | { 125 | // ignored 126 | } 127 | } 128 | 129 | internal static IEnumerable<(Platform platform, string gameId, string dlcId)> ReadDlcChoices() 130 | { 131 | if (!File.Exists(DlcChoicesPath)) 132 | return Enumerable.Empty<(Platform platform, string gameId, string dlcId)>(); 133 | try 134 | { 135 | return JsonConvert.DeserializeObject(File.ReadAllText(DlcChoicesPath), typeof(IEnumerable<(Platform platform, string gameId, string dlcId)>)) as 136 | IEnumerable<(Platform platform, string gameId, string dlcId)>; 137 | } 138 | catch 139 | { 140 | return Enumerable.Empty<(Platform platform, string gameId, string dlcId)>(); 141 | } 142 | } 143 | 144 | internal static void WriteDlcChoices(List<(Platform platform, string gameId, string dlcId)> choices) 145 | { 146 | try 147 | { 148 | if (choices is null || !choices.Any()) 149 | File.Delete(DlcChoicesPath); 150 | else 151 | File.WriteAllText(DlcChoicesPath, JsonConvert.SerializeObject(choices)); 152 | } 153 | catch 154 | { 155 | // ignored 156 | } 157 | } 158 | 159 | internal static IEnumerable<(Platform platform, string id, string proxy, bool enabled)> ReadKoaloaderChoices() 160 | { 161 | if (!File.Exists(KoaloaderProxyChoicesPath)) 162 | return Enumerable.Empty<(Platform platform, string id, string proxy, bool enabled)>(); 163 | try 164 | { 165 | return JsonConvert.DeserializeObject(File.ReadAllText(KoaloaderProxyChoicesPath), 166 | typeof(IEnumerable<(Platform platform, string id, string proxy, bool enabled)>)) as 167 | IEnumerable<(Platform platform, string id, string proxy, bool enabled)>; 168 | } 169 | catch 170 | { 171 | return Enumerable.Empty<(Platform platform, string id, string proxy, bool enabled)>(); 172 | } 173 | } 174 | 175 | internal static void WriteKoaloaderProxyChoices(IEnumerable<(Platform platform, string id, string proxy, bool enabled)> choices) 176 | { 177 | try 178 | { 179 | if (choices is null || !choices.Any()) 180 | File.Delete(KoaloaderProxyChoicesPath); 181 | else 182 | File.WriteAllText(KoaloaderProxyChoicesPath, JsonConvert.SerializeObject(choices)); 183 | } 184 | catch 185 | { 186 | // ignored 187 | } 188 | } 189 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### CreamInstaller: Automatic DLC Unlocker Installer & Configuration Generator 2 | 3 | ##DONATE 4 | 5 | # Https://ubd.one/donate 6 | 7 | ###### 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! 8 | --- 9 | #### Description: 10 | Automatically finds all installed Steam, Epic and Ubisoft games with their respective DLC-related DLL locations on the user's computer, 11 | parses SteamCMD, Steam Store and Epic Games Store for user-selected games' DLCs, then provides a very simple graphical interface 12 | utilizing the gathered information for the maintenance of DLC unlockers. 13 | 14 | The primary function of the program is to **automatically generate and install DLC unlockers** for whichever 15 | games and DLCs the user selects; however, through the use of **right-click context menus** the user can also: 16 | * automatically repair the Paradox Launcher 17 | * open parsed Steam and/or Epic Games appinfo in Notepad(++) 18 | * refresh parsed Steam and/or Epic Games appinfo 19 | * open root game directories and important DLL directories in Explorer 20 | * open SteamDB, ScreamDB, Steam Store, Epic Games Store, Steam Community, Ubisoft Store, and official game website links (where applicable) in the default browser 21 | 22 | --- 23 | #### Features: 24 | * Automatic download and installation of SteamCMD as necessary whenever a Steam game is chosen. *For gathering appinfo such as name, buildid, listofdlc, depots, etc.* 25 | * Automatic gathering and caching of information for all selected Steam and Epic games and **ALL** of their DLCs. 26 | * Automatic DLL installation and configuration generation for Koaloader, SmokeAPI, ScreamAPI, Uplay R1 Unlocker and Uplay R2 Unlocker. 27 | * Automatic uninstallation of DLLs and configurations for Koaloader, CreamAPI, SmokeAPI, ScreamAPI, Uplay R1 Unlocker and Uplay R2 Unlocker. 28 | * Automatic reparation of the Paradox Launcher (and manually via the right-click context menu "Repair" option). *For when the launcher updates whilst you have CreamAPI, SmokeAPI or ScreamAPI installed to it.* 29 | 30 | --- 31 | #### Installation: 32 | 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)). 33 | 2. Extract the executable to anywhere on your computer you want. *It's completely self-contained.* 34 | 35 | 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). 36 | 37 | --- 38 | #### **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. 39 | 40 | --- 41 | #### Usage: 42 | 1. Start the program executable. *Read above under Installation if it doesn't launch.* 43 | 2. Choose which programs and/or games the program should scan for DLC. *The program automatically gathers all installed games from Steam, Epic and Ubisoft directories.* 44 | 3. Wait for the program to download and install SteamCMD (if you chose a Steam game). *Very fast, depends on internet speed.* 45 | 4. Wait for the program to gather and cache the chosen games' information & DLCs. *May take a good amount of time on the first run, depends on how many games you chose and how many DLCs they have.* 46 | 5. **CAREFULLY** select which games' DLCs you wish to unlock. *Obviously none of the DLC unlockers are tested for every single game!* 47 | 6. Choose whether or not to install with Koaloader, and if so then also pick the proxy DLL to use. *If the default version.dll doesn't work, then see [here](https://forum.ubden.com.tr/konu/creaminstaller-auto-dlc-unlocker-installer-config-gen.1602/#google_vignette) to find one that does.* 48 | 7. Click the **Generate and Install** button. 49 | 8. Click the **OK** button to close the program. 50 | 9. If any of the DLC unlockers cause problems with any of the games you installed them on, simply go back to step 5 and select what games you wish you **revert** changes to, and instead click the **Uninstall** button this time. 51 | 52 | --- 53 | ##### Bugs/Crashes/Issues: 54 | 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! 55 | 56 | --- 57 | -------------------------------------------------------------------------------- /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberSys/CreamInstaller/68b5fb7f9850c85a8d56f8d740f4d50a8ae412e0/preview.png --------------------------------------------------------------------------------