├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── SC2 Cross Platform Campaign Manager.sln ├── SC2 Custom Campaign Manager ├── App.xaml ├── App.xaml.cs ├── AppShell.xaml ├── AppShell.xaml.cs ├── CampaignUiElements.cs ├── Consts.cs ├── MainPage.xaml ├── MainPage.xaml.cs ├── MauiProgram.cs ├── Platforms │ ├── Android │ │ ├── AndroidManifest.xml │ │ ├── MainActivity.cs │ │ ├── MainApplication.cs │ │ └── Resources │ │ │ └── values │ │ │ └── colors.xml │ ├── MacCatalyst │ │ ├── AppDelegate.cs │ │ ├── Info.plist │ │ └── Program.cs │ ├── Tizen │ │ ├── Main.cs │ │ └── tizen-manifest.xml │ ├── Windows │ │ ├── App.xaml │ │ ├── App.xaml.cs │ │ ├── Package.appxmanifest │ │ └── app.manifest │ └── iOS │ │ ├── AppDelegate.cs │ │ ├── Info.plist │ │ └── Program.cs ├── Properties │ └── launchSettings.json ├── Resources │ ├── AppIcon │ │ ├── appicon-176.png │ │ ├── appicon-44.png │ │ ├── appicon-88.png │ │ ├── appicon-lg-1240.png │ │ ├── appicon-lg-310.png │ │ ├── appicon-lg-388.png │ │ ├── appicon-lg-465.png │ │ ├── appicon-lg-620.png │ │ ├── appicon-md-150.png │ │ ├── appicon-md-188.png │ │ ├── appicon-md-225.png │ │ ├── appicon-md-300.png │ │ ├── appicon-md-600.png │ │ ├── appicon-sm - Copy.png │ │ ├── appicon-sm-107.png │ │ ├── appicon-sm-142.png │ │ ├── appicon-sm-71.png │ │ ├── appicon-sm-89.png │ │ ├── appicon-sm.png │ │ ├── appicon-wide-1240.png │ │ ├── appicon-wide-310.png │ │ ├── appicon-wide-388.png │ │ ├── appicon-wide-465.png │ │ ├── appicon-wide-620.png │ │ ├── appicon-wide.svg │ │ ├── appicon.svg │ │ ├── appiconfg.svg │ │ └── package-logo-200.png │ ├── Fonts │ │ ├── OpenSans-Regular.ttf │ │ └── OpenSans-Semibold.ttf │ ├── Images │ │ └── dotnet_bot.svg │ ├── Raw │ │ └── AboutAssets.txt │ ├── Splash │ │ └── splash.svg │ └── Styles │ │ ├── Colors.xaml │ │ └── Styles.xaml ├── SC2 Custom Campaign Manager.csproj ├── app-icon-wd.scale-100.png ├── app-icon-wd.scale-125.png ├── app-icon-wd.scale-150.png ├── app-icon-wd.scale-200.png ├── app-icon-wd.scale-400.png ├── appicon-lg.scale-100.png ├── appicon-lg.scale-125.png ├── appicon-lg.scale-150.png ├── appicon-lg.scale-200.png ├── appicon-lg.scale-400.png ├── appicon-md.scale-100.png ├── appicon-md.scale-125.png ├── appicon-md.scale-150.png ├── appicon-md.scale-200.png ├── appicon-md.scale-400.png ├── appicon-sm.scale-100.png ├── appicon-sm.scale-125.png ├── appicon-sm.scale-150.png ├── appicon-sm.scale-200.png ├── appicon-sm.scale-400.png ├── appicon.altform-unplated_targetsize-16.png ├── appicon.altform-unplated_targetsize-24.png ├── appicon.altform-unplated_targetsize-256.png ├── appicon.altform-unplated_targetsize-32.png ├── appicon.altform-unplated_targetsize-48.png ├── appicon.scale-100.png ├── appicon.scale-125.png ├── appicon.scale-150.png ├── appicon.scale-200.png ├── appicon.scale-400.png ├── appicon.targetsize-16.png ├── appicon.targetsize-16_altform-unplated.png ├── appicon.targetsize-24.png ├── appicon.targetsize-24_altform-unplated.png ├── appicon.targetsize-256.png ├── appicon.targetsize-256_altform-unplated.png ├── appicon.targetsize-32.png ├── appicon.targetsize-32_altform-unplated.png ├── appicon.targetsize-48.png ├── appicon.targetsize-48_altform-unplated.png ├── package-logo.scale-400.png └── project.assets.json ├── SC2_CCM_Common ├── Campaign.cs ├── CampaignType.cs ├── Functional.cs ├── Log.cs ├── Mod.cs ├── ModDirectoryInfo.cs ├── ModFileSystem.cs ├── ModManagerException.cs ├── SC2CCM.cs ├── SC2Config.cs ├── SC2ConfigData.cs ├── SC2_CCM_Common.csproj └── ZipArchiveExtensions.cs ├── SC2_CCM_Common_Test ├── ModTest.cs ├── SC2_CCM_Common_Test.csproj ├── Sc2ConfigTest.cs └── Usings.cs ├── SC2_CCM_WinForm ├── AboutBox.Designer.cs ├── AboutBox.cs ├── AboutBox.resx ├── CampaignUiElements.cs ├── Consts.cs ├── MainPage.Designer.cs ├── MainPage.cs ├── MainPage.resx ├── Program.cs ├── SC2_CCM_WinForm.csproj └── appicon.ico ├── global.json └── package-app-file.sh /.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 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # Tye 66 | .tye/ 67 | 68 | # ASP.NET Scaffolding 69 | ScaffoldingReadMe.txt 70 | 71 | # StyleCop 72 | StyleCopReport.xml 73 | 74 | # Files built by Visual Studio 75 | *_i.c 76 | *_p.c 77 | *_h.h 78 | *.ilk 79 | *.meta 80 | *.obj 81 | *.iobj 82 | *.pch 83 | *.pdb 84 | *.ipdb 85 | *.pgc 86 | *.pgd 87 | *.rsp 88 | *.sbr 89 | *.tlb 90 | *.tli 91 | *.tlh 92 | *.tmp 93 | *.tmp_proj 94 | *_wpftmp.csproj 95 | *.log 96 | *.vspscc 97 | *.vssscc 98 | .builds 99 | *.pidb 100 | *.svclog 101 | *.scc 102 | 103 | # Chutzpah Test files 104 | _Chutzpah* 105 | 106 | # Visual C++ cache files 107 | ipch/ 108 | *.aps 109 | *.ncb 110 | *.opendb 111 | *.opensdf 112 | *.sdf 113 | *.cachefile 114 | *.VC.db 115 | *.VC.VC.opendb 116 | 117 | # Visual Studio profiler 118 | *.psess 119 | *.vsp 120 | *.vspx 121 | *.sap 122 | 123 | # Visual Studio Trace Files 124 | *.e2e 125 | 126 | # TFS 2012 Local Workspace 127 | $tf/ 128 | 129 | # Guidance Automation Toolkit 130 | *.gpState 131 | 132 | # ReSharper is a .NET coding add-in 133 | _ReSharper*/ 134 | *.[Rr]e[Ss]harper 135 | *.DotSettings.user 136 | 137 | # TeamCity is a build add-in 138 | _TeamCity* 139 | 140 | # DotCover is a Code Coverage Tool 141 | *.dotCover 142 | 143 | # AxoCover is a Code Coverage Tool 144 | .axoCover/* 145 | !.axoCover/settings.json 146 | 147 | # Coverlet is a free, cross platform Code Coverage Tool 148 | coverage*.json 149 | coverage*.xml 150 | coverage*.info 151 | 152 | # Visual Studio code coverage results 153 | *.coverage 154 | *.coveragexml 155 | 156 | # NCrunch 157 | _NCrunch_* 158 | .*crunch*.local.xml 159 | nCrunchTemp_* 160 | 161 | # MightyMoose 162 | *.mm.* 163 | AutoTest.Net/ 164 | 165 | # Web workbench (sass) 166 | .sass-cache/ 167 | 168 | # Installshield output folder 169 | [Ee]xpress/ 170 | 171 | # DocProject is a documentation generator add-in 172 | DocProject/buildhelp/ 173 | DocProject/Help/*.HxT 174 | DocProject/Help/*.HxC 175 | DocProject/Help/*.hhc 176 | DocProject/Help/*.hhk 177 | DocProject/Help/*.hhp 178 | DocProject/Help/Html2 179 | DocProject/Help/html 180 | 181 | # Click-Once directory 182 | publish/ 183 | 184 | # Publish Web Output 185 | *.[Pp]ublish.xml 186 | *.azurePubxml 187 | # Note: Comment the next line if you want to checkin your web deploy settings, 188 | # but database connection strings (with potential passwords) will be unencrypted 189 | *.pubxml 190 | *.publishproj 191 | 192 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 193 | # checkin your Azure Web App publish settings, but sensitive information contained 194 | # in these scripts will be unencrypted 195 | PublishScripts/ 196 | 197 | # NuGet Packages 198 | *.nupkg 199 | # NuGet Symbol Packages 200 | *.snupkg 201 | # The packages folder can be ignored because of Package Restore 202 | **/[Pp]ackages/* 203 | # except build/, which is used as an MSBuild target. 204 | !**/[Pp]ackages/build/ 205 | # Uncomment if necessary however generally it will be regenerated when needed 206 | #!**/[Pp]ackages/repositories.config 207 | # NuGet v3's project.json files produces more ignorable files 208 | *.nuget.props 209 | *.nuget.targets 210 | 211 | # Microsoft Azure Build Output 212 | csx/ 213 | *.build.csdef 214 | 215 | # Microsoft Azure Emulator 216 | ecf/ 217 | rcf/ 218 | 219 | # Windows Store app package directories and files 220 | AppPackages/ 221 | BundleArtifacts/ 222 | Package.StoreAssociation.xml 223 | _pkginfo.txt 224 | *.appx 225 | *.appxbundle 226 | *.appxupload 227 | 228 | # Visual Studio cache files 229 | # files ending in .cache can be ignored 230 | *.[Cc]ache 231 | # but keep track of directories ending in .cache 232 | !?*.[Cc]ache/ 233 | 234 | # Others 235 | ClientBin/ 236 | ~$* 237 | *~ 238 | *.dbmdl 239 | *.dbproj.schemaview 240 | *.jfm 241 | *.pfx 242 | *.publishsettings 243 | orleans.codegen.cs 244 | 245 | # Including strong name files can present a security risk 246 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 247 | #*.snk 248 | 249 | # Since there are multiple workflows, uncomment next line to ignore bower_components 250 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 251 | #bower_components/ 252 | 253 | # RIA/Silverlight projects 254 | Generated_Code/ 255 | 256 | # Backup & report files from converting an old project file 257 | # to a newer Visual Studio version. Backup files are not needed, 258 | # because we have git ;-) 259 | _UpgradeReport_Files/ 260 | Backup*/ 261 | UpgradeLog*.XML 262 | UpgradeLog*.htm 263 | ServiceFabricBackup/ 264 | *.rptproj.bak 265 | 266 | # SQL Server files 267 | *.mdf 268 | *.ldf 269 | *.ndf 270 | 271 | # Business Intelligence projects 272 | *.rdl.data 273 | *.bim.layout 274 | *.bim_*.settings 275 | *.rptproj.rsuser 276 | *- [Bb]ackup.rdl 277 | *- [Bb]ackup ([0-9]).rdl 278 | *- [Bb]ackup ([0-9][0-9]).rdl 279 | 280 | # Microsoft Fakes 281 | FakesAssemblies/ 282 | 283 | # GhostDoc plugin setting file 284 | *.GhostDoc.xml 285 | 286 | # Node.js Tools for Visual Studio 287 | .ntvs_analysis.dat 288 | node_modules/ 289 | 290 | # Visual Studio 6 build log 291 | *.plg 292 | 293 | # Visual Studio 6 workspace options file 294 | *.opt 295 | 296 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 297 | *.vbw 298 | 299 | # Visual Studio LightSwitch build output 300 | **/*.HTMLClient/GeneratedArtifacts 301 | **/*.DesktopClient/GeneratedArtifacts 302 | **/*.DesktopClient/ModelManifest.xml 303 | **/*.Server/GeneratedArtifacts 304 | **/*.Server/ModelManifest.xml 305 | _Pvt_Extensions 306 | 307 | # Paket dependency manager 308 | .paket/paket.exe 309 | paket-files/ 310 | 311 | # FAKE - F# Make 312 | .fake/ 313 | 314 | # CodeRush personal settings 315 | .cr/personal 316 | 317 | # Python Tools for Visual Studio (PTVS) 318 | __pycache__/ 319 | *.pyc 320 | 321 | # Cake - Uncomment if you are using it 322 | # tools/** 323 | # !tools/packages.config 324 | 325 | # Tabs Studio 326 | *.tss 327 | 328 | # Telerik's JustMock configuration file 329 | *.jmconfig 330 | 331 | # BizTalk build output 332 | *.btp.cs 333 | *.btm.cs 334 | *.odx.cs 335 | *.xsd.cs 336 | 337 | # OpenCover UI analysis results 338 | OpenCover/ 339 | 340 | # Azure Stream Analytics local run output 341 | ASALocalRun/ 342 | 343 | # MSBuild Binary and Structured Log 344 | *.binlog 345 | 346 | # NVidia Nsight GPU debugger configuration file 347 | *.nvuser 348 | 349 | # MFractors (Xamarin productivity tool) working folder 350 | .mfractor/ 351 | 352 | # Local History for Visual Studio 353 | .localhistory/ 354 | 355 | # BeatPulse healthcheck temp database 356 | healthchecksdb 357 | 358 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 359 | MigrationBackup/ 360 | 361 | # Ionide (cross platform F# VS Code tools) working folder 362 | .ionide/ 363 | 364 | # Fody - auto-generated XML schema 365 | FodyWeavers.xsd 366 | 367 | ## 368 | ## Visual studio for Mac 369 | ## 370 | 371 | 372 | # globs 373 | Makefile.in 374 | *.userprefs 375 | *.usertasks 376 | config.make 377 | config.status 378 | aclocal.m4 379 | install-sh 380 | autom4te.cache/ 381 | *.tar.gz 382 | tarballs/ 383 | test-results/ 384 | 385 | # Mac bundle stuff 386 | *.dmg 387 | *.app 388 | 389 | # content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore 390 | # General 391 | .DS_Store 392 | .AppleDouble 393 | .LSOverride 394 | 395 | # Icon must end with two \r 396 | Icon 397 | 398 | 399 | # Thumbnails 400 | ._* 401 | 402 | # Files that might appear in the root of a volume 403 | .DocumentRevisions-V100 404 | .fseventsd 405 | .Spotlight-V100 406 | .TemporaryItems 407 | .Trashes 408 | .VolumeIcon.icns 409 | .com.apple.timemachine.donotpresent 410 | 411 | # Directories potentially created on remote AFP share 412 | .AppleDB 413 | .AppleDesktop 414 | Network Trash Folder 415 | Temporary Items 416 | .apdisk 417 | 418 | # content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore 419 | # Windows thumbnail cache files 420 | Thumbs.db 421 | ehthumbs.db 422 | ehthumbs_vista.db 423 | 424 | # Dump file 425 | *.stackdump 426 | 427 | # Folder config file 428 | [Dd]esktop.ini 429 | 430 | # Recycle Bin used on file shares 431 | $RECYCLE.BIN/ 432 | 433 | # Windows Installer files 434 | *.cab 435 | *.msi 436 | *.msix 437 | *.msm 438 | *.msp 439 | 440 | # Windows shortcuts 441 | *.lnk 442 | 443 | # JetBrains Rider 444 | .idea/ 445 | *.sln.iml 446 | 447 | ## 448 | ## Visual Studio Code 449 | ## 450 | .vscode/* 451 | !.vscode/settings.json 452 | !.vscode/tasks.json 453 | !.vscode/launch.json 454 | !.vscode/extensions.json 455 | 456 | out/ 457 | out/* 458 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, caste, color, religion, or sexual 11 | identity and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the overall 27 | community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or advances of 32 | any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email address, 36 | without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | opensource@matthewtolman.com. 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series of 87 | actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or permanent 94 | ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within the 114 | community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.1, available at 120 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 121 | 122 | Community Impact Guidelines were inspired by 123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 127 | [https://www.contributor-covenant.org/translations][translations]. 128 | 129 | [homepage]: https://www.contributor-covenant.org 130 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 131 | [Mozilla CoC]: https://github.com/mozilla/diversity 132 | [FAQ]: https://www.contributor-covenant.org/faq 133 | [translations]: https://www.contributor-covenant.org/translations 134 | 135 | 136 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Ensure that a clean build is done when reviewing a pull request. 11 | 2. Update the README.md with details of changes to the interface, this includes new environment 12 | variables, exposed ports, useful file locations and container parameters. 13 | 3. Increase the version numbers in any examples files and the README.md to the new version that this 14 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 15 | 4. You may merge the Pull Request in once you have the sign-off of one other developers, or if you 16 | do not have permission to do that, you may request the reviewer to merge it for you. 17 | 18 | ## Code of Conduct 19 | 20 | This project and everyone participating in it are governed by our [Code of Conduct](https://github.com/matthewtolman/sc2-custom-mod-manager/blob/main/CODE_OF_CONDUCT.md). By participating 21 | you are expected to uphold this code. Please report unacceptable behavior to opensource@matthewtolman.com. 22 | 23 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Matthew Tolman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StarCraft II Custom Campaign Mod Manager 2 | 3 | This is a work-in-progress cross-platform custom campaign mod manager for StarCraft II based on the awesome work of the GiantGrantGames community. 4 | GiantGrantGames and his community have come up with a custom campaign mod manager that allows modders to use the standard campaign UI for custom campaigns. 5 | They have developed and freely released their mod manager, however their mod manager only works on Windows. 6 | 7 | This mod manager aims to work on both Mac and Windows as both platforms are officially supported by Blizzard for StarCraft II. 8 | 9 | Currently only Mac on Intel processors and Windows 10 and 11 have been tested and verified. 10 | 11 | ## MAUI UI 12 | 13 | Tested on Mac and Windows. Published for Mac, haven't figured out publishing for Windows. WinForm is used for the published Windows version. 14 | 15 | ### Build Requirements 16 | 17 | For Windows, the simplest setup is to install Visual Studio 2022 and add Android, iOS, C#, and MAUI to your Visual Studio. 18 | Howeer, if you don't want to install Visual Studio, then you can install just the .NET CLI and go from there. 19 | For Mac users, you'll want to install install the .NET CLI (Visual Studio 2022 for Mac is still in pre-release). 20 | 21 | This project requires .NET 6 to be installed as well as the MAUI workload. 22 | To get everything setup, do the following: 23 | 24 | * Go to https://dotnet.microsoft.com/en-us/download and install .NET 6 25 | * Run the following command: 26 | * Windows: `dotnet workload install maui` (may need to run your powershell/cmd as an Administrator) 27 | * Mac: `sudo dotnet workload install maui` 28 | * Once that finishes, run `dotnet run --project "SC2 Custom Campaign Manager"` to launch the program 29 | 30 | ## Building 31 | 32 | ### Mac 33 | To build, run `dotnet run --project "SC2 Custom Campaign Manager" -f:net6.0-maccatalyst -c:Debug` 34 | 35 | To create a pkg file, run `dotnet build "SC2 Custom Campaign Manager" -f:net6.0-maccatalyst -c:Debug /p:CreatePackage=true` 36 | 37 | #### Local publishing 38 | 39 | * Run `dotnet publish "SC2 Custom Campaign Manager" -f:net6.0-maccatalyst -c:Debug -o out` 40 | 41 | ### Windows 42 | To build, run `dotnet build "SC2 Custom Campaign Manager" -f:net6.0-windows -c:Release` 43 | 44 | ## WinForm 45 | 46 | The WinForm UI has only been tested with Windows. The easiest way to build it is to load the solution in Visual Studio and then run the WinForm project. 47 | This is what's published for Windows 48 | 49 | ### Publishing 50 | 51 | I use Visual Studio's publishing feature for this project. Publishing is currently for the "folder publish" type and "folder publish" location. My settings are as follows: 52 | 53 | * 64-bit Settings 54 | * Configuration: Release | Any CPU 55 | * Target framework: net6.0-windows 56 | * Deployment mode: Self-contained 57 | * Target runtime: win-x64 58 | * Target loxation: bin\Release\net6.0-windows\publish\win-x64\ 59 | * File publish options 60 | * Produce single file: Checked 61 | * Enable ReadyToRun compilation: Checked 62 | * 32-bit Settings 63 | * Configuration: Release | Any CPU 64 | * Target framework: net6.0-windows 65 | * Deployment mode: Self-contained 66 | * Target runtime: win-x86 67 | * Target loxation: bin\Release\net6.0-windows\publish\win-x86\ 68 | * File publish options 69 | * Produce single file: Checked 70 | * Enable ReadyToRun compilation: Checked 71 | 72 | ## Code Modularity 73 | 74 | All of the core logic, logging, etc is in the Commons library. This means that the UI can easily be changed out as needed per platform. Originally, there were two UIs, but I decided to just move to one UI for now. However, in the future if someone wanted to create another UI on-top, it would be very simple. 75 | 76 | The other advantage of this modularity is the UI code is both small and easy to read, so learning how to adapt new UIs should be fairly straightforward. 77 | 78 | UI code is responsible for the following: 79 | * Overriding where logging goes and doing any log rotation (if needed) 80 | * This allows for UIs to choose an "internet connected" log reporting, local logging, no logging, etc 81 | * Defining a "ShowMessage" method which takes a string and (hopefully) shows the user a message in some sort of UI logging 82 | * Initializing an instance of SC2CCM 83 | * Presenting the current UI state to the user (state is read from SC2CCM) 84 | * Setting up UI handlers to forward events to SC2CCM 85 | 86 | That's pretty much it. All of the unzipping, detecting installed campaigns, loading configuration, log messages, etc. is handled by the commons module. Manually written UI code should be around 300 lines of code, including class/method boilerplate and comments 87 | 88 | ## Features 89 | 90 | * Import and load different campaigns 91 | * Remembers last loaded campaign between sessions 92 | * Allows toggling cutom campaigns 93 | * Displays campaign descriptions 94 | 95 | ### Lacking (not done yet) 96 | 97 | * Drag and drop for imports 98 | 99 | -------------------------------------------------------------------------------- /SC2 Cross Platform Campaign Manager.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31611.283 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SC2 Custom Campaign Manager", "SC2 Custom Campaign Manager\SC2 Custom Campaign Manager.csproj", "{0BB0964D-BD7C-43CC-B32B-9368D1C08E2D}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SC2_CCM_Common", "SC2_CCM_Common\SC2_CCM_Common.csproj", "{102D5E83-2BE5-4714-B11C-26EE0A25B47E}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SC2_CCM_Common_Test", "SC2_CCM_Common_Test\SC2_CCM_Common_Test.csproj", "{EB3127DA-E92D-4019-975F-EBDD76C6C2ED}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SC2_CCM_WinForm", "SC2_CCM_WinForm\SC2_CCM_WinForm.csproj", "{FBCB3DF4-0ABC-4C38-97C9-18E3E81D230E}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {0BB0964D-BD7C-43CC-B32B-9368D1C08E2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {0BB0964D-BD7C-43CC-B32B-9368D1C08E2D}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {0BB0964D-BD7C-43CC-B32B-9368D1C08E2D}.Debug|Any CPU.Deploy.0 = Debug|Any CPU 23 | {0BB0964D-BD7C-43CC-B32B-9368D1C08E2D}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {0BB0964D-BD7C-43CC-B32B-9368D1C08E2D}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {0BB0964D-BD7C-43CC-B32B-9368D1C08E2D}.Release|Any CPU.Deploy.0 = Release|Any CPU 26 | {102D5E83-2BE5-4714-B11C-26EE0A25B47E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {102D5E83-2BE5-4714-B11C-26EE0A25B47E}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {102D5E83-2BE5-4714-B11C-26EE0A25B47E}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {102D5E83-2BE5-4714-B11C-26EE0A25B47E}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {EB3127DA-E92D-4019-975F-EBDD76C6C2ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {EB3127DA-E92D-4019-975F-EBDD76C6C2ED}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {EB3127DA-E92D-4019-975F-EBDD76C6C2ED}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {EB3127DA-E92D-4019-975F-EBDD76C6C2ED}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {FBCB3DF4-0ABC-4C38-97C9-18E3E81D230E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {FBCB3DF4-0ABC-4C38-97C9-18E3E81D230E}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {FBCB3DF4-0ABC-4C38-97C9-18E3E81D230E}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {FBCB3DF4-0ABC-4C38-97C9-18E3E81D230E}.Release|Any CPU.Build.0 = Release|Any CPU 38 | EndGlobalSection 39 | GlobalSection(SolutionProperties) = preSolution 40 | HideSolutionNode = FALSE 41 | EndGlobalSection 42 | GlobalSection(ExtensibilityGlobals) = postSolution 43 | SolutionGuid = {BF3CF8DE-45BE-4793-BF44-0222CD143170} 44 | EndGlobalSection 45 | EndGlobal 46 | -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/App.xaml: -------------------------------------------------------------------------------- 1 |  2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/App.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace SC2_Custom_Campaign_Manager; 2 | 3 | public partial class App : Application 4 | { 5 | public App() 6 | { 7 | InitializeComponent(); 8 | MainPage = new AppShell(); 9 | } 10 | } -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/AppShell.xaml: -------------------------------------------------------------------------------- 1 |  2 | 8 | 9 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/AppShell.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace SC2_Custom_Campaign_Manager; 2 | 3 | public partial class AppShell : Shell 4 | { 5 | public AppShell() 6 | { 7 | InitializeComponent(); 8 | AppShellContent.Title = Consts.AppName; 9 | } 10 | } -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/CampaignUiElements.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace SC2_Custom_Campaign_Manager 3 | { 4 | /// 5 | /// Collection of UI elements for a campaign 6 | /// 7 | public class CampaignUiElements 8 | { 9 | public readonly Switch ModsEnabledSwitch; 10 | public readonly Picker ModPicker; 11 | public readonly Label CampaignNameLabel; 12 | public readonly Label AuthorLabel; 13 | public readonly Label DescriptionLabel; 14 | public readonly Label VersionLabel; 15 | 16 | public CampaignUiElements( 17 | Switch modsEnabledSwitch, 18 | Picker modPicker, 19 | Label campaignNameLabel, 20 | Label authorLabel, 21 | Label descriptionLabel, 22 | Label versionLabel 23 | ) 24 | { 25 | this.ModsEnabledSwitch = modsEnabledSwitch; 26 | this.ModPicker = modPicker; 27 | this.CampaignNameLabel = campaignNameLabel; 28 | this.AuthorLabel = authorLabel; 29 | this.DescriptionLabel = descriptionLabel; 30 | this.VersionLabel = versionLabel; 31 | } 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Consts.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace SC2_Custom_Campaign_Manager; 4 | 5 | /// 6 | /// Program-wide constants 7 | /// 8 | public static class Consts 9 | { 10 | /// 11 | /// Name of the application 12 | /// 13 | public const string AppName = "SC2 Custom Campaign Manager: Maui Edition"; 14 | 15 | /// 16 | /// Version of the appication 17 | /// 18 | public static string Version => Assembly.GetEntryAssembly()?.GetCustomAttribute()?.InformationalVersion ?? "0.1"; 19 | } -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/MainPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Maui.Devices.Sensors; 2 | using SC2_CCM_Common; 3 | 4 | namespace SC2_Custom_Campaign_Manager; 5 | 6 | /// 7 | /// Main Page for the Maui Edition 8 | /// 9 | public partial class MainPage : ContentPage 10 | { 11 | /// 12 | /// SC2CCM (Core logic) 13 | /// 14 | // ReSharper disable once InconsistentNaming 15 | private readonly SC2CCM _sc2ccm; 16 | 17 | /// 18 | /// UI elements tied to a campaign 19 | /// 20 | private readonly Dictionary _campaignUi; 21 | 22 | /// 23 | /// Mapping of campaign type to custom compaign data 24 | /// 25 | private Dictionary Campaigns { get; } 26 | 27 | public MainPage() 28 | { 29 | Log.Logger.Information("Starting {AppName} v{Version}", Consts.AppName, Consts.Version); 30 | 31 | // Get our UI components ready 32 | InitializeComponent(); 33 | 34 | // Show our welcome message 35 | ShowMessage($"Welcome to {Consts.AppName} v{Consts.Version}!"); 36 | 37 | _sc2ccm = new SC2CCM(ShowMessage); 38 | 39 | // Create our campaign list 40 | Campaigns = new List() 41 | { 42 | new Campaign(_sc2ccm, CampaignType.WingsOfLiberty), 43 | new Campaign(_sc2ccm, CampaignType.HeartOfTheSwarm), 44 | new Campaign(_sc2ccm, CampaignType.LegacyOfTheVoid), 45 | new Campaign(_sc2ccm, CampaignType.NovaCovertOps) 46 | }.ToDictionary(c => c.Type); 47 | 48 | // Create our UI list 49 | _campaignUi = new Dictionary 50 | { 51 | { CampaignType.WingsOfLiberty, new CampaignUiElements(CustomWoLEnabled, WoLModPicker, WoLName, WoLAuthor, WoLDescription, WoLVersion) }, 52 | { CampaignType.HeartOfTheSwarm, new CampaignUiElements(CustomHotsEnabled, HotsModPicker, HotSName, HotsAuthor, HotsDescription, HotsVersion) }, 53 | { CampaignType.LegacyOfTheVoid, new CampaignUiElements(CustomLotvEnabled, LotvModPicker, LotVName, LotvAuthor, LotvDescription, LotvVersion) }, 54 | { CampaignType.NovaCovertOps, new CampaignUiElements(CustomNcoEnabled, NcoModPicker, NcoName, NcoAuthor, NcoDescription, NcoVersion) } 55 | }; 56 | 57 | // Setup our per-campaign data 58 | foreach (var campaignType in Campaigns.Keys) 59 | { 60 | // Setup our switch error handlers 61 | _campaignUi[campaignType].ModsEnabledSwitch.Toggled += (object? sender, ToggledEventArgs e) => 62 | { 63 | var campaign = Campaigns[campaignType]; 64 | if (e.Value != campaign.ModsEnabled) 65 | { 66 | if (e.Value) 67 | { 68 | campaign.EnableMods(); 69 | } 70 | else 71 | { 72 | campaign.DisableMods(); 73 | } 74 | } 75 | // When we do a toggle, we only want to do a quick sync 76 | // Full syncs set input field values, which will cause an infinite loop 77 | // Quick syncs only update output UI fields 78 | QuickUiSync(campaignType); 79 | }; 80 | 81 | // Do a full UI sync for that campaign 82 | FullUiSync(campaignType); 83 | 84 | var campaign = Campaigns[campaignType]; 85 | 86 | // Install the mod our config remembers, or reset to vanilla 87 | if (campaign.ModsEnabled && campaign.ActiveMod != null) 88 | { 89 | _sc2ccm.InstallMod(campaign.ActiveMod); 90 | } 91 | else 92 | { 93 | _sc2ccm.Reset(campaignType); 94 | } 95 | } 96 | 97 | if (!_sc2ccm.GoodState()) 98 | { 99 | SetStarCraft2Location.IsVisible = true; 100 | } 101 | } 102 | 103 | /// 104 | /// Sync output UI item fields for a campaign (labels, descriptions, etc) 105 | /// 106 | /// 107 | private void QuickUiSync(CampaignType campaignType) 108 | { 109 | var ui = _campaignUi[campaignType]; 110 | var campaign = Campaigns[campaignType]; 111 | ui.CampaignNameLabel.Text = campaign.Name; 112 | ui.AuthorLabel.Text = campaign.ActiveModAuthor; 113 | ui.AuthorLabel.LineBreakMode = LineBreakMode.WordWrap; 114 | ui.AuthorLabel.WidthRequest = 180; 115 | ui.DescriptionLabel.Text = campaign.ActiveModDescription; 116 | ui.DescriptionLabel.LineBreakMode = LineBreakMode.WordWrap; 117 | ui.DescriptionLabel.WidthRequest = 260; 118 | ui.ModsEnabledSwitch.IsEnabled = _sc2ccm.GoodState(); 119 | ui.ModPicker.IsEnabled = campaign.ModsEnabled && _sc2ccm.GoodState(); 120 | ui.VersionLabel.Text = campaign.ActiveModVersion; 121 | ImportButton.IsEnabled = _sc2ccm.GoodState(); 122 | } 123 | 124 | /// 125 | /// Syncs both input and output fields for a campaign. 126 | /// DO NOT CALL FROM CAMPAIGN FIELD INPUT HANDLERS! DOING SO CAN CAUSE INFINITE LOOPS! 127 | /// 128 | /// 129 | private void FullUiSync(CampaignType campaignType) 130 | { 131 | var ui = _campaignUi[campaignType]; 132 | var campaign = Campaigns[campaignType]; 133 | ui.ModsEnabledSwitch.IsToggled = campaign.ModsEnabled; 134 | 135 | // Temporarily unbind to allow us to change it without triggering too many events 136 | // We also want to avoid changing the active mod when we null out the selected item 137 | ui.ModPicker.SelectedIndexChanged -= ModPickerOnSelectedIndexChanged; 138 | 139 | // Unbind selected item first; without this step changing the source will cause an index-out-of-bounds error 140 | ui.ModPicker.SelectedItem = null; 141 | 142 | // Change the source 143 | ui.ModPicker.ItemsSource = campaign.ModOptions.ToList(); 144 | 145 | // Now set the active mod option 146 | ui.ModPicker.SelectedItem = campaign.ActiveModOption; 147 | 148 | // Rebind our event handler 149 | ui.ModPicker.SelectedIndexChanged += ModPickerOnSelectedIndexChanged; 150 | 151 | // Sync our output UI 152 | QuickUiSync(campaignType); 153 | } 154 | 155 | /// 156 | /// Does a full UI refresh 157 | /// DO NOT CALL FROM CAMPAIGN FIELD INPUT HANDLERS! DOING SO CAN CAUSE INFINITE LOOPS! 158 | /// 159 | private void UiRefresh() 160 | { 161 | SetStarCraft2Location.IsVisible = !_sc2ccm.GoodState(); 162 | foreach (var campaignType in Campaigns.Keys) 163 | { 164 | FullUiSync(campaignType); 165 | } 166 | } 167 | 168 | /// 169 | /// Shows a file selection dialog to the user 170 | /// 171 | /// 172 | /// 173 | private static async Task PromptUserToPickFile(PickOptions options) 174 | { 175 | try 176 | { 177 | return await FilePicker.Default.PickAsync(options); 178 | } 179 | catch (Exception) 180 | { 181 | // The user canceled or something went wrong 182 | return null; 183 | } 184 | } 185 | 186 | /// 187 | /// Import button handler. Will import a new mod and then refresh the UI 188 | /// It is async to run on another thread 189 | /// 190 | /// 191 | /// 192 | private async void ImportButton_Clicked(object? sender, EventArgs e) 193 | { 194 | var res = await PromptUserToPickFile(PickOptions.Default); 195 | if (res != null) 196 | { 197 | MainThread.BeginInvokeOnMainThread(() => 198 | { 199 | ImportButton.IsEnabled = false; 200 | ShowMessage($"Importing {res.FileName}..."); 201 | }); 202 | _sc2ccm.Import(res.FullPath); 203 | MainThread.BeginInvokeOnMainThread(UiRefresh); 204 | } 205 | } 206 | 207 | /// 208 | /// About button handler. Will show an alert describing the project and application 209 | /// 210 | /// 211 | /// 212 | private void Info_OnClicked(object? sender, EventArgs e) 213 | { 214 | DisplayAlert($"{Consts.AppName} v{Consts.Version}", 215 | "This is the Maui Edition of the Custom Campaign manager developed by Matt Tolman. This is not the official GiantGrantGames version of the campaign manager. The goal is to provide a tool that works on Mac as well as Windows so that Mac users can enjoy the GiantGrantGames mod ecosystem.", "Ok"); 216 | } 217 | 218 | /// 219 | /// Shows a message to a user 220 | /// 221 | /// 222 | private void ShowMessage(string obj) 223 | { 224 | Log.Logger.Verbose("Showed message: {Message}", obj); 225 | var label = new Label 226 | { 227 | Text = obj 228 | }; 229 | MessageOutput.Children.Insert(0, label); 230 | } 231 | 232 | /// 233 | /// Prompts the user to show us where they installed StarCraft II 234 | /// 235 | /// 236 | /// 237 | private async Task UserPromptSc2Path() 238 | { 239 | SetStarCraft2Location.IsEnabled = false; 240 | var picker = await FilePicker.Default.PickAsync(PickOptions.Default); 241 | if (picker != null) 242 | { 243 | var location = picker.FullPath; 244 | 245 | if (location != null && location != "") 246 | { 247 | _sc2ccm.SubmitStarCraft2Location(location); 248 | MainThread.BeginInvokeOnMainThread(UiRefresh); 249 | return; 250 | } 251 | } 252 | SetStarCraft2Location.IsEnabled = true; 253 | await DisplayAlert("SC2 Custom Mod Manager", "Unable to proceed without valid StarCraft II executable.", "Ok"); 254 | } 255 | 256 | /// 257 | /// Mod selection picker handler change event. 258 | /// Since we use ModOption for our option items, we can use the same handler for all pickers 259 | /// 260 | /// 261 | /// 262 | private void ModPickerOnSelectedIndexChanged(object? sender, EventArgs e) 263 | { 264 | if (sender is not Picker picker) 265 | { 266 | return; 267 | } 268 | 269 | if (picker.SelectedItem is not Campaign.ModOption option) 270 | { 271 | return; 272 | } 273 | 274 | Campaigns[option.CampaignType].SelectOption(option); 275 | QuickUiSync(option.CampaignType); 276 | } 277 | 278 | async private void SetStarCraft2Location_OnClicked(object? sender, EventArgs e) 279 | { 280 | await UserPromptSc2Path(); 281 | } 282 | } -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/MauiProgram.cs: -------------------------------------------------------------------------------- 1 | namespace SC2_Custom_Campaign_Manager; 2 | 3 | public static class MauiProgram 4 | { 5 | public static MauiApp CreateMauiApp() 6 | { 7 | var builder = MauiApp.CreateBuilder(); 8 | builder 9 | .UseMauiApp() 10 | .ConfigureFonts(fonts => 11 | { 12 | fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); 13 | fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); 14 | }); 15 | 16 | return builder.Build(); 17 | } 18 | } -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Platforms/Android/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Platforms/Android/MainActivity.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.Content.PM; 3 | using Android.OS; 4 | 5 | namespace SC2_Custom_Campaign_Manager; 6 | 7 | [Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, 8 | ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | 9 | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)] 10 | public class MainActivity : MauiAppCompatActivity 11 | { 12 | } -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Platforms/Android/MainApplication.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.Runtime; 3 | 4 | namespace SC2_Custom_Campaign_Manager; 5 | 6 | [Application] 7 | public class MainApplication : MauiApplication 8 | { 9 | public MainApplication(IntPtr handle, JniHandleOwnership ownership) 10 | : base(handle, ownership) 11 | { 12 | } 13 | 14 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); 15 | } -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Platforms/Android/Resources/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #512BD4 4 | #2B0B98 5 | #2B0B98 6 | -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Platforms/MacCatalyst/AppDelegate.cs: -------------------------------------------------------------------------------- 1 | using Foundation; 2 | 3 | namespace SC2_Custom_Campaign_Manager; 4 | 5 | [Register("AppDelegate")] 6 | public class AppDelegate : MauiUIApplicationDelegate 7 | { 8 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); 9 | } -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Platforms/MacCatalyst/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIDeviceFamily 6 | 7 | 1 8 | 2 9 | 10 | UIRequiredDeviceCapabilities 11 | 12 | arm64 13 | 14 | UISupportedInterfaceOrientations 15 | 16 | UIInterfaceOrientationPortrait 17 | UIInterfaceOrientationLandscapeLeft 18 | UIInterfaceOrientationLandscapeRight 19 | 20 | UISupportedInterfaceOrientations~ipad 21 | 22 | UIInterfaceOrientationPortrait 23 | UIInterfaceOrientationPortraitUpsideDown 24 | UIInterfaceOrientationLandscapeLeft 25 | UIInterfaceOrientationLandscapeRight 26 | 27 | XSAppIconAssets 28 | Assets.xcassets/appicon.appiconset 29 | 30 | 31 | -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Platforms/MacCatalyst/Program.cs: -------------------------------------------------------------------------------- 1 | using ObjCRuntime; 2 | using Serilog; 3 | using Serilog.Events; 4 | using UIKit; 5 | using Log = SC2_CCM_Common.Log; 6 | 7 | namespace SC2_Custom_Campaign_Manager; 8 | 9 | /// 10 | /// Main program for Mac 11 | /// 12 | public class Program 13 | { 14 | // This is the main entry point of the application. 15 | static void Main(string[] args) 16 | { 17 | // Select our log file 18 | var logFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), 19 | Path.Combine("SC2CCM", "SC2CCM.log")); 20 | 21 | // Rotate away the old log file (keep a history of 1) 22 | try 23 | { 24 | if (File.Exists(logFile)) 25 | { 26 | var oldLogFile = $"{logFile}.old"; 27 | if (File.Exists(oldLogFile)) 28 | { 29 | File.Delete(oldLogFile); 30 | } 31 | File.Move(logFile, oldLogFile); 32 | } 33 | } 34 | catch(Exception e) 35 | { 36 | Console.WriteLine("Failed to rotate logs! " + e.Message); 37 | } 38 | 39 | // Initialize our logger 40 | Log.Logger = new LoggerConfiguration() 41 | .WriteTo.File( 42 | logFile, 43 | fileSizeLimitBytes: 2 * 1024 * 1024, // 2MB 44 | restrictedToMinimumLevel: LogEventLevel.Debug 45 | ) 46 | .WriteTo.Console(restrictedToMinimumLevel: LogEventLevel.Verbose) 47 | .CreateLogger(); 48 | Console.WriteLine($"Log file {logFile}"); 49 | 50 | try 51 | { 52 | // if you want to use a different Application Delegate class from "AppDelegate" 53 | // you can specify it here. 54 | UIApplication.Main(args, null, typeof(AppDelegate)); 55 | } 56 | catch (Exception ex) 57 | { 58 | // Make sure we log program crashes 59 | Log.Logger.Fatal(ex, "Program Crash Detected!"); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Platforms/Tizen/Main.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Maui; 3 | using Microsoft.Maui.Hosting; 4 | 5 | namespace SC2_Custom_Campaign_Manager; 6 | 7 | class Program : MauiApplication 8 | { 9 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); 10 | 11 | static void Main(string[] args) 12 | { 13 | var app = new Program(); 14 | app.Run(args); 15 | } 16 | } -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Platforms/Tizen/tizen-manifest.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | appicon.xhigh.png 7 | 8 | 9 | 10 | 11 | http://tizen.org/privilege/internet 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Platforms/Windows/App.xaml: -------------------------------------------------------------------------------- 1 |  7 | 8 | 9 | -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Platforms/Windows/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml; 2 | 3 | // To learn more about WinUI, the WinUI project structure, 4 | // and more about our project templates, see: http://aka.ms/winui-project-info. 5 | 6 | namespace SC2_Custom_Campaign_Manager.WinUI; 7 | 8 | /// 9 | /// Provides application-specific behavior to supplement the default Application class. 10 | /// 11 | public partial class App : MauiWinUIApplication 12 | { 13 | /// 14 | /// Initializes the singleton application object. This is the first line of authored code 15 | /// executed, and as such is the logical equivalent of main() or WinMain(). 16 | /// 17 | public App() 18 | { 19 | /// TODO: Setup logging 20 | this.InitializeComponent(); 21 | } 22 | 23 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); 24 | } -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Platforms/Windows/Package.appxmanifest: -------------------------------------------------------------------------------- 1 |  2 | 8 | 9 | 10 | 11 | 12 | SC2 Custom Campaign Manager MAUI Edition 13 | TofuRama 14 | package-logo.png 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Platforms/Windows/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | true/PM 12 | PerMonitorV2, PerMonitor 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Platforms/iOS/AppDelegate.cs: -------------------------------------------------------------------------------- 1 | using Foundation; 2 | 3 | namespace SC2_Custom_Campaign_Manager; 4 | 5 | [Register("AppDelegate")] 6 | public class AppDelegate : MauiUIApplicationDelegate 7 | { 8 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); 9 | } -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Platforms/iOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | LSRequiresIPhoneOS 6 | 7 | UIDeviceFamily 8 | 9 | 1 10 | 2 11 | 12 | UIRequiredDeviceCapabilities 13 | 14 | arm64 15 | 16 | UISupportedInterfaceOrientations 17 | 18 | UIInterfaceOrientationPortrait 19 | UIInterfaceOrientationLandscapeLeft 20 | UIInterfaceOrientationLandscapeRight 21 | 22 | UISupportedInterfaceOrientations~ipad 23 | 24 | UIInterfaceOrientationPortrait 25 | UIInterfaceOrientationPortraitUpsideDown 26 | UIInterfaceOrientationLandscapeLeft 27 | UIInterfaceOrientationLandscapeRight 28 | 29 | XSAppIconAssets 30 | Assets.xcassets/appicon.appiconset 31 | 32 | 33 | -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Platforms/iOS/Program.cs: -------------------------------------------------------------------------------- 1 | using ObjCRuntime; 2 | using UIKit; 3 | 4 | namespace SC2_Custom_Campaign_Manager; 5 | 6 | public class Program 7 | { 8 | // This is the main entry point of the application. 9 | static void Main(string[] args) 10 | { 11 | // if you want to use a different Application Delegate class from "AppDelegate" 12 | // you can specify it here. 13 | UIApplication.Main(args, null, typeof(AppDelegate)); 14 | } 15 | } -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Windows Machine": { 4 | "commandName": "MsixPackage", 5 | "nativeDebugging": false 6 | }, 7 | "Mac": { 8 | "nativeDebugging": false, 9 | "commandName": "Project" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/AppIcon/appicon-176.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/Resources/AppIcon/appicon-176.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/AppIcon/appicon-44.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/Resources/AppIcon/appicon-44.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/AppIcon/appicon-88.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/Resources/AppIcon/appicon-88.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/AppIcon/appicon-lg-1240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/Resources/AppIcon/appicon-lg-1240.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/AppIcon/appicon-lg-310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/Resources/AppIcon/appicon-lg-310.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/AppIcon/appicon-lg-388.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/Resources/AppIcon/appicon-lg-388.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/AppIcon/appicon-lg-465.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/Resources/AppIcon/appicon-lg-465.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/AppIcon/appicon-lg-620.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/Resources/AppIcon/appicon-lg-620.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/AppIcon/appicon-md-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/Resources/AppIcon/appicon-md-150.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/AppIcon/appicon-md-188.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/Resources/AppIcon/appicon-md-188.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/AppIcon/appicon-md-225.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/Resources/AppIcon/appicon-md-225.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/AppIcon/appicon-md-300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/Resources/AppIcon/appicon-md-300.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/AppIcon/appicon-md-600.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/Resources/AppIcon/appicon-md-600.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/AppIcon/appicon-sm - Copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/Resources/AppIcon/appicon-sm - Copy.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/AppIcon/appicon-sm-107.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/Resources/AppIcon/appicon-sm-107.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/AppIcon/appicon-sm-142.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/Resources/AppIcon/appicon-sm-142.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/AppIcon/appicon-sm-71.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/Resources/AppIcon/appicon-sm-71.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/AppIcon/appicon-sm-89.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/Resources/AppIcon/appicon-sm-89.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/AppIcon/appicon-sm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/Resources/AppIcon/appicon-sm.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/AppIcon/appicon-wide-1240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/Resources/AppIcon/appicon-wide-1240.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/AppIcon/appicon-wide-310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/Resources/AppIcon/appicon-wide-310.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/AppIcon/appicon-wide-388.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/Resources/AppIcon/appicon-wide-388.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/AppIcon/appicon-wide-465.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/Resources/AppIcon/appicon-wide-465.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/AppIcon/appicon-wide-620.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/Resources/AppIcon/appicon-wide-620.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/AppIcon/appicon-wide.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | StarCraft II 9 | 10 | 11 | Custom Campaign 12 | Manager 13 | 14 | 15 | -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/AppIcon/appicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | SC2 7 | 8 | 9 | CCM 10 | 11 | 12 | -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/AppIcon/appiconfg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SC2 6 | CCM 7 | -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/AppIcon/package-logo-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/Resources/AppIcon/package-logo-200.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/Fonts/OpenSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/Resources/Fonts/OpenSans-Regular.ttf -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/Fonts/OpenSans-Semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/Resources/Fonts/OpenSans-Semibold.ttf -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/Images/dotnet_bot.svg: -------------------------------------------------------------------------------- 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 | 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 | -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/Raw/AboutAssets.txt: -------------------------------------------------------------------------------- 1 | Any raw assets you want to be deployed with your application can be placed in 2 | this directory (and child directories). Deployment of the asset to your application 3 | is automatically handled by the following `MauiAsset` Build Action within your `.csproj`. 4 | 5 | 6 | 7 | These files will be deployed with you package and will be accessible using Essentials: 8 | 9 | async Task LoadMauiAsset() 10 | { 11 | using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt"); 12 | using var reader = new StreamReader(stream); 13 | 14 | var contents = reader.ReadToEnd(); 15 | } 16 | -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/Splash/splash.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/Resources/Styles/Colors.xaml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 6 | 7 | #1565c0 8 | #002171 9 | #63a4ff 10 | #63a4ff 11 | White 12 | Black 13 | #E1E1E1 14 | #C8C8C8 15 | #ACACAC 16 | #919191 17 | #6E6E6E 18 | #404040 19 | #212121 20 | #141414 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | #F7B548 36 | #FFD590 37 | #FFE5B9 38 | #28C2D1 39 | #7BDDEF 40 | #C3F2F4 41 | #3E8EED 42 | #72ACF1 43 | #A7CBF6 44 | 45 | -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/SC2 Custom Campaign Manager.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0-maccatalyst 5 | $(TargetFrameworks);net6.0-windows10.0.19041.0 6 | 7 | 8 | Exe 9 | SC2_Custom_Campaign_Manager 10 | true 11 | true 12 | enable 13 | 14 | 15 | SC2CCM 16 | 17 | 18 | com.mtolman.sc2_custom_campaign_manager 19 | E56D783F-80C9-4EEC-A8F1-A717D40396F7 20 | SC2CCM.Maui 21 | 22 | 23 | 0.2.3 24 | 1 25 | 0.2.3 26 | 27 | 14.2 28 | 14.0 29 | 21.0 30 | 10.0.17763.0 31 | 10.0.17763.0 32 | 6.5 33 | enable 34 | $(MSBuildProjectName) 35 | False 36 | True 37 | C211EA69E2273BA2ABA100F97B5888D4CA158DC3 38 | SHA512 39 | True 40 | True 41 | True 42 | 0 43 | True 44 | en 45 | AnyCPU 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 | -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/app-icon-wd.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/app-icon-wd.scale-100.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/app-icon-wd.scale-125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/app-icon-wd.scale-125.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/app-icon-wd.scale-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/app-icon-wd.scale-150.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/app-icon-wd.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/app-icon-wd.scale-200.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/app-icon-wd.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/app-icon-wd.scale-400.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon-lg.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon-lg.scale-100.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon-lg.scale-125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon-lg.scale-125.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon-lg.scale-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon-lg.scale-150.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon-lg.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon-lg.scale-200.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon-lg.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon-lg.scale-400.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon-md.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon-md.scale-100.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon-md.scale-125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon-md.scale-125.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon-md.scale-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon-md.scale-150.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon-md.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon-md.scale-200.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon-md.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon-md.scale-400.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon-sm.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon-sm.scale-100.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon-sm.scale-125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon-sm.scale-125.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon-sm.scale-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon-sm.scale-150.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon-sm.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon-sm.scale-200.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon-sm.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon-sm.scale-400.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon.altform-unplated_targetsize-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon.altform-unplated_targetsize-16.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon.altform-unplated_targetsize-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon.altform-unplated_targetsize-24.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon.altform-unplated_targetsize-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon.altform-unplated_targetsize-256.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon.altform-unplated_targetsize-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon.altform-unplated_targetsize-32.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon.altform-unplated_targetsize-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon.altform-unplated_targetsize-48.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon.scale-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon.scale-100.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon.scale-125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon.scale-125.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon.scale-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon.scale-150.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon.scale-200.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon.scale-400.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon.targetsize-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon.targetsize-16.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon.targetsize-16_altform-unplated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon.targetsize-16_altform-unplated.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon.targetsize-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon.targetsize-24.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon.targetsize-24_altform-unplated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon.targetsize-24_altform-unplated.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon.targetsize-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon.targetsize-256.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon.targetsize-256_altform-unplated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon.targetsize-256_altform-unplated.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon.targetsize-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon.targetsize-32.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon.targetsize-32_altform-unplated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon.targetsize-32_altform-unplated.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon.targetsize-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon.targetsize-48.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/appicon.targetsize-48_altform-unplated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/appicon.targetsize-48_altform-unplated.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/package-logo.scale-400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2 Custom Campaign Manager/package-logo.scale-400.png -------------------------------------------------------------------------------- /SC2 Custom Campaign Manager/project.assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "runtimes": { 3 | "osx-x64": { 4 | "#import": [] 5 | }, 6 | "win10-x64": { 7 | "#import": [] 8 | }, 9 | "osx.11.0-arm64": { 10 | "#import": [] 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /SC2_CCM_Common/Campaign.cs: -------------------------------------------------------------------------------- 1 | namespace SC2_CCM_Common; 2 | 3 | /// 4 | /// Represents a moddable StarCraft II Campaign 5 | /// 6 | public class Campaign 7 | { 8 | 9 | /// 10 | /// Represents a mod select option. Can also represent "no mod selected" 11 | /// 12 | public class ModOption 13 | { 14 | /// 15 | /// Selected mod (or null for "no mod selected") 16 | /// 17 | public Mod? Mod; 18 | 19 | /// 20 | /// Campaign type tied to the mod 21 | /// 22 | public CampaignType CampaignType; 23 | 24 | /// 25 | /// Creates a mod option 26 | /// 27 | /// Mod to represent (or "null" for no mod) 28 | /// Campaign mod is tied to (useful for a "null" mod) 29 | public ModOption(Mod? mod, CampaignType campaignType) 30 | { 31 | Mod = mod; 32 | CampaignType = campaignType; 33 | } 34 | 35 | /// 36 | /// Converts a mod option to a user-facing string 37 | /// 38 | /// 39 | public override string ToString() 40 | { 41 | return Mod?.Title ?? "-- NO CAMPAIGN SELECTED --"; 42 | } 43 | 44 | /// 45 | /// Returns whether the mod option is equal to an object 46 | /// 47 | /// 48 | /// 49 | public override bool Equals(object? obj) 50 | { 51 | return obj is ModOption other && Equals(other); 52 | } 53 | 54 | /// 55 | /// Returns whether the mod option is equal to another mod 56 | /// 57 | /// 58 | /// 59 | public bool Equals(ModOption other) 60 | { 61 | return Equals(Mod, other.Mod) && CampaignType == other.CampaignType; 62 | } 63 | 64 | /// 65 | /// Returns the hash code for a mod option 66 | /// 67 | /// 68 | public override int GetHashCode() 69 | { 70 | return HashCode.Combine(Mod, (int)CampaignType); 71 | } 72 | } 73 | 74 | /// 75 | /// Create a new Campaign object 76 | /// 77 | /// Reference to SC2CCM (used to communicate with config) 78 | /// Type associated with the campaign 79 | public Campaign(SC2CCM sc2Ccm, CampaignType type) 80 | { 81 | _sc2ccm = sc2Ccm; 82 | Type = type; 83 | _noModOption = new ModOption(null, Type); 84 | Log.Logger.Information("Created campaign object for {Campaign}", type); 85 | } 86 | 87 | /// 88 | /// Type of the campaign 89 | /// 90 | public CampaignType Type { get; } 91 | 92 | /// 93 | /// Dictionary of mod title to mod for the campaign 94 | /// 95 | public Dictionary Mods => _sc2ccm.Mods(Type); 96 | 97 | /// 98 | /// Represents the "no mod" option 99 | /// 100 | private readonly ModOption _noModOption; 101 | 102 | /// 103 | /// Gets an enumerable of all mod options 104 | /// 105 | public IEnumerable ModOptions { 106 | get 107 | { 108 | return Mods.Values.Select((m, i) => new ModOption(m, Type)).Prepend(_noModOption); 109 | } 110 | } 111 | 112 | /// 113 | /// Gets the human-readable name of the campaign 114 | /// 115 | public string Name => CampaignName(Type); 116 | 117 | /// 118 | /// Returns the campaign name for a campaign type 119 | /// 120 | /// 121 | /// 122 | public static string CampaignName(CampaignType type) 123 | { 124 | return type switch 125 | { 126 | CampaignType.NovaCovertOps => "Nova Covert Ops", 127 | CampaignType.WingsOfLiberty => "Wings of Liberty", 128 | CampaignType.HeartOfTheSwarm => "Heart of the Swarm", 129 | CampaignType.LegacyOfTheVoid => "Legacy of the Void", 130 | _ => "Unknown" 131 | }; 132 | } 133 | 134 | /// 135 | /// Gets the currently active mod, or null if no mods are active 136 | /// Note: This does not account for if mods are enabled (another check is needed) 137 | /// 138 | public Mod? ActiveMod 139 | { 140 | get 141 | { 142 | var loaded = _sc2ccm.GetSelectedModTitle(Type); 143 | return loaded == null || !Mods.ContainsKey(loaded) ? null : Mods[loaded]; 144 | } 145 | } 146 | 147 | /// 148 | /// Returns the actively selected mod option 149 | /// 150 | public ModOption ActiveModOption => ActiveMod != null ? new ModOption(ActiveMod, Type) : _noModOption; 151 | 152 | /// 153 | /// Returns the author for the actively selected mod 154 | /// 155 | public string ActiveModAuthor => ActiveMod?.Author ?? "N/A"; 156 | 157 | /// 158 | /// Returns the description for the actively selected mod 159 | /// 160 | public string ActiveModDescription => ActiveMod?.Desc ?? "N/A"; 161 | 162 | /// 163 | /// Returns whether mods are enabled for the campaign 164 | /// 165 | public bool ModsEnabled => _sc2ccm.ModsEnabled(Type); 166 | 167 | /// 168 | /// Version for the active mod 169 | /// 170 | public string ActiveModVersion => ActiveMod?.Version ?? "N/A"; 171 | 172 | /// 173 | /// Reference to SC2CCM 174 | /// 175 | // ReSharper disable once InconsistentNaming 176 | private readonly SC2CCM _sc2ccm; 177 | 178 | /// 179 | /// Mark a ModOption as selected. Will also tell SC2CCM to install the mod (or uninstall if the selection is to remove mods) 180 | /// 181 | /// 182 | public void SelectOption(ModOption? modOption) 183 | { 184 | Log.Logger.Information("Selected Mod {ModOption} for {Campaign}", modOption, Type); 185 | if (modOption?.Mod == null || !ModsEnabled) 186 | { 187 | _sc2ccm.Reset(Type); 188 | } 189 | else 190 | { 191 | _sc2ccm.InstallMod(modOption.Mod); 192 | } 193 | } 194 | 195 | /// 196 | /// Disable mods for the campaign 197 | /// Will also propagate through to SC2CCM 198 | /// 199 | public void DisableMods() 200 | { 201 | Log.Logger.Information("Turned off mods for {Campaign}", Type); 202 | _sc2ccm.DisableMods(Type); 203 | } 204 | 205 | /// 206 | /// Enable mods for the campaign 207 | /// Will also propagate through to SC2CCM 208 | /// 209 | public void EnableMods() 210 | { 211 | Log.Logger.Information("Turned on mods for {Campaign}", Type); 212 | _sc2ccm.EnableMods(Type); 213 | } 214 | } -------------------------------------------------------------------------------- /SC2_CCM_Common/CampaignType.cs: -------------------------------------------------------------------------------- 1 | namespace SC2_CCM_Common 2 | { 3 | /// 4 | /// Represents the type of custom campaign (e.g. Wings of Liberty) 5 | /// 6 | public enum CampaignType 7 | { 8 | None, 9 | WingsOfLiberty, 10 | HeartOfTheSwarm, 11 | LegacyOfTheVoid, 12 | NovaCovertOps 13 | } 14 | } -------------------------------------------------------------------------------- /SC2_CCM_Common/Functional.cs: -------------------------------------------------------------------------------- 1 | namespace SC2_CCM_Common 2 | { 3 | /// 4 | /// Helper function for high-order functions 5 | /// 6 | public static class Functional 7 | { 8 | /// 9 | /// Takes several predicate functions and returns a function that will check if a value passes all predicates 10 | /// 11 | /// Predicate functions to take 12 | /// Type of input for the predicate 13 | /// A function that takes an input and returns whether it passes all predicates 14 | public static Func And(params Func[] funcs) 15 | { 16 | return input => funcs.All(func => func(input)); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /SC2_CCM_Common/Log.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | using Serilog.Core; 3 | 4 | namespace SC2_CCM_Common; 5 | 6 | /// 7 | /// Logging Class 8 | /// 9 | public static class Log 10 | { 11 | /// 12 | /// Logger used by SC2_CCM_Common 13 | /// 14 | public static Logger Logger { get; set; } = new LoggerConfiguration() 15 | .WriteTo.Console() 16 | .CreateLogger(); 17 | } -------------------------------------------------------------------------------- /SC2_CCM_Common/Mod.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | namespace SC2_CCM_Common; 4 | 5 | /// 6 | /// Represents a Mod for a campaign 7 | /// 8 | public class Mod 9 | { 10 | /// 11 | /// Title of the mod 12 | /// 13 | public string Title { get; set; } = "N/A"; 14 | 15 | /// 16 | /// Author of the mod 17 | /// 18 | public string Author { get; set; } = "N/A"; 19 | 20 | /// 21 | /// Description of the mod 22 | /// 23 | public string Desc { get; set; } = "N/A"; 24 | 25 | /// 26 | /// Campaign type for the mod 27 | /// 28 | public CampaignType CampaignType { get; set; } 29 | 30 | /// 31 | /// Gets the mod path 32 | /// 33 | public string Path { get; set; } = ""; 34 | 35 | /// 36 | /// Gets the mod version 37 | /// 38 | public string Version { get; set; } = "N/A"; 39 | 40 | 41 | /// 42 | /// Sets the campaign type using the campaign name 43 | /// 44 | /// Name of the campaign to get the campaign type from 45 | public Mod SetCampaignFromString(string campaignName) 46 | { 47 | campaignName = campaignName.ToLower(); 48 | if (CampaignNameContainsAnyOf(campaignName, "wings", "liberty", "wol")) 49 | { 50 | CampaignType = CampaignType.WingsOfLiberty; 51 | } 52 | else if (CampaignNameContainsAnyOf(campaignName, "heart", "swarm", "hots")) 53 | { 54 | CampaignType = CampaignType.HeartOfTheSwarm; 55 | } 56 | else if (CampaignNameContainsAnyOf(campaignName, "legacy", "void", "lotv")) 57 | { 58 | CampaignType = CampaignType.LegacyOfTheVoid; 59 | } 60 | else if (CampaignNameContainsAnyOf(campaignName, "nova", "covert", "ops", "nco")) 61 | { 62 | CampaignType = CampaignType.NovaCovertOps; 63 | } 64 | else 65 | { 66 | Log.Logger.Warning("Cannot find mod type for {CampaignType}", campaignName); 67 | CampaignType = CampaignType.None; 68 | } 69 | 70 | return this; 71 | } 72 | 73 | /// 74 | /// Returns whether a campaign name contains any of the provided search strings 75 | /// 76 | /// Campaign name to search 77 | /// Strings to search for 78 | /// 79 | private bool CampaignNameContainsAnyOf(string campaignName, params string[] searches) 80 | { 81 | return searches.Any(campaignName.Contains); 82 | } 83 | 84 | /// 85 | /// Create a mod object from a mod directory info 86 | /// 87 | /// 88 | /// 89 | public static Mod From(ModDirectoryInfo info) 90 | { 91 | var mod = new Mod(); 92 | var metadataFile = info.MetadataTxtFiles[0]; 93 | Log.Logger.Information("Loading mod from {MetadataFile}", metadataFile); 94 | foreach (var readLine in File.ReadLines(metadataFile)) 95 | { 96 | ProcessLine(readLine, ref mod); 97 | } 98 | 99 | mod.Path = System.IO.Path.GetDirectoryName(metadataFile)!; 100 | return mod; 101 | } 102 | 103 | /// 104 | /// Process a single line from a metadata file 105 | /// 106 | /// 107 | /// 108 | private static void ProcessLine(string metadataFileLine, ref Mod mod) 109 | { 110 | var linePieces = metadataFileLine.Split(new []{ '=' }, 2); 111 | var header = linePieces[0].ToLower(); 112 | var value = linePieces.Length > 1 ? linePieces[1] : "N/A"; 113 | switch (header) 114 | { 115 | case "title": 116 | mod.Title = value; 117 | break; 118 | case "desc": 119 | mod.Desc = value; 120 | break; 121 | case "campaign": 122 | mod.SetCampaignFromString(value); 123 | break; 124 | case "version": 125 | mod.Version = value; 126 | break; 127 | case "author": 128 | mod.Author = value; 129 | break; 130 | } 131 | } 132 | 133 | /// 134 | /// Returns the string representation of a mod 135 | /// 136 | /// 137 | public override string ToString() 138 | { 139 | return JsonSerializer.Serialize(this); 140 | } 141 | } -------------------------------------------------------------------------------- /SC2_CCM_Common/ModDirectoryInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Serilog.Core; 4 | 5 | namespace SC2_CCM_Common 6 | { 7 | /// 8 | /// Information for a mod directory 9 | /// 10 | public class ModDirectoryInfo 11 | { 12 | /// 13 | /// Collection of metadata text files found in mod 14 | /// 15 | public readonly string[] MetadataTxtFiles; 16 | 17 | /// 18 | /// Directory for mod information 19 | /// 20 | private readonly string _directory; 21 | 22 | /// 23 | /// Create Information object for a mod directory 24 | /// 25 | /// Directory to pull mod information from 26 | public ModDirectoryInfo(string dir) 27 | { 28 | MetadataTxtFiles = System.IO.Directory.GetFiles(dir, "metadata.txt", SearchOption.AllDirectories); 29 | _directory = dir; 30 | } 31 | 32 | /// 33 | /// Validates mod directory information 34 | /// 35 | /// 36 | /// Processor for user-facing messages 37 | /// 38 | /// 39 | public bool Validate(Action messageProcessor) 40 | { 41 | if (MetadataTxtFiles.Length == 0) 42 | { 43 | Log.Logger.Error("Could not load mod from {Directory}. Could not find metadata file!", _directory); 44 | messageProcessor($"FAILED TO LOAD: Unable to find metadata.txt for \"{_directory}\"!"); 45 | return false; 46 | } 47 | else if (MetadataTxtFiles.Length != 1) 48 | { 49 | Log.Logger.Error("Could not load mod from {Directory}. Found too many metadata files! {MetadataFiles}", _directory, MetadataTxtFiles); 50 | messageProcessor($"FAILED TO LOAD: Unable to find metadata.txt for \"{_directory}\"!"); 51 | return false; 52 | } 53 | 54 | return true; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /SC2_CCM_Common/ModManagerException.cs: -------------------------------------------------------------------------------- 1 | namespace SC2_CCM_Common 2 | { 3 | public class ModManagerException : Exception 4 | { 5 | public ModManagerException(string message) : base(message) {} 6 | } 7 | } -------------------------------------------------------------------------------- /SC2_CCM_Common/SC2CCM.cs: -------------------------------------------------------------------------------- 1 | #if WINDOWS 2 | using Microsoft.Win32; 3 | #endif 4 | 5 | namespace SC2_CCM_Common 6 | { 7 | /// 8 | /// Core logic for StarCraft II Custom Campaign Manger 9 | /// 10 | public class SC2CCM 11 | { 12 | /// 13 | /// File system for managing mods 14 | /// 15 | private readonly ModFileSystem _modFileSystem; 16 | 17 | /// 18 | /// Mod campaigns 19 | /// 20 | private readonly SC2Config _config; 21 | 22 | /// 23 | /// Creates a new SC2CCM object 24 | /// 25 | /// 26 | /// Processor for any user-facing messages. Allows UI code to display messages from non-UI code 27 | /// 28 | /// 29 | /// Method to retrieve StarCraft II executable if auto-detect fails 30 | /// 31 | public SC2CCM(Action messageProcessor) 32 | { 33 | _config = SC2Config.Load(); 34 | _modFileSystem = new ModFileSystem(_config, messageProcessor); 35 | 36 | if (GoodState()) 37 | { 38 | Load(); 39 | } 40 | else 41 | { 42 | messageProcessor("ERROR! Unable to find StarCraft II! Please click \"Set StarCraft II Location\" and select your StarCraft II installation!"); 43 | } 44 | } 45 | 46 | /// 47 | /// Returns whether SC2CCM is in a good state (aka knows where starcraft 2 is) 48 | /// 49 | /// 50 | public bool GoodState() 51 | { 52 | return _config.StarCraft2Exe != ""; 53 | } 54 | 55 | public void SubmitStarCraft2Location(string location) 56 | { 57 | #if WINDOWS 58 | if (!File.Exists(location)) 59 | #else 60 | if (!Directory.Exists(location)) 61 | #endif 62 | { 63 | Log.Logger.Debug("Invalid location {Location} provided", location); 64 | } 65 | else 66 | { 67 | _config.StarCraft2Exe = location; 68 | Load(); 69 | } 70 | } 71 | 72 | /// 73 | /// Imports files as mods 74 | /// 75 | /// 76 | /// Collection of full paths to mod files to import 77 | /// 78 | public void Import(params string[] filePaths) 79 | { 80 | if (!GoodState()) 81 | { 82 | Log.Logger.Debug("Not in a good state yet, skipping import"); 83 | return; 84 | } 85 | 86 | foreach (var path in filePaths) 87 | { 88 | _modFileSystem.Import(path); 89 | } 90 | 91 | Load(); 92 | } 93 | 94 | /// 95 | /// Loads state from disc 96 | /// 97 | private void Load() 98 | { 99 | if (!GoodState()) 100 | { 101 | Log.Logger.Debug("Not in a good state yet, skipping load"); 102 | return; 103 | } 104 | 105 | Log.Logger.Debug("Running SC2CCM Load"); 106 | _modFileSystem.EnsureDirectories(); 107 | _modFileSystem.UnzipCustomCampaigns(); 108 | _modFileSystem.HandleCustomCampaignDependencies(); 109 | _modFileSystem.LoadMods(); 110 | } 111 | 112 | /// 113 | /// Returns all mods with their campaign-and-mod-title mappings 114 | /// 115 | /// 116 | public Dictionary> Mods() 117 | { 118 | return _modFileSystem.Mods; 119 | } 120 | 121 | /// 122 | /// Returns a dictionary of all mods for a campaign with the mod title related to the mod 123 | /// 124 | /// 125 | /// 126 | public Dictionary Mods(CampaignType campaignType) 127 | { 128 | var mods = _modFileSystem.Mods; 129 | return mods.ContainsKey(campaignType) ? mods[campaignType] : new Dictionary() ; 130 | } 131 | 132 | /// 133 | /// Installs a mod to StarCraft II 134 | /// 135 | /// Mod to install 136 | public void InstallMod(Mod mod) 137 | { 138 | if (!GoodState()) 139 | { 140 | Log.Logger.Debug("Not in a good state yet, skipping mod install"); 141 | return; 142 | } 143 | 144 | _modFileSystem.Install(mod); 145 | _config.SetLoadedMod(mod.CampaignType, mod.Title); 146 | } 147 | 148 | /// 149 | /// Resets a campaign to be un-modded 150 | /// 151 | /// Campaign type to reset 152 | public void Reset(CampaignType campaignType) 153 | { 154 | if (!GoodState()) 155 | { 156 | Log.Logger.Debug("Not in a good state yet, skipping campaign reset"); 157 | return; 158 | } 159 | 160 | _modFileSystem.Reset(campaignType); 161 | _config.SetLoadedMod(campaignType, null); 162 | } 163 | 164 | /// 165 | /// Returns the title for the currently selected mod for a campaign 166 | /// 167 | /// 168 | /// 169 | public string? GetSelectedModTitle(CampaignType type) 170 | { 171 | return _config.GetLoadedMod(type); 172 | } 173 | 174 | /// 175 | /// Checks whether mods are enabled for a campaign 176 | /// 177 | /// 178 | /// 179 | public bool ModsEnabled(CampaignType type) 180 | { 181 | return _config.ModsEnabled(type); 182 | } 183 | 184 | /// 185 | /// Disables mods for a campaign 186 | /// Will also reset to the un-modded campaign 187 | /// 188 | /// 189 | public void DisableMods(CampaignType type) 190 | { 191 | if (!GoodState()) 192 | { 193 | Log.Logger.Debug("Not in a good state yet, skipping campaign mod disable"); 194 | return; 195 | } 196 | 197 | _modFileSystem.Reset(type); 198 | _config.SetModEnabled(type, false); 199 | } 200 | 201 | /// 202 | /// Enables mods for a campaign 203 | /// Will also install any selected mods for that campaign 204 | /// 205 | /// 206 | public void EnableMods(CampaignType type) 207 | { 208 | if (!GoodState()) 209 | { 210 | Log.Logger.Debug("Not in a good state yet, skipping campaign mod enable"); 211 | return; 212 | } 213 | 214 | var modToInstall = _config.GetLoadedMod(type); 215 | if (modToInstall != null) 216 | { 217 | if (Mods(type).ContainsKey(modToInstall)) 218 | { 219 | _modFileSystem.Install(Mods(type)[modToInstall]); 220 | } 221 | else 222 | { 223 | _modFileSystem.Reset(type); 224 | _config.SetLoadedMod(type, null); 225 | } 226 | } 227 | else 228 | { 229 | _modFileSystem.Reset(type); 230 | } 231 | _config.SetModEnabled(type, true); 232 | } 233 | } 234 | } -------------------------------------------------------------------------------- /SC2_CCM_Common/SC2Config.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Text.Json; 3 | 4 | #if WINDOWS 5 | using Microsoft.Win32; 6 | #endif 7 | 8 | namespace SC2_CCM_Common 9 | { 10 | /// 11 | /// Represents Configuration for SC2 CCM 12 | /// 13 | // ReSharper disable once InconsistentNaming 14 | public class SC2Config 15 | { 16 | /// 17 | /// Default legacy config path 18 | /// 19 | private static string LegacyConfigPath => 20 | Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), 21 | Path.Combine("SC2CCM", "SC2CCM.txt")); 22 | 23 | /// 24 | /// Default new config path 25 | /// 26 | private static string NewConfigPath => 27 | Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), 28 | Path.Combine("SC2CCM", "SC2CCM.json")); 29 | 30 | /// 31 | /// Current legacy config path for SC2Config object 32 | /// 33 | private readonly string _legacyConfigPath; 34 | 35 | /// 36 | /// Current new config path for SC2Config object 37 | /// 38 | private readonly string _newConfigPath; 39 | 40 | /// 41 | /// Underlying data for SC2Config 42 | /// 43 | private readonly SC2ConfigData _data; 44 | 45 | /// 46 | /// StarCraft II Executable directory 47 | /// 48 | public string StarCraft2Dir => Path.GetDirectoryName(_data.StarCraft2Exe)!; 49 | 50 | /// 51 | /// StarCraft II Executable 52 | /// 53 | public string StarCraft2Exe 54 | { 55 | get => _data.StarCraft2Exe; 56 | set 57 | { 58 | _data.StarCraft2Exe = value; 59 | Save(); 60 | } 61 | } 62 | 63 | /// 64 | /// Convenience wrapper around mod selection info which also does automatic saves on change 65 | /// 66 | private ImmutableDictionary> ModSelectionInfo 67 | { 68 | get => _data.ModSelectionInfo; 69 | set 70 | { 71 | _data.ModSelectionInfo = value; 72 | Save(); 73 | } 74 | } 75 | 76 | /// 77 | /// Constructor for SC2Config 78 | /// 79 | /// Underlying data 80 | /// Legacy config file path 81 | /// New config file path 82 | private SC2Config(SC2ConfigData data, string legacyConfigPath, string newConfigPath) 83 | { 84 | _data = data; 85 | _legacyConfigPath = legacyConfigPath; 86 | _newConfigPath = newConfigPath; 87 | } 88 | 89 | /// 90 | /// Sets the currently loaded campaign mod for the given campaign type 91 | /// 92 | /// 93 | /// 94 | public void SetLoadedMod(CampaignType campaignType, string? modName) 95 | { 96 | ModSelectionInfo = ModSelectionInfo.SetItem(campaignType, 97 | ModSelectionInfo[campaignType].SetItem("mod", modName)); 98 | } 99 | 100 | /// 101 | /// Returns whether campaign mods are enabled for the given campaign type 102 | /// 103 | /// 104 | /// 105 | public bool ModsEnabled(CampaignType campaignType) 106 | { 107 | return ModSelectionInfo[campaignType]["enabled"] == "on"; 108 | } 109 | 110 | /// 111 | /// Sets whether mods are enabled for the given campaign type 112 | /// 113 | /// 114 | /// 115 | public void SetModEnabled(CampaignType campaignType, bool enabled) 116 | { 117 | ModSelectionInfo = ModSelectionInfo.SetItem(campaignType, 118 | ModSelectionInfo[campaignType].SetItem("enabled", enabled ? "on" : "off")); 119 | } 120 | 121 | /// 122 | /// Gets the loaded mod for the given campaign type 123 | /// 124 | /// 125 | /// 126 | public string? GetLoadedMod(CampaignType campaignType) 127 | { 128 | return ModSelectionInfo[campaignType]["mod"]; 129 | } 130 | 131 | /// 132 | /// Returns a new, blank configuration 133 | /// 134 | /// Path to StarCraft II Executable 135 | /// Path for legacy configuration file 136 | /// Path for new configuration file 137 | /// 138 | private static SC2Config NewConfig(string starCraft2Path, string legacyConfigPath, string newConfigPath) 139 | { 140 | ImmutableDictionary MakeDictEntry() => ImmutableDictionary.Create().Add("enabled", "off").Add("mod", null); 141 | 142 | var blankConfig = ImmutableDictionary.Create>() 143 | .Add(CampaignType.WingsOfLiberty, MakeDictEntry()) 144 | .Add(CampaignType.HeartOfTheSwarm, MakeDictEntry()) 145 | .Add(CampaignType.LegacyOfTheVoid, MakeDictEntry()) 146 | .Add(CampaignType.NovaCovertOps, MakeDictEntry()); 147 | 148 | var data = new SC2ConfigData 149 | { 150 | StarCraft2Exe = starCraft2Path, 151 | ModSelectionInfo = blankConfig 152 | }; 153 | 154 | return new SC2Config(data, legacyConfigPath, newConfigPath); 155 | } 156 | 157 | /// 158 | /// Saves the current configuration 159 | /// 160 | private void Save() 161 | { 162 | try 163 | { 164 | File.WriteAllText(_legacyConfigPath, StarCraft2Exe); 165 | File.WriteAllText(_newConfigPath, JsonSerializer.Serialize(_data)); 166 | Log.Logger.Information("Saved config. Legacy: {LegacyPath}, New: {NewPath}", _legacyConfigPath, _newConfigPath); 167 | } 168 | catch (Exception e) 169 | { 170 | Log.Logger.Error(e, "Could not save config!"); 171 | Console.WriteLine($"Unable to save! {e.Message}; {e.StackTrace}"); 172 | } 173 | } 174 | 175 | /// 176 | /// Initializes a blank configuration and saves it to disk. Also handles auto-detecting the StarCraft II executable 177 | /// 178 | /// Fallback path finder in case auto-detect fails to find StarCraft II 179 | /// Legacy configuration file path 180 | /// New configuration file path 181 | /// Configuration object 182 | /// 183 | private static SC2Config InitBlankConfig(string legacyPath, string newPath) 184 | { 185 | Log.Logger.Warning("Initializing from blank config"); 186 | try 187 | { 188 | Directory.CreateDirectory(Path.GetDirectoryName(legacyPath)!); 189 | } 190 | catch (IOException e) 191 | { 192 | Log.Logger.Error(e, "Could not create configuration file!"); 193 | throw new ModManagerException("Unable to create configuration file/folder\nTry running this as administrator."); 194 | } 195 | #if WINDOWS2 196 | try 197 | { 198 | using RegistryKey? registryKey = Registry.LocalMachine.OpenSubKey("Software\\Classes\\Blizzard.SC2Save\\shell\\open\\command"); 199 | if (registryKey != null) 200 | { 201 | var obj = registryKey.GetValue(null); 202 | if (obj != null) 203 | { 204 | string directoryName = Path.GetDirectoryName(Path.GetDirectoryName(obj.ToString().Replace(" \"%1\"", "").Trim('"')))!; 205 | var config = NewConfig(directoryName + "\\StarCraft II.exe", legacyPath, newPath); 206 | config.Save(); 207 | return config; 208 | } 209 | } 210 | } 211 | catch (Exception ex) 212 | { 213 | var config = NewConfig("C:\\Program Files (x86)\\StarCraft II\\StarCraft II.exe", legacyPath, newPath); 214 | config.Save(); 215 | return config; 216 | } 217 | #else 218 | var macOsPath = "/Applications/StarCraft II/StarCraft II.app"; 219 | if (Directory.Exists(macOsPath)) 220 | { 221 | var config = NewConfig(macOsPath,legacyPath, newPath); 222 | config.Save(); 223 | return config; 224 | } 225 | else if (Directory.Exists("/Applications/StarCraft 2/StarCraft II.app")) 226 | { 227 | var config = NewConfig("/Applications/StarCraft 2/StarCraft II.app",legacyPath, newPath); 228 | config.Save(); 229 | return config; 230 | } 231 | else if (Directory.Exists("/Applications/StarCraft 2/StarCraft 2.app")) 232 | { 233 | var config = NewConfig("/Applications/StarCraft 2/StarCraft 2.app",legacyPath, newPath); 234 | config.Save(); 235 | return config; 236 | } 237 | #endif 238 | 239 | Log.Logger.Warning("Could not find default StarCraft II!"); 240 | var cfg = NewConfig("", legacyPath, newPath); 241 | cfg.Save(); 242 | return cfg; 243 | } 244 | 245 | /// 246 | /// Loads the SC2CCM Config file and returns an object representing that file 247 | /// Will create a new config and auto-detect StarCraft II if needed 248 | /// 249 | /// Fallback method to call in case can't auto-detect StarCraft II config 250 | /// Legacy config file path 251 | /// New config file path 252 | /// 253 | public static SC2Config Load(string? legacyPath = null, string? newPath = null) 254 | { 255 | var legacyConfigPath = legacyPath ?? LegacyConfigPath; 256 | var newConfigPath = newPath ?? NewConfigPath; 257 | 258 | if (!Directory.Exists(Path.GetDirectoryName(legacyConfigPath))) 259 | { 260 | Directory.CreateDirectory(Path.GetDirectoryName(legacyConfigPath)!); 261 | } 262 | if (!Directory.Exists(Path.GetDirectoryName(newConfigPath))) 263 | { 264 | Directory.CreateDirectory(Path.GetDirectoryName(newConfigPath)!); 265 | } 266 | 267 | if (!File.Exists(legacyConfigPath)) 268 | { 269 | return InitBlankConfig(legacyPath: legacyConfigPath, newPath: newConfigPath); 270 | } 271 | else if (!File.Exists(newConfigPath)) 272 | { 273 | return MigrateLegacyConfig(legacyPath: legacyConfigPath, newPath: newConfigPath); 274 | } 275 | else 276 | { 277 | var config = FromFile(newConfigPath, legacyConfigPath); 278 | 279 | // If we failed to load our config file, just get a new, blank config 280 | if (config != null) 281 | { 282 | Log.Logger.Information("Loaded config from {ConfigPath}", newConfigPath); 283 | return config; 284 | } 285 | 286 | Log.Logger.Error("Unable to load config at {ConfigPath}. Resetting Config and retrying!", newConfigPath); 287 | File.Delete(legacyConfigPath); 288 | File.Delete(newConfigPath); 289 | return InitBlankConfig(legacyConfigPath, newConfigPath); 290 | } 291 | } 292 | 293 | /// 294 | /// Creates an SC2Config from a JSON file 295 | /// 296 | /// Path to the JSON file 297 | /// Path to the legacy config file (if not provided, will do best guess) 298 | /// 299 | private static SC2Config? FromFile(string newConfigPath, string? legacyConfigPath = null) 300 | { 301 | try 302 | { 303 | var text = File.ReadAllText(newConfigPath); 304 | var data = JsonSerializer.Deserialize(text); 305 | if (data == null) 306 | { 307 | Log.Logger.Error("Unable to parse JSON into SC2ConfigData! {Json}", text); 308 | return null; 309 | } 310 | 311 | legacyConfigPath ??= Path.Combine(Path.GetDirectoryName(newConfigPath)!, "SC2CCM.txt"); 312 | 313 | return new SC2Config(data, newConfigPath, legacyConfigPath); 314 | } 315 | catch (Exception e) 316 | { 317 | Log.Logger.Error(e, "Unable to load config from {ConfigPath}!", newConfigPath); 318 | return null; 319 | } 320 | } 321 | 322 | /// 323 | /// Migrates the legacy configuration txt-only file to the newer JSON file. 324 | /// Legacy file used for compatability with GiantGrantGames CCM (for future Windows port of this app). 325 | /// 326 | /// 327 | /// 328 | /// 329 | /// 330 | private static SC2Config MigrateLegacyConfig(string legacyPath, string newPath) 331 | { 332 | Log.Logger.Warning("Migrating from legacy config"); 333 | string str = File.ReadLines(legacyPath).First(); 334 | #if WINDOWS 335 | if (!File.Exists(str)) 336 | #else 337 | if (!Directory.Exists(str)) 338 | #endif 339 | { 340 | // If the config is corrupted, clear it and try again 341 | Log.Logger.Error("Bad StarCraft II location detected in legacy config! Resetting config {BadPath}", str); 342 | File.Delete(legacyPath); 343 | return InitBlankConfig(legacyPath, newPath); 344 | } 345 | else 346 | { 347 | var config = NewConfig(str, legacyPath, newPath); 348 | config.Save(); 349 | return config; 350 | } 351 | } 352 | } 353 | } 354 | 355 | -------------------------------------------------------------------------------- /SC2_CCM_Common/SC2ConfigData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Immutable; 3 | 4 | namespace SC2_CCM_Common 5 | { 6 | /// 7 | /// Serializable data for SC2 Config 8 | /// 9 | // ReSharper disable once InconsistentNaming 10 | public class SC2ConfigData 11 | { 12 | /// 13 | /// StarCraft II Executable Path 14 | /// 15 | public string StarCraft2Exe { get; set; } = ""; 16 | 17 | /// 18 | /// Campaign Options and information 19 | /// 20 | public ImmutableDictionary> ModSelectionInfo { get; set; } = ImmutableDictionary.Create>(); 21 | 22 | /// 23 | /// Extra data for future use 24 | /// 25 | public ImmutableDictionary? ExtraData { get; set; } = null; 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /SC2_CCM_Common/SC2_CCM_Common.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | $(DefineConstants)TRACE 11 | 12 | 13 | 14 | $(DefineConstants)TRACE 15 | 16 | 17 | 18 | $(DefineConstants);WINDOWS 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /SC2_CCM_Common/ZipArchiveExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Compression; 4 | using System.Linq; 5 | 6 | namespace SC2_CCM_Common 7 | { 8 | /// 9 | /// Extensions for Zip Archives 10 | /// 11 | public static class ZipArchiveExtensions 12 | { 13 | /// 14 | /// Extracts a zip archive to a directory 15 | /// 16 | /// 17 | /// 18 | public static void ExtractToDirectory( 19 | this ZipArchive archive, 20 | string destinationDirectory 21 | ) 22 | { 23 | string fullName = Directory.CreateDirectory(destinationDirectory).FullName; 24 | foreach (var zipArchiveEntry in archive.Entries) 25 | { 26 | ExtractEntry(fullName, zipArchiveEntry); 27 | } 28 | } 29 | 30 | /// 31 | /// Extracts a zip entry to a specific location 32 | /// 33 | /// 34 | /// 35 | /// 36 | private static void ExtractEntry(string fullName, ZipArchiveEntry zipArchiveEntry) 37 | { 38 | var fullPath = Path.GetFullPath(Path.Combine(fullName, zipArchiveEntry.FullName)); 39 | if (!fullPath.StartsWith(fullName, StringComparison.OrdinalIgnoreCase)) 40 | { 41 | Log.Logger.Error("Zip archive tried to extract files outside of destination directory! {Zip}", fullPath); 42 | throw new IOException( 43 | "Zip archive attempted to extract file(s) outside of destination directory! Stopping extraction. See this link for more info: https://snyk.io/research/zip-slip-vulnerability"); 44 | } 45 | 46 | var dirName = Path.GetDirectoryName(fullPath); 47 | if (dirName == null) 48 | { 49 | Log.Logger.Error("Could not determine directory name for zip {Zip}!", fullPath); 50 | throw new IOException("Could not determine directory name for nested zip directory!"); 51 | } 52 | else if (zipArchiveEntry.Name.Length == 0) 53 | { 54 | Directory.CreateDirectory(dirName); 55 | } 56 | else 57 | { 58 | if (!Directory.Exists(dirName)) 59 | { 60 | Directory.CreateDirectory(dirName); 61 | } 62 | zipArchiveEntry.ExtractToFile(fullPath, true); 63 | } 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /SC2_CCM_Common_Test/ModTest.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using SC2_CCM_Common; 3 | 4 | namespace SC2_CCM_Common_Test; 5 | 6 | public class ModTest 7 | { 8 | [Fact] 9 | public void SetCampaignTypeFromName() 10 | { 11 | var mod = new Mod(); 12 | Assert.Equal(CampaignType.WingsOfLiberty, mod.SetCampaignFromString("WINGS").CampaignType); 13 | Assert.Equal(CampaignType.WingsOfLiberty, mod.SetCampaignFromString("liberty").CampaignType); 14 | Assert.Equal(CampaignType.WingsOfLiberty, mod.SetCampaignFromString("WoL").CampaignType); 15 | Assert.Equal(CampaignType.WingsOfLiberty, mod.SetCampaignFromString("Wings of Liberty").CampaignType); 16 | 17 | Assert.Equal(CampaignType.HeartOfTheSwarm, mod.SetCampaignFromString("HeArT").CampaignType); 18 | Assert.Equal(CampaignType.HeartOfTheSwarm, mod.SetCampaignFromString("sWARm").CampaignType); 19 | Assert.Equal(CampaignType.HeartOfTheSwarm, mod.SetCampaignFromString("HoTS").CampaignType); 20 | Assert.Equal(CampaignType.HeartOfTheSwarm, mod.SetCampaignFromString("Heart of the Swarm").CampaignType); 21 | 22 | Assert.Equal(CampaignType.LegacyOfTheVoid, mod.SetCampaignFromString("Legacy").CampaignType); 23 | Assert.Equal(CampaignType.LegacyOfTheVoid, mod.SetCampaignFromString("VOID").CampaignType); 24 | Assert.Equal(CampaignType.LegacyOfTheVoid, mod.SetCampaignFromString("loTv").CampaignType); 25 | Assert.Equal(CampaignType.LegacyOfTheVoid, mod.SetCampaignFromString("Legacy of the Void").CampaignType); 26 | 27 | Assert.Equal(CampaignType.NovaCovertOps, mod.SetCampaignFromString("ncO").CampaignType); 28 | Assert.Equal(CampaignType.NovaCovertOps, mod.SetCampaignFromString("Ops").CampaignType); 29 | Assert.Equal(CampaignType.NovaCovertOps, mod.SetCampaignFromString("CoVERt").CampaignType); 30 | Assert.Equal(CampaignType.NovaCovertOps, mod.SetCampaignFromString("NoVa").CampaignType); 31 | Assert.Equal(CampaignType.NovaCovertOps, mod.SetCampaignFromString("Nova Covert Ops").CampaignType); 32 | } 33 | 34 | [Fact] 35 | public void ModToString() 36 | { 37 | var mod = new Mod(); 38 | mod.Author = "Grant"; 39 | mod.Title = "Grant's Dream Mod"; 40 | mod.Desc = "Grant's Dream Mod made real"; 41 | mod.Path = "/into/grants/dreams"; 42 | mod.Version = "The.Best"; 43 | mod.SetCampaignFromString("wol"); 44 | 45 | var jsonStr = mod.ToString(); 46 | var jsonData = JsonSerializer.Deserialize>(jsonStr)!; 47 | Assert.Equal(mod.Author, jsonData["Author"].ToString()); 48 | Assert.Equal(mod.Title, jsonData["Title"].ToString()); 49 | Assert.Equal(mod.Desc, jsonData["Desc"].ToString()); 50 | Assert.Equal(mod.Path, jsonData["Path"].ToString()); 51 | Assert.Equal(mod.Version, jsonData["Version"].ToString()); 52 | Assert.Equal((int)mod.CampaignType, ((JsonElement)jsonData["CampaignType"]).GetInt32()); 53 | } 54 | } -------------------------------------------------------------------------------- /SC2_CCM_Common_Test/SC2_CCM_Common_Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | all 17 | 18 | 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | all 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /SC2_CCM_Common_Test/Sc2ConfigTest.cs: -------------------------------------------------------------------------------- 1 | using SC2_CCM_Common; 2 | 3 | namespace SC2_CCM_Common_Test; 4 | 5 | public class Sc2ConfigTest 6 | { 7 | [Fact] 8 | public void ConfigLoadTest() 9 | { 10 | var tmpDir = Path.Combine(Path.GetTempPath(), "SC2CCM"); 11 | 12 | try 13 | { 14 | var legacyConfig = Path.Combine(tmpDir, "SC2CCM.txt"); 15 | var newConfig = Path.Combine(tmpDir, "SC2CCM.json"); 16 | 17 | // Clean up 18 | if (File.Exists(legacyConfig)) 19 | { 20 | File.Delete(legacyConfig); 21 | } 22 | 23 | if (File.Exists(newConfig)) 24 | { 25 | File.Delete(newConfig); 26 | } 27 | 28 | // Blank Config 29 | var config = SC2Config.Load(legacyConfig, newConfig); 30 | 31 | Assert.NotNull(config); 32 | 33 | var oldExe = config.StarCraft2Exe; 34 | 35 | Assert.False(config.ModsEnabled(CampaignType.WingsOfLiberty)); 36 | Assert.False(config.ModsEnabled(CampaignType.HeartOfTheSwarm)); 37 | Assert.False(config.ModsEnabled(CampaignType.LegacyOfTheVoid)); 38 | Assert.False(config.ModsEnabled(CampaignType.NovaCovertOps)); 39 | 40 | Assert.Null(config.GetLoadedMod(CampaignType.WingsOfLiberty)); 41 | Assert.Null(config.GetLoadedMod(CampaignType.HeartOfTheSwarm)); 42 | Assert.Null(config.GetLoadedMod(CampaignType.LegacyOfTheVoid)); 43 | Assert.Null(config.GetLoadedMod(CampaignType.NovaCovertOps)); 44 | 45 | File.Delete(newConfig); 46 | 47 | // Migrate Config 48 | config = SC2Config.Load(legacyConfig, newConfig); 49 | Assert.Equal(oldExe, config.StarCraft2Exe); 50 | 51 | Assert.False(config.ModsEnabled(CampaignType.WingsOfLiberty)); 52 | Assert.False(config.ModsEnabled(CampaignType.HeartOfTheSwarm)); 53 | Assert.False(config.ModsEnabled(CampaignType.LegacyOfTheVoid)); 54 | Assert.False(config.ModsEnabled(CampaignType.NovaCovertOps)); 55 | 56 | Assert.Null(config.GetLoadedMod(CampaignType.WingsOfLiberty)); 57 | Assert.Null(config.GetLoadedMod(CampaignType.HeartOfTheSwarm)); 58 | Assert.Null(config.GetLoadedMod(CampaignType.LegacyOfTheVoid)); 59 | Assert.Null(config.GetLoadedMod(CampaignType.NovaCovertOps)); 60 | 61 | config.SetModEnabled(CampaignType.HeartOfTheSwarm, true); 62 | config.SetModEnabled(CampaignType.LegacyOfTheVoid, true); 63 | 64 | config.SetLoadedMod(CampaignType.WingsOfLiberty, "Mobius"); 65 | config.SetLoadedMod(CampaignType.HeartOfTheSwarm, "Real Scale"); 66 | 67 | // New Config 68 | config = SC2Config.Load(legacyConfig, newConfig); 69 | Assert.Equal(oldExe, config.StarCraft2Exe); 70 | 71 | Assert.False(config.ModsEnabled(CampaignType.WingsOfLiberty)); 72 | Assert.True(config.ModsEnabled(CampaignType.HeartOfTheSwarm)); 73 | Assert.True(config.ModsEnabled(CampaignType.LegacyOfTheVoid)); 74 | Assert.False(config.ModsEnabled(CampaignType.NovaCovertOps)); 75 | 76 | Assert.Equal(config.GetLoadedMod(CampaignType.WingsOfLiberty), "Mobius"); 77 | Assert.Equal(config.GetLoadedMod(CampaignType.HeartOfTheSwarm), "Real Scale"); 78 | Assert.Null(config.GetLoadedMod(CampaignType.LegacyOfTheVoid)); 79 | Assert.Null(config.GetLoadedMod(CampaignType.NovaCovertOps)); 80 | 81 | } 82 | finally 83 | { 84 | Directory.Delete(tmpDir, true); 85 | } 86 | } 87 | 88 | [Fact] 89 | public async void ConfigModSettingChanges() 90 | { 91 | var tmpDir = Path.Combine(Path.GetTempPath(), "SC2CCM"); 92 | 93 | try 94 | { 95 | var legacyConfig = Path.Combine(tmpDir, "SC2CCM.txt"); 96 | var newConfig = Path.Combine(tmpDir, "SC2CCM.json"); 97 | 98 | var config = SC2Config.Load(legacyConfig, newConfig); 99 | Assert.False(config.ModsEnabled(CampaignType.WingsOfLiberty)); 100 | config.SetModEnabled(CampaignType.WingsOfLiberty, true); 101 | Assert.True(config.ModsEnabled(CampaignType.WingsOfLiberty)); 102 | config.SetModEnabled(CampaignType.WingsOfLiberty, false); 103 | Assert.False(config.ModsEnabled(CampaignType.WingsOfLiberty)); 104 | 105 | Assert.Null(config.GetLoadedMod(CampaignType.WingsOfLiberty)); 106 | config.SetLoadedMod(CampaignType.WingsOfLiberty, "Real Scale"); 107 | Assert.Equal("Real Scale", config.GetLoadedMod(CampaignType.WingsOfLiberty)); 108 | config.SetLoadedMod(CampaignType.WingsOfLiberty, null); 109 | Assert.Null(config.GetLoadedMod(CampaignType.WingsOfLiberty)); 110 | } 111 | finally 112 | { 113 | Directory.Delete(tmpDir, true); 114 | } 115 | } 116 | 117 | [Fact] 118 | public async void CorruptedConfigRecovery() 119 | { 120 | var tmpDir = Path.Combine(Path.GetTempPath(), "SC2CCM"); 121 | 122 | try 123 | { 124 | var legacyConfig = Path.Combine(tmpDir, "SC2CCM.txt"); 125 | var newConfig = Path.Combine(tmpDir, "SC2CCM.json"); 126 | 127 | var config = SC2Config.Load(legacyConfig, newConfig); 128 | var detectedExe = config.StarCraft2Exe; 129 | 130 | File.WriteAllText(newConfig, "INVALID JSON"); 131 | File.WriteAllText(legacyConfig, "INVALID PATH"); 132 | 133 | config = SC2Config.Load(legacyConfig, newConfig); 134 | Assert.Equal(detectedExe, config.StarCraft2Exe); 135 | 136 | File.Delete(newConfig); 137 | File.WriteAllText(legacyConfig, "INVALID PATH"); 138 | 139 | config = SC2Config.Load(legacyConfig, newConfig); 140 | Assert.Equal(detectedExe, config.StarCraft2Exe); 141 | } 142 | finally 143 | { 144 | Directory.Delete(tmpDir, true); 145 | } 146 | } 147 | } -------------------------------------------------------------------------------- /SC2_CCM_Common_Test/Usings.cs: -------------------------------------------------------------------------------- 1 | global using Xunit; -------------------------------------------------------------------------------- /SC2_CCM_WinForm/AboutBox.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace SC2_CCM_WinForm 2 | { 3 | partial class AboutBox 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | protected override void Dispose(bool disposing) 14 | { 15 | if (disposing && (components != null)) 16 | { 17 | components.Dispose(); 18 | } 19 | base.Dispose(disposing); 20 | } 21 | 22 | #region Windows Form Designer generated code 23 | 24 | /// 25 | /// Required method for Designer support - do not modify 26 | /// the contents of this method with the code editor. 27 | /// 28 | private void InitializeComponent() 29 | { 30 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(AboutBox)); 31 | this.tableLayoutPanel = new System.Windows.Forms.TableLayoutPanel(); 32 | this.logoPictureBox = new System.Windows.Forms.PictureBox(); 33 | this.labelProductName = new System.Windows.Forms.Label(); 34 | this.labelVersion = new System.Windows.Forms.Label(); 35 | this.labelCopyright = new System.Windows.Forms.Label(); 36 | this.labelCompanyName = new System.Windows.Forms.Label(); 37 | this.textBoxDescription = new System.Windows.Forms.TextBox(); 38 | this.okButton = new System.Windows.Forms.Button(); 39 | this.tableLayoutPanel.SuspendLayout(); 40 | ((System.ComponentModel.ISupportInitialize)(this.logoPictureBox)).BeginInit(); 41 | this.SuspendLayout(); 42 | // 43 | // tableLayoutPanel 44 | // 45 | this.tableLayoutPanel.ColumnCount = 2; 46 | this.tableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 33F)); 47 | this.tableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 67F)); 48 | this.tableLayoutPanel.Controls.Add(this.logoPictureBox, 0, 0); 49 | this.tableLayoutPanel.Controls.Add(this.labelProductName, 1, 0); 50 | this.tableLayoutPanel.Controls.Add(this.labelVersion, 1, 1); 51 | this.tableLayoutPanel.Controls.Add(this.labelCopyright, 1, 2); 52 | this.tableLayoutPanel.Controls.Add(this.labelCompanyName, 1, 3); 53 | this.tableLayoutPanel.Controls.Add(this.textBoxDescription, 1, 4); 54 | this.tableLayoutPanel.Controls.Add(this.okButton, 1, 5); 55 | this.tableLayoutPanel.Dock = System.Windows.Forms.DockStyle.Fill; 56 | this.tableLayoutPanel.Location = new System.Drawing.Point(10, 10); 57 | this.tableLayoutPanel.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); 58 | this.tableLayoutPanel.Name = "tableLayoutPanel"; 59 | this.tableLayoutPanel.RowCount = 6; 60 | this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 10F)); 61 | this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 10F)); 62 | this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 10F)); 63 | this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 10F)); 64 | this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); 65 | this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 10F)); 66 | this.tableLayoutPanel.Size = new System.Drawing.Size(487, 307); 67 | this.tableLayoutPanel.TabIndex = 0; 68 | // 69 | // logoPictureBox 70 | // 71 | this.logoPictureBox.Dock = System.Windows.Forms.DockStyle.Fill; 72 | this.logoPictureBox.Image = ((System.Drawing.Image)(resources.GetObject("logoPictureBox.Image"))); 73 | this.logoPictureBox.Location = new System.Drawing.Point(4, 3); 74 | this.logoPictureBox.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); 75 | this.logoPictureBox.Name = "logoPictureBox"; 76 | this.tableLayoutPanel.SetRowSpan(this.logoPictureBox, 6); 77 | this.logoPictureBox.Size = new System.Drawing.Size(152, 301); 78 | this.logoPictureBox.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage; 79 | this.logoPictureBox.TabIndex = 12; 80 | this.logoPictureBox.TabStop = false; 81 | // 82 | // labelProductName 83 | // 84 | this.labelProductName.Dock = System.Windows.Forms.DockStyle.Fill; 85 | this.labelProductName.Location = new System.Drawing.Point(167, 0); 86 | this.labelProductName.Margin = new System.Windows.Forms.Padding(7, 0, 4, 0); 87 | this.labelProductName.MaximumSize = new System.Drawing.Size(0, 20); 88 | this.labelProductName.Name = "labelProductName"; 89 | this.labelProductName.Size = new System.Drawing.Size(316, 20); 90 | this.labelProductName.TabIndex = 19; 91 | this.labelProductName.Text = "SC2 Custom Campaign Manager: XP Edition"; 92 | this.labelProductName.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; 93 | // 94 | // labelVersion 95 | // 96 | this.labelVersion.Dock = System.Windows.Forms.DockStyle.Fill; 97 | this.labelVersion.Location = new System.Drawing.Point(167, 30); 98 | this.labelVersion.Margin = new System.Windows.Forms.Padding(7, 0, 4, 0); 99 | this.labelVersion.MaximumSize = new System.Drawing.Size(0, 20); 100 | this.labelVersion.Name = "labelVersion"; 101 | this.labelVersion.Size = new System.Drawing.Size(316, 20); 102 | this.labelVersion.TabIndex = 0; 103 | this.labelVersion.Text = "Version"; 104 | this.labelVersion.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; 105 | // 106 | // labelCopyright 107 | // 108 | this.labelCopyright.Dock = System.Windows.Forms.DockStyle.Fill; 109 | this.labelCopyright.Location = new System.Drawing.Point(167, 60); 110 | this.labelCopyright.Margin = new System.Windows.Forms.Padding(7, 0, 4, 0); 111 | this.labelCopyright.MaximumSize = new System.Drawing.Size(0, 20); 112 | this.labelCopyright.Name = "labelCopyright"; 113 | this.labelCopyright.Size = new System.Drawing.Size(316, 20); 114 | this.labelCopyright.TabIndex = 21; 115 | this.labelCopyright.Text = "Copyright"; 116 | this.labelCopyright.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; 117 | // 118 | // labelCompanyName 119 | // 120 | this.labelCompanyName.Dock = System.Windows.Forms.DockStyle.Fill; 121 | this.labelCompanyName.Location = new System.Drawing.Point(167, 90); 122 | this.labelCompanyName.Margin = new System.Windows.Forms.Padding(7, 0, 4, 0); 123 | this.labelCompanyName.MaximumSize = new System.Drawing.Size(0, 20); 124 | this.labelCompanyName.Name = "labelCompanyName"; 125 | this.labelCompanyName.Size = new System.Drawing.Size(316, 20); 126 | this.labelCompanyName.TabIndex = 22; 127 | this.labelCompanyName.Text = "Company Name"; 128 | this.labelCompanyName.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; 129 | // 130 | // textBoxDescription 131 | // 132 | this.textBoxDescription.Dock = System.Windows.Forms.DockStyle.Fill; 133 | this.textBoxDescription.Location = new System.Drawing.Point(167, 123); 134 | this.textBoxDescription.Margin = new System.Windows.Forms.Padding(7, 3, 4, 3); 135 | this.textBoxDescription.Multiline = true; 136 | this.textBoxDescription.Name = "textBoxDescription"; 137 | this.textBoxDescription.ReadOnly = true; 138 | this.textBoxDescription.ScrollBars = System.Windows.Forms.ScrollBars.Both; 139 | this.textBoxDescription.Size = new System.Drawing.Size(316, 147); 140 | this.textBoxDescription.TabIndex = 23; 141 | this.textBoxDescription.TabStop = false; 142 | this.textBoxDescription.Text = "Description"; 143 | // 144 | // okButton 145 | // 146 | this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 147 | this.okButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; 148 | this.okButton.Location = new System.Drawing.Point(395, 277); 149 | this.okButton.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); 150 | this.okButton.Name = "okButton"; 151 | this.okButton.Size = new System.Drawing.Size(88, 27); 152 | this.okButton.TabIndex = 24; 153 | this.okButton.Text = "&OK"; 154 | this.okButton.Click += new System.EventHandler(this.okButton_Click); 155 | // 156 | // AboutBox 157 | // 158 | this.AcceptButton = this.okButton; 159 | this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); 160 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 161 | this.ClientSize = new System.Drawing.Size(507, 327); 162 | this.Controls.Add(this.tableLayoutPanel); 163 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 164 | this.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); 165 | this.MaximizeBox = false; 166 | this.MinimizeBox = false; 167 | this.Name = "AboutBox"; 168 | this.Padding = new System.Windows.Forms.Padding(10); 169 | this.ShowIcon = false; 170 | this.ShowInTaskbar = false; 171 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 172 | this.Text = "AboutBox"; 173 | this.tableLayoutPanel.ResumeLayout(false); 174 | this.tableLayoutPanel.PerformLayout(); 175 | ((System.ComponentModel.ISupportInitialize)(this.logoPictureBox)).EndInit(); 176 | this.ResumeLayout(false); 177 | 178 | } 179 | 180 | #endregion 181 | 182 | private System.Windows.Forms.TableLayoutPanel tableLayoutPanel; 183 | private System.Windows.Forms.PictureBox logoPictureBox; 184 | private System.Windows.Forms.Label labelProductName; 185 | private System.Windows.Forms.Label labelVersion; 186 | private System.Windows.Forms.Label labelCopyright; 187 | private System.Windows.Forms.Label labelCompanyName; 188 | private System.Windows.Forms.TextBox textBoxDescription; 189 | private System.Windows.Forms.Button okButton; 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /SC2_CCM_WinForm/AboutBox.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Drawing; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Threading.Tasks; 8 | using System.Windows.Forms; 9 | 10 | namespace SC2_CCM_WinForm 11 | { 12 | partial class AboutBox : Form 13 | { 14 | public AboutBox() 15 | { 16 | InitializeComponent(); 17 | this.Text = String.Format("About {0}", Consts.AppName); 18 | this.labelProductName.Text = Consts.AppName; 19 | this.labelVersion.Text = String.Format("Version {0}", Consts.Version); 20 | this.labelCopyright.Text = "Copyright 2022. MIT License"; 21 | this.labelCompanyName.Text = "Matt Tolman"; 22 | this.textBoxDescription.Text = "This is the Cross Platform (XP) Edition of the Custom Campaign manager developed by Matt Tolman. This is not the official GiantGrantGames version of the campaign manager. The goal is to provide a tool that works on Mac as well as Windows so that users on different platforms can enjoy the GiantGrantGames mod ecosystem."; 23 | } 24 | 25 | #region Assembly Attribute Accessors 26 | 27 | public string AssemblyTitle 28 | { 29 | get 30 | { 31 | object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyTitleAttribute), false); 32 | if (attributes.Length > 0) 33 | { 34 | AssemblyTitleAttribute titleAttribute = (AssemblyTitleAttribute)attributes[0]; 35 | if (titleAttribute.Title != "") 36 | { 37 | return titleAttribute.Title; 38 | } 39 | } 40 | return System.IO.Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().CodeBase); 41 | } 42 | } 43 | 44 | public string AssemblyVersion 45 | { 46 | get 47 | { 48 | return Assembly.GetExecutingAssembly().GetName().Version.ToString(); 49 | } 50 | } 51 | 52 | public string AssemblyDescription 53 | { 54 | get 55 | { 56 | object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyDescriptionAttribute), false); 57 | if (attributes.Length == 0) 58 | { 59 | return ""; 60 | } 61 | return ((AssemblyDescriptionAttribute)attributes[0]).Description; 62 | } 63 | } 64 | 65 | public string AssemblyProduct 66 | { 67 | get 68 | { 69 | object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyProductAttribute), false); 70 | if (attributes.Length == 0) 71 | { 72 | return ""; 73 | } 74 | return ((AssemblyProductAttribute)attributes[0]).Product; 75 | } 76 | } 77 | 78 | public string AssemblyCopyright 79 | { 80 | get 81 | { 82 | object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false); 83 | if (attributes.Length == 0) 84 | { 85 | return ""; 86 | } 87 | return ((AssemblyCopyrightAttribute)attributes[0]).Copyright; 88 | } 89 | } 90 | 91 | public string AssemblyCompany 92 | { 93 | get 94 | { 95 | object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyCompanyAttribute), false); 96 | if (attributes.Length == 0) 97 | { 98 | return ""; 99 | } 100 | return ((AssemblyCompanyAttribute)attributes[0]).Company; 101 | } 102 | } 103 | #endregion 104 | 105 | private void okButton_Click(object sender, EventArgs e) 106 | { 107 | this.Close(); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /SC2_CCM_WinForm/CampaignUiElements.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace SC2_CCM_WinForm 3 | { 4 | /// 5 | /// Collection of UI elements for a campaign 6 | /// 7 | public class CampaignUiElements 8 | { 9 | public readonly CheckBox ModsEnabledCheckBox; 10 | public readonly ComboBox ModPicker; 11 | public readonly Label CampaignNameLabel; 12 | public readonly Label AuthorLabel; 13 | public readonly Label DescriptionLabel; 14 | public readonly Label VersionLabel; 15 | 16 | public CampaignUiElements( 17 | CheckBox modsEnabledCheckBox, 18 | ComboBox modPicker, 19 | Label campaignNameLabel, 20 | Label authorLabel, 21 | Label descriptionLabel, 22 | Label versionLabel 23 | ) 24 | { 25 | this.ModsEnabledCheckBox = modsEnabledCheckBox; 26 | this.ModPicker = modPicker; 27 | this.CampaignNameLabel = campaignNameLabel; 28 | this.AuthorLabel = authorLabel; 29 | this.DescriptionLabel = descriptionLabel; 30 | this.VersionLabel = versionLabel; 31 | } 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /SC2_CCM_WinForm/Consts.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace SC2_CCM_WinForm; 4 | 5 | /// 6 | /// Program-wide constants 7 | /// 8 | public static class Consts 9 | { 10 | /// 11 | /// Name of the application 12 | /// 13 | public const string AppName = "SC2 Custom Campaign Manager: XP Edition"; 14 | 15 | /// 16 | /// Version of the appication 17 | /// 18 | public static string Version => Assembly.GetEntryAssembly()?.GetCustomAttribute()?.InformationalVersion ?? "0.1"; 19 | } -------------------------------------------------------------------------------- /SC2_CCM_WinForm/MainPage.cs: -------------------------------------------------------------------------------- 1 | using SC2_CCM_Common; 2 | using Syroot.Windows.IO; 3 | 4 | namespace SC2_CCM_WinForm 5 | { 6 | public partial class MainPage : Form 7 | { 8 | /// 9 | /// SC2CCM (Core logic) 10 | /// 11 | // ReSharper disable once InconsistentNaming 12 | private readonly SC2CCM _sc2ccm; 13 | 14 | /// 15 | /// Required designer variable. 16 | /// 17 | private System.ComponentModel.IContainer components = null; 18 | 19 | /// 20 | /// UI elements tied to a campaign 21 | /// 22 | private Dictionary _campaignUi { get; } 23 | 24 | /// 25 | /// Mapping of campaign type to custom compaign data 26 | /// 27 | private readonly Dictionary Campaigns; 28 | 29 | public MainPage() 30 | { 31 | Log.Logger.Information("Starting {AppName} v{Version}", Consts.AppName, Consts.Version); 32 | InitializeComponent(); 33 | messageLabel.Width = messagesPanel.Width - 6; 34 | 35 | // Show our welcome message 36 | ShowMessage($"Welcome to {Consts.AppName} v{Consts.Version}!"); 37 | 38 | _sc2ccm = new SC2CCM(ShowMessage); 39 | 40 | // Create our campaign list 41 | Campaigns = new List() 42 | { 43 | new Campaign(_sc2ccm, CampaignType.WingsOfLiberty), 44 | new Campaign(_sc2ccm, CampaignType.HeartOfTheSwarm), 45 | new Campaign(_sc2ccm, CampaignType.LegacyOfTheVoid), 46 | new Campaign(_sc2ccm, CampaignType.NovaCovertOps) 47 | }.ToDictionary(c => c.Type); 48 | 49 | // Create our UI list 50 | _campaignUi = new Dictionary 51 | { 52 | { CampaignType.WingsOfLiberty, new CampaignUiElements(wolEnabledCheckbox, wolCampaignSelect, wolTitle, wolAuthorLabel, wolDescription, wolVersion) }, 53 | { CampaignType.HeartOfTheSwarm, new CampaignUiElements(hosEnabledCheckbox, hosCampaignSelect, hosTitle, hosAuthorLabel, hosDescriptionLabel, hosVersionLabel) }, 54 | { CampaignType.LegacyOfTheVoid, new CampaignUiElements(lotvEnabledCheckbox, lotvCampaignSelect, lotvTitle, lotvAuthorLabel, lotvDescriptionLabel, lotvVersionLabel) }, 55 | { CampaignType.NovaCovertOps, new CampaignUiElements(ncoEnabledCheckbox, ncoCampaignSelect, ncoTitle, ncoAuthorLabel, ncoDescriptionLabel, ncoVersionLabel) } 56 | }; 57 | 58 | 59 | // Setup our per-campaign data 60 | foreach (var campaignType in Campaigns.Keys) 61 | { 62 | // Setup our switch error handlers 63 | _campaignUi[campaignType].ModsEnabledCheckBox.CheckedChanged += (object? sender, EventArgs e) => 64 | { 65 | if (sender is not CheckBox checkbox) 66 | { 67 | return; 68 | } 69 | var campaign = Campaigns[campaignType]; 70 | if (checkbox.Checked != campaign.ModsEnabled) 71 | { 72 | if (checkbox.Checked) 73 | { 74 | campaign.EnableMods(); 75 | } 76 | else 77 | { 78 | campaign.DisableMods(); 79 | } 80 | } 81 | // When we do a toggle, we only want to do a quick sync 82 | // Full syncs set input field values, which will cause an infinite loop 83 | // Quick syncs only update output UI fields 84 | QuickUiSync(campaignType); 85 | }; 86 | 87 | var campaign = Campaigns[campaignType]; 88 | 89 | // Install the mod our config remembers, or reset to vanilla 90 | if (campaign.ModsEnabled && campaign.ActiveMod != null) 91 | { 92 | _sc2ccm.InstallMod(campaign.ActiveMod); 93 | } 94 | else 95 | { 96 | _sc2ccm.Reset(campaignType); 97 | } 98 | } 99 | 100 | installLocationButton.Visible = !_sc2ccm.GoodState(); 101 | this.Shown += (object? sender, EventArgs e) => UiRefresh(); 102 | } 103 | 104 | 105 | /// 106 | /// Sync output UI item fields for a campaign (labels, descriptions, etc) 107 | /// 108 | /// 109 | private void QuickUiSync(CampaignType campaignType) 110 | { 111 | var ui = _campaignUi[campaignType]; 112 | var campaign = Campaigns[campaignType]; 113 | ui.CampaignNameLabel.Text = campaign.Name; 114 | ui.AuthorLabel.Text = campaign.ActiveModAuthor; 115 | ui.DescriptionLabel.Text = campaign.ActiveModDescription; 116 | ui.ModsEnabledCheckBox.Enabled = _sc2ccm.GoodState(); 117 | ui.ModPicker.Enabled = campaign.ModsEnabled && _sc2ccm.GoodState(); 118 | ui.VersionLabel.Text = campaign.ActiveModVersion; 119 | importButton.Enabled = _sc2ccm.GoodState(); 120 | } 121 | 122 | /// 123 | /// Syncs both input and output fields for a campaign. 124 | /// DO NOT CALL FROM CAMPAIGN FIELD INPUT HANDLERS! DOING SO CAN CAUSE INFINITE LOOPS! 125 | /// 126 | /// 127 | private void FullUiSync(CampaignType campaignType) 128 | { 129 | var ui = _campaignUi[campaignType]; 130 | var campaign = Campaigns[campaignType]; 131 | ui.ModsEnabledCheckBox.Checked = campaign.ModsEnabled; 132 | 133 | // Temporarily unbind to allow us to change it without triggering too many events 134 | // We also want to avoid changing the active mod when we null out the selected item 135 | ui.ModPicker.SelectedIndexChanged -= ModPickerOnSelectedIndexChanged; 136 | 137 | // Unbind selected item first; without this step changing the source will cause an index-out-of-bounds error 138 | ui.ModPicker.SelectedItem = null; 139 | 140 | // Change the source 141 | ui.ModPicker.DataSource = campaign.ModOptions.ToList(); 142 | 143 | // Now set the active mod option 144 | ui.ModPicker.SelectedItem = campaign.ActiveModOption; 145 | 146 | // Rebind our event handler 147 | ui.ModPicker.SelectedIndexChanged += ModPickerOnSelectedIndexChanged; 148 | 149 | // Sync our output UI 150 | QuickUiSync(campaignType); 151 | } 152 | 153 | /// 154 | /// Does a full UI refresh 155 | /// DO NOT CALL FROM CAMPAIGN FIELD INPUT HANDLERS! DOING SO CAN CAUSE INFINITE LOOPS! 156 | /// 157 | private void UiRefresh() 158 | { 159 | installLocationButton.Visible = !_sc2ccm.GoodState(); 160 | importButton.Enabled = _sc2ccm.GoodState(); 161 | foreach (var campaignType in Campaigns.Keys) 162 | { 163 | FullUiSync(campaignType); 164 | } 165 | } 166 | 167 | /// 168 | /// Mod selection picker handler change event. 169 | /// Since we use ModOption for our option items, we can use the same handler for all pickers 170 | /// 171 | /// 172 | /// 173 | private void ModPickerOnSelectedIndexChanged(object? sender, EventArgs e) 174 | { 175 | if (sender is not ComboBox picker) 176 | { 177 | return; 178 | } 179 | 180 | if (picker.SelectedItem is not Campaign.ModOption option) 181 | { 182 | return; 183 | } 184 | 185 | Campaigns[option.CampaignType].SelectOption(option); 186 | QuickUiSync(option.CampaignType); 187 | } 188 | 189 | /// 190 | /// Shows a message to a user 191 | /// 192 | /// 193 | private void ShowMessage(string obj) 194 | { 195 | Log.Logger.Verbose("Showed message: {Message}", obj); 196 | messageLabel.Text = obj + "\n" + messageLabel.Text; 197 | } 198 | 199 | /// 200 | /// Shows a file selection dialog to the user 201 | /// 202 | /// 203 | /// 204 | private static String? PromptUserToPickCampaign() 205 | { 206 | try 207 | { 208 | using (var dialog = new OpenFileDialog()) 209 | { 210 | dialog.InitialDirectory = KnownFolders.Downloads.Path; 211 | dialog.Filter = "zip files (*.zip)|*.zip|All files (*.*)|*.*"; 212 | dialog.FilterIndex = 0; 213 | dialog.RestoreDirectory = true; 214 | 215 | if (dialog.ShowDialog() == DialogResult.OK) 216 | { 217 | return dialog.FileName; 218 | } 219 | } 220 | } 221 | catch (Exception) 222 | {} 223 | 224 | // The user canceled or something went wrong 225 | return null; 226 | } 227 | 228 | private void importButton_Click(object sender, EventArgs e) 229 | { 230 | var res = PromptUserToPickCampaign(); 231 | if (res != null) 232 | { 233 | importButton.Enabled = false; 234 | ShowMessage($"Importing {res}..."); 235 | _sc2ccm.Import(res); 236 | UiRefresh(); 237 | } 238 | } 239 | 240 | private void aboutButton_Click(object sender, EventArgs e) 241 | { 242 | var aboutBox = new AboutBox(); 243 | aboutBox.ShowDialog(); 244 | } 245 | 246 | private void installLocationButton_Click(object sender, EventArgs e) 247 | { 248 | installLocationButton.Enabled = false; 249 | try 250 | { 251 | using (var dialog = new OpenFileDialog()) 252 | { 253 | dialog.InitialDirectory = KnownFolders.Downloads.Path; 254 | dialog.Filter = "exe files (*.exe)|*.exe|All files (*.*)|*.*"; 255 | dialog.FilterIndex = 0; 256 | dialog.RestoreDirectory = true; 257 | 258 | if (dialog.ShowDialog() == DialogResult.OK) 259 | { 260 | _sc2ccm.SubmitStarCraft2Location(dialog.FileName); 261 | UiRefresh(); 262 | } 263 | } 264 | } 265 | finally 266 | { 267 | installLocationButton.Enabled = true; 268 | } 269 | } 270 | } 271 | } -------------------------------------------------------------------------------- /SC2_CCM_WinForm/MainPage.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 | 62 | 63 | AAABAAEALCwAAAEAIADIHwAAFgAAACgAAAAsAAAAWAAAAAEAIAAAAAAAQB4AABMLAAATCwAAAAAAAAAA 64 | AADMEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 65 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 66 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 67 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 68 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 69 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 70 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 71 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 72 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 73 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 74 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 75 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 76 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 77 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 78 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 79 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 80 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 81 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 82 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 83 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 84 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 85 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 86 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 87 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 88 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 89 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 90 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 91 | EP/MEBD/zRQU/9U5Of/NFxf/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wSEv/UNjb/zhoa/8wQ 92 | EP/MEBD/zBAQ/8wQEP/OGRn/zx4e/8wQEP/MEBD/zRcX/88gIP/MEBD/zBAQ/80UFP/QIiL/zBIS/8wQ 93 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/0zEx//C6 94 | uv/88/P//ff3//zy8v/tqqr/1Tk5/8wQEP/MEBD/zBAQ/9AjI//usbH/++3t//339//99/f/77Oz/9hH 95 | R//MEBD/zBAQ/+WHh//0y8v/zBAQ/8wQEP/mjIz///39/9EmJv/MEBD/10JC///////QIiL/zBAQ/8wQ 96 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/88cHP/219f/8b29/9U7 97 | O//MEhL/2EZG//G8vP/10tL/zRQU/8wQEP/NFRX/8sLC//XQ0P/XRET/zRMT/9Y9Pf/tq6v/+ubm/88c 98 | HP/MEBD/5YeH//TLy//MEBD/zBER//jd3f/54eH/4nZ2/8wQEP/XQkL//////9AiIv/MEBD/zBAQ/8wQ 99 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/6JOT//je3v/PHx//zBAQ/8wQ 100 | EP/MEBD/0Sgo//329v/idnb/zBAQ/+J3d//77u7/0isr/8wQEP/MEBD/zBAQ/84aGv/66Oj/6JKS/8wQ 101 | EP/lh4f/9MvL/8wQEP/WPz///fb2/9xaWv/0ysr/zBAQ/9dCQv//////0CIi/8wQEP/MEBD/zBAQ/8wQ 102 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/0y8v/6JOT/8wQEP/MEBD/zBAQ/8wQ 103 | EP/MEBD/2U5O/9Q3N//MEBD/7q6u/+6vr//MEBD/zBAQ/8wQEP/MEBD/zBAQ/9dERP/WQUH/zBAQ/+WH 104 | h//0y8v/zBAQ/+eQkP/urq7/zRUV//vr6//SLi7/10JC///////QIiL/zBAQ/8wQEP/MEBD/zBAQ/8wQ 105 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zRQU//339//fa2v/zBAQ/8wQEP/MEBD/zBAQ/8wQ 106 | EP/MEBD/zBAQ/8wQEP/43t7/5YeH/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/5YeH//TL 107 | y//MERH/+eHh/91eXv/MEBD/6p6e/+SAgP/XQkL//////9AiIv/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 108 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/NFBT//fb2/99qav/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 109 | EP/MEBD/zBAQ//jc3P/lhob/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/lh4f/9MvL/9dE 110 | RP/99fX/zhkZ/8wQEP/ZS0v/9tPT/9dCQv//////0CIi/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 111 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/yxMT/6JGR/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zRcX/8wQ 112 | EP/MEBD/7Kio/+6trf/MEBD/zBAQ/8wQEP/MEBD/zBAQ/80XF//MEBD/zBAQ/+WHh//0y8v/6JWV/+6t 113 | rf/MEBD/zBAQ/80TE//65+f/32ho///////QIiL/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 114 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/+aLi//76+v/0Scn/8wQEP/MEBD/zBAQ/9IsLP/88/P/3WBg/8wQ 115 | EP/gb2///ff3/9U4OP/MEBD/zBAQ/8wQEP/PHh7/+ufn/+N6ev/MEBD/5YeH//TNzf/65ub/3F1d/8wQ 116 | EP/MEBD/zBAQ/+mWlv/xvLz//////9AiIv/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 117 | EP/MEBD/zBAQ/8wQEP/MEBD/zhcX//G/v//0zMz/3WBg/9AlJf/aUFD/88fH//PGxv/MEhL/zBAQ/8wS 118 | Ev/sqKj/99ra/99oaP/RJyf/2EdH/++2tv/329v/zhgY/8wQEP/lh4f//ff3//319f/OGBj/zBAQ/8wQ 119 | EP/MEBD/10RE//78/P//////0CIi/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 120 | EP/MEBD/zBAQ/8wQEP/MEBD/zhoa/+mZmf/32tr///////nh4f/pmZn/0i0t/8wQEP/MEBD/zBAQ/80U 121 | FP/mi4v/9dLS///+/v/66en/66Gh/9U5Of/MEBD/zBAQ/+SCgv/99vb/7Kio/8wQEP/MEBD/zBAQ/8wQ 122 | EP/MERH/99ra//329v/QIiL/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 123 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/PHh7/zBER/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 124 | EP/MEBD/zx0d/8wSEv/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 125 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 126 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 127 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 128 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 129 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 130 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 131 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 132 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 133 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 134 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 135 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 136 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 137 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBER/9lO 138 | Tv/jfHz/7Kam/+mamv/faWn/0CIi/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zRUV/9dFRf/kgYH/7ays/+eP 139 | j//XRET/zBAQ/8wQEP/MEBD/3FlZ/+WHh//lh4f/5YeH/+WHh//lh4f/5YeH/+WHh//OGRn/zBAQ/8wQ 140 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/80XF//spKT///////zz 141 | 8//zx8f/9dLS//74+P/54eH/1Tk5/8wQEP/MEBD/zBAQ/9ItLf/76ur///////fZ2f/xv7///vv7//// 142 | ///qnp7/zBER/8wQEP/idnb///////zv7//55OT/+eTk//nk5P/55OT/+eTk/88gIP/MEBD/zBAQ/8wQ 143 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/54+P///9/f/jfn7/zBIS/8wQ 144 | EP/MEBD/0ioq//XPz//32Nj/zBAQ/8wQEP/OGRn/9dLS//rp6f/UNDT/zBAQ/8wQEP/QIiL/6pyc//// 145 | ///jfn7/zBAQ/80XF//329v/9c/P/88cHP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 146 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/329v/77Oz/8wQEP/MEBD/zBAQ/8wQ 147 | EP/MEBD/32dn///////SLS3/zBAQ/+aKiv/++/v/10VF/8wQEP/MEBD/zBAQ/8wQEP/OGhr/+ujo//rp 148 | 6f/NFhb/zBAQ/9Q3N//66Oj/9dHR/9EmJv/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 149 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zRMT/9xaWv/VODj/zBAQ/8wQEP/MEBD/zBAQ/8wS 150 | Ev/spKT//vn5/84bG//MEBD/8b+///XS0v/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/bVFT/4G5u/84a 151 | Gv/MEBD/zBAQ/9MxMf/54eH/+eHh/9MyMv/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 152 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBER/9Q3N//ieXn/9MzM//// 153 | ///qnZ3/zBAQ/8wREf/87+//7aqq/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 154 | EP/MEBD/zBAQ/9IqKv/329v/+unp/9U7O//MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 155 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/0CIi/+ePj//43Nz////////////439//4nd3/8wR 156 | Ef/MEBD/zx4e///////nj4//zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 157 | EP/MEBD/zBAQ/9EmJv/21tb/++7u/9EoKP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 158 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/9EnJ//329v///////bT0//jfn7/1DU1/8wREf/MEBD/zBAQ/8wQ 159 | EP/MEBD/+ePj/+6trf/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 160 | EP/MEBD/zBAQ/9AkJP/99vb/7q+v/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 161 | EP/MEBD/zBAQ/8wQEP/MEBD/43t7///////ZT0//zBAQ/8wQEP/MEBD/zBAQ/9MwMP/SLi7/zBAQ/8wQ 162 | EP/urq7/99ra/8wSEv/MEBD/zBAQ/8wQEP/MEBD/zBAQ/9dDQ//TMjL/zBER/88fH//YSUn/zhsb/8wQ 163 | EP/MEBD/zBAQ/+ylpf/99PT/zRMT/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 164 | EP/MEBD/zBAQ/8wQEP/njY3//ff3/88eHv/MEBD/zBAQ/8wQEP/OGhr/++zs/++0tP/MEBD/zBAQ/+Bt 165 | bf//////43x8/8wQEP/MEBD/zBAQ/8wQEP/SLCz//fX1//TLy//MERH/10RE///////gbm7/zBAQ/8wQ 166 | EP/MEBD/7Kam//319f/MEhL/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 167 | EP/MEBD/zBAQ/9dDQ///////7a2t/9hKSv/PHBz/2UtL/+2qqv//////3mJi/8wQEP/MEBD/zBER/+mY 168 | mP/++/v/5YSE/9Y+Pv/OGBj/2lNT//PIyP/+/Pz/2U9P/8wQEP/NFRX/99nZ//fa2v/bVFT/zx8f/99p 169 | af/++Pj/7Kam/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 170 | EP/MEBD/zBAQ/91eXv/99PT//////////////////vj4/+BsbP/MEBD/zBAQ/8wQEP/MEBD/zBER/+eP 171 | j//76ur////////9/f//////+ubm/9xcXP/MEBD/zBAQ/8wQEP/UNTX/9MvL/////////////////+2s 172 | rP/PHh7/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 173 | EP/MEBD/zBAQ/84YGP/WQUH/3WJi/9ZAQP/OGBj/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wR 174 | Ef/UNjb/3WJi/9hISP/OFxf/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MERH/0Sgo/9Y/P//PICD/zBAQ/8wQ 175 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 176 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 177 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 178 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 179 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 180 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 181 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 182 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 183 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 184 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 185 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 186 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 187 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 188 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 189 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 190 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 191 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 192 | EP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQEP/MEBD/zBAQ/8wQ 193 | EP/MEBD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 194 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 195 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 196 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 197 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 198 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== 199 | 200 | 201 | -------------------------------------------------------------------------------- /SC2_CCM_WinForm/Program.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Events; 2 | using Serilog; 3 | using Log = SC2_CCM_Common.Log; 4 | 5 | namespace SC2_CCM_WinForm 6 | { 7 | internal static class Program 8 | { 9 | /// 10 | /// The main entry point for the application. 11 | /// 12 | [STAThread] 13 | static void Main() 14 | { 15 | // Select our log file 16 | var logFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), 17 | Path.Combine("SC2CCM", "SC2CCM.log")); 18 | 19 | // Rotate away the old log file (keep a history of 1) 20 | try 21 | { 22 | if (File.Exists(logFile)) 23 | { 24 | var oldLogFile = $"{logFile}.old"; 25 | if (File.Exists(oldLogFile)) 26 | { 27 | File.Delete(oldLogFile); 28 | } 29 | File.Move(logFile, oldLogFile); 30 | } 31 | } 32 | catch (Exception e) 33 | { 34 | Console.WriteLine("Failed to rotate logs! " + e.Message); 35 | } 36 | 37 | // Initialize our logger 38 | Log.Logger = new LoggerConfiguration() 39 | .WriteTo.File( 40 | logFile, 41 | fileSizeLimitBytes: 2 * 1024 * 1024, // 2MB 42 | restrictedToMinimumLevel: LogEventLevel.Debug 43 | ) 44 | .WriteTo.Console(restrictedToMinimumLevel: LogEventLevel.Verbose) 45 | .CreateLogger(); 46 | Console.WriteLine($"Log file {logFile}"); 47 | 48 | try 49 | { 50 | // To customize application configuration such as set high DPI settings or default font, 51 | // see https://aka.ms/applicationconfiguration. 52 | ApplicationConfiguration.Initialize(); 53 | Application.Run(new MainPage()); 54 | } 55 | catch (Exception ex) 56 | { 57 | // Make sure we log program crashes 58 | Log.Logger.Fatal(ex, "Program Crash Detected!"); 59 | } 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /SC2_CCM_WinForm/SC2_CCM_WinForm.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net6.0-windows 6 | enable 7 | true 8 | enable 9 | SC2 Custom Campaign Manager 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /SC2_CCM_WinForm/appicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewtolman/sc2-custom-mod-manager/1f93b7c8a21fa22717f5b8e1cf8066f90cc6d06f/SC2_CCM_WinForm/appicon.ico -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "6.0.0", 4 | "rollForward": "latestMajor", 5 | "allowPrerelease": true 6 | } 7 | } -------------------------------------------------------------------------------- /package-app-file.sh: -------------------------------------------------------------------------------- 1 | #rm -rf out/avalon && dotnet restore -r osx-x64 && dotnet publish SC2_Avalonia_UI -c:Release -o out/avalon --sc --nologo -r:osx-x64 -p:PublishSingleFile=true 2 | 3 | rm -rf out/mac-app 4 | mkdir -p out/mac-app/SC2\ CCM\ Avalon\ Edition/SC2\ CCM\ Avalon\ Edition.app/Contents/Frameworks 5 | mkdir -p out/mac-app/SC2\ CCM\ Avalon\ Edition/SC2\ CCM\ Avalon\ Edition.app/Contents/MacOS 6 | cp -R -f out/avalon/* out/mac-app/SC2\ CCM\ Avalon\ Edition/SC2\ CCM\ Avalon\ Edition.app/Contents/MacOS 7 | cp -R -f out/avalon/*.dylib out/mac-app/SC2\ CCM\ Avalon\ Edition/SC2\ CCM\ Avalon\ Edition.app/Contents/Frameworks 8 | cd "out/mac-app/" 9 | 10 | APP_ENTITLEMENTS="Sc2 CCM Avalon EditionEntitlements.entitlements" 11 | APP_SIGNING_IDENTITY="3rd Party Mac Developer Application: [***]" 12 | INSTALLER_SIGNING_IDENTITY="3rd Party Mac Developer Installer: [***]" 13 | APP_NAME="SC2 CCM Avalon Edition/SC2 CCM Avalon Edition.app" 14 | 15 | productbuild --component SC2\ CCM\ Avalon\ Edition/SC2\ CCM\ Avalon\ Edition.app /Applicat ions --sign "$INSTALLER_SIGNING_IDENTITY" SC2\ CCM\ Avalon\ Edition.pkg --------------------------------------------------------------------------------