├── .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 |
15 |
--------------------------------------------------------------------------------
/SC2 Custom Campaign Manager/Resources/AppIcon/appicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
--------------------------------------------------------------------------------
/SC2 Custom Campaign Manager/Resources/AppIcon/appiconfg.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/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 |
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 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------