Compare commits

..

184 Commits

Author SHA1 Message Date
Matt Nadareski
fc3ef36fef Attempt to add CD to existing actions 2024-02-23 12:36:55 -05:00
Matt Nadareski
6298487346 Fix link in README 2024-02-23 11:26:45 -05:00
Matt Nadareski
727d9844d5 Fix whitespace that got unwhitespaced 2024-02-23 11:18:36 -05:00
Matt Nadareski
72e7619e2d Remove net35 from MPF... again 2024-02-23 11:17:44 -05:00
Matt Nadareski
24b4647037 Remove now-unnecessary restore step 2024-02-23 11:16:44 -05:00
Matt Nadareski
713b3f0557 Fix net35 build issue 2024-02-23 11:11:48 -05:00
Matt Nadareski
f796a9b131 More tweaks to CI 2024-02-23 10:59:40 -05:00
Matt Nadareski
2cdf92bf92 Rename badges for GHA 2024-02-23 10:26:27 -05:00
Matt Nadareski
ccc1687f1a Add GHA CI status badges 2024-02-23 10:23:18 -05:00
Matt Nadareski
6057ec3a59 Split CI workflow files 2024-02-23 10:20:30 -05:00
Matt Nadareski
2a5e736285 Reorganize solution items 2024-02-23 10:11:39 -05:00
Deterous
010ef9016b Add CI via GitHub Workflows (#657)
* Create ci.yml

* Update ci.yml

* Rename to master

* Update CHANGELIST.md
2024-02-23 07:08:59 -08:00
Deterous
02606318b0 Opt-in automatic IRD creation after PS3 dump (#655)
* Opt-in automatic IRD creation after PS3 dump

* Add tabs before an endregion

* Prevent double checking for existing files

* Spin off IRD creation into new thread
2024-02-22 19:55:10 -08:00
Matt Nadareski
d4f641b122 Update README with current build instructions 2024-02-21 19:28:11 -05:00
Matt Nadareski
a1dd6e2d21 Fix misattributed artifact 2024-02-21 11:14:56 -05:00
Matt Nadareski
d35679d688 Make AppVeyor builds framework-dependent 2024-02-21 11:00:18 -05:00
Matt Nadareski
83f5083ce7 Add x86 builds to AppVeyor 2024-02-21 10:59:30 -05:00
Matt Nadareski
5b6457f4b7 Remove DIC and Aaru bundles from CI 2024-02-21 10:51:05 -05:00
Deterous
c6517d526b Hide unavailable dumping programs (#654) 2024-02-21 07:14:39 -08:00
Matt Nadareski
e35f1fc2ec Readd x86 builds by default 2024-02-21 00:52:40 -05:00
Matt Nadareski
14f4128d4a Fix double git hash version (feat. Deterous) 2024-02-21 00:03:39 -05:00
Matt Nadareski
5465252dc7 Port build script fixes from BOS 2024-02-20 23:16:22 -05:00
Matt Nadareski
2573b47a79 Remove debugging lines from build script 2024-02-20 20:52:49 -05:00
Matt Nadareski
fe20905524 Bump version 2024-02-20 10:34:48 -05:00
Deterous
88f19180a4 Update LibIRD, disable UI elements when creating IRD (#653)
* Update LibIRD, disable UI when creating IRD

* Update changelog
2024-02-20 07:25:03 -08:00
Deterous
de89968a1d Add a GUI for PS3 IRD Creation (#647)
* Create a non-functional Create IRD window

* Add PIC and getkey.log options for IRDs

* Disable IRD creation window for unsupport .NET versions

* Finalise UI and parse inputs

* Add LibIRD package

* Manually define PIC in IRD creation

* Better output file browser

* Bump LibIRD version

* Update changelog

* Custom Disc ID, bump LibIRD version

* Ignore custom Disc ID for BD-50

* Provide a status message when creating IRD

* Better logpath enabled logic

* Nicer PIC UX

* Scrollbar only appears for unusually tall PIC
2024-02-18 17:38:28 -08:00
Matt Nadareski
8fc53c91b0 Limit DVD protection outputs (fixes #651) 2024-02-17 23:23:00 -05:00
Matt Nadareski
1a1fbd4b40 Fix Aaru drive parameter generation (fixes #652) 2024-02-17 23:09:29 -05:00
Matt Nadareski
cac6c3049b Add funworld Photo Play detection (fixes #650) 2024-02-15 12:56:30 -05:00
Matt Nadareski
6a6871e922 Write outputs with UTF-8 2024-02-15 12:00:40 -05:00
Deterous
4a02a3efac Fix DIC log parsing for SS version (#649) 2024-02-12 08:30:50 -08:00
Matt Nadareski
f6eb961af4 Make Redumper the default for new users (fixes #638) 2024-02-06 12:57:26 -05:00
Matt Nadareski
faeaaef02a Remove .NET 6 from auto-builds (fixes #646) 2024-02-06 12:50:01 -05:00
Matt Nadareski
ebf393e634 Bump version and copyright 2024-02-06 11:20:57 -05:00
Deterous
3fbd4ea719 Fix compiler warning (#645)
* Fix compiler warning about PresentationFramework.Aero2

* Update changelog
2024-02-06 07:53:45 -08:00
Matt Nadareski
d09ff6cf1c Enable Windows builds on Linux and Mac 2024-02-05 00:41:11 -05:00
Matt Nadareski
1dc0d57d47 Remove -disc2 from Cleanrip serial (fixes #644) 2024-02-03 19:39:30 -05:00
Matt Nadareski
a748bd4d3a Fix build from rushed code 2024-02-01 11:11:13 -05:00
Matt Nadareski
35dec7fe57 Retrieve serial from Cleanrip (fixes #643) 2024-02-01 11:05:42 -05:00
Deterous
c22d16349a Verbose Redumper log by default (#642) 2024-01-30 19:12:30 -08:00
Deterous
0d77a8950c Exclude extra tracks when finding disc matches (#641) 2024-01-30 19:08:41 -08:00
Deterous
285e94ca69 Parse PSX/PS2/KP2 exe date from logs (#639)
* Parse EXE date from Redumper

* Parse EXE date from DIC logs

* Fix DIC exe date parsing

* Split PS EXE name from EXE info

* Remove redundant path splitting
2024-01-30 19:00:08 -08:00
Matt Nadareski
747ac4ea3b Detect Photo CD 2024-01-25 20:53:21 -05:00
Matt Nadareski
405ae7c7e4 Add UMD handling for the disc info window 2024-01-24 22:56:50 -05:00
Matt Nadareski
f5ebe968c0 Fix information pulling for CleanRip and UIC (fixes #637) 2024-01-24 22:50:16 -05:00
Deterous
06a61b17cb Add a GUI for MPF.Check (#635)
* Draft MPF.Check GUI window

* Functional MPF.Check GUI

* Update changelog

* Show DiscInformationWindow after Check Dump

* Change layout

* Refactor
2024-01-24 11:57:21 -08:00
Matt Nadareski
9e8e4f6e36 Skip warning line in Redumper log 2024-01-24 10:17:58 -05:00
Deterous
fa72211b57 Normalize Disc Title in Submission Info (#634)
* NormalizeDiscTitle for SubmissionInfo

* Update changelog
2024-01-19 09:53:52 -08:00
Deterous
d5f66000a9 Differentiate CD32 from CDTV (#633) 2024-01-18 20:28:23 -08:00
Deterous
a52ba0aa7a Detect CDTV discs (#632) 2024-01-18 20:00:45 -08:00
Deterous
eb045928f9 Prevent crashing on invalid parameters (#631)
* Prevent crashing on invalid parameters

* Parse hex strings properly

* Helper function for hex numbers

* remove region label
2024-01-18 19:16:09 -08:00
fuzzball
440302495b Correct missing space in PVD (#628)
* Remove trim

* Update changelist
2024-01-16 05:54:56 -08:00
Deterous
0732e9db78 Retrieve volume label from logs (#627)
* Retrieve volume label from DIC and redumper logs

* Fix logic

* Remove unnecessary using

* Update changelog

* Sanitise label somewhat

* Refactor
2024-01-15 19:14:34 -08:00
Deterous
a167652b2b Check for presence of complete dump from other programs (#625)
* Check for presence of complete dump from other programs

* Better changelog message

* Refactor
2024-01-10 19:24:17 -08:00
Deterous
cfa07c1918 Allow variables in output path (#624)
* Allow variables in output path and default output path

* Remove angle brackets when normalising path

* Add tooltip hover text for default output path

* Better tooltip formatting

* Use percent sign rather than angle brackets as variable delimiter

* Refactor
2024-01-10 16:42:34 -08:00
Deterous
53b31f91cf Use PSX/PS2 serial as filename when Volume Label not present (#623)
* Use PSX/PS2 serial as volume label if none found

* Refactor
2024-01-09 14:02:17 -08:00
Deterous
01cbd2cff5 Update Redumper to build 311 (#622)
* Update redumper to build 311

* Update changelog

* Deal with new redumper parameters properly
2024-01-08 17:45:08 -08:00
Deterous
65ad629ee0 Cleanup !protectionInfo.txt (#621)
* Remove drive letter from !protectionInfo.txt

* Sort files in !protectionInfo.txt

* Add option for removing drive letter

* Refactor niceties
2024-01-08 17:20:30 -08:00
fuzzball
06adbde715 Support ringcode and PIC for triple/quad-layer (#620)
* Support ringcodes for triple/quad-layer

* Increase PIC length of triple/quad-layer for dic

* Increase PIC length of triple/quad-layer for redumper

* Change the method of determining PIC length

* Update change logs

* Remove braces from if statements
2024-01-08 13:53:48 -08:00
Deterous
1e5000bd8a Support redumper skeleton and hash files (#616)
* Add .hash/.skeleton files to log zip, update redumper to build 306, update SabreTools.Serialization

* Improvements to .skeleton/.hash code

* Revert redumper update, remove redudant string creation

* Deal with nullable strings

* Update changelog

* Invert if for readability
2024-01-05 20:06:46 -08:00
Deterous
8cb0b37e80 Get BD PIC Identifier for redumper (#619)
* Get PIC Identifier for bluray redumper

* Update changelog
2024-01-02 17:50:48 -08:00
Matt Nadareski
32c12e1332 Make missing hash data clearer 2023-12-14 20:27:38 -05:00
Matt Nadareski
09b307aa24 Fix commented out code 2023-12-07 23:22:12 -05:00
Matt Nadareski
a5a8fbbf51 Update Redumper to build 294 2023-12-07 21:38:38 -05:00
Matt Nadareski
b366d236c8 Update RedumpLib 2023-12-05 11:18:58 -05:00
Matt Nadareski
a833e926f3 Bump version 2023-12-04 12:10:19 -05:00
Matt Nadareski
950be07bf0 Handle or suppress some messages 2023-12-01 23:58:01 -05:00
Matt Nadareski
4c5c1417e9 Remove .NET Framework 3.5 from build script 2023-12-01 23:31:59 -05:00
Matt Nadareski
6fdc3412e0 Fix build warning for NRE 2023-12-01 23:28:45 -05:00
Matt Nadareski
807b0c5f9e Fix using SHA-1 for track checks (fixes #613) 2023-12-01 20:46:38 -05:00
Matt Nadareski
9e0b64a1d1 Fix broken tests 2023-12-01 20:39:36 -05:00
Matt Nadareski
8cfbf2d9f1 Bump version 2023-12-01 12:49:43 -05:00
Matt Nadareski
0064737130 Handle most VS and dotnet differences 2023-11-30 23:34:44 -05:00
Matt Nadareski
292e3999c5 Reference .NET Framework 3.0 for 3.5 2023-11-30 20:51:04 -05:00
Matt Nadareski
5ed1e94d84 Import WinFX for .NET Framework 3.5 2023-11-30 18:00:45 -05:00
Matt Nadareski
5b094f57cb Update USE_ALL in Powershell script 2023-11-30 13:43:20 -05:00
Matt Nadareski
8066b5541e Update Redumper to build 271 2023-11-30 13:39:32 -05:00
Matt Nadareski
921d0207c2 Fix cross-framework UI styles 2023-11-30 13:12:56 -05:00
Matt Nadareski
4374ff7f74 Fix most .NET Framework 3.5 issues 2023-11-30 12:58:06 -05:00
Matt Nadareski
0be5825b5e Fix cross-framework UI rendering 2023-11-30 11:54:40 -05:00
Matt Nadareski
14c630bea7 Fix Powershell build script 2023-11-30 03:19:39 -05:00
Matt Nadareski
9a66c685fd Replace build script with Powershell
Thanks to Deterous for the original script conversion.
2023-11-30 00:48:20 -05:00
Matt Nadareski
5e0fa1ad47 Add Disc ID and Key fields in info window (fixes #609) 2023-11-30 00:08:39 -05:00
Matt Nadareski
79065dcc69 Read CSS for some copy protections (fixes #608) 2023-11-29 23:57:58 -05:00
Matt Nadareski
3d7355aee1 Bump version 2023-11-29 23:36:57 -05:00
Matt Nadareski
6e9a6724c3 Update Redumper to build 268 2023-11-29 17:30:46 -05:00
Matt Nadareski
be35acfb48 Support .NET Framework 3.5 in UI 2023-11-23 03:37:00 -05:00
Matt Nadareski
f1a46c2e82 Get UI building with Framework 4.0 again 2023-11-23 03:05:52 -05:00
Matt Nadareski
872959c889 Temporarily remove .NET Framework 4.0 from UI 2023-11-23 02:32:57 -05:00
Matt Nadareski
b848a401f8 Get UI building with Framework 4.0 2023-11-23 01:19:17 -05:00
Matt Nadareski
ee4762f8b3 Support .NET Framework 3.5 in UI.Core 2023-11-22 23:50:37 -05:00
Matt Nadareski
d68bcfb96a Support .NET Framework 2.0
This does not include the UI, as per the same reasons why .NET Framework 4.0 doesn't support it.
2023-11-22 23:38:59 -05:00
Matt Nadareski
d2433e4749 Update changelog 2023-11-22 23:28:57 -05:00
Matt Nadareski
56ec0e7057 Update compatibility libraries 2023-11-22 23:28:24 -05:00
Matt Nadareski
26e5d33d17 Support .NET Framework 3.5
This does not include the UI, as per the same reasons why .NET Framework 4.0 doesn't support it.
2023-11-22 16:26:31 -05:00
Matt Nadareski
8b8b51ace4 Re-enable .NET Framework 4.0 building in Check 2023-11-22 16:02:08 -05:00
Matt Nadareski
f350904441 Re-enable .NET Framework 4.0 building in UI.Core 2023-11-22 16:00:18 -05:00
Matt Nadareski
8e8e3368d0 Re-enable .NET Framework 4.0 building in Core 2023-11-22 15:56:43 -05:00
Matt Nadareski
4d8153dba1 Update to BinaryObjectScanner 3.0.2 2023-11-22 13:46:39 -05:00
Matt Nadareski
e4e4b5ecde Temporarily remove .NET Framework 4.0 2023-11-21 11:13:58 -05:00
Matt Nadareski
8373a6b8f5 Support proper async in .NET Framework 4.0 2023-11-20 16:46:54 -05:00
Matt Nadareski
45c51ebc80 Use TryGetValue on dictionaries 2023-11-20 13:24:17 -05:00
Matt Nadareski
af27085cc1 Get Check building with Framework 4.0 2023-11-20 13:20:48 -05:00
Matt Nadareski
82e3707dce Get UI.Core building with Framework 4.0 2023-11-20 13:19:30 -05:00
Matt Nadareski
85192e8d3e Get Core building with Framework 4.0 2023-11-20 13:15:06 -05:00
Matt Nadareski
6f784a352e Update Xunit packages 2023-11-20 12:29:33 -05:00
Matt Nadareski
ee707cf1af Update to BinaryObjectScanner 3.0.1 2023-11-20 12:28:52 -05:00
Matt Nadareski
a14c998b3b More C# 12 cleanup in Core 2023-11-16 15:33:37 -05:00
Matt Nadareski
004208df6a Perform some post-move cleanup 2023-11-16 15:20:19 -05:00
Matt Nadareski
2f765146d1 Update RedumpLib and use moved methods 2023-11-16 12:45:47 -05:00
Matt Nadareski
a7d548f7ce Trim PS3 serial and add unrelated notes (fixes #607) 2023-11-16 12:18:07 -05:00
Matt Nadareski
fbdb9875f3 Add C#12 syntax to tests 2023-11-15 16:39:52 -05:00
Matt Nadareski
39a524e3cc Fix reversed ringcode test 2023-11-15 16:32:23 -05:00
Matt Nadareski
9740ca3a7a Suppress deprecation warnings 2023-11-15 16:29:47 -05:00
Matt Nadareski
f8d81972bf Prepare XAML for ancient .NET support 2023-11-15 12:10:54 -05:00
Matt Nadareski
fe9302a553 Perform more ancient .NET support work 2023-11-15 11:58:53 -05:00
Matt Nadareski
c0ed7a7a0e Fix TLS for older .NET 2023-11-15 00:18:52 -05:00
Matt Nadareski
b0b48743ac Support ancient .NET in UI 2023-11-15 00:08:43 -05:00
Matt Nadareski
47e79dab31 Support ancient .NET in Check 2023-11-14 23:53:15 -05:00
Matt Nadareski
90edc42fdf Support C# 12 syntax 2023-11-14 23:40:41 -05:00
Matt Nadareski
45db365705 Support ancient .NET in UI Core 2023-11-14 23:14:33 -05:00
Matt Nadareski
952828dddd Support ancient .NET in Core
This does not include .NET Framework 4.0 due to some issues with libraries.
2023-11-14 23:09:55 -05:00
Matt Nadareski
4a1e953ffd Fix BE flag logic bug in DIC (fixes #606) 2023-11-14 21:27:09 -05:00
Matt Nadareski
25740c2936 Zip manufacturer files for Redumper (fixes #604) 2023-11-14 21:19:52 -05:00
Matt Nadareski
3696257940 Add Bandai Pippin detection (fixes #600) 2023-11-14 21:16:47 -05:00
Matt Nadareski
4d46b7db5c Bump version 2023-11-14 16:39:19 -05:00
Matt Nadareski
21f5f29ac0 Update to .NET 8 (fixes #593) 2023-11-14 16:37:41 -05:00
Matt Nadareski
436e198826 Bump library versions 2023-11-14 16:32:32 -05:00
Matt Nadareski
91eb8c96c3 Update changelog 2023-11-08 21:03:27 -05:00
Matt Nadareski
edf0bcb4fe Update documentation for BOS change 2023-11-08 21:03:09 -05:00
Matt Nadareski
1526714ab9 Remove Unshield reference 2023-11-07 14:32:18 -05:00
Matt Nadareski
767e0bb05b Remove .NET Framework 4.8 from issue report 2023-11-07 12:03:45 -05:00
Matt Nadareski
52e781329c Make releases less verbose 2023-11-07 11:13:12 -05:00
Matt Nadareski
0c1395d3ec Remove lingering script reference 2023-11-07 00:23:55 -05:00
Matt Nadareski
791d9cdb7b Consolidate build script information 2023-11-07 00:22:54 -05:00
Matt Nadareski
7d7dc4ee4e Even more README cleanup 2023-11-07 00:18:54 -05:00
Matt Nadareski
97fd04b13a Remove 4.8 note from build script 2023-11-06 23:38:27 -05:00
Matt Nadareski
899db4c8c0 Remove redundant information 2023-11-06 23:15:27 -05:00
Matt Nadareski
bba204cbfe Update README for Legacy 2023-11-06 23:13:08 -05:00
Matt Nadareski
145660e9f9 Address lingering modern .NET syntax 2023-11-06 23:06:11 -05:00
Matt Nadareski
24de14aea5 Remove .NET Framework 4.8 gated code 2023-11-06 22:07:40 -05:00
Matt Nadareski
d57161e4d1 Remove .NET Framework 4.8 from projects 2023-11-06 21:00:07 -05:00
Matt Nadareski
a066a5234a Remove .NET Framework 4.8 from build 2023-11-06 20:57:03 -05:00
Matt Nadareski
229db5dda2 Bump version 2023-11-06 10:14:16 -05:00
Deterous
4f2a8d354a Update to MMI 3.0.0 (#603) 2023-11-06 07:10:25 -08:00
Matt Nadareski
b886471d72 Update changelog 2023-11-05 19:03:52 -05:00
Deterous
2bab266ae2 Enable browsing for Redumper path (#602) 2023-11-05 15:54:09 -08:00
Matt Nadareski
6c5fd9bac8 Fix default layerbreak output (fixes #601) 2023-11-05 11:39:56 -05:00
Matt Nadareski
08e93d7f13 Update changelog 2023-11-03 23:35:41 -04:00
Deterous
7a510e084b Pull PS3 Firmware Version (#599)
* Attempt to read PS3 firmware version

* Improve PS3 Firmware Version parsing

* Enable PS3 Firmware Version detection for all 3 Programs

* Big endian byte location

* Contents field not Comments field
2023-11-03 20:35:24 -07:00
Matt Nadareski
da46d20ffc Fix PS3 version finding 2023-11-03 22:41:22 -04:00
Matt Nadareski
234c0bfbab Try to get PS3 data from SFB 2023-11-03 15:34:05 -04:00
Matt Nadareski
82d60dbf4a Update changelog 2023-11-03 11:23:28 -04:00
Deterous
6dffb80609 Focus main window after child window closes (#596) 2023-11-03 08:23:11 -07:00
Matt Nadareski
267c0e3184 Update Redumper to build 247 2023-11-01 13:07:36 -04:00
Matt Nadareski
033ccbbe67 Update changelog 2023-10-31 19:59:38 -04:00
Deterous
c31b3f5894 Remove psxt001z Pkg Ref in MPF.Test (#592) 2023-10-31 16:58:43 -07:00
Matt Nadareski
9b1a303fea Bump version 2023-10-31 14:21:46 -04:00
Matt Nadareski
80a0f6da35 Attempt to fix window owner issue 2023-10-30 21:59:30 -04:00
Matt Nadareski
0c30eb7a60 Enable HD-DVD for Redumper in UI 2023-10-30 21:55:03 -04:00
Matt Nadareski
7a8125bb71 Update Redumper to build 246 2023-10-30 21:52:34 -04:00
Matt Nadareski
c4beffd845 Add PIC output for Redumper 2023-10-30 21:43:43 -04:00
Matt Nadareski
f97c112a53 Remove duplicate check 2023-10-30 18:44:08 -04:00
Matt Nadareski
5ef43ab6be Fix build 2023-10-30 18:40:17 -04:00
Matt Nadareski
2c399f99bf Update changelog 2023-10-30 18:38:03 -04:00
Deterous
42e9eb0b96 Fix typo in ProgramSupportsMeida (#591) 2023-10-30 15:36:51 -07:00
Matt Nadareski
e67d65f908 Remove .manufacturer for Bluray 2023-10-30 18:32:58 -04:00
Matt Nadareski
4ec8954b14 Handle a couple of messages 2023-10-30 16:42:15 -04:00
Matt Nadareski
1a6abb039c Compile most regex statements 2023-10-30 16:31:50 -04:00
Matt Nadareski
bb5d1e5ac8 Remove now-obsolete notes 2023-10-30 13:07:29 -04:00
Matt Nadareski
03c4c475eb Add Bluray layerbreak support for Redumper 2023-10-30 12:56:58 -04:00
Matt Nadareski
04d7817d28 Enable Bluray dumping with Redumper 2023-10-30 12:45:19 -04:00
Matt Nadareski
7cd100bc53 Update Redumper to build 244 2023-10-30 12:09:38 -04:00
Matt Nadareski
019924232a Remove and Sort Usings 2023-10-30 01:55:56 -04:00
Matt Nadareski
b5fc9f0275 Ignore empty protection results (fixes #590) 2023-10-30 01:05:15 -04:00
Matt Nadareski
44509b72ed Fix whitespace 2023-10-26 12:37:36 -04:00
Matt Nadareski
d532a63dbd Version-gate new switch statement 2023-10-26 12:36:44 -04:00
Matt Nadareski
227785b079 Remove unncessary summary 2023-10-26 12:33:28 -04:00
Matt Nadareski
0e364be998 Separate out static hashing 2023-10-26 12:16:42 -04:00
Matt Nadareski
7ae1f64ee3 Move all hashing to Hasher 2023-10-26 11:56:38 -04:00
Matt Nadareski
92463a103d Make Hasher more consistent 2023-10-26 11:54:13 -04:00
Matt Nadareski
101cdb7b34 Avoid unncessary allocation in hashing 2023-10-26 11:42:36 -04:00
Matt Nadareski
e924299f85 Add other non-cryptographic hashes 2023-10-26 11:37:41 -04:00
Matt Nadareski
b983f7eb4a Move Hash enum and simplify 2023-10-26 11:30:00 -04:00
89 changed files with 10072 additions and 8980 deletions

4
.editorconfig Normal file
View File

@@ -0,0 +1,4 @@
[*.cs]
# SYSLIB1045: Convert to 'GeneratedRegexAttribute'.
dotnet_diagnostic.SYSLIB1045.severity = silent

View File

@@ -8,8 +8,9 @@ assignees: mnadareski
---
**Before You Submit**
- Remember to try the [latest WIP build](https://ci.appveyor.com/project/mnadareski/mpf/build/artifacts) to see if the feature already exists.
- Is it copy protection related? If so, report the issue [here](https://github.com/mnadareski/BurnOutSharp/issues) instead.
- Is it copy protection related? If so, report the issue [here](hhttps://github.com/SabreTools/BinaryObjectScanner/issues) instead.
- Check [previous issues](https://github.com/SabreTools/MPF/issues) to see if any of those are related to what you're about to ask for.
If none of those apply, then continue...

View File

@@ -8,8 +8,9 @@ assignees: mnadareski
---
**Before You Submit**
- Remember to try the [latest WIP build](https://ci.appveyor.com/project/mnadareski/mpf/build/artifacts) to see if the feature already exists.
- Is it copy protection related? If so, report the issue [here](https://github.com/mnadareski/BurnOutSharp/issues) instead.
- Is it copy protection related? If so, report the issue [here](hhttps://github.com/SabreTools/BinaryObjectScanner/issues) instead.
- Check [previous issues](https://github.com/SabreTools/MPF/issues) to see if any of those are related to what you're about to ask for.
If none of those apply, then continue...
@@ -18,4 +19,4 @@ If none of those apply, then continue...
A clear and concise description of what the information is. Ex. With the latest build of DumpingProgram, it [...]
**Additional context**
Add any other context or screenshots about the information here.
Add any other context or screenshots about the information here.

View File

@@ -8,15 +8,16 @@ assignees: mnadareski
---
**Before You Submit**
- Remember to try the [latest WIP build](https://ci.appveyor.com/project/mnadareski/mpf/build/artifacts) to see if the issue has already been addressed.
- Is it copy protection related? If so, report the issue [here](https://github.com/mnadareski/BurnOutSharp/issues) instead.
- Is it copy protection related? If so, report the issue [here](hhttps://github.com/SabreTools/BinaryObjectScanner/issues) instead.
- Check multiple discs to help narrow down the issue
- Check the Options to see if changing any of those affects your issue.
If all of those fail, then continue...
**Version**
What version are you using?
What version are you using?
- [ ] Stable release (version here)
- [ ] WIP release (version here)
@@ -24,15 +25,15 @@ What version are you using?
**Build**
What runtime version are you using?
- [ ] .NET Framework 4.8 running on (Operating System)
- [ ] .NET 6.0 running on (Operating System)
- [ ] .NET 7.0 running on (Operating System)
- [ ] .NET 8.0 running on (Operating System)
**Describe the issue**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'

52
.github/workflows/build_check.yml vendored Normal file
View File

@@ -0,0 +1,52 @@
name: MPF Check
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
project: [MPF.Check]
runtime: [win-x86, win-x64, linux-x64, osx-x64] #[win-x86, win-x64, win-arm64, linux-x64, linux-arm64, osx-x64]
framework: [net8.0] #[net20, net35, net40, net452, net472, net48, netcoreapp3.1, net5.0, net6.0, net7.0, net8.0]
conf: [Release, Debug]
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet publish ${{ matrix.project }}/${{ matrix.project }}.csproj -f ${{ matrix.framework }} -r ${{ matrix.runtime }} -c ${{ matrix.conf == 'Release' && 'Release -p:DebugType=None -p:DebugSymbols=false' || 'Debug'}} --self-contained true --version-suffix ${{ github.sha }} ${{ (startsWith(matrix.framework, 'net5') || startsWith(matrix.framework, 'net6') || startsWith(matrix.framework, 'net7') || startsWith(matrix.framework, 'net8')) && '-p:PublishSingleFile=true' || ''}}
- name: Upload build
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_${{ matrix.conf }}
path: ${{ matrix.project }}/bin/${{ matrix.conf }}/${{ matrix.framework }}/${{ matrix.runtime }}/publish/
- name: Upload to rolling
uses: ncipollo/release-action@v1
with:
allowUpdates: True
artifacts: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_${{ matrix.conf }}
generateReleaseNotes: true
omitBody: True
omitBodyDuringUpdate: True
omitNameDuringUpdate: True
prerelease: True
replacesArtifacts: True
tag: "check-rolling"
updateOnlyUnreleased: True

60
.github/workflows/build_ui.yml vendored Normal file
View File

@@ -0,0 +1,60 @@
name: MPF UI
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
project: [MPF]
runtime: [win-x86, win-x64]
framework: [net8.0-windows] #[net40, net452, net472, net48, netcoreapp3.1, net5.0-windows, net6.0-windows, net7.0-windows, net8.0-windows]
conf: [Release, Debug]
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet publish ${{ matrix.project }}/${{ matrix.project }}.csproj -f ${{ matrix.framework }} -r ${{ matrix.runtime }} -c ${{ matrix.conf == 'Release' && 'Release -p:DebugType=None -p:DebugSymbols=false' || 'Debug'}} --self-contained true --version-suffix ${{ github.sha }} ${{ (startsWith(matrix.framework, 'net5') || startsWith(matrix.framework, 'net6') || startsWith(matrix.framework, 'net7') || startsWith(matrix.framework, 'net8')) && '-p:PublishSingleFile=true' || ''}}
- name: Bundle Redumper
run: |
wget https://github.com/superg/redumper/releases/download/build_311/redumper-2024.01.08_build311-win64.zip
unzip redumper-2024.01.08_build311-win64.zip
mkdir -p MPF/bin/${{ matrix.conf }}/${{ matrix.framework }}/${{ matrix.runtime }}/publish/Programs/Redumper
mv redumper-2024.01.08_build311-win64/bin/redumper.exe MPF/bin/${{ matrix.conf }}/${{ matrix.framework }}/${{ matrix.runtime }}/publish/Programs/Redumper/
- name: Upload build
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_${{ matrix.conf }}
path: ${{ matrix.project }}/bin/${{ matrix.conf }}/${{ matrix.framework }}/${{ matrix.runtime }}/publish/
- name: Upload to rolling
uses: ncipollo/release-action@v1
with:
allowUpdates: True
artifacts: "*.zip"
generateReleaseNotes: true
omitBody: True
omitBodyDuringUpdate: True
omitNameDuringUpdate: True
prerelease: True
removeArtifacts: True
replacesArtifacts: True
tag: "ui-rolling"
updateOnlyUnreleased: True

View File

@@ -1,3 +1,198 @@
### WIP (xxxx-xx-xx)
- Remove debugging lines from build script
- Port build script fixes from BOS
- Fix double git hash version (feat. Deterous)
- Readd x86 builds by default
- Hide unavailable dumping programs (Deterous)
- Remove DIC and Aaru bundles from CI
- Add x86 builds to AppVeyor
- Make AppVeyor builds framework-dependent
- Fix misattributed artifact
- Update README with current build instructions
- Opt-in automatic IRD creation after PS3 dump (Deterous)
- Add CI via Github Workflows (Deterous)
- Reorganize solution items
- Split CI workflow files
- Add GHA CI status badges
- Rename badges for GHA
- More tweaks to CI
- Fix net35 build issue
- Remove now-unnecessary restore step
- Remove net35 from MPF... again
- Fix whitespace that got unwhitespaced
- Fix link in README
- Attempt to add CD to existing actions
### 3.1.1 (2024-02-20)
- Remove .NET 6 from auto-builds
- Make Redumper the default for new users
- Fix DIC log parsing for SS version (Deterous)
- Write outputs with UTF-8
- Add funworld Photo Play detection
- Fix Aaru drive parameter generation
- Limit DVD protection outputs
- Add a GUI for PS3 IRD Creation (Deterous)
- Update LibIRD, disable UI elements when creating IRD (Deterous)
### 3.1.0 (2024-02-06)
- Update RedumpLib
- Update Redumper to build 294
- Fix commented out code
- Make missing hash data clearer
- Get BD PIC Identifier for redumper (Deterous)
- Support redumper skeleton and hash files (Deterous)
- Support ringcode and PIC for triple/quad-layer (fuzz6001)
- Cleanup !protectionInfo.txt (Deterous)
- Update Redumper to build 311 (Deterous)
- Use PSX/PS2 serial as filename when Volume Label not present (Deterous)
- Allow variables in output path (Deterous)
- Check for presence of complete dump from other programs (Deterous)
- Retrieve volume label from logs (Deterous)
- Correct missing space in PVD (fuzz6001)
- Prevent crashing on invalid parameters (Deterous)
- Detect CDTV discs (Deterous)
- Differentiate CD32 from CDTV (Deterous)
- Normalise Disc Titles in Submission Info (Deterous)
- Skip warning line in Redumper log
- Add a GUI for MPF.Check (Deterous)
- Fix information pulling for CleanRip and UIC
- Add UMD handling for the disc info window
- Detect Photo CD
- Parse PSX/PS2/KP2 exe date from logs (Deterous)
- Exclude extra tracks when finding disc matches (Deterous)
- Verbose Redumper log by default (Deterous)
- Retrieve serial from Cleanrip
- Fix build from rushed code
- Remove `-disc2` from Cleanrip serial
- Enable Windows builds on Linux and Mac
- Fix compiler warning (Deterous)
### 3.0.3 (2023-12-04)
- Fix broken tests
- Fix using SHA-1 for track checks
- Fix build warning for NRE
- Remove .NET Framework 3.5 from build script
- Handle or suppress some messages
### 3.0.2 (2023-12-01)
- Read CSS for some copy protections
- Add Disc ID and Key fields in info window
- Replace build script with Powershell
- Fix Powershell build script
- Fix cross-framework UI rendering
- Fix most .NET Framework 3.5 issues
- Fix cross-framework UI styles
- Update Redumper to build 271
- Update USE_ALL in Powershell script
- Import WinFX for .NET Framework 3.5
- Reference .NET Framework 3.0 for 3.5
- Handle most VS and dotnet differences
### 3.0.1 (2023-11-30)
- Add Bandai Pippin detection
- Zip manufacturer files for Redumper
- Fix BE flag logic bug in DIC
- Support ancient .NET in Core
- Support ancient .NET in UI Core
- Support C# 12 syntax
- Support ancient .NET in Check
- Support ancient .NET in UI
- Fix TLS for older .NET
- Perform more ancient .NET support work
- Prepare XAML for ancient .NET support
- Suppress deprecation warnings
- Fix reversed ringcode test
- Add C#12 syntax to tests
- Trim PS3 serial and add unrelated notes
- Update RedumpLib and use moved methods
- Perform some post-move cleanup
- More C# 12 cleanup in Core
- Update to BinaryObjectScanner 3.0.1
- Update Xunit packages
- Get Core building with Framework 4.0
- Get UI.Core building with Framework 4.0
- Get Check building with Framework 4.0
- Use TryGetValue on dictionaries
- Support proper async in .NET Framework 4.0
- Temporarily remove .NET Framework 4.0
- Update to BinaryObjectScanner 3.0.2
- Re-enable .NET Framework 4.0 building in Core
- Re-enable .NET Framework 4.0 building in UI.Core
- Re-enable .NET Framework 4.0 building in Check
- Support .NET Framework 3.5
- Update compatibility libraries
- Support .NET Framework 2.0
- Support .NET Framework 3.5 in UI.Core
- Get UI building with Framework 4.0
- Temporarily remove .NET Framework 4.0 from UI
- Get UI building with Framework 4.0 again
- Support .NET Framework 3.5 in UI
- Update Redumper to build 268
### 3.0.0 (2023-11-14)
- Remove .NET Framework 4.8 from build
- Remove .NET Framework 4.8 from projects
- Remove .NET Framework 4.8 gated code
- Address lingering modern .NET syntax
- Update README for Legacy
- Remove redundant information
- Remove 4.8 note from build script
- Even more README cleanup
- Consolidate build script information
- Remove lingering script reference
- Make releases less verbose
- Remove .NET Framework 4.8 from issue report
- Remove Unshield reference
- Update documentation for BOS change
- Bump library versions
- Update to .NET 8
### 2.7.5 (2023-11-06)
- Remove psxt001z Pkg Ref in MPF.Test (Deterous)
- Update Redumper to build 247
- Focus main window after child window closes (Deterous)
- Try to get PS3 data from SFB
- Fix PS3 version finding
- Pull PS3 Firmware Version (Deterous)
- Fix default layerbreak output
- Enable browsing for Redumper path (Deterous)
- Update to MMI 3.0.0 (Deterous)
### 2.7.4 (2023-10-31)
- Move Hash enum and simplify
- Add other non-cryptographic hashes
- Avoid unncessary allocation in hashing
- Make Hasher more consistent
- Move all hashing to Hasher
- Separate out static hashing
- Remove unncessary summary
- Version-gate new switch statement
- Ignore empty protection results
- Remove and Sort Usings
- Update Redumper to build 244
- Enable Bluray dumping with Redumper
- Add Bluray layerbreak support for Redumper
- Remove now-obsolete notes
- Compile most regex statements
- Handle a couple of messages
- Remove .manufacturer for Bluray
- Fix typo that disables DIC during media check (Deterous)
- Fix build
- Remove duplicate check
- Add PIC output for Redumper
- Update Redumper to build 246
- Enable HD-DVD for Redumper in UI
- Attempt to fix window owner issue
### 2.7.3 (2023-10-26)
- Split InfoTool into 2 classes

View File

@@ -1,18 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net48;net6.0;net7.0</TargetFrameworks>
<RuntimeIdentifiers>win-x64;linux-x64;osx-x64</RuntimeIdentifiers>
<!-- Assembly Properties -->
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
<OutputType>Exe</OutputType>
<Title>MPF Check</Title>
<Description>Validator for various dumping programs</Description>
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
<Copyright>Copyright (c)2019-2023</Copyright>
<VersionPrefix>2.7.3</VersionPrefix>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)'!='net48'">
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<VersionPrefix>3.1.1</VersionPrefix>
<!-- Package Properties -->
<Title>MPF Check</Title>
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
<Description>Validator for various dumping programs</Description>
<Copyright>Copyright (c) Matt Nadareski 2019-2024</Copyright>
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
<RepositoryUrl>https://github.com/SabreTools/MPF</RepositoryUrl>
<RepositoryType>git</RepositoryType>
</PropertyGroup>
<ItemGroup>
@@ -24,14 +32,14 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="BurnOutSharp" PrivateAssets="build; analyzers" ExcludeAssets="contentFiles" Version="2.9.0" GeneratePathProperty="true">
<PackageReference Include="BinaryObjectScanner" PrivateAssets="build; analyzers" ExcludeAssets="contentFiles" Version="3.0.2" GeneratePathProperty="true">
<IncludeAssets>runtime; compile; build; native; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="SabreTools.RedumpLib" Version="1.1.1" />
<PackageReference Include="SabreTools.RedumpLib" Version="1.3.2" />
</ItemGroup>
<ItemGroup>
<Content Include="$(PkgBurnOutSharp)\content\**" PackagePath="contentFiles\any\any;content" CopyToOutputDirectory="Always" PackageCopyToOutput="true" />
<Content Include="$(PkgBinaryObjectScanner)\content\**" PackagePath="contentFiles\any\any;content" CopyToOutputDirectory="Always" PackageCopyToOutput="true" />
</ItemGroup>
</Project>

View File

@@ -45,12 +45,12 @@ namespace MPF.Check
protectionProgress.ProgressChanged += ConsoleLogger.ProgressUpdated;
// Validate the supplied credentials
#if NET48
(bool? _, string message) = RedumpWebClient.ValidateCredentials(options?.RedumpUsername, options?.RedumpPassword);
#if NETFRAMEWORK
(bool? _, string? message) = RedumpWebClient.ValidateCredentials(options.RedumpUsername ?? string.Empty, options.RedumpPassword ?? string.Empty);
#else
(bool? _, string? message) = RedumpHttpClient.ValidateCredentials(options.RedumpUsername ?? string.Empty, options.RedumpPassword ?? string.Empty).ConfigureAwait(false).GetAwaiter().GetResult();
#endif
if (!string.IsNullOrWhiteSpace(message))
if (!string.IsNullOrEmpty(message))
Console.WriteLine(message);
// Loop through all the rest of the args
@@ -67,18 +67,20 @@ namespace MPF.Check
string filepath = Path.GetFullPath(args[i].Trim('"'));
// Now populate an environment
#if NET48
Drive drive = null;
#else
Drive? drive = null;
#endif
if (!string.IsNullOrWhiteSpace(path))
drive = Drive.Create(null, path);
if (!string.IsNullOrEmpty(path))
drive = Drive.Create(null, path!);
var env = new DumpEnvironment(options, filepath, drive, knownSystem, mediaType, internalProgram: null, parameters: null);
// Finally, attempt to do the output dance
#if NET40
var resultTask = env.VerifyAndSaveDumpOutput(resultProgress, protectionProgress);
resultTask.Wait();
var result = resultTask.Result;
#else
var result = env.VerifyAndSaveDumpOutput(resultProgress, protectionProgress).ConfigureAwait(false).GetAwaiter().GetResult();
#endif
Console.WriteLine(result.Message);
}
}
@@ -87,11 +89,7 @@ namespace MPF.Check
/// Display help for MPF.Check
/// </summary>
/// <param name="error">Error string to prefix the help text with</param>
#if NET48
private static void DisplayHelp(string error = null)
#else
private static void DisplayHelp(string? error = null)
#endif
{
if (error != null)
Console.WriteLine(error);

View File

@@ -9,11 +9,7 @@ namespace MPF.Core
/// <summary>
/// Simple process counter to write to console
/// </summary>
#if NET48
public static void ProgressUpdated(object sender, Result value)
#else
public static void ProgressUpdated(object? sender, Result value)
#endif
{
Console.WriteLine(value.Message);
}
@@ -21,11 +17,7 @@ namespace MPF.Core
/// <summary>
/// Simple process counter to write to console
/// </summary>
#if NET48
public static void ProgressUpdated(object sender, ProtectionProgress value)
#else
public static void ProgressUpdated(object? sender, ProtectionProgress value)
#endif
{
Console.WriteLine($"{value.Percentage * 100:N2}%: {value.Filename} - {value.Protection}");
}

View File

@@ -1,5 +1,9 @@
using System;
#if NET20 || NET35
using System.Collections.Generic;
#else
using System.Collections.Concurrent;
#endif
using System.IO;
using System.Reflection;
using MPF.Core.Data;
@@ -18,17 +22,13 @@ namespace MPF.Core.Converters
/// <returns>InternalDriveType, if possible, null on error</returns>
public static InternalDriveType? ToInternalDriveType(this DriveType driveType)
{
switch (driveType)
return driveType switch
{
case DriveType.CDRom:
return InternalDriveType.Optical;
case DriveType.Fixed:
return InternalDriveType.HardDisk;
case DriveType.Removable:
return InternalDriveType.Removable;
default:
return null;
}
DriveType.CDRom => (InternalDriveType?)InternalDriveType.Optical,
DriveType.Fixed => (InternalDriveType?)InternalDriveType.HardDisk,
DriveType.Removable => (InternalDriveType?)InternalDriveType.Removable,
_ => null,
};
}
#endregion
@@ -38,10 +38,10 @@ namespace MPF.Core.Converters
/// <summary>
/// Long name method cache
/// </summary>
#if NET48
private static readonly ConcurrentDictionary<Type, MethodInfo> LongNameMethods = new ConcurrentDictionary<Type, MethodInfo>();
#if NET20 || NET35
private static readonly Dictionary<Type, MethodInfo?> LongNameMethods = [];
#else
private static readonly ConcurrentDictionary<Type, MethodInfo?> LongNameMethods = new ConcurrentDictionary<Type, MethodInfo?>();
private static readonly ConcurrentDictionary<Type, MethodInfo?> LongNameMethods = [];
#endif
/// <summary>
@@ -58,11 +58,14 @@ namespace MPF.Core.Converters
if (!LongNameMethods.TryGetValue(sourceType, out var method))
{
method = typeof(Extensions).GetMethod("LongName", new[] { typeof(Nullable<>).MakeGenericType(sourceType) });
if (method == null)
method = typeof(EnumConverter).GetMethod("LongName", new[] { typeof(Nullable<>).MakeGenericType(sourceType) });
method = typeof(Extensions).GetMethod("LongName", [typeof(Nullable<>).MakeGenericType(sourceType)]);
method ??= typeof(EnumConverter).GetMethod("LongName", [typeof(Nullable<>).MakeGenericType(sourceType)]);
#if NET20 || NET35
LongNameMethods[sourceType] = method;
#else
LongNameMethods.TryAdd(sourceType, method);
#endif
}
if (method != null)
@@ -84,36 +87,27 @@ namespace MPF.Core.Converters
/// <returns>String representing the value, if possible</returns>
public static string LongName(this InternalProgram? prog)
{
switch (prog)
return (prog) switch
{
#region Dumping support
case InternalProgram.Aaru:
return "Aaru";
case InternalProgram.DiscImageCreator:
return "DiscImageCreator";
case InternalProgram.Redumper:
return "redumper";
InternalProgram.Aaru => "Aaru",
InternalProgram.DiscImageCreator => "DiscImageCreator",
InternalProgram.Redumper => "Redumper",
#endregion
#region Verification support only
case InternalProgram.CleanRip:
return "CleanRip";
case InternalProgram.DCDumper:
return "DCDumper";
case InternalProgram.UmdImageCreator:
return "UmdImageCreator";
InternalProgram.CleanRip => "CleanRip",
InternalProgram.DCDumper => "DCDumper",
InternalProgram.UmdImageCreator => "UmdImageCreator",
#endregion
case InternalProgram.NONE:
default:
return "Unknown";
}
InternalProgram.NONE => "Unknown",
_ => "Unknown",
};
}
#endregion
@@ -125,46 +119,35 @@ namespace MPF.Core.Converters
/// </summary>
/// <param name="internalProgram">String value to convert</param>
/// <returns>InternalProgram represented by the string, if possible</returns>
#if NET48
public static InternalProgram ToInternalProgram(string internalProgram)
#else
public static InternalProgram ToInternalProgram(string? internalProgram)
#endif
{
switch (internalProgram?.ToLowerInvariant())
return (internalProgram?.ToLowerInvariant()) switch
{
// Dumping support
case "aaru":
case "chef":
case "dichef":
case "discimagechef":
return InternalProgram.Aaru;
case "creator":
case "dic":
case "dicreator":
case "discimagecreator":
return InternalProgram.DiscImageCreator;
case "rd":
case "redumper":
return InternalProgram.Redumper;
"aaru"
or "chef"
or "dichef"
or "discimagechef" => InternalProgram.Aaru,
"creator"
or "dic"
or "dicreator"
or "discimagecreator" => InternalProgram.DiscImageCreator,
"rd"
or "redumper" => InternalProgram.Redumper,
// Verification support only
case "cleanrip":
case "cr":
return InternalProgram.CleanRip;
case "dc":
case "dcd":
case "dcdumper":
return InternalProgram.DCDumper;
case "uic":
case "umd":
case "umdcreator":
case "umdimagecreator":
return InternalProgram.UmdImageCreator;
"cleanrip"
or "cr" => InternalProgram.CleanRip,
"dc"
or "dcd"
or "dcdumper" => InternalProgram.DCDumper,
"uic"
or "umd"
or "umdcreator"
or "umdimagecreator" => InternalProgram.UmdImageCreator,
default:
return InternalProgram.NONE;
}
_ => InternalProgram.NONE,
};
}
/// <summary>
@@ -174,170 +157,140 @@ namespace MPF.Core.Converters
/// <returns>MediaType represented by the string, if possible</returns>
public static MediaType ToMediaType(string type)
{
switch (type.ToLowerInvariant())
return (type.ToLowerInvariant()) switch
{
#region Punched Media
case "aperture":
case "aperturecard":
case "aperture card":
return MediaType.ApertureCard;
case "jacquardloom":
case "jacquardloomcard":
case "jacquard loom card":
return MediaType.JacquardLoomCard;
case "magneticstripe":
case "magneticstripecard":
case "magnetic stripe card":
return MediaType.MagneticStripeCard;
case "opticalphone":
case "opticalphonecard":
case "optical phonecard":
return MediaType.OpticalPhonecard;
case "punchcard":
case "punchedcard":
case "punched card":
return MediaType.PunchedCard;
case "punchtape":
case "punchedtape":
case "punched tape":
return MediaType.PunchedTape;
"aperture"
or "aperturecard"
or "aperture card" => MediaType.ApertureCard,
"jacquardloom"
or "jacquardloomcard"
or "jacquard loom card" => MediaType.JacquardLoomCard,
"magneticstripe"
or "magneticstripecard"
or "magnetic stripe card" => MediaType.MagneticStripeCard,
"opticalphone"
or "opticalphonecard"
or "optical phonecard" => MediaType.OpticalPhonecard,
"punchcard"
or "punchedcard"
or "punched card" => MediaType.PunchedCard,
"punchtape"
or "punchedtape"
or "punched tape" => MediaType.PunchedTape,
#endregion
#region Tape
case "openreel":
case "openreeltape":
case "open reel tape":
return MediaType.OpenReel;
case "datacart":
case "datacartridge":
case "datatapecartridge":
case "data tape cartridge":
return MediaType.DataCartridge;
case "cassette":
case "cassettetape":
case "cassette tape":
return MediaType.Cassette;
"openreel"
or "openreeltape"
or "open reel tape" => MediaType.OpenReel,
"datacart"
or "datacartridge"
or "datatapecartridge"
or "data tape cartridge" => MediaType.DataCartridge,
"cassette"
or "cassettetape"
or "cassette tape" => MediaType.Cassette,
#endregion
#region Disc / Disc
case "bd":
case "bdrom":
case "bd-rom":
case "bluray":
return MediaType.BluRay;
case "cd":
case "cdrom":
case "cd-rom":
return MediaType.CDROM;
case "dvd":
case "dvd5":
case "dvd-5":
case "dvd9":
case "dvd-9":
case "dvdrom":
case "dvd-rom":
return MediaType.DVD;
case "fd":
case "floppy":
case "floppydisk":
case "floppy disk":
case "floppy diskette":
return MediaType.FloppyDisk;
case "floptical":
return MediaType.Floptical;
case "gd":
case "gdrom":
case "gd-rom":
return MediaType.GDROM;
case "hddvd":
case "hd-dvd":
case "hddvdrom":
case "hd-dvd-rom":
return MediaType.HDDVD;
case "hdd":
case "harddisk":
case "hard disk":
return MediaType.HardDisk;
case "bernoullidisk":
case "iomegabernoullidisk":
case "bernoulli disk":
case "iomega bernoulli disk":
return MediaType.IomegaBernoulliDisk;
case "jaz":
case "iomegajaz":
case "iomega jaz":
return MediaType.IomegaJaz;
case "zip":
case "zipdisk":
case "iomegazip":
case "iomega zip":
return MediaType.IomegaZip;
case "ldrom":
case "lvrom":
case "ld-rom":
case "lv-rom":
case "laserdisc":
case "laservision":
case "ld-rom / lv-rom":
return MediaType.LaserDisc;
case "64dd":
case "n64dd":
case "64dddisk":
case "n64dddisk":
case "64dd disk":
case "n64dd disk":
return MediaType.Nintendo64DD;
case "fds":
case "famicom":
case "nfds":
case "nintendofamicom":
case "famicomdisksystem":
case "famicom disk system":
case "famicom disk system disk":
return MediaType.NintendoFamicomDiskSystem;
case "gc":
case "gamecube":
case "nintendogamecube":
case "nintendo gamecube":
case "gamecube disc":
case "gamecube game disc":
return MediaType.NintendoGameCubeGameDisc;
case "wii":
case "nintendowii":
case "nintendo wii":
case "nintendo wii disc":
case "wii optical disc":
return MediaType.NintendoWiiOpticalDisc;
case "wiiu":
case "nintendowiiu":
case "nintendo wiiu":
case "nintendo wiiu disc":
case "wiiu optical disc":
case "wii u optical disc":
return MediaType.NintendoWiiUOpticalDisc;
case "umd":
return MediaType.UMD;
"bd"
or "bdrom"
or "bd-rom"
or "bluray" => MediaType.BluRay,
"cd"
or "cdrom"
or "cd-rom" => MediaType.CDROM,
"dvd"
or "dvd5"
or "dvd-5"
or "dvd9"
or "dvd-9"
or "dvdrom"
or "dvd-rom" => MediaType.DVD,
"fd"
or "floppy"
or "floppydisk"
or "floppy disk"
or "floppy diskette" => MediaType.FloppyDisk,
"floptical" => MediaType.Floptical,
"gd"
or "gdrom"
or "gd-rom" => MediaType.GDROM,
"hddvd"
or "hd-dvd"
or "hddvdrom"
or "hd-dvd-rom" => MediaType.HDDVD,
"hdd"
or "harddisk"
or "hard disk" => MediaType.HardDisk,
"bernoullidisk"
or "iomegabernoullidisk"
or "bernoulli disk"
or "iomega bernoulli disk" => MediaType.IomegaBernoulliDisk,
"jaz"
or "iomegajaz"
or "iomega jaz" => MediaType.IomegaJaz,
"zip"
or "zipdisk"
or "iomegazip"
or "iomega zip" => MediaType.IomegaZip,
"ldrom"
or "lvrom"
or "ld-rom"
or "lv-rom"
or "laserdisc"
or "laservision"
or "ld-rom / lv-rom" => MediaType.LaserDisc,
"64dd"
or "n64dd"
or "64dddisk"
or "n64dddisk"
or "64dd disk"
or "n64dd disk" => MediaType.Nintendo64DD,
"fds"
or "famicom"
or "nfds"
or "nintendofamicom"
or "famicomdisksystem"
or "famicom disk system"
or "famicom disk system disk" => MediaType.NintendoFamicomDiskSystem,
"gc"
or "gamecube"
or "nintendogamecube"
or "nintendo gamecube"
or "gamecube disc"
or "gamecube game disc" => MediaType.NintendoGameCubeGameDisc,
"wii"
or "nintendowii"
or "nintendo wii"
or "nintendo wii disc"
or "wii optical disc" => MediaType.NintendoWiiOpticalDisc,
"wiiu"
or "nintendowiiu"
or "nintendo wiiu"
or "nintendo wiiu disc"
or "wiiu optical disc"
or "wii u optical disc" => MediaType.NintendoWiiUOpticalDisc,
"umd" => MediaType.UMD,
#endregion
// Unsorted Formats
case "cartridge":
return MediaType.Cartridge;
case "ced":
case "rcaced":
case "rca ced":
case "videodisc":
case "rca videodisc":
return MediaType.CED;
"cartridge" => MediaType.Cartridge,
"ced"
or "rcaced"
or "rca ced"
or "videodisc"
or "rca videodisc" => MediaType.CED,
default:
return MediaType.NONE;
}
_ => MediaType.NONE,
};
}
#endregion

View File

@@ -14,38 +14,45 @@ namespace MPF.Core.Data
public const string StopDumping = "Stop Dumping";
// Byte arrays for signatures
public static readonly byte[] SaturnSectorZeroStart = new byte[] { 0x53, 0x45, 0x47, 0x41, 0x20, 0x53, 0x45, 0x47, 0x41, 0x53, 0x41, 0x54, 0x55, 0x52, 0x4E, 0x20 };
public static readonly byte[] SaturnSectorZeroStart = [0x53, 0x45, 0x47, 0x41, 0x20, 0x53, 0x45, 0x47, 0x41, 0x53, 0x41, 0x54, 0x55, 0x52, 0x4E, 0x20];
// Lists of known drive speed ranges
#if NET20 || NET35 || NET40
public static IList<int> CD { get; } = new List<int> { 1, 2, 3, 4, 6, 8, 12, 16, 20, 24, 32, 40, 44, 48, 52, 56, 72 };
public static IList<int> DVD { get; } = CD.Where(s => s <= 24).ToList();
public static IList<int> HDDVD { get; } = CD.Where(s => s <= 24).ToList();
public static IList<int> BD { get; } = CD.Where(s => s <= 16).ToList();
public static IList<int> Unknown { get; } = new List<int> { 1 };
#else
public static IReadOnlyList<int> CD { get; } = new List<int> { 1, 2, 3, 4, 6, 8, 12, 16, 20, 24, 32, 40, 44, 48, 52, 56, 72 };
public static IReadOnlyList<int> DVD { get; } = CD.Where(s => s <= 24).ToList();
public static IReadOnlyList<int> HDDVD { get; } = CD.Where(s => s <= 24).ToList();
public static IReadOnlyList<int> BD { get; } = CD.Where(s => s <= 16).ToList();
public static IReadOnlyList<int> Unknown { get; } = new List<int> { 1 };
#endif
/// <summary>
/// Get list of all drive speeds for a given MediaType
/// </summary>
/// <param name="type">MediaType? that represents the current item</param>
/// <returns>Read-only list of drive speeds</returns>
#if NET20 || NET35 || NET40
public static IList<int> GetSpeedsForMediaType(MediaType? type)
#else
public static IReadOnlyList<int> GetSpeedsForMediaType(MediaType? type)
#endif
{
switch (type)
return type switch
{
case MediaType.CDROM:
case MediaType.GDROM:
return CD;
case MediaType.DVD:
case MediaType.NintendoGameCubeGameDisc:
case MediaType.NintendoWiiOpticalDisc:
return DVD;
case MediaType.HDDVD:
return HDDVD;
case MediaType.BluRay:
return BD;
default:
return Unknown;
}
MediaType.CDROM
or MediaType.GDROM => CD,
MediaType.DVD
or MediaType.NintendoGameCubeGameDisc
or MediaType.NintendoWiiOpticalDisc => DVD,
MediaType.HDDVD => HDDVD,
MediaType.BluRay => BD,
_ => Unknown,
};
}
}
@@ -54,65 +61,6 @@ namespace MPF.Core.Data
/// </summary>
public static class Template
{
// Manual information
public const string TitleField = "Title";
public const string ForeignTitleField = "Foreign Title (Non-latin)";
public const string DiscNumberField = "Disc Number / Letter";
public const string DiscTitleField = "Disc Title";
public const string SystemField = "System";
public const string MediaTypeField = "Media Type";
public const string CategoryField = "Category";
public const string RegionField = "Region";
public const string LanguagesField = "Languages";
public const string PlaystationLanguageSelectionViaField = "Language Selection Via";
public const string DiscSerialField = "Disc Serial";
public const string BarcodeField = "Barcode";
public const string CommentsField = "Comments";
public const string ContentsField = "Contents";
public const string VersionField = "Version";
public const string EditionField = "Edition/Release";
public const string PlayStation3WiiDiscKeyField = "Disc Key";
public const string PlayStation3DiscIDField = "Disc ID";
public const string GameCubeWiiBCAField = "BCA";
public const string CopyProtectionField = "Copy Protection";
public const string MasteringRingField = "Mastering Code (laser branded/etched)";
public const string MasteringSIDField = "Mastering SID Code";
public const string MouldSIDField = "Mould SID Code";
public const string AdditionalMouldField = "Additional Mould";
public const string ToolstampField = "Toolstamp or Mastering Code (engraved/stamped)";
// Automatic Information
public const string DumpingProgramField = "Dumping Program";
public const string DumpingDateField = "Date";
public const string DumpingDriveManufacturer = "Manufacturer";
public const string DumpingDriveModel = "Model";
public const string DumpingDriveFirmware = "Firmware";
public const string ReportedDiscType = "Reported Disc Type";
public const string PVDField = "Primary Volume Descriptor (PVD)";
public const string DATField = "DAT";
public const string SizeField = "Size";
public const string CRC32Field = "CRC32";
public const string MD5Field = "MD5";
public const string SHA1Field = "SHA1";
public const string FullyMatchingIDField = "Fully Matching ID";
public const string PartiallyMatchingIDsField = "Partially Matching IDs";
public const string ErrorCountField = "Error Count";
public const string CuesheetField = "Cuesheet";
public const string SubIntentionField = "SubIntention Data (SecuROM/LibCrypt)";
public const string WriteOffsetField = "Write Offset";
public const string LayerbreakField = "Layerbreak";
public const string EXEDateBuildDate = "EXE/Build Date";
public const string HeaderField = "Header";
public const string PICField = "Permanent Information & Control (PIC)";
public const string PlayStationEDCField = "EDC";
public const string PlayStationAntiModchipField = "Anti-modchip";
public const string PlayStationLibCryptField = "LibCrypt";
public const string XBOXSSRanges = "Security Sector Ranges";
// Default values
public const string RequiredValue = "(REQUIRED)";
public const string RequiredIfExistsValue = "(REQUIRED, IF EXISTS)";
public const string OptionalValue = "(OPTIONAL)";

View File

@@ -2,10 +2,11 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
#if NET462_OR_GREATER || NETCOREAPP
using Microsoft.Management.Infrastructure;
using Microsoft.Management.Infrastructure.Generic;
#endif
using MPF.Core.Converters;
using MPF.Core.Utilities;
using SabreTools.RedumpLib.Data;
namespace MPF.Core.Data
@@ -14,7 +15,6 @@ namespace MPF.Core.Data
/// Represents information for a single drive
/// </summary>
/// <remarks>
/// TODO: This needs to be less Windows-centric. Devices do not always have a single letter that can be used.
/// TODO: Can the Aaru models be used instead of the ones I've created here?
/// </remarks>
public class Drive
@@ -29,20 +29,12 @@ namespace MPF.Core.Data
/// <summary>
/// Drive partition format
/// </summary>
#if NET48
public string DriveFormat { get; private set; } = null;
#else
public string? DriveFormat { get; private set; } = null;
#endif
/// <summary>
/// Windows drive path
/// </summary>
#if NET48
public string Name { get; private set; } = null;
#else
public string? Name { get; private set; } = null;
#endif
/// <summary>
/// Represents if Windows has marked the drive as active
@@ -58,11 +50,7 @@ namespace MPF.Core.Data
/// Media label as read by Windows
/// </summary>
/// <remarks>The try/catch is needed because Windows will throw an exception if the drive is not marked as active</remarks>
#if NET48
public string VolumeLabel { get; private set; } = null;
#else
public string? VolumeLabel { get; private set; } = null;
#endif
#endregion
@@ -70,26 +58,40 @@ namespace MPF.Core.Data
/// <summary>
/// Media label as read by Windows, formatted to avoid odd outputs
/// If no volume label present, use PSX or PS2 serial if valid
/// Otherwise, use "track" as volume label
/// </summary>
#if NET48
public string FormattedVolumeLabel
#else
public string? FormattedVolumeLabel
#endif
{
get
{
string volumeLabel = Template.DiscNotDetected;
if (this.MarkedActive)
string? volumeLabel = Template.DiscNotDetected;
if (!this.MarkedActive)
return volumeLabel;
if (!string.IsNullOrEmpty(this.VolumeLabel))
{
if (string.IsNullOrWhiteSpace(this.VolumeLabel))
volumeLabel = "track";
else
volumeLabel = this.VolumeLabel;
volumeLabel = this.VolumeLabel;
}
else
{
// No Volume Label found, fallback to something sensible
switch (this.GetRedumpSystem(null))
{
case RedumpSystem.SonyPlayStation:
case RedumpSystem.SonyPlayStation2:
InfoTool.GetPlayStationExecutableInfo(this.Name, out string? serial, out _, out _);
volumeLabel = serial ?? "track";
break;
default:
volumeLabel = "track";
break;
}
}
foreach (char c in Path.GetInvalidFileNameChars())
volumeLabel = volumeLabel.Replace(c, '_');
volumeLabel = volumeLabel?.Replace(c, '_');
return volumeLabel;
}
@@ -113,11 +115,7 @@ namespace MPF.Core.Data
/// </summary>
/// <param name="driveType">InternalDriveType value representing the drive type</param>
/// <param name="devicePath">Path to the device according to the local machine</param>
#if NET48
public static Drive Create(InternalDriveType? driveType, string devicePath)
#else
public static Drive? Create(InternalDriveType? driveType, string devicePath)
#endif
{
// Create a new, empty drive object
var drive = new Drive()
@@ -126,16 +124,12 @@ namespace MPF.Core.Data
};
// If we have an invalid device path, return null
if (string.IsNullOrWhiteSpace(devicePath))
if (string.IsNullOrEmpty(devicePath))
return null;
// Sanitize a Windows-formatted long device path
if (devicePath.StartsWith("\\\\.\\"))
#if NET48
devicePath = devicePath.Substring("\\\\.\\".Length);
#else
devicePath = devicePath["\\\\.\\".Length..];
#endif
// Create and validate the drive info object
var driveInfo = new DriveInfo(devicePath);
@@ -152,11 +146,7 @@ namespace MPF.Core.Data
/// Populate all fields from a DriveInfo object
/// </summary>
/// <param name="driveInfo">DriveInfo object to populate from</param>
#if NET48
private void PopulateFromDriveInfo(DriveInfo driveInfo)
#else
private void PopulateFromDriveInfo(DriveInfo? driveInfo)
#endif
{
// If we have an invalid DriveInfo, just return
if (driveInfo == null || driveInfo == default)
@@ -189,7 +179,7 @@ namespace MPF.Core.Data
public static List<Drive> CreateListOfDrives(bool ignoreFixedDrives)
{
var drives = GetDriveList(ignoreFixedDrives);
drives = drives.OrderBy(i => i == null ? "\0" : i.Name).ToList();
drives = [.. drives.OrderBy(i => i == null ? "\0" : i.Name)];
return drives;
}
@@ -198,11 +188,7 @@ namespace MPF.Core.Data
/// </summary>
/// <param name="system"></param>
/// <returns></returns>
#if NET48
public (MediaType?, string) GetMediaType(RedumpSystem? system)
#else
public (MediaType?, string?) GetMediaType(RedumpSystem? system)
#endif
{
// Take care of the non-optical stuff first
switch (this.InternalDriveType)
@@ -283,26 +269,40 @@ namespace MPF.Core.Data
public RedumpSystem? GetRedumpSystem(RedumpSystem? defaultValue)
{
// If we can't read the media in that drive, we can't do anything
if (!Directory.Exists(this.Name))
if (string.IsNullOrEmpty(this.Name) || !Directory.Exists(this.Name))
return defaultValue;
// We're going to assume for floppies, HDDs, and removable drives
// TODO: Try to be smarter about this
if (this.InternalDriveType != Data.InternalDriveType.Optical)
return RedumpSystem.IBMPCcompatible;
// Check volume labels first
RedumpSystem? systemFromLabel = GetRedumpSystemFromVolumeLabel();
RedumpSystem? systemFromLabel = GetRedumpSystemFromVolumeLabel(this.VolumeLabel);
if (systemFromLabel != null)
return systemFromLabel;
// Get a list of files for quicker checking
#region Arcade
// funworld Photo Play
if (File.Exists(Path.Combine(this.Name, "PP.INF"))
&& Directory.Exists(Path.Combine(this.Name, "PPINC")))
{
return RedumpSystem.funworldPhotoPlay;
}
#endregion
#region Consoles
// Bandai Playdia Quick Interactive System
try
{
#if NET20 || NET35
List<string> files = [.. Directory.GetFiles(this.Name, "*", SearchOption.TopDirectoryOnly)];
#else
List<string> files = Directory.EnumerateFiles(this.Name, "*", SearchOption.TopDirectoryOnly).ToList();
#endif
if (files.Any(f => f.EndsWith(".AJS", StringComparison.OrdinalIgnoreCase))
&& files.Any(f => f.EndsWith(".GLB", StringComparison.OrdinalIgnoreCase)))
@@ -312,8 +312,31 @@ namespace MPF.Core.Data
}
catch { }
// Bandai Pippin
if (File.Exists(Path.Combine(this.Name, "PippinAuthenticationFile")))
{
return RedumpSystem.BandaiPippin;
}
// Commodore CDTV/CD32
#if NET20 || NET35
if (File.Exists(Path.Combine(Path.Combine(this.Name, "S"), "STARTUP-SEQUENCE")))
#else
if (File.Exists(Path.Combine(this.Name, "S", "STARTUP-SEQUENCE")))
#endif
{
if (File.Exists(Path.Combine(this.Name, "CDTV.TM")))
return RedumpSystem.CommodoreAmigaCDTV;
else
return RedumpSystem.CommodoreAmigaCD32;
}
// Mattel Fisher-Price iXL
#if NET20 || NET35
if (File.Exists(Path.Combine(Path.Combine(this.Name, "iXL"), "iXLUpdater.exe")))
#else
if (File.Exists(Path.Combine(this.Name, "iXL", "iXLUpdater.exe")))
#endif
{
return RedumpSystem.MattelFisherPriceiXL;
}
@@ -322,7 +345,11 @@ namespace MPF.Core.Data
try
{
if (Directory.Exists(Path.Combine(this.Name, "$SystemUpdate"))
#if NET20 || NET35
&& Directory.GetFiles(Path.Combine(this.Name, "$SystemUpdate")).Any()
#else
&& Directory.EnumerateFiles(Path.Combine(this.Name, "$SystemUpdate")).Any()
#endif
&& this.TotalSize <= 500_000_000)
{
return RedumpSystem.MicrosoftXbox360;
@@ -334,7 +361,11 @@ namespace MPF.Core.Data
try
{
if (Directory.Exists(Path.Combine(this.Name, "MSXC"))
#if NET20 || NET35
&& Directory.GetFiles(Path.Combine(this.Name, "MSXC")).Any())
#else
&& Directory.EnumerateFiles(Path.Combine(this.Name, "MSXC")).Any())
#endif
{
return RedumpSystem.MicrosoftXboxOne;
}
@@ -348,10 +379,17 @@ namespace MPF.Core.Data
}
// Sega Mega-CD / Sega-CD
#if NET20 || NET35
if (File.Exists(Path.Combine(Path.Combine(this.Name, "_BOOT"), "IP.BIN"))
|| File.Exists(Path.Combine(Path.Combine(this.Name, "_BOOT"), "SP.BIN"))
|| File.Exists(Path.Combine(Path.Combine(this.Name, "_BOOT"), "SP_AS.BIN"))
|| File.Exists(Path.Combine(this.Name, "FILESYSTEM.BIN")))
#else
if (File.Exists(Path.Combine(this.Name, "_BOOT", "IP.BIN"))
|| File.Exists(Path.Combine(this.Name, "_BOOT", "SP.BIN"))
|| File.Exists(Path.Combine(this.Name, "_BOOT", "SP_AS.BIN"))
|| File.Exists(Path.Combine(this.Name, "FILESYSTEM.BIN")))
#endif
{
return RedumpSystem.SegaMegaCDSegaCD;
}
@@ -397,7 +435,11 @@ namespace MPF.Core.Data
// Is used as an on-disc update for the base game app without needing to get update from the internet.
// "/addcont/GAME_SERIAL/CONTENT_ID/ac.pkg" can be found in Redump entry 97619.
// Originally on disc as "/addcont/CUSA00288/FFXIVEXPS400001A/ac.pkg".
#if NET20 || NET35
if (File.Exists(Path.Combine(Path.Combine(Path.Combine(this.Name, "PS4"), "UPDATE"), "PS4UPDATE.PUP")))
#else
if (File.Exists(Path.Combine(this.Name, "PS4", "UPDATE", "PS4UPDATE.PUP")))
#endif
{
return RedumpSystem.SonyPlayStation4;
}
@@ -433,13 +475,21 @@ namespace MPF.Core.Data
try
{
if (Directory.Exists(Path.Combine(this.Name, "AUDIO_TS"))
#if NET20 || NET35
&& Directory.GetFiles(Path.Combine(this.Name, "AUDIO_TS")).Any())
#else
&& Directory.EnumerateFiles(Path.Combine(this.Name, "AUDIO_TS")).Any())
#endif
{
return RedumpSystem.DVDAudio;
}
else if (Directory.Exists(Path.Combine(this.Name, "VIDEO_TS"))
#if NET20 || NET35
&& Directory.GetFiles(Path.Combine(this.Name, "VIDEO_TS")).Any())
#else
&& Directory.EnumerateFiles(Path.Combine(this.Name, "VIDEO_TS")).Any())
#endif
{
return RedumpSystem.DVDVideo;
}
@@ -450,18 +500,41 @@ namespace MPF.Core.Data
try
{
if (Directory.Exists(Path.Combine(this.Name, "HVDVD_TS"))
#if NET20 || NET35
&& Directory.GetFiles(Path.Combine(this.Name, "HVDVD_TS")).Any())
#else
&& Directory.EnumerateFiles(Path.Combine(this.Name, "HVDVD_TS")).Any())
#endif
{
return RedumpSystem.HDDVDVideo;
}
}
catch { }
// Photo CD
try
{
if (Directory.Exists(Path.Combine(this.Name, "PHOTO_CD"))
#if NET20 || NET35
&& Directory.GetFiles(Path.Combine(this.Name, "PHOTO_CD")).Any())
#else
&& Directory.EnumerateFiles(Path.Combine(this.Name, "PHOTO_CD")).Any())
#endif
{
return RedumpSystem.PhotoCD;
}
}
catch { }
// VCD
try
{
if (Directory.Exists(Path.Combine(this.Name, "VCD"))
#if NET20 || NET35
&& Directory.GetFiles(Path.Combine(this.Name, "VCD")).Any())
#else
&& Directory.EnumerateFiles(Path.Combine(this.Name, "VCD")).Any())
#endif
{
return RedumpSystem.VideoCD;
}
@@ -478,48 +551,48 @@ namespace MPF.Core.Data
/// Get the current system from the drive volume label
/// </summary>
/// <returns>The system based on volume label, null if none detected</returns>
public RedumpSystem? GetRedumpSystemFromVolumeLabel()
public static RedumpSystem? GetRedumpSystemFromVolumeLabel(string? volumeLabel)
{
// If the volume label is empty, we can't do anything
if (string.IsNullOrWhiteSpace(this.VolumeLabel))
if (string.IsNullOrEmpty(volumeLabel))
return null;
// Audio CD
if (this.VolumeLabel.Equals("Audio CD", StringComparison.OrdinalIgnoreCase))
if (volumeLabel!.Equals("Audio CD", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.AudioCD;
// Microsoft Xbox
if (this.VolumeLabel.Equals("SEP13011042", StringComparison.OrdinalIgnoreCase))
if (volumeLabel.Equals("SEP13011042", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.MicrosoftXbox;
else if (this.VolumeLabel.Equals("SEP13011042072", StringComparison.OrdinalIgnoreCase))
else if (volumeLabel.Equals("SEP13011042072", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.MicrosoftXbox;
// Microsoft Xbox 360
if (this.VolumeLabel.Equals("XBOX360", StringComparison.OrdinalIgnoreCase))
if (volumeLabel.Equals("XBOX360", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.MicrosoftXbox360;
else if (this.VolumeLabel.Equals("XGD2DVD_NTSC", StringComparison.OrdinalIgnoreCase))
else if (volumeLabel.Equals("XGD2DVD_NTSC", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.MicrosoftXbox360;
// Microsoft Xbox 360 - Too overly broad even if a lot of discs use this
//if (this.VolumeLabel.Equals("CD_ROM", StringComparison.OrdinalIgnoreCase))
//if (volumeLabel.Equals("CD_ROM", StringComparison.OrdinalIgnoreCase))
// return RedumpSystem.MicrosoftXbox360; // Also for Xbox One?
//if (this.VolumeLabel.Equals("DVD_ROM", StringComparison.OrdinalIgnoreCase))
//if (volumeLabel.Equals("DVD_ROM", StringComparison.OrdinalIgnoreCase))
// return RedumpSystem.MicrosoftXbox360;
// Sega Mega-CD / Sega-CD
if (this.VolumeLabel.Equals("Sega_CD", StringComparison.OrdinalIgnoreCase))
if (volumeLabel.Equals("Sega_CD", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.SegaMegaCDSegaCD;
// Sony PlayStation 3
if (this.VolumeLabel.Equals("PS3VOLUME", StringComparison.OrdinalIgnoreCase))
if (volumeLabel.Equals("PS3VOLUME", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.SonyPlayStation3;
// Sony PlayStation 4
if (this.VolumeLabel.Equals("PS4VOLUME", StringComparison.OrdinalIgnoreCase))
if (volumeLabel.Equals("PS4VOLUME", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.SonyPlayStation4;
// Sony PlayStation 5
if (this.VolumeLabel.Equals("PS5VOLUME", StringComparison.OrdinalIgnoreCase))
if (volumeLabel.Equals("PS5VOLUME", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.SonyPlayStation5;
return null;
@@ -576,6 +649,7 @@ namespace MPF.Core.Data
}
// Find and update all floppy drives
#if NET462_OR_GREATER || NETCOREAPP
try
{
CimSession session = CimSession.Create(null);
@@ -596,6 +670,7 @@ namespace MPF.Core.Data
{
// No-op
}
#endif
return drives;
}

View File

@@ -1,5 +1,25 @@
namespace MPF.Core.Data
{
/// <summary>
/// Available hashing types
/// </summary>
public enum Hash
{
CRC32,
#if NET6_0_OR_GREATER
CRC64,
#endif
MD5,
SHA1,
SHA256,
SHA384,
SHA512,
#if NET6_0_OR_GREATER
XxHash32,
XxHash64,
#endif
}
/// <summary>
/// Drive type for dumping
/// </summary>

View File

@@ -8,37 +8,23 @@ namespace MPF.Core.Data
{
public class IniFile : IDictionary<string, string>
{
#if NET48
private Dictionary<string, string> _keyValuePairs = new Dictionary<string, string>();
#else
private Dictionary<string, string> _keyValuePairs = new();
#endif
private Dictionary<string, string> _keyValuePairs = [];
public string this[string key]
{
get
{
#if NET48
if (_keyValuePairs == null)
_keyValuePairs = new Dictionary<string, string>();
#else
_keyValuePairs ??= new Dictionary<string, string>();
#endif
_keyValuePairs ??= [];
key = key.ToLowerInvariant();
if (_keyValuePairs.ContainsKey(key))
return _keyValuePairs[key];
if (_keyValuePairs.TryGetValue(key, out string? val))
return val;
return string.Empty;
}
set
{
#if NET48
if (_keyValuePairs == null)
_keyValuePairs = new Dictionary<string, string>();
#else
_keyValuePairs ??= new Dictionary<string, string>();
#endif
_keyValuePairs ??= [];
key = key.ToLowerInvariant();
_keyValuePairs[key] = value;
@@ -93,10 +79,8 @@ namespace MPF.Core.Data
if (!File.Exists(path))
return false;
using (var fileStream = File.OpenRead(path))
{
return Parse(fileStream);
}
using var fileStream = File.OpenRead(path);
return Parse(fileStream);
}
/// <summary>
@@ -111,55 +95,49 @@ namespace MPF.Core.Data
// Keys are case-insensitive by default
try
{
using (var sr = new StreamReader(stream))
using var sr = new StreamReader(stream);
string section = string.Empty;
while (!sr.EndOfStream)
{
string section = string.Empty;
while (!sr.EndOfStream)
var line = sr.ReadLine()?.Trim();
// Empty lines are skipped
if (string.IsNullOrEmpty(line))
{
var line = sr.ReadLine()?.Trim();
// Empty lines are skipped
if (string.IsNullOrWhiteSpace(line))
{
// No-op, we don't process empty lines
}
// Comments start with ';'
else if (line.StartsWith(";"))
{
// No-op, we don't process comments
}
// Section titles are surrounded by square brackets
else if (line.StartsWith("["))
{
section = line.TrimStart('[').TrimEnd(']');
}
// Valid INI lines are in the format key=value
#if NET48
else if (line.Contains("="))
#else
else if (line.Contains('='))
#endif
{
// Split the line by '=' for key-value pairs
string[] data = line.Split('=');
// If the value field contains an '=', we need to put them back in
string key = data[0].Trim();
string value = string.Join("=", data.Skip(1)).Trim();
// Section names are prepended to the key with a '.' separating
if (!string.IsNullOrEmpty(section))
key = $"{section}.{key}";
// Set or overwrite keys in the returned dictionary
_keyValuePairs[key.ToLowerInvariant()] = value;
}
// All other lines are ignored
// No-op, we don't process empty lines
}
// Comments start with ';'
else if (line!.StartsWith(";"))
{
// No-op, we don't process comments
}
// Section titles are surrounded by square brackets
else if (line.StartsWith("["))
{
section = line.TrimStart('[').TrimEnd(']');
}
// Valid INI lines are in the format key=value
else if (line.Contains('='))
{
// Split the line by '=' for key-value pairs
string[] data = line.Split('=');
// If the value field contains an '=', we need to put them back in
string key = data[0].Trim();
string value = string.Join("=", data.Skip(1).ToArray()).Trim();
// Section names are prepended to the key with a '.' separating
if (!string.IsNullOrEmpty(section))
key = $"{section}.{key}";
// Set or overwrite keys in the returned dictionary
_keyValuePairs[key.ToLowerInvariant()] = value;
}
// All other lines are ignored
}
}
catch
@@ -180,10 +158,8 @@ namespace MPF.Core.Data
if (_keyValuePairs == null || _keyValuePairs.Count == 0)
return false;
using (var fileStream = File.OpenWrite(path))
{
return Write(fileStream);
}
using var fileStream = File.OpenWrite(path);
return Write(fileStream);
}
/// <summary>
@@ -201,39 +177,37 @@ namespace MPF.Core.Data
try
{
using (var sw = new StreamWriter(stream))
// Order the dictionary by keys to link sections together
using var sw = new StreamWriter(stream);
var orderedKeyValuePairs = _keyValuePairs.OrderBy(kvp => kvp.Key);
string section = string.Empty;
foreach (var keyValuePair in orderedKeyValuePairs)
{
// Order the dictionary by keys to link sections together
var orderedKeyValuePairs = _keyValuePairs.OrderBy(kvp => kvp.Key);
// Extract the key and value
string key = keyValuePair.Key;
string value = keyValuePair.Value;
string section = string.Empty;
foreach (var keyValuePair in orderedKeyValuePairs)
// We assume '.' is a section name separator
if (key.Contains('.'))
{
// Extract the key and value
string key = keyValuePair.Key;
string value = keyValuePair.Value;
// Split the key by '.'
string[] data = keyValuePair.Key.Split('.');
// We assume '.' is a section name separator
if (key.Contains('.'))
// If the key contains an '.', we need to put them back in
string newSection = data[0].Trim();
key = string.Join(".", data.Skip(1).ToArray()).Trim();
// If we have a new section, write it out
if (!string.Equals(newSection, section, StringComparison.OrdinalIgnoreCase))
{
// Split the key by '.'
string[] data = keyValuePair.Key.Split('.');
// If the key contains an '.', we need to put them back in
string newSection = data[0].Trim();
key = string.Join(".", data.Skip(1)).Trim();
// If we have a new section, write it out
if (!string.Equals(newSection, section, StringComparison.OrdinalIgnoreCase))
{
sw.WriteLine($"[{newSection}]");
section = newSection;
}
sw.WriteLine($"[{newSection}]");
section = newSection;
}
// Now write out the key and value in a standardized way
sw.WriteLine($"{key}={value}");
}
// Now write out the key and value in a standardized way
sw.WriteLine($"{key}={value}");
}
}
catch

View File

@@ -9,11 +9,7 @@ namespace MPF.Core.Data
/// <summary>
/// All settings in the form of a dictionary
/// </summary>
#if NET48
public Dictionary<string, string> Settings { get; private set; }
#else
public Dictionary<string, string?> Settings { get; private set; }
#endif
/// <summary>
/// Indicate if the program is being run with a clean configuration
@@ -29,11 +25,7 @@ namespace MPF.Core.Data
/// <summary>
/// Path to Aaru
/// </summary>
#if NET48
public string AaruPath
#else
public string? AaruPath
#endif
{
get { return GetStringSetting(Settings, "AaruPath", "Programs\\Aaru\\Aaru.exe"); }
set { Settings["AaruPath"] = value; }
@@ -42,11 +34,7 @@ namespace MPF.Core.Data
/// <summary>
/// Path to DiscImageCreator
/// </summary>
#if NET48
public string DiscImageCreatorPath
#else
public string? DiscImageCreatorPath
#endif
{
get { return GetStringSetting(Settings, "DiscImageCreatorPath", "Programs\\Creator\\DiscImageCreator.exe"); }
set { Settings["DiscImageCreatorPath"] = value; }
@@ -55,11 +43,7 @@ namespace MPF.Core.Data
/// <summary>
/// Path to Redumper
/// </summary>
#if NET48
public string RedumperPath
#else
public string? RedumperPath
#endif
{
get { return GetStringSetting(Settings, "RedumperPath", "Programs\\Redumper\\redumper.exe"); }
set { Settings["RedumperPath"] = value; }
@@ -72,9 +56,9 @@ namespace MPF.Core.Data
{
get
{
var valueString = GetStringSetting(Settings, "InternalProgram", InternalProgram.DiscImageCreator.ToString());
var valueString = GetStringSetting(Settings, "InternalProgram", InternalProgram.Redumper.ToString());
var valueEnum = EnumConverter.ToInternalProgram(valueString);
return valueEnum == InternalProgram.NONE ? InternalProgram.DiscImageCreator : valueEnum;
return valueEnum == InternalProgram.NONE ? InternalProgram.Redumper : valueEnum;
}
set
{
@@ -116,11 +100,7 @@ namespace MPF.Core.Data
/// <summary>
/// Default output path for dumps
/// </summary>
#if NET48
public string DefaultOutputPath
#else
public string? DefaultOutputPath
#endif
{
get { return GetStringSetting(Settings, "DefaultOutputPath", "ISO"); }
set { Settings["DefaultOutputPath"] = value; }
@@ -341,7 +321,7 @@ namespace MPF.Core.Data
/// </summary>
public bool RedumperEnableVerbose
{
get { return GetBooleanSetting(Settings, "RedumperEnableVerbose", false); }
get { return GetBooleanSetting(Settings, "RedumperEnableVerbose", true); }
set { Settings["RedumperEnableVerbose"] = value.ToString(); }
}
@@ -520,6 +500,20 @@ namespace MPF.Core.Data
set { Settings["DeleteUnnecessaryFiles"] = value.ToString(); }
}
/// <summary>
/// Create a PS3 IRD file after dumping PS3 BD-ROM discs
/// Always returns false if not compiled with .NET Core 6 or newer
/// </summary>
public bool CreateIRDAfterDumping
{
#if NET6_0_OR_GREATER
get { return GetBooleanSetting(Settings, "CreateIRDAfterDumping", false); }
#else
get { return false; }
#endif
set { Settings["CreateIRDAfterDumping"] = value.ToString(); }
}
#endregion
#region Skip Options
@@ -573,6 +567,15 @@ namespace MPF.Core.Data
set { Settings["IncludeDebugProtectionInformation"] = value.ToString(); }
}
/// <summary>
/// Remove drive letters from protection scan output
/// </summary>
public bool HideDriveLetters
{
get { return GetBooleanSetting(Settings, "HideDriveLetters", false); }
set { Settings["HideDriveLetters"] = value.ToString(); }
}
#endregion
#region Logging Options
@@ -599,22 +602,14 @@ namespace MPF.Core.Data
#region Redump Login Information
#if NET48
public string RedumpUsername
#else
public string? RedumpUsername
#endif
{
get { return GetStringSetting(Settings, "RedumpUsername", ""); }
set { Settings["RedumpUsername"] = value; }
}
// TODO: Figure out a way to keep this encrypted in some way, BASE64 to start?
#if NET48
public string RedumpPassword
#else
public string? RedumpPassword
#endif
{
get
{
@@ -626,7 +621,7 @@ namespace MPF.Core.Data
/// <summary>
/// Determine if a complete set of Redump credentials might exist
/// </summary>
public bool HasRedumpLogin { get => !string.IsNullOrWhiteSpace(RedumpUsername) && !string.IsNullOrWhiteSpace(RedumpPassword); }
public bool HasRedumpLogin { get => !string.IsNullOrEmpty(RedumpUsername) && !string.IsNullOrEmpty(RedumpPassword); }
#endregion
@@ -634,44 +629,24 @@ namespace MPF.Core.Data
/// Constructor taking a dictionary for settings
/// </summary>
/// <param name="settings"></param>
#if NET48
public Options(Dictionary<string, string> settings = null)
#else
public Options(Dictionary<string, string?>? settings = null)
#endif
{
#if NET48
this.Settings = settings ?? new Dictionary<string, string>();
#else
this.Settings = settings ?? new Dictionary<string, string?>();
#endif
this.Settings = settings ?? [];
}
/// <summary>
/// Constructor taking an existing Options object
/// </summary>
/// <param name="source"></param>
#if NET48
public Options(Options source)
#else
public Options(Options? source)
#endif
{
#if NET48
Settings = new Dictionary<string, string>(source?.Settings ?? new Dictionary<string, string>());
#else
Settings = new Dictionary<string, string?>(source?.Settings ?? new Dictionary<string, string?>());
#endif
Settings = new Dictionary<string, string?>(source?.Settings ?? []);
}
/// <summary>
/// Accessor for the internal dictionary
/// </summary>
#if NET48
public string this[string key]
#else
public string? this[string key]
#endif
{
get => this.Settings[key];
set => this.Settings[key] = value;
@@ -686,11 +661,7 @@ namespace MPF.Core.Data
/// <param name="key">Setting key to get a value for</param>
/// <param name="defaultValue">Default value to return if no value is found</param>
/// <returns>Setting value if possible, default value otherwise</returns>
#if NET48
private static bool GetBooleanSetting(Dictionary<string, string> settings, string key, bool defaultValue)
#else
private static bool GetBooleanSetting(Dictionary<string, string?> settings, string key, bool defaultValue)
#endif
{
if (settings.ContainsKey(key))
{
@@ -712,11 +683,7 @@ namespace MPF.Core.Data
/// <param name="key">Setting key to get a value for</param>
/// <param name="defaultValue">Default value to return if no value is found</param>
/// <returns>Setting value if possible, default value otherwise</returns>
#if NET48
private static int GetInt32Setting(Dictionary<string, string> settings, string key, int defaultValue)
#else
private static int GetInt32Setting(Dictionary<string, string?> settings, string key, int defaultValue)
#endif
{
if (settings.ContainsKey(key))
{
@@ -738,11 +705,7 @@ namespace MPF.Core.Data
/// <param name="key">Setting key to get a value for</param>
/// <param name="defaultValue">Default value to return if no value is found</param>
/// <returns>Setting value if possible, default value otherwise</returns>
#if NET48
private static string GetStringSetting(Dictionary<string, string> settings, string key, string defaultValue)
#else
private static string? GetStringSetting(Dictionary<string, string?> settings, string key, string? defaultValue)
#endif
{
if (settings.ContainsKey(key))
return settings[key];

View File

@@ -1,16 +1,24 @@
using System;
#if NET20 || NET35
using System.Collections.Generic;
#else
using System.Collections.Concurrent;
#endif
using System.Threading;
using System.Threading.Tasks;
namespace MPF.Core.Data
{
public class ProcessingQueue<T> : IDisposable
public sealed class ProcessingQueue<T> : IDisposable
{
/// <summary>
/// Internal queue to hold data to process
/// </summary>
#if NET20 || NET35
private readonly Queue<T> InternalQueue;
#else
private readonly ConcurrentQueue<T> InternalQueue;
#endif
/// <summary>
/// Custom processing step for dequeued data
@@ -24,10 +32,20 @@ namespace MPF.Core.Data
public ProcessingQueue(Action<T> customProcessing)
{
#if NET20 || NET35
this.InternalQueue = new Queue<T>();
#else
this.InternalQueue = new ConcurrentQueue<T>();
#endif
this.CustomProcessing = customProcessing;
this.TokenSource = new CancellationTokenSource();
#if NET20 || NET35
Task.Run(() => ProcessQueue());
#elif NET40
Task.Factory.StartNew(() => ProcessQueue());
#else
Task.Run(() => ProcessQueue(), this.TokenSource.Token);
#endif
}
/// <summary>
@@ -39,7 +57,7 @@ namespace MPF.Core.Data
/// Enqueue a new item for processing
/// </summary>
/// <param name="item"></param>
public void Enqueue(T item)
public void Enqueue(T? item)
{
// Only accept new data when not cancelled
if (item != null && !this.TokenSource.IsCancellationRequested)
@@ -54,7 +72,11 @@ namespace MPF.Core.Data
while (true)
{
// Nothing in the queue means we get to idle
if (this.InternalQueue.Count == 0)
#if NET20 || NET35
if (InternalQueue.Count == 0)
#else
if (InternalQueue.IsEmpty)
#endif
{
if (this.TokenSource.IsCancellationRequested)
break;
@@ -63,12 +85,17 @@ namespace MPF.Core.Data
continue;
}
#if NET20 || NET35
// Get the next item from the queue and invoke the lambda, if possible
this.CustomProcessing?.Invoke(this.InternalQueue.Dequeue());
#else
// Get the next item from the queue
if (!this.InternalQueue.TryDequeue(out var nextItem))
continue;
// Invoke the lambda, if possible
this.CustomProcessing?.Invoke(nextItem);
#endif
}
}
}

View File

@@ -1,9 +1,11 @@
namespace MPF.Core.Data
using System;
namespace MPF.Core.Data
{
/// <summary>
/// Generic success/failure result object, with optional message
/// </summary>
public class Result
public class Result : System.EventArgs
{
/// <summary>
/// Internal representation of success
@@ -24,33 +26,30 @@
/// <summary>
/// Create a default success result with no message
/// </summary>
public static Result Success() => new Result(true, "");
public static Result Success() => new(true, "");
/// <summary>
/// Create a success result with a custom message
/// </summary>
/// <param name="message">String to add as a message</param>
#if NET48
public static Result Success(string message) => new Result(true, message);
#else
public static Result Success(string? message) => new Result(true, message ?? string.Empty);
#endif
public static Result Success(string? message) => new(true, message ?? string.Empty);
/// <summary>
/// Create a default failure result with no message
/// </summary>
/// <returns></returns>
public static Result Failure() => new Result(false, "");
public static Result Failure() => new(false, "");
/// <summary>
/// Create a failure result with a custom message
/// </summary>
/// <param name="message">String to add as a message</param>
#if NET48
public static Result Failure(string message) => new Result(false, message);
#else
public static Result Failure(string? message) => new Result(false, message ?? string.Empty);
#endif
public static Result Failure(string? message) => new(false, message ?? string.Empty);
internal static Result Success(object value)
{
throw new NotImplementedException();
}
/// <summary>
/// Results can be compared to boolean values based on the success value

View File

@@ -2,11 +2,12 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using BinaryObjectScanner;
using MPF.Core.Data;
using MPF.Core.Utilities;
using MPF.Core.Modules;
using MPF.Core.Utilities;
using SabreTools.RedumpLib;
using SabreTools.RedumpLib.Data;
namespace MPF.Core
@@ -30,11 +31,7 @@ namespace MPF.Core
/// <summary>
/// Drive object representing the current drive
/// </summary>
#if NET48
public Drive Drive { get; private set; }
#else
public Drive? Drive { get; private set; }
#endif
/// <summary>
/// Currently selected system
@@ -59,11 +56,7 @@ namespace MPF.Core
/// <summary>
/// Parameters object representing what to send to the internal program
/// </summary>
#if NET48
public BaseParameters Parameters { get; private set; }
#else
public BaseParameters? Parameters { get; private set; }
#endif
#endregion
@@ -72,8 +65,8 @@ namespace MPF.Core
/// <summary>
/// Generic way of reporting a message
/// </summary>
#if NET48
public EventHandler<string> ReportStatus;
#if NET20 || NET35 || NET40
public EventHandler<BaseParameters.StringEventArgs>? ReportStatus;
#else
public EventHandler<string>? ReportStatus;
#endif
@@ -81,17 +74,13 @@ namespace MPF.Core
/// <summary>
/// Queue of items that need to be logged
/// </summary>
#if NET48
private ProcessingQueue<string> outputQueue;
#else
private ProcessingQueue<string>? outputQueue;
#endif
/// <summary>
/// Event handler for data returned from a process
/// </summary>
#if NET48
private void OutputToLog(object proc, string args) => outputQueue?.Enqueue(args);
#if NET20 || NET35 || NET40
private void OutputToLog(object? proc, BaseParameters.StringEventArgs args) => outputQueue?.Enqueue(args.Value);
#else
private void OutputToLog(object? proc, string args) => outputQueue?.Enqueue(args);
#endif
@@ -99,7 +88,11 @@ namespace MPF.Core
/// <summary>
/// Process the outputs in the queue
/// </summary>
#if NET20 || NET35 || NET40
private void ProcessOutputs(string nextOutput) => ReportStatus?.Invoke(this, new BaseParameters.StringEventArgs { Value = nextOutput });
#else
private void ProcessOutputs(string nextOutput) => ReportStatus?.Invoke(this, nextOutput);
#endif
#endregion
@@ -115,19 +108,11 @@ namespace MPF.Core
/// <param name="parameters"></param>
public DumpEnvironment(Data.Options options,
string outputPath,
#if NET48
Drive drive,
#else
Drive? drive,
#endif
RedumpSystem? system,
MediaType? type,
InternalProgram? internalProgram,
#if NET48
string parameters)
#else
string? parameters)
#endif
{
// Set options object
Options = options;
@@ -151,47 +136,8 @@ namespace MPF.Core
/// Set the parameters object based on the internal program and parameters string
/// </summary>
/// <param name="parameters">String representation of the parameters</param>
#if NET48
public void SetParameters(string parameters)
#else
public void SetParameters(string? parameters)
#endif
{
#if NET48
switch (InternalProgram)
{
// Dumping support
case InternalProgram.Aaru:
Parameters = new Modules.Aaru.Parameters(parameters) { ExecutablePath = Options.AaruPath };
break;
case InternalProgram.DiscImageCreator:
Parameters = new Modules.DiscImageCreator.Parameters(parameters) { ExecutablePath = Options.DiscImageCreatorPath };
break;
case InternalProgram.Redumper:
Parameters = new Modules.Redumper.Parameters(parameters) { ExecutablePath = Options.RedumperPath };
break;
// Verification support only
case InternalProgram.CleanRip:
Parameters = new Modules.CleanRip.Parameters(parameters) { ExecutablePath = null };
break;
case InternalProgram.DCDumper:
Parameters = null; // TODO: Create correct parameter type when supported
break;
case InternalProgram.UmdImageCreator:
Parameters = new Modules.UmdImageCreator.Parameters(parameters) { ExecutablePath = null };
break;
// This should never happen, but it needs a fallback
default:
Parameters = new Modules.DiscImageCreator.Parameters(parameters) { ExecutablePath = Options.DiscImageCreatorPath };
break;
}
#else
Parameters = InternalProgram switch
{
// Dumping support
@@ -204,10 +150,12 @@ namespace MPF.Core
InternalProgram.DCDumper => null, // TODO: Create correct parameter type when supported
InternalProgram.UmdImageCreator => new Modules.UmdImageCreator.Parameters(parameters) { ExecutablePath = null },
// If no dumping program found, set to null
InternalProgram.NONE => null,
// This should never happen, but it needs a fallback
_ => new Modules.DiscImageCreator.Parameters(parameters) { ExecutablePath = Options.DiscImageCreatorPath },
_ => new Modules.Redumper.Parameters(parameters) { ExecutablePath = Options.RedumperPath },
};
#endif
// Set system and type
if (Parameters != null)
@@ -222,11 +170,7 @@ namespace MPF.Core
/// </summary>
/// <param name="driveSpeed">Nullable int representing the drive speed</param>
/// <returns>String representing the params, null on error</returns>
#if NET48
public string GetFullParameters(int? driveSpeed)
#else
public string? GetFullParameters(int? driveSpeed)
#endif
{
// Populate with the correct params for inputs (if we're not on the default option)
if (System != null && Type != MediaType.NONE)
@@ -236,40 +180,21 @@ namespace MPF.Core
return null;
// Set the proper parameters
#if NET48
switch (InternalProgram)
{
case InternalProgram.Aaru:
Parameters = new Modules.Aaru.Parameters(System, Type, Drive.Name, OutputPath, driveSpeed, Options);
break;
case InternalProgram.DiscImageCreator:
Parameters = new Modules.DiscImageCreator.Parameters(System, Type, Drive.Name, OutputPath, driveSpeed, Options);
break;
case InternalProgram.Redumper:
Parameters = new Modules.Redumper.Parameters(System, Type, Drive.Name, OutputPath, driveSpeed, Options);
break;
// This should never happen, but it needs a fallback
default:
Parameters = new Modules.DiscImageCreator.Parameters(System, Type, Drive.Name, OutputPath, driveSpeed, Options);
break;
}
#else
Parameters = InternalProgram switch
{
InternalProgram.Aaru => new Modules.Aaru.Parameters(System, Type, Drive.Name, OutputPath, driveSpeed, Options),
InternalProgram.DiscImageCreator => new Modules.DiscImageCreator.Parameters(System, Type, Drive.Name, OutputPath, driveSpeed, Options),
InternalProgram.Redumper => new Modules.Redumper.Parameters(System, Type, Drive.Name, OutputPath, driveSpeed, Options),
// If no dumping program found, set to null
InternalProgram.NONE => null,
// This should never happen, but it needs a fallback
_ => new Modules.DiscImageCreator.Parameters(System, Type, Drive.Name, OutputPath, driveSpeed, Options),
_ => new Modules.Redumper.Parameters(System, Type, Drive.Name, OutputPath, driveSpeed, Options),
};
#endif
// Generate and return the param string
return Parameters.GenerateParameters();
return Parameters?.GenerateParameters();
}
return null;
@@ -287,29 +212,21 @@ namespace MPF.Core
/// <summary>
/// Eject the disc using DiscImageCreator
/// </summary>
#if NET48
public async Task<string> EjectDisc() =>
#else
public async Task<string?> EjectDisc() =>
#endif
await RunStandaloneDiscImageCreatorCommand(Modules.DiscImageCreator.CommandStrings.Eject);
/// <summary>
/// Reset the current drive using DiscImageCreator
/// </summary>
#if NET48
public async Task<string> ResetDrive() =>
#else
public async Task<string?> ResetDrive() =>
#endif
await RunStandaloneDiscImageCreatorCommand(Modules.DiscImageCreator.CommandStrings.Reset);
/// <summary>
/// Execute the initial invocation of the dumping programs
/// </summary>
/// <param name="progress">Optional result progress callback</param>
#if NET48
public async Task<Result> Run(IProgress<Result> progress = null)
#if NET40
public Result Run(IProgress<Result>? progress = null)
#else
public async Task<Result> Run(IProgress<Result>? progress = null)
#endif
@@ -335,10 +252,15 @@ namespace MPF.Core
progress?.Report(Result.Success($"Executing {InternalProgram}... {(Options.ToolsInSeparateWindow ? "please wait!" : "see log for output!")}"));
var directoryName = Path.GetDirectoryName(OutputPath);
if (!string.IsNullOrWhiteSpace(directoryName))
if (!string.IsNullOrEmpty(directoryName))
Directory.CreateDirectory(directoryName);
#if NET40
var executeTask = Task.Factory.StartNew(() => Parameters.ExecuteInternalProgram(Options.ToolsInSeparateWindow));
executeTask.Wait();
#else
await Task.Run(() => Parameters.ExecuteInternalProgram(Options.ToolsInSeparateWindow));
#endif
progress?.Report(Result.Success($"{InternalProgram} has finished!"));
// Remove event handler if needed
@@ -360,18 +282,14 @@ namespace MPF.Core
/// <param name="seedInfo">A seed SubmissionInfo object that contains user data</param>
/// <returns>Result instance with the outcome</returns>
public async Task<Result> VerifyAndSaveDumpOutput(
#if NET48
IProgress<Result> resultProgress = null,
IProgress<ProtectionProgress> protectionProgress = null,
Func<SubmissionInfo, (bool?, SubmissionInfo)> processUserInfo = null,
SubmissionInfo seedInfo = null)
#else
IProgress<Result>? resultProgress = null,
IProgress<ProtectionProgress>? protectionProgress = null,
IProgress<BinaryObjectScanner.ProtectionProgress>? protectionProgress = null,
Func<SubmissionInfo?, (bool?, SubmissionInfo?)>? processUserInfo = null,
SubmissionInfo? seedInfo = null)
#endif
{
if (Parameters == null)
return Result.Failure("Error! Current configuration is not supported!");
resultProgress?.Report(Result.Success("Gathering submission information... please wait!"));
// Get the output directory and filename separately
@@ -379,10 +297,10 @@ namespace MPF.Core
var outputFilename = Path.GetFileName(OutputPath);
// Check to make sure that the output had all the correct files
(bool foundFiles, List<string> missingFiles) = InfoTool.FoundAllFiles(outputDirectory, outputFilename, Parameters, false);
(bool foundFiles, List<string> missingFiles) = Parameters.FoundAllFiles(outputDirectory, outputFilename, false);
if (!foundFiles)
{
resultProgress?.Report(Result.Failure($"There were files missing from the output:\n{string.Join("\n", missingFiles)}"));
resultProgress?.Report(Result.Failure($"There were files missing from the output:\n{string.Join("\n", [.. missingFiles])}"));
return Result.Failure("Error! Please check output directory as dump may be incomplete!");
}
@@ -403,7 +321,7 @@ namespace MPF.Core
if (seedInfo != null)
{
resultProgress?.Report(Result.Success("Injecting user-supplied information..."));
SubmissionInfoTool.InjectSubmissionInformation(submissionInfo, seedInfo);
Builder.InjectSubmissionInformation(submissionInfo, seedInfo);
resultProgress?.Report(Result.Success("Information injection complete!"));
}
@@ -437,12 +355,12 @@ namespace MPF.Core
// Process special fields for site codes
resultProgress?.Report(Result.Success("Processing site codes..."));
InfoTool.ProcessSpecialFields(submissionInfo);
Formatter.ProcessSpecialFields(submissionInfo);
resultProgress?.Report(Result.Success("Processing complete!"));
// Format the information for the text output
resultProgress?.Report(Result.Success("Formatting information..."));
(var formattedValues, var formatResult) = InfoTool.FormatOutputData(submissionInfo, Options);
(var formattedValues, var formatResult) = Formatter.FormatOutputData(submissionInfo, Options.EnableRedumpCompatibility);
if (formattedValues == null)
resultProgress?.Report(Result.Success(formatResult));
else
@@ -460,14 +378,17 @@ namespace MPF.Core
resultProgress?.Report(Result.Failure(txtResult));
// Write the copy protection output
if (Options.ScanForProtection && Options.OutputSeparateProtectionFile)
if (submissionInfo?.CopyProtection?.FullProtections != null && submissionInfo.CopyProtection.FullProtections.Any())
{
resultProgress?.Report(Result.Success("Writing protection to !protectionInfo.txt..."));
bool scanSuccess = InfoTool.WriteProtectionData(outputDirectory, filenameSuffix, submissionInfo);
if (scanSuccess)
resultProgress?.Report(Result.Success("Writing complete!"));
else
resultProgress?.Report(Result.Failure("Writing could not complete!"));
if (Options.ScanForProtection && Options.OutputSeparateProtectionFile)
{
resultProgress?.Report(Result.Success("Writing protection to !protectionInfo.txt..."));
bool scanSuccess = InfoTool.WriteProtectionData(outputDirectory, filenameSuffix, submissionInfo, Options.HideDriveLetters);
if (scanSuccess)
resultProgress?.Report(Result.Success("Writing complete!"));
else
resultProgress?.Report(Result.Failure("Writing could not complete!"));
}
}
// Write the JSON output, if required
@@ -503,6 +424,19 @@ namespace MPF.Core
resultProgress?.Report(Result.Failure(deleteResult));
}
#if NET6_0_OR_GREATER
// Create PS3 IRD, if required
if (Options.CreateIRDAfterDumping && System == RedumpSystem.SonyPlayStation3 && Type == MediaType.BluRay)
{
resultProgress?.Report(Result.Success("Creating IRD... please wait!"));
(bool deleteSuccess, string deleteResult) = await InfoTool.WriteIRD(OutputPath, submissionInfo?.Extras?.DiscKey, submissionInfo?.Extras?.DiscID, submissionInfo?.Extras?.PIC, submissionInfo?.SizeAndChecksums?.Layerbreak, submissionInfo?.SizeAndChecksums?.CRC32);
if (deleteSuccess)
resultProgress?.Report(Result.Success(deleteResult));
else
resultProgress?.Report(Result.Failure(deleteResult));
}
#endif
resultProgress?.Report(Result.Success("Submission information process complete!"));
return Result.Success();
}
@@ -535,14 +469,18 @@ namespace MPF.Core
private static async Task<string> ExecuteInternalProgram(BaseParameters parameters)
{
Process childProcess;
#if NET40
string output = await Task.Factory.StartNew(() =>
#else
string output = await Task.Run(() =>
#endif
{
childProcess = new Process()
{
StartInfo = new ProcessStartInfo()
{
FileName = parameters.ExecutablePath,
Arguments = parameters.GenerateParameters(),
FileName = parameters.ExecutablePath!,
Arguments = parameters.GenerateParameters()!,
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardInput = true,
@@ -586,7 +524,7 @@ namespace MPF.Core
return Result.Failure($"Error! {Parameters.ExecutablePath} does not exist!");
// Validate that the dumping drive doesn't contain the executable
string fullExecutablePath = Path.GetFullPath(Parameters.ExecutablePath);
string fullExecutablePath = Path.GetFullPath(Parameters.ExecutablePath!);
if (Drive?.Name != null && fullExecutablePath.StartsWith(Drive.Name))
return Result.Failure("Error! Cannot dump same drive that executable resides on!");
@@ -601,7 +539,7 @@ namespace MPF.Core
private bool RequiredProgramsExist()
{
// Validate that the path is configured
if (string.IsNullOrWhiteSpace(Options.DiscImageCreatorPath))
if (string.IsNullOrEmpty(Options.DiscImageCreatorPath))
return false;
// Validate that the required program exists
@@ -616,11 +554,7 @@ namespace MPF.Core
/// </summary>
/// <param name="command">Command string to run</param>
/// <returns>The output of the command on success, null on error</returns>
#if NET48
private async Task<string> RunStandaloneDiscImageCreatorCommand(string command)
#else
private async Task<string?> RunStandaloneDiscImageCreatorCommand(string command)
#endif
{
// Validate that DiscImageCreator is all set
if (!RequiredProgramsExist())

View File

@@ -1,41 +1,68 @@
using System;
using System.Collections.Generic;
using System.IO;
#if NET6_0_OR_GREATER
using System.IO.Hashing;
#endif
using System.Linq;
using System.Security.Cryptography;
using System.Threading.Tasks;
using MPF.Core.Data;
namespace MPF.Core.Hashing
{
/// <summary>
/// Available hashing types
/// </summary>
[Flags]
public enum Hash
public sealed class Hasher : IDisposable
{
CRC32 = 1 << 0,
MD5 = 1 << 1,
SHA1 = 1 << 2,
SHA256 = 1 << 3,
SHA384 = 1 << 4,
SHA512 = 1 << 5,
#region Properties
// Special combinations
Standard = CRC32 | MD5 | SHA1,
All = CRC32 | MD5 | SHA1 | SHA256 | SHA384 | SHA512,
}
/// <summary>
/// Async hashing class wraper
/// </summary>
public class Hasher
{
/// <summary>
/// Hash type associated with the current state
/// </summary>
#if NETFRAMEWORK || NETCOREAPP3_1
public Hash HashType { get; private set; }
#if NET48
private object _hasher;
#else
private object? _hasher;
public Hash HashType { get; init; }
#endif
/// <summary>
/// Current hash in bytes
/// </summary>
public byte[]? CurrentHashBytes
{
get
{
return (_hasher) switch
{
HashAlgorithm ha => ha.Hash,
NonCryptographicHashAlgorithm ncha => ncha.GetCurrentHash().Reverse().ToArray(),
_ => null,
};
}
}
/// <summary>
/// Current hash as a string
/// </summary>
public string? CurrentHashString => ByteArrayToString(CurrentHashBytes);
#endregion
#region Private Fields
/// <summary>
/// Internal hasher being used for processing
/// </summary>
/// <remarks>May be either a HashAlgorithm or NonCryptographicHashAlgorithm</remarks>
private object? _hasher;
#endregion
#region Constructors
/// <summary>
/// Constructor
/// </summary>
/// <param name="hashType">Hash type to instantiate</param>
public Hasher(Hash hashType)
{
this.HashType = hashType;
@@ -47,58 +74,231 @@ namespace MPF.Core.Hashing
/// </summary>
private void GetHasher()
{
switch (HashType)
_hasher = HashType switch
{
case Hash.CRC32:
_hasher = new Crc32();
break;
case Hash.MD5:
_hasher = MD5.Create();
break;
case Hash.SHA1:
_hasher = SHA1.Create();
break;
case Hash.SHA256:
_hasher = SHA256.Create();
break;
case Hash.SHA384:
_hasher = SHA384.Create();
break;
case Hash.SHA512:
_hasher = SHA512.Create();
break;
}
Hash.CRC32 => new Crc32(),
#if NET6_0_OR_GREATER
Hash.CRC64 => new Crc64(),
#endif
Hash.MD5 => MD5.Create(),
Hash.SHA1 => SHA1.Create(),
Hash.SHA256 => SHA256.Create(),
Hash.SHA384 => SHA384.Create(),
Hash.SHA512 => SHA512.Create(),
#if NET6_0_OR_GREATER
Hash.XxHash32 => new XxHash32(),
Hash.XxHash64 => new XxHash64(),
#endif
_ => null,
};
}
/// <inheritdoc/>
public void Dispose()
{
if (_hasher is IDisposable disposable)
disposable.Dispose();
}
#endregion
#region Static Hashing
/// <summary>
/// Get hashes from an input file path
/// </summary>
/// <param name="filename">Path to the input file</param>
/// <returns>True if hashing was successful, false otherwise</returns>
public static bool GetFileHashes(string filename, out long size, out string? crc32, out string? md5, out string? sha1)
{
// Set all initial values
crc32 = null; md5 = null; sha1 = null;
// Get all file hashes
var fileHashes = GetFileHashes(filename, out size);
if (fileHashes == null)
return false;
// Assign the file hashes and return
crc32 = fileHashes[Hash.CRC32];
md5 = fileHashes[Hash.MD5];
sha1 = fileHashes[Hash.SHA1];
return true;
}
/// <summary>
/// Get hashes from an input file path
/// </summary>
/// <param name="filename">Path to the input file</param>
/// <returns>Dictionary containing hashes on success, null on error</returns>
public static Dictionary<Hash, string?>? GetFileHashes(string filename, out long size)
{
// If the file doesn't exist, we can't do anything
if (!File.Exists(filename))
{
size = -1;
return null;
}
// Set the file size
size = new FileInfo(filename).Length;
// Open the input file
var input = File.OpenRead(filename);
// Return the hashes from the stream
return GetStreamHashes(input);
}
/// <summary>
/// Get hashes from an input Stream
/// </summary>
/// <param name="input">Stream to hash</param>
/// <returns>Dictionary containing hashes on success, null on error</returns>
public static Dictionary<Hash, string?>? GetStreamHashes(Stream input)
{
// Create the output dictionary
var hashDict = new Dictionary<Hash, string?>();
try
{
// Get a list of hashers to run over the buffer
var hashers = new Dictionary<Hash, Hasher>
{
{ Hash.CRC32, new Hasher(Hash.CRC32) },
#if NET6_0_OR_GREATER
{ Hash.CRC64, new Hasher(Hash.CRC64) },
#endif
{ Hash.MD5, new Hasher(Hash.MD5) },
{ Hash.SHA1, new Hasher(Hash.SHA1) },
{ Hash.SHA256, new Hasher(Hash.SHA256) },
{ Hash.SHA384, new Hasher(Hash.SHA384) },
{ Hash.SHA512, new Hasher(Hash.SHA512) },
#if NET6_0_OR_GREATER
{ Hash.XxHash32, new Hasher(Hash.XxHash32) },
{ Hash.XxHash64, new Hasher(Hash.XxHash64) },
#endif
};
// Initialize the hashing helpers
var loadBuffer = new ThreadLoadBuffer(input);
int buffersize = 3 * 1024 * 1024;
byte[] buffer0 = new byte[buffersize];
byte[] buffer1 = new byte[buffersize];
/*
Please note that some of the following code is adapted from
RomVault. This is a modified version of how RomVault does
threaded hashing. As such, some of the terminology and code
is the same, though variable names and comments may have
been tweaked to better fit this code base.
*/
// Pre load the first buffer
long refsize = input.Length;
int next = refsize > buffersize ? buffersize : (int)refsize;
input.Read(buffer0, 0, next);
int current = next;
refsize -= next;
bool bufferSelect = true;
while (current > 0)
{
// Trigger the buffer load on the second buffer
next = refsize > buffersize ? buffersize : (int)refsize;
if (next > 0)
loadBuffer.Trigger(bufferSelect ? buffer1 : buffer0, next);
byte[] buffer = bufferSelect ? buffer0 : buffer1;
#if NET20 || NET35
// Run hashers sequentially on each chunk
foreach (var h in hashers)
{
h.Value.Process(buffer, current);
}
#else
// Run hashers in parallel on each chunk
Parallel.ForEach(hashers, h => h.Value.Process(buffer, current));
#endif
// Wait for the load buffer worker, if needed
if (next > 0)
loadBuffer.Wait();
// Setup for the next hashing step
current = next;
refsize -= next;
bufferSelect = !bufferSelect;
}
// Finalize all hashing helpers
loadBuffer.Finish();
#if NET20 || NET35
foreach (var h in hashers)
{
h.Value.Terminate();
}
#else
Parallel.ForEach(hashers, h => h.Value.Terminate());
#endif
// Get the results
hashDict[Hash.CRC32] = hashers[Hash.CRC32].CurrentHashString;
#if NET6_0_OR_GREATER
hashDict[Hash.CRC64] = hashers[Hash.CRC64].CurrentHashString;
#endif
hashDict[Hash.MD5] = hashers[Hash.MD5].CurrentHashString;
hashDict[Hash.SHA1] = hashers[Hash.SHA1].CurrentHashString;
hashDict[Hash.SHA256] = hashers[Hash.SHA256].CurrentHashString;
hashDict[Hash.SHA384] = hashers[Hash.SHA384].CurrentHashString;
hashDict[Hash.SHA512] = hashers[Hash.SHA512].CurrentHashString;
#if NET6_0_OR_GREATER
hashDict[Hash.XxHash32] = hashers[Hash.XxHash32].CurrentHashString;
hashDict[Hash.XxHash64] = hashers[Hash.XxHash64].CurrentHashString;
#endif
// Dispose of the hashers
loadBuffer.Dispose();
foreach (var hasher in hashers.Values)
{
hasher.Dispose();
}
return hashDict;
}
catch (IOException)
{
return null;
}
finally
{
input.Dispose();
}
}
#endregion
#region Hashing
/// <summary>
/// Process a buffer of some length with the internal hash algorithm
/// </summary>
public void Process(byte[] buffer, int size)
{
switch (HashType)
switch (_hasher)
{
case Hash.CRC32:
var bufferSpan = new ReadOnlySpan<byte>(buffer, 0, size);
(_hasher as NonCryptographicHashAlgorithm)?.Append(bufferSpan);
case HashAlgorithm ha:
ha.TransformBlock(buffer, 0, size, null, 0);
break;
case Hash.MD5:
case Hash.SHA1:
case Hash.SHA256:
case Hash.SHA384:
case Hash.SHA512:
(_hasher as HashAlgorithm)?.TransformBlock(buffer, 0, size, null, 0);
case NonCryptographicHashAlgorithm ncha:
#if NET20 || NET35 || NET40
byte[] bufferSpan = new byte[size];
Array.Copy(buffer, bufferSpan, size);
#else
var bufferSpan = new ReadOnlySpan<byte>(buffer, 0, size);
#endif
ncha.Append(bufferSpan);
break;
}
}
@@ -106,68 +306,21 @@ namespace MPF.Core.Hashing
/// <summary>
/// Finalize the internal hash algorigthm
/// </summary>
/// <remarks>NonCryptographicHashAlgorithm implementations do not need finalization</remarks>
public void Terminate()
{
byte[] emptyBuffer = Array.Empty<byte>();
switch (HashType)
byte[] emptyBuffer = [];
switch (_hasher)
{
case Hash.CRC32:
// No finalization is needed
break;
case Hash.MD5:
case Hash.SHA1:
case Hash.SHA256:
case Hash.SHA384:
case Hash.SHA512:
(_hasher as HashAlgorithm)?.TransformFinalBlock(emptyBuffer, 0, 0);
case HashAlgorithm ha:
ha.TransformFinalBlock(emptyBuffer, 0, 0);
break;
}
}
/// <summary>
/// Get internal hash as a byte array
/// </summary>
#if NET48
public byte[] GetHash()
#else
public byte[]? GetHash()
#endif
{
if (_hasher == null)
return null;
#endregion
switch (HashType)
{
case Hash.CRC32:
return (_hasher as NonCryptographicHashAlgorithm)?.GetCurrentHash()?.Reverse().ToArray();
case Hash.MD5:
case Hash.SHA1:
case Hash.SHA256:
case Hash.SHA384:
case Hash.SHA512:
return (_hasher as HashAlgorithm)?.Hash;
}
return null;
}
/// <summary>
/// Get internal hash as a string
/// </summary>
#if NET48
public string GetHashString()
#else
public string? GetHashString()
#endif
{
var hash = GetHash();
if (hash == null)
return null;
return ByteArrayToString(hash);
}
#region Helpers
/// <summary>
/// Convert a byte array to a hex string
@@ -175,11 +328,7 @@ namespace MPF.Core.Hashing
/// <param name="bytes">Byte array to convert</param>
/// <returns>Hex string representing the byte array</returns>
/// <link>http://stackoverflow.com/questions/311165/how-do-you-convert-byte-array-to-hexadecimal-string-and-vice-versa</link>
#if NET48
private static string ByteArrayToString(byte[] bytes)
#else
private static string? ByteArrayToString(byte[]? bytes)
#endif
{
// If we get null in, we send null out
if (bytes == null)
@@ -195,5 +344,7 @@ namespace MPF.Core.Hashing
return null;
}
}
#endregion
}
}

View File

@@ -0,0 +1,184 @@
#if NETFRAMEWORK || NETCOREAPP3_1 || NET5_0
/*
Copyright (c) 2012-2015 Eugene Larchenko (spct@mail.ru)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
using System;
//namespace OptimizedCRC
namespace MPF.Core.Hashing
{
/// <summary>
/// Shell class to trick older versions into using CRC-32 properly
/// </summary>
internal abstract class NonCryptographicHashAlgorithm
{
#if NET20 || NET35 || NET40
/// <summary>
/// When overridden in a derived class, appends the contents of source to
/// the data already processed for the current hash computation.
/// </summary>
/// <param name="source">The data to process.</param>
public abstract void Append(byte[] source);
#else
/// <summary>
/// When overridden in a derived class, appends the contents of source to
/// the data already processed for the current hash computation.
/// </summary>
/// <param name="source">The data to process.</param>
public abstract void Append(ReadOnlySpan<byte> source);
#endif
/// <summary>
/// Gets the current computed hash value without modifying accumulated state.
/// </summary>
/// <returns>The hash value for the data already provided.</returns>
public abstract byte[] GetCurrentHash();
}
/// <remarks>
/// Some changes have been made to this code to make it more similar to the System.IO.Hashing implementations
/// </remarks>
internal class Crc32 : NonCryptographicHashAlgorithm, IDisposable
{
private const uint kCrcPoly = 0xEDB88320;
private const uint kInitial = 0xFFFFFFFF;
private const int CRC_NUM_TABLES = 8;
private static readonly uint[] Table;
static Crc32()
{
unchecked
{
Table = new uint[256 * CRC_NUM_TABLES];
int i;
for (i = 0; i < 256; i++)
{
uint r = (uint)i;
for (int j = 0; j < 8; j++)
{
r = (r >> 1) ^ (kCrcPoly & ~((r & 1) - 1));
}
Table[i] = r;
}
for (; i < 256 * CRC_NUM_TABLES; i++)
{
uint r = Table[i - 256];
Table[i] = Table[r & 0xFF] ^ (r >> 8);
}
}
}
public uint UnsignedValue;
public Crc32()
{
Init();
}
/// <summary>
/// Reset CRC
/// </summary>
public void Init()
{
UnsignedValue = kInitial;
}
/// <inheritdoc/>
public override byte[] GetCurrentHash()
{
return BitConverter.GetBytes(~UnsignedValue);
}
/// <inheritdoc/>
#if NET20 || NET35 || NET40
public override void Append(byte[] source)
{
Update(source, 0, source.Length);
}
#else
public override void Append(ReadOnlySpan<byte> source)
{
byte[] sourceBytes = source.ToArray();
Update(sourceBytes, 0, sourceBytes.Length);
}
#endif
private void Update(byte[] data, int offset, int count)
{
_ = new ArraySegment<byte>(data, offset, count); // check arguments
if (count == 0)
{
return;
}
var table = Table;
uint crc = UnsignedValue;
for (; (offset & 7) != 0 && count != 0; count--)
{
crc = (crc >> 8) ^ table[(byte)crc ^ data[offset++]];
}
if (count >= 8)
{
/*
* Idea from 7-zip project sources (http://7-zip.org/sdk.html)
*/
int end = (count - 8) & ~7;
count -= end;
end += offset;
while (offset != end)
{
crc ^= (uint)(data[offset] + (data[offset + 1] << 8) + (data[offset + 2] << 16) + (data[offset + 3] << 24));
uint high = (uint)(data[offset + 4] + (data[offset + 5] << 8) + (data[offset + 6] << 16) + (data[offset + 7] << 24));
offset += 8;
crc = table[(byte)crc + 0x700]
^ table[(byte)(crc >>= 8) + 0x600]
^ table[(byte)(crc >>= 8) + 0x500]
^ table[/*(byte)*/(crc >> 8) + 0x400]
^ table[(byte)(high) + 0x300]
^ table[(byte)(high >>= 8) + 0x200]
^ table[(byte)(high >>= 8) + 0x100]
^ table[/*(byte)*/(high >> 8) + 0x000];
}
}
while (count-- != 0)
{
crc = (crc >> 8) ^ table[(byte)crc ^ data[offset++]];
}
UnsignedValue = crc;
}
public void Dispose()
{
UnsignedValue = 0;
}
}
}
#endif

View File

@@ -5,17 +5,13 @@ using System.Threading;
//namespace Compress.ThreadReaders
namespace MPF.Core.Hashing
{
public class ThreadLoadBuffer : IDisposable
public sealed class ThreadLoadBuffer : IDisposable
{
private readonly AutoResetEvent _waitEvent;
private readonly AutoResetEvent _outEvent;
private readonly Thread _tWorker;
#if NET48
private byte[] _buffer;
#else
private byte[]? _buffer;
#endif
private int _size;
private readonly Stream _ds;
private bool _finished;

File diff suppressed because it is too large Load Diff

View File

@@ -1,35 +1,68 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net48;net6.0;net7.0</TargetFrameworks>
<RuntimeIdentifiers>win-x64;linux-x64;osx-x64</RuntimeIdentifiers>
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
<Copyright>Copyright (c)2019-2023</Copyright>
<VersionPrefix>2.7.3</VersionPrefix>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)'!='net48'">
<!-- Assembly Properties -->
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<VersionPrefix>3.1.1</VersionPrefix>
<!-- Package Properties -->
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
<Description>Common code for all MPF implementations</Description>
<Copyright>Copyright (c) Matt Nadareski 2019-2024</Copyright>
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
<RepositoryUrl>https://github.com/SabreTools/MPF</RepositoryUrl>
<RepositoryType>git</RepositoryType>
</PropertyGroup>
<ItemGroup>
<InternalsVisibleTo Include="MPF.Test" />
</ItemGroup>
<!-- Support for old .NET versions -->
<ItemGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net40`))">
<PackageReference Include="MinAsyncBridge" Version="0.12.4" />
<PackageReference Include="MinTasksExtensionsBridge" Version="0.3.4" />
<PackageReference Include="MinThreadingBridge" Version="0.11.4" />
</ItemGroup>
<ItemGroup Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`))">
<PackageReference Include="System.IO.Compression" Version="4.3.0" />
</ItemGroup>
<ItemGroup Condition="$(TargetFramework.StartsWith(`net4`)) AND !$(TargetFramework.StartsWith(`net40`))">
<PackageReference Include="IndexRange" Version="1.0.3" />
</ItemGroup>
<ItemGroup Condition="$(TargetFramework.StartsWith(`net452`))">
<PackageReference Include="Microsoft.Net.Http" Version="2.2.29" />
</ItemGroup>
<ItemGroup Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`)) AND !$(TargetFramework.StartsWith(`net452`))">
<PackageReference Include="Microsoft.Management.Infrastructure" Version="3.0.0" />
<PackageReference Include="System.IO.Compression.ZipFile" Version="4.3.0" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
</ItemGroup>
<ItemGroup Condition="$(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`))">
<PackageReference Include="System.IO.Hashing" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BurnOutSharp" PrivateAssets="build; analyzers" ExcludeAssets="contentFiles" Version="2.9.0" GeneratePathProperty="true">
<PackageReference Include="BinaryObjectScanner" PrivateAssets="build; analyzers" ExcludeAssets="contentFiles" Version="3.0.2" GeneratePathProperty="true">
<IncludeAssets>runtime; compile; build; native; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Management.Infrastructure" Version="3.0.0-preview.4" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="psxt001z" Version="0.21.0-beta1" />
<PackageReference Include="SabreTools.Models" Version="1.1.5" />
<PackageReference Include="SabreTools.RedumpLib" Version="1.1.1" />
<PackageReference Include="SabreTools.Serialization" Version="1.1.7" />
<PackageReference Include="System.IO.Compression" Version="4.3.0" />
<PackageReference Include="System.IO.Compression.ZipFile" Version="4.3.0" />
<PackageReference Include="System.IO.Hashing" Version="7.0.0" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
<PackageReference Include="psxt001z.Library" Version="0.21.0-beta3" />
<PackageReference Include="SabreTools.Models" Version="1.3.0" />
<PackageReference Include="SabreTools.RedumpLib" Version="1.3.2" />
<PackageReference Include="SabreTools.Serialization" Version="1.3.1" />
</ItemGroup>
<ItemGroup Condition="$(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`))">
<PackageReference Include="LibIRD" Version="0.6.0" />
</ItemGroup>
</Project>

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
@@ -14,13 +15,25 @@ namespace MPF.Core.Modules
{
#region Event Handlers
#if NET20 || NET35 || NET40
/// <summary>
/// Wrapper event args class for old .NET
/// </summary>
public class StringEventArgs : EventArgs
{
public string? Value { get; set; }
}
/// <summary>
/// Geneeic way of reporting a message
/// </summary>
/// <param name="message">String value to report</param>
#if NET48
public EventHandler<string> ReportStatus;
public EventHandler<StringEventArgs>? ReportStatus;
#else
/// <summary>
/// Geneeic way of reporting a message
/// </summary>
/// <param name="message">String value to report</param>
public EventHandler<string>? ReportStatus;
#endif
@@ -31,20 +44,12 @@ namespace MPF.Core.Modules
/// <summary>
/// Base command to run
/// </summary>
#if NET48
public string BaseCommand { get; set; }
#else
public string? BaseCommand { get; set; }
#endif
/// <summary>
/// Set of flags to pass to the executable
/// </summary>
#if NET48
protected Dictionary<string, bool?> flags = new Dictionary<string, bool?>();
#else
protected Dictionary<string, bool?> flags = new();
#endif
protected Dictionary<string, bool?> flags = [];
protected internal IEnumerable<string> Keys => flags.Keys;
/// <summary>
@@ -54,8 +59,8 @@ namespace MPF.Core.Modules
{
get
{
if (flags.ContainsKey(key))
return flags[key];
if (flags.TryGetValue(key, out bool? val))
return val;
return null;
}
@@ -68,11 +73,12 @@ namespace MPF.Core.Modules
/// <summary>
/// Process to track external program
/// </summary>
#if NET48
private Process process;
#else
private Process? process;
#endif
/// <summary>
/// All found volume labels and their corresponding file systems
/// </summary>
public Dictionary<string, List<string>>? VolumeLabels;
#endregion
@@ -81,30 +87,18 @@ namespace MPF.Core.Modules
/// <summary>
/// Command to flag support mappings
/// </summary>
#if NET48
public Dictionary<string, List<string>> CommandSupport => GetCommandSupport();
#else
public Dictionary<string, List<string>>? CommandSupport => GetCommandSupport();
#endif
/// <summary>
/// Input path for operations
/// </summary>
#if NET48
public virtual string InputPath => null;
#else
public virtual string? InputPath => null;
#endif
/// <summary>
/// Output path for operations
/// </summary>
/// <returns>String representing the path, null on error</returns>
#if NET48
public virtual string OutputPath => null;
#else
public virtual string? OutputPath => null;
#endif
/// <summary>
/// Get the processing speed from the implementation
@@ -118,11 +112,7 @@ namespace MPF.Core.Modules
/// <summary>
/// Path to the executable
/// </summary>
#if NET48
public string ExecutablePath { get; set; }
#else
public string? ExecutablePath { get; set; }
#endif
/// <summary>
/// Program that this set of parameters represents
@@ -145,11 +135,7 @@ namespace MPF.Core.Modules
/// Populate a Parameters object from a param string
/// </summary>
/// <param name="parameters">String possibly representing a set of parameters</param>
#if NET48
public BaseParameters(string parameters)
#else
public BaseParameters(string? parameters)
#endif
{
// If any parameters are not valid, wipe out everything
if (!ValidateAndSetParameters(parameters))
@@ -165,11 +151,7 @@ namespace MPF.Core.Modules
/// <param name="filename">Filename to use</param>
/// <param name="driveSpeed">Drive speed to use</param>
/// <param name="options">Options object containing all settings that may be used for setting parameters</param>
#if NET48
public BaseParameters(RedumpSystem? system, MediaType? type, string drivePath, string filename, int? driveSpeed, Options options)
#else
public BaseParameters(RedumpSystem? system, MediaType? type, string? drivePath, string filename, int? driveSpeed, Options options)
#endif
{
this.System = system;
this.Type = type;
@@ -194,11 +176,7 @@ namespace MPF.Core.Modules
/// <param name="basePath">Base filename and path to use for checking</param>
/// <param name="drive">Drive representing the disc to get information from</param>
/// <param name="includeArtifacts">True to include output files as encoded artifacts, false otherwise</param>
#if NET48
public abstract void GenerateSubmissionInfo(SubmissionInfo submissionInfo, Options options, string basePath, Drive drive, bool includeArtifacts);
#else
public abstract void GenerateSubmissionInfo(SubmissionInfo submissionInfo, Options options, string basePath, Drive? drive, bool includeArtifacts);
#endif
#endregion
@@ -208,54 +186,34 @@ namespace MPF.Core.Modules
/// Get all commands mapped to the supported flags
/// </summary>
/// <returns>Mappings from command to supported flags</returns>
#if NET48
public virtual Dictionary<string, List<string>> GetCommandSupport() => null;
#else
public virtual Dictionary<string, List<string>>? GetCommandSupport() => null;
#endif
/// <summary>
/// Blindly generate a parameter string based on the inputs
/// </summary>
/// <returns>Parameter string for invocation, null on error</returns>
#if NET48
public virtual string GenerateParameters() => null;
#else
public virtual string? GenerateParameters() => null;
#endif
/// <summary>
/// Get the default extension for a given media type
/// </summary>
/// <param name="mediaType">MediaType value to check</param>
/// <returns>String representing the media type, null on error</returns>
#if NET48
public virtual string GetDefaultExtension(MediaType? mediaType) => null;
#else
public virtual string? GetDefaultExtension(MediaType? mediaType) => null;
#endif
/// <summary>
/// Generate a list of all deleteable files generated
/// </summary>
/// <param name="basePath">Base filename and path to use for checking</param>
/// <returns>List of all deleteable file paths, empty otherwise</returns>
#if NET48
public virtual List<string> GetDeleteableFilePaths(string basePath) => new List<string>();
#else
public virtual List<string> GetDeleteableFilePaths(string basePath) => new();
#endif
/// <summary>
/// Generate a list of all log files generated
/// </summary>
/// <param name="basePath">Base filename and path to use for checking</param>
/// <returns>List of all log file paths, empty otherwise</returns>
#if NET48
public virtual List<string> GetLogFilePaths(string basePath) => new List<string>();
#else
public virtual List<string> GetLogFilePaths(string basePath) => new();
#endif
/// <summary>
/// Get the MediaType from the current set of parameters
@@ -280,9 +238,9 @@ namespace MPF.Core.Modules
return false;
if (this.BaseCommand == null)
return false;
if (!CommandSupport.ContainsKey(this.BaseCommand))
if (!CommandSupport.TryGetValue(this.BaseCommand, out var supported))
return false;
return CommandSupport[this.BaseCommand].Contains(flag);
return supported.Contains(flag);
}
/// <summary>
@@ -303,22 +261,14 @@ namespace MPF.Core.Modules
/// <param name="filename">Filename to use</param>
/// <param name="driveSpeed">Drive speed to use</param>
/// <param name="options">Options object containing all settings that may be used for setting parameters</param>
#if NET48
protected virtual void SetDefaultParameters(string drivePath, string filename, int? driveSpeed, Options options) { }
#else
protected virtual void SetDefaultParameters(string? drivePath, string filename, int? driveSpeed, Options options) { }
#endif
/// <summary>
/// Scan a possible parameter string and populate whatever possible
/// </summary>
/// <param name="parameters">String possibly representing parameters</param>
/// <returns>True if the parameters were set correctly, false otherwise</returns>
#if NET48
protected virtual bool ValidateAndSetParameters(string parameters) => !string.IsNullOrWhiteSpace(parameters);
#else
protected virtual bool ValidateAndSetParameters(string? parameters) => !string.IsNullOrWhiteSpace(parameters);
#endif
protected virtual bool ValidateAndSetParameters(string? parameters) => !string.IsNullOrEmpty(parameters);
#endregion
@@ -333,7 +283,7 @@ namespace MPF.Core.Modules
// Create the start info
var startInfo = new ProcessStartInfo()
{
FileName = ExecutablePath,
FileName = ExecutablePath!,
Arguments = GenerateParameters() ?? "",
CreateNoWindow = !separateWindow,
UseShellExecute = separateWindow,
@@ -350,8 +300,13 @@ namespace MPF.Core.Modules
// Start processing tasks, if necessary
if (!separateWindow)
{
#if NET40
Logging.OutputToLog(process.StandardOutput, this, ReportStatus);
Logging.OutputToLog(process.StandardError, this, ReportStatus);
#else
_ = Logging.OutputToLog(process.StandardOutput, this, ReportStatus);
_ = Logging.OutputToLog(process.StandardError, this, ReportStatus);
#endif
}
process.WaitForExit();
@@ -374,7 +329,7 @@ namespace MPF.Core.Modules
{ }
}
#endregion
#endregion
#region Parameter Parsing
@@ -392,11 +347,7 @@ namespace MPF.Core.Modules
/// </summary>
/// <param name="content">String content to encode</param>
/// <returns>Base64-encoded contents, if possible</returns>
#if NET48
protected static string GetBase64(string content)
#else
protected static string? GetBase64(string? content)
#endif
{
if (string.IsNullOrEmpty(content))
return null;
@@ -411,11 +362,7 @@ namespace MPF.Core.Modules
/// <param name="filename">file location</param>
/// <param name="binary">True if should read as binary, false otherwise (default)</param>
/// <returns>Full text of the file, null on error</returns>
#if NET48
protected static string GetFullFile(string filename, bool binary = false)
#else
protected static string? GetFullFile(string filename, bool binary = false)
#endif
{
// If the file doesn't exist, we can't get info from it
if (!File.Exists(filename))
@@ -545,11 +492,7 @@ namespace MPF.Core.Modules
/// <param name="longFlagString">Long flag string, if available</param>
/// <param name="i">Reference to the position in the parts</param>
/// <returns>True if the parameter was processed successfully or skipped, false otherwise</returns>
#if NET48
protected bool ProcessFlagParameter(List<string> parts, string shortFlagString, string longFlagString, ref int i)
#else
protected bool ProcessFlagParameter(List<string> parts, string? shortFlagString, string longFlagString, ref int i)
#endif
{
if (parts == null)
return false;
@@ -585,11 +528,7 @@ namespace MPF.Core.Modules
/// <param name="i">Reference to the position in the parts</param>
/// <param name="missingAllowed">True if missing values are allowed, false otherwise</param>
/// <returns>True if the parameter was processed successfully or skipped, false otherwise</returns>
#if NET48
protected bool ProcessBooleanParameter(List<string> parts, string shortFlagString, string longFlagString, ref int i, bool missingAllowed = false)
#else
protected bool ProcessBooleanParameter(List<string> parts, string? shortFlagString, string longFlagString, ref int i, bool missingAllowed = false)
#endif
{
if (parts == null)
return false;
@@ -664,11 +603,7 @@ namespace MPF.Core.Modules
/// <param name="i">Reference to the position in the parts</param>
/// <param name="missingAllowed">True if missing values are allowed, false otherwise</param>
/// <returns>SByte value if success, SByte.MinValue if skipped, null on error/returns>
#if NET48
protected sbyte? ProcessInt8Parameter(List<string> parts, string shortFlagString, string longFlagString, ref int i, bool missingAllowed = false)
#else
protected sbyte? ProcessInt8Parameter(List<string> parts, string? shortFlagString, string longFlagString, ref int i, bool missingAllowed = false)
#endif
{
if (parts == null)
return null;
@@ -705,7 +640,12 @@ namespace MPF.Core.Modules
i++;
(string value, long factor) = ExtractFactorFromValue(parts[i]);
return (sbyte)(sbyte.Parse(value) * factor);
if (sbyte.TryParse(value, out sbyte sByteValue))
return (sbyte)(sByteValue * factor);
string hexValue = RemoveHexIdentifier(value);
if (sbyte.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out sbyte sByteHexValue))
return (sbyte)(sByteHexValue * factor);
return null;
}
else if (parts[i].StartsWith(shortFlagString + "=") || parts[i].StartsWith(longFlagString + "="))
{
@@ -720,7 +660,12 @@ namespace MPF.Core.Modules
this[longFlagString] = true;
(string value, long factor) = ExtractFactorFromValue(valuePart);
return (sbyte)(sbyte.Parse(value) * factor);
if (sbyte.TryParse(value, out sbyte sByteValue))
return (sbyte)(sByteValue * factor);
string hexValue = RemoveHexIdentifier(value);
if (sbyte.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out sbyte sByteHexValue))
return (sbyte)(sByteHexValue * factor);
return null;
}
return SByte.MinValue;
@@ -746,11 +691,7 @@ namespace MPF.Core.Modules
/// <param name="i">Reference to the position in the parts</param>
/// <param name="missingAllowed">True if missing values are allowed, false otherwise</param>
/// <returns>Int16 value if success, Int16.MinValue if skipped, null on error/returns>
#if NET48
protected short? ProcessInt16Parameter(List<string> parts, string shortFlagString, string longFlagString, ref int i, bool missingAllowed = false)
#else
protected short? ProcessInt16Parameter(List<string> parts, string? shortFlagString, string longFlagString, ref int i, bool missingAllowed = false)
#endif
{
if (parts == null)
return null;
@@ -786,7 +727,12 @@ namespace MPF.Core.Modules
this[longFlagString] = true;
i++;
(string value, long factor) = ExtractFactorFromValue(parts[i]);
return (short)(short.Parse(value) * factor);
if (short.TryParse(value, out short shortValue))
return (short)(shortValue * factor);
string hexValue = RemoveHexIdentifier(value);
if (short.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out short shortHexValue))
return (short)(shortHexValue * factor);
return null;
}
else if (parts[i].StartsWith(shortFlagString + "=") || parts[i].StartsWith(longFlagString + "="))
{
@@ -801,7 +747,12 @@ namespace MPF.Core.Modules
this[longFlagString] = true;
(string value, long factor) = ExtractFactorFromValue(valuePart);
return (short)(short.Parse(value) * factor);
if (short.TryParse(value, out short shortValue))
return (short)(shortValue * factor);
string hexValue = RemoveHexIdentifier(value);
if (short.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out short shortHexValue))
return (short)(shortHexValue * factor);
return null;
}
return Int16.MinValue;
@@ -827,11 +778,7 @@ namespace MPF.Core.Modules
/// <param name="i">Reference to the position in the parts</param>
/// <param name="missingAllowed">True if missing values are allowed, false otherwise</param>
/// <returns>Int32 value if success, Int32.MinValue if skipped, null on error/returns>
#if NET48
protected int? ProcessInt32Parameter(List<string> parts, string shortFlagString, string longFlagString, ref int i, bool missingAllowed = false)
#else
protected int? ProcessInt32Parameter(List<string> parts, string? shortFlagString, string longFlagString, ref int i, bool missingAllowed = false)
#endif
{
if (parts == null)
return null;
@@ -867,7 +814,12 @@ namespace MPF.Core.Modules
this[longFlagString] = true;
i++;
(string value, long factor) = ExtractFactorFromValue(parts[i]);
return (int)(int.Parse(value) * factor);
if (int.TryParse(value, out int intValue))
return (int)(intValue * factor);
string hexValue = RemoveHexIdentifier(value);
if (int.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out int intHexValue))
return (int)(intHexValue * factor);
return null;
}
else if (parts[i].StartsWith(shortFlagString + "=") || parts[i].StartsWith(longFlagString + "="))
{
@@ -882,7 +834,12 @@ namespace MPF.Core.Modules
this[longFlagString] = true;
(string value, long factor) = ExtractFactorFromValue(valuePart);
return (int)(int.Parse(value) * factor);
if (int.TryParse(value, out int intValue))
return (int)(intValue * factor);
string hexValue = RemoveHexIdentifier(value);
if (int.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out int intHexValue))
return (int)(intHexValue * factor);
return null;
}
return Int32.MinValue;
@@ -908,11 +865,7 @@ namespace MPF.Core.Modules
/// <param name="i">Reference to the position in the parts</param>
/// <param name="missingAllowed">True if missing values are allowed, false otherwise</param>
/// <returns>Int64 value if success, Int64.MinValue if skipped, null on error/returns>
#if NET48
protected long? ProcessInt64Parameter(List<string> parts, string shortFlagString, string longFlagString, ref int i, bool missingAllowed = false)
#else
protected long? ProcessInt64Parameter(List<string> parts, string? shortFlagString, string longFlagString, ref int i, bool missingAllowed = false)
#endif
{
if (parts == null)
return null;
@@ -948,7 +901,12 @@ namespace MPF.Core.Modules
this[longFlagString] = true;
i++;
(string value, long factor) = ExtractFactorFromValue(parts[i]);
return long.Parse(value) * factor;
if (long.TryParse(value, out long longValue))
return (long)(longValue * factor);
string hexValue = RemoveHexIdentifier(value);
if (long.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out long longHexValue))
return (long)(longHexValue * factor);
return null;
}
else if (parts[i].StartsWith(shortFlagString + "=") || parts[i].StartsWith(longFlagString + "="))
{
@@ -963,7 +921,12 @@ namespace MPF.Core.Modules
this[longFlagString] = true;
(string value, long factor) = ExtractFactorFromValue(valuePart);
return long.Parse(value) * factor;
if (long.TryParse(value, out long longValue))
return (long)(longValue * factor);
string hexValue = RemoveHexIdentifier(value);
if (long.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out long longHexValue))
return (long)(longHexValue * factor);
return null;
}
return Int64.MinValue;
@@ -977,12 +940,8 @@ namespace MPF.Core.Modules
/// <param name="i">Reference to the position in the parts</param>
/// <param name="missingAllowed">True if missing values are allowed, false otherwise</param>
/// <returns>String value if possible, string.Empty on missing, null on error</returns>
#if NET48
protected string ProcessStringParameter(List<string> parts, string flagString, ref int i, bool missingAllowed = false)
#else
protected string? ProcessStringParameter(List<string> parts, string flagString, ref int i, bool missingAllowed = false)
#endif
=> ProcessStringParameter(parts, null, flagString, ref i, missingAllowed);
=> ProcessStringParameter(parts, null, flagString, ref i, missingAllowed);
/// <summary>
/// Process a string parameter
@@ -993,11 +952,7 @@ namespace MPF.Core.Modules
/// <param name="i">Reference to the position in the parts</param>
/// <param name="missingAllowed">True if missing values are allowed, false otherwise</param>
/// <returns>String value if possible, string.Empty on missing, null on error</returns>
#if NET48
protected string ProcessStringParameter(List<string> parts, string shortFlagString, string longFlagString, ref int i, bool missingAllowed = false)
#else
protected string? ProcessStringParameter(List<string> parts, string? shortFlagString, string longFlagString, ref int i, bool missingAllowed = false)
#endif
{
if (parts == null)
return null;
@@ -1022,7 +977,7 @@ namespace MPF.Core.Modules
return null;
}
else if (string.IsNullOrWhiteSpace(parts[i + 1]))
else if (string.IsNullOrEmpty(parts[i + 1]))
{
if (missingAllowed)
this[longFlagString] = true;
@@ -1072,11 +1027,7 @@ namespace MPF.Core.Modules
/// <param name="i">Reference to the position in the parts</param>
/// <param name="missingAllowed">True if missing values are allowed, false otherwise</param>
/// <returns>Byte value if success, Byte.MinValue if skipped, null on error/returns>
#if NET48
protected byte? ProcessUInt8Parameter(List<string> parts, string shortFlagString, string longFlagString, ref int i, bool missingAllowed = false)
#else
protected byte? ProcessUInt8Parameter(List<string> parts, string? shortFlagString, string longFlagString, ref int i, bool missingAllowed = false)
#endif
{
if (parts == null)
return null;
@@ -1113,7 +1064,12 @@ namespace MPF.Core.Modules
i++;
(string value, long factor) = ExtractFactorFromValue(parts[i]);
return (byte)(byte.Parse(value) * factor);
if (byte.TryParse(value, out byte byteValue))
return (byte)(byteValue * factor);
string hexValue = RemoveHexIdentifier(value);
if (byte.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out byte byteHexValue))
return (byte)(byteHexValue * factor);
return null;
}
else if (parts[i].StartsWith(shortFlagString + "=") || parts[i].StartsWith(longFlagString + "="))
{
@@ -1128,14 +1084,19 @@ namespace MPF.Core.Modules
this[longFlagString] = true;
(string value, long factor) = ExtractFactorFromValue(valuePart);
return (byte)(byte.Parse(value) * factor);
if (byte.TryParse(value, out byte byteValue))
return (byte)(byteValue * factor);
string hexValue = RemoveHexIdentifier(value);
if (byte.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out byte byteHexValue))
return (byte)(byteHexValue * factor);
return null;
}
return Byte.MinValue;
}
/// <summary>
/// Get yhe trimmed value and multiplication factor from a value
/// Get the trimmed value and multiplication factor from a value
/// </summary>
/// <param name="value">String value to treat as suffixed number</param>
/// <returns>Trimmed value and multiplication factor</returns>
@@ -1196,6 +1157,58 @@ namespace MPF.Core.Modules
return (value, factor);
}
/// <summary>
/// Removes a leading 0x if it exists, case insensitive
/// </summary>
/// <param name="value">String with removed leading 0x</param>
/// <returns></returns>
private static string RemoveHexIdentifier(string value)
{
if (value.Length <= 2)
return value;
if (value[0] != '0')
return value;
if (value[1] != 'x' && value[1] != 'X')
return value;
return value.Substring(2);
}
#endregion
#region Methods to Move
/// <summary>
/// Get the hex contents of the PIC file
/// </summary>
/// <param name="picPath">Path to the PIC.bin file associated with the dump</param>
/// <param name="trimLength">Number of characters to trim the PIC to, if -1, ignored</param>
/// <returns>PIC data as a hex string if possible, null on error</returns>
/// <remarks>https://stackoverflow.com/questions/9932096/add-separator-to-string-at-every-n-characters</remarks>
protected static string? GetPIC(string picPath, int trimLength = -1)
{
// If the file doesn't exist, we can't get the info
if (!File.Exists(picPath))
return null;
try
{
var hex = GetFullFile(picPath, true);
if (hex == null)
return null;
if (trimLength > -1)
hex = hex.Substring(0, trimLength);
return Regex.Replace(hex, ".{32}", "$0\n", RegexOptions.Compiled);
}
catch
{
// We don't care what the error was right now
return null;
}
}
#endregion
}
}

View File

@@ -4,10 +4,9 @@ using System.Linq;
using System.Text.RegularExpressions;
using MPF.Core.Converters;
using MPF.Core.Data;
using SabreTools.RedumpLib;
using SabreTools.RedumpLib.Data;
#pragma warning disable IDE0051 // Remove unused private members
namespace MPF.Core.Modules.CleanRip
{
/// <summary>
@@ -23,18 +22,10 @@ namespace MPF.Core.Modules.CleanRip
#endregion
/// <inheritdoc/>
#if NET48
public Parameters(string parameters) : base(parameters) { }
#else
public Parameters(string? parameters) : base(parameters) { }
#endif
/// <inheritdoc/>
#if NET48
public Parameters(RedumpSystem? system, MediaType? type, string drivePath, string filename, int? driveSpeed, Options options)
#else
public Parameters(RedumpSystem? system, MediaType? type, string? drivePath, string filename, int? driveSpeed, Options options)
#endif
: base(system, type, drivePath, filename, driveSpeed, options)
{
}
@@ -69,33 +60,25 @@ namespace MPF.Core.Modules.CleanRip
}
/// <inheritdoc/>
#if NET48
public override void GenerateSubmissionInfo(SubmissionInfo info, Options options, string basePath, Drive drive, bool includeArtifacts)
#else
public override void GenerateSubmissionInfo(SubmissionInfo info, Options options, string basePath, Drive? drive, bool includeArtifacts)
#endif
{
// Ensure that required sections exist
info = SubmissionInfoTool.EnsureAllSections(info);
info = Builder.EnsureAllSections(info);
// TODO: Determine if there's a CleanRip version anywhere
#if NET48
info.DumpingInfo.DumpingProgram = EnumConverter.LongName(this.InternalProgram);
#else
info.DumpingInfo!.DumpingProgram = EnumConverter.LongName(this.InternalProgram);
#endif
info.DumpingInfo.DumpingDate = InfoTool.GetFileModifiedDate(basePath + "-dumpinfo.txt")?.ToString("yyyy-MM-dd HH:mm:ss");
// Get the Datafile information
var datafile = GenerateCleanripDatafile(basePath + ".iso", basePath + "-dumpinfo.txt");
// Fill in the hash data
info.TracksAndWriteOffsets!.ClrMameProData = InfoTool.GenerateDatfile(datafile);
// Get the individual hash data, as per internal
if (InfoTool.GetISOHashValues(datafile, out long size, out var crc32, out var md5, out var sha1))
{
#if NET48
info.SizeAndChecksums.Size = size;
#else
info.SizeAndChecksums!.Size = size;
#endif
info.SizeAndChecksums.CRC32 = crc32;
info.SizeAndChecksums.MD5 = md5;
info.SizeAndChecksums.SHA1 = sha1;
@@ -112,23 +95,14 @@ namespace MPF.Core.Modules.CleanRip
case MediaType.NintendoGameCubeGameDisc:
case MediaType.NintendoWiiOpticalDisc:
if (File.Exists(basePath + ".bca"))
#if NET48
info.Extras.BCA = GetBCA(basePath + ".bca");
#else
info.Extras!.BCA = GetBCA(basePath + ".bca");
#endif
if (GetGameCubeWiiInformation(basePath + "-dumpinfo.txt", out Region? gcRegion, out var gcVersion, out var gcName))
if (GetGameCubeWiiInformation(basePath + "-dumpinfo.txt", out Region? gcRegion, out var gcVersion, out var gcName, out var gcSerial))
{
#if NET48
info.CommonDiscInfo.Region = gcRegion ?? info.CommonDiscInfo.Region;
info.VersionAndEditions.Version = gcVersion ?? info.VersionAndEditions.Version;
info.CommonDiscInfo.CommentsSpecialFields[SiteCode.InternalName] = gcName ?? string.Empty;
#else
info.CommonDiscInfo!.Region = gcRegion ?? info.CommonDiscInfo.Region;
info.VersionAndEditions!.Version = gcVersion ?? info.VersionAndEditions.Version;
info.CommonDiscInfo.CommentsSpecialFields![SiteCode.InternalName] = gcName ?? string.Empty;
#endif
info.CommonDiscInfo.CommentsSpecialFields![SiteCode.InternalSerialName] = gcSerial ?? string.Empty;
}
break;
@@ -137,11 +111,7 @@ namespace MPF.Core.Modules.CleanRip
// Fill in any artifacts that exist, Base64-encoded, if we need to
if (includeArtifacts)
{
#if NET48
if (info.Artifacts == null) info.Artifacts = new Dictionary<string, string>();
#else
info.Artifacts ??= new Dictionary<string, string>();
#endif
info.Artifacts ??= [];
if (File.Exists(basePath + ".bca"))
info.Artifacts["bca"] = GetBase64(GetFullFile(basePath + ".bca", binary: true)) ?? string.Empty;
@@ -180,71 +150,56 @@ namespace MPF.Core.Modules.CleanRip
/// <param name="iso">Path to ISO file</param>
/// <param name="dumpinfo">Path to discinfo file</param>
/// <returns></returns>
#if NET48
private static Datafile GenerateCleanripDatafile(string iso, string dumpinfo)
#else
private static Datafile? GenerateCleanripDatafile(string iso, string dumpinfo)
#endif
{
// If the file doesn't exist, we can't get info from it
if (!File.Exists(dumpinfo))
// If the files don't exist, we can't get info from it
if (!File.Exists(iso) || !File.Exists(dumpinfo))
return null;
using (var sr = File.OpenText(dumpinfo))
long size = new FileInfo(iso).Length;
string crc = string.Empty;
string md5 = string.Empty;
string sha1 = string.Empty;
try
{
long size = new FileInfo(iso).Length;
string crc = string.Empty;
string md5 = string.Empty;
string sha1 = string.Empty;
try
{
// Make sure this file is a dumpinfo
if (sr.ReadLine()?.Contains("--File Generated by CleanRip") != true)
return null;
// Read all lines and gather dat information
while (!sr.EndOfStream)
{
var line = sr.ReadLine()?.Trim();
if (string.IsNullOrWhiteSpace(line))
continue;
#if NET48
else if (line.StartsWith("CRC32"))
crc = line.Substring(7).ToLowerInvariant();
else if (line.StartsWith("MD5"))
md5 = line.Substring(5);
else if (line.StartsWith("SHA-1"))
sha1 = line.Substring(7);
#else
else if (line.StartsWith("CRC32"))
crc = line[7..].ToLowerInvariant();
else if (line.StartsWith("MD5"))
md5 = line[5..];
else if (line.StartsWith("SHA-1"))
sha1 = line[7..];
#endif
}
return new Datafile
{
Games = new Game[]
{
new Game
{
Roms = new Rom[]
{
new Rom { Name = Path.GetFileName(iso), Size = size.ToString(), Crc = crc, Md5 = md5, Sha1 = sha1 },
}
}
}
};
}
catch
{
// We don't care what the exception is right now
// Make sure this file is a dumpinfo
using var sr = File.OpenText(dumpinfo);
if (sr.ReadLine()?.Contains("--File Generated by CleanRip") != true)
return null;
// Read all lines and gather dat information
while (!sr.EndOfStream)
{
var line = sr.ReadLine()?.Trim();
if (string.IsNullOrEmpty(line))
continue;
else if (line!.StartsWith("CRC32"))
crc = line.Substring(7).ToLowerInvariant();
else if (line.StartsWith("MD5"))
md5 = line.Substring(5);
else if (line.StartsWith("SHA-1"))
sha1 = line.Substring(7);
}
return new Datafile
{
Games =
[
new()
{
Roms =
[
new Rom { Name = Path.GetFileName(iso), Size = size.ToString(), Crc = crc, Md5 = md5, Sha1 = sha1 },
]
}
]
};
}
catch
{
// We don't care what the exception is right now
return null;
}
}
@@ -254,11 +209,7 @@ namespace MPF.Core.Modules.CleanRip
/// <param name="bcaPath">Path to the BCA file associated with the dump</param>
/// <returns>BCA data as a hex string if possible, null on error</returns>
/// <remarks>https://stackoverflow.com/questions/9932096/add-separator-to-string-at-every-n-characters</remarks>
#if NET48
private static string GetBCA(string bcaPath)
#else
private static string? GetBCA(string bcaPath)
#endif
{
// If the file doesn't exist, we can't get the info
if (!File.Exists(bcaPath))
@@ -285,59 +236,44 @@ namespace MPF.Core.Modules.CleanRip
/// <param name="iso">Path to ISO file</param>
/// <param name="dumpinfo">Path to discinfo file</param>
/// <returns></returns>
#if NET48
private static string GetCleanripDatfile(string iso, string dumpinfo)
#else
private static string? GetCleanripDatfile(string iso, string dumpinfo)
#endif
{
// If the file doesn't exist, we can't get info from it
if (!File.Exists(dumpinfo))
// If the files don't exist, we can't get info from it
if (!File.Exists(iso) || !File.Exists(dumpinfo))
return null;
using (var sr = File.OpenText(dumpinfo))
long size = new FileInfo(iso).Length;
string crc = string.Empty;
string md5 = string.Empty;
string sha1 = string.Empty;
try
{
long size = new FileInfo(iso).Length;
string crc = string.Empty;
string md5 = string.Empty;
string sha1 = string.Empty;
try
{
// Make sure this file is a dumpinfo
if (sr.ReadLine()?.Contains("--File Generated by CleanRip") != true)
return null;
// Read all lines and gather dat information
while (!sr.EndOfStream)
{
var line = sr.ReadLine()?.Trim();
if (string.IsNullOrWhiteSpace(line))
continue;
#if NET48
else if (line.StartsWith("CRC32"))
crc = line.Substring(7).ToLowerInvariant();
else if (line.StartsWith("MD5"))
md5 = line.Substring(5);
else if (line.StartsWith("SHA-1"))
sha1 = line.Substring(7);
#else
else if (line.StartsWith("CRC32"))
crc = line[7..].ToLowerInvariant();
else if (line.StartsWith("MD5"))
md5 = line[5..];
else if (line.StartsWith("SHA-1"))
sha1 = line[7..];
#endif
}
return $"<rom name=\"{Path.GetFileName(iso)}\" size=\"{size}\" crc=\"{crc}\" md5=\"{md5}\" sha1=\"{sha1}\" />";
}
catch
{
// We don't care what the exception is right now
// Make sure this file is a dumpinfo
using var sr = File.OpenText(dumpinfo);
if (sr.ReadLine()?.Contains("--File Generated by CleanRip") != true)
return null;
// Read all lines and gather dat information
while (!sr.EndOfStream)
{
var line = sr.ReadLine()?.Trim();
if (string.IsNullOrEmpty(line))
continue;
else if (line!.StartsWith("CRC32"))
crc = line.Substring(7).ToLowerInvariant();
else if (line.StartsWith("MD5"))
md5 = line.Substring(5);
else if (line.StartsWith("SHA-1"))
sha1 = line.Substring(7);
}
return $"<rom name=\"{Path.GetFileName(iso)}\" size=\"{size}\" crc=\"{crc}\" md5=\"{md5}\" sha1=\"{sha1}\" />";
}
catch
{
// We don't care what the exception is right now
return null;
}
}
@@ -348,124 +284,109 @@ namespace MPF.Core.Modules.CleanRip
/// <param name="region">Output region, if possible</param>
/// <param name="version">Output internal version of the game</param>
/// <param name="name">Output internal name of the game</param>
/// <param name="serial">Output internal serial of the game</param>
/// <returns></returns>
#if NET48
private static bool GetGameCubeWiiInformation(string dumpinfo, out Region? region, out string version, out string name)
#else
private static bool GetGameCubeWiiInformation(string dumpinfo, out Region? region, out string? version, out string? name)
#endif
private static bool GetGameCubeWiiInformation(string dumpinfo, out Region? region, out string? version, out string? name, out string? serial)
{
region = null; version = null; name = null;
region = null; version = null; name = null; serial = null;
// If the file doesn't exist, we can't get info from it
if (!File.Exists(dumpinfo))
return false;
using (var sr = File.OpenText(dumpinfo))
try
{
try
// Make sure this file is a dumpinfo
using var sr = File.OpenText(dumpinfo);
if (sr.ReadLine()?.Contains("--File Generated by CleanRip") != true)
return false;
// Read all lines and gather dat information
while (!sr.EndOfStream)
{
// Make sure this file is a dumpinfo
if (sr.ReadLine()?.Contains("--File Generated by CleanRip") != true)
return false;
// Read all lines and gather dat information
while (!sr.EndOfStream)
var line = sr.ReadLine()?.Trim();
if (string.IsNullOrEmpty(line))
{
var line = sr.ReadLine()?.Trim();
if (string.IsNullOrWhiteSpace(line))
{
continue;
}
else if (line.StartsWith("Version"))
{
#if NET48
version = line.Substring("Version: ".Length);
#else
version = line["Version: ".Length..];
#endif
}
else if (line.StartsWith("Internal Name"))
{
#if NET48
name = line.Substring("Internal Name: ".Length);
#else
name = line["Internal Name: ".Length..];
#endif
}
else if (line.StartsWith("Filename"))
{
#if NET48
string serial = line.Substring("Filename: ".Length);
#else
string serial = line["Filename: ".Length..];
#endif
continue;
}
else if (line!.StartsWith("Version"))
{
version = line.Substring("Version: ".Length);
}
else if (line.StartsWith("Internal Name"))
{
name = line.Substring("Internal Name: ".Length);
}
else if (line.StartsWith("Filename"))
{
serial = line.Substring("Filename: ".Length);
if (serial.EndsWith("-disc2"))
serial = serial.Replace("-disc2", string.Empty);
// char gameType = serial[0];
// string gameid = serial[1] + serial[2];
// string version = serial[4] + serial[5]
// char gameType = serial[0];
// string gameid = serial[1] + serial[2];
// string version = serial[4] + serial[5]
switch (serial[3])
{
case 'A':
region = Region.World;
break;
case 'D':
region = Region.Germany;
break;
case 'E':
region = Region.UnitedStatesOfAmerica;
break;
case 'F':
region = Region.France;
break;
case 'I':
region = Region.Italy;
break;
case 'J':
region = Region.Japan;
break;
case 'K':
region = Region.SouthKorea;
break;
case 'L':
region = Region.Europe; // Japanese import to Europe
break;
case 'M':
region = Region.Europe; // American import to Europe
break;
case 'N':
region = Region.UnitedStatesOfAmerica; // Japanese import to USA
break;
case 'P':
region = Region.Europe;
break;
case 'R':
region = Region.RussianFederation;
break;
case 'S':
region = Region.Spain;
break;
case 'Q':
region = Region.SouthKorea; // Korea with Japanese language
break;
case 'T':
region = Region.SouthKorea; // Korea with English language
break;
case 'X':
region = null; // Not a real region code
break;
}
switch (serial[3])
{
case 'A':
region = Region.World;
break;
case 'D':
region = Region.Germany;
break;
case 'E':
region = Region.UnitedStatesOfAmerica;
break;
case 'F':
region = Region.France;
break;
case 'I':
region = Region.Italy;
break;
case 'J':
region = Region.Japan;
break;
case 'K':
region = Region.SouthKorea;
break;
case 'L':
region = Region.Europe; // Japanese import to Europe
break;
case 'M':
region = Region.Europe; // American import to Europe
break;
case 'N':
region = Region.UnitedStatesOfAmerica; // Japanese import to USA
break;
case 'P':
region = Region.Europe;
break;
case 'R':
region = Region.RussianFederation;
break;
case 'S':
region = Region.Spain;
break;
case 'Q':
region = Region.SouthKorea; // Korea with Japanese language
break;
case 'T':
region = Region.SouthKorea; // Korea with English language
break;
case 'X':
region = null; // Not a real region code
break;
}
}
}
return true;
}
catch
{
// We don't care what the exception is right now
return false;
}
return true;
}
catch
{
// We don't care what the exception is right now
return false;
}
}

View File

@@ -6,139 +6,67 @@ namespace MPF.Core.Modules
public class Datafile
{
[XmlElement("header")]
#if NET48
public Header Header;
#else
public Header? Header;
#endif
[XmlElement("game")]
#if NET48
public Game[] Games;
#else
public Game[]? Games;
#endif
}
public class Header
{
[XmlElement("name")]
#if NET48
public string Name;
#else
public string? Name;
#endif
[XmlElement("description")]
#if NET48
public string Description;
#else
public string? Description;
#endif
[XmlElement("version")]
#if NET48
public string Version;
#else
public string? Version;
#endif
[XmlElement("date")]
#if NET48
public string Date;
#else
public string? Date;
#endif
[XmlElement("author")]
#if NET48
public string Author;
#else
public string? Author;
#endif
[XmlElement("homepage")]
#if NET48
public string Homepage;
#else
public string? Homepage;
#endif
[XmlElement("url")]
#if NET48
public string Url;
#else
public string? Url;
#endif
}
public class Game
{
[XmlAttribute("name")]
#if NET48
public string Name;
#else
public string? Name;
#endif
[XmlElement("category")]
#if NET48
public string Category;
#else
public string? Category;
#endif
[XmlElement("description")]
#if NET48
public string Description;
#else
public string? Description;
#endif
[XmlElement("rom")]
#if NET48
public Rom[] Roms;
#else
public Rom[]? Roms;
#endif
}
public class Rom
{
[XmlAttribute("name")]
#if NET48
public string Name;
#else
public string? Name;
#endif
[XmlAttribute("size")]
#if NET48
public string Size;
#else
public string? Size;
#endif
[XmlAttribute("crc")]
#if NET48
public string Crc;
#else
public string? Crc;
#endif
[XmlAttribute("md5")]
#if NET48
public string Md5;
#else
public string? Md5;
#endif
[XmlAttribute("sha1")]
#if NET48
public string Sha1;
#else
public string? Sha1;
#endif
// TODO: Add extended hashes here
}

View File

@@ -13,33 +13,25 @@ namespace MPF.Core.Modules.DiscImageCreator
/// <returns>RedumpSystem if possible, null on error</returns>
public static RedumpSystem? ToRedumpSystem(string baseCommand)
{
switch (baseCommand)
return baseCommand switch
{
case CommandStrings.Audio:
return RedumpSystem.AudioCD;
case CommandStrings.CompactDisc:
case CommandStrings.Data:
case CommandStrings.DigitalVideoDisc:
case CommandStrings.Disk:
case CommandStrings.Floppy:
case CommandStrings.Tape:
return RedumpSystem.IBMPCcompatible;
case CommandStrings.GDROM:
case CommandStrings.Swap:
return RedumpSystem.SegaDreamcast;
case CommandStrings.BluRay:
return RedumpSystem.SonyPlayStation3;
case CommandStrings.SACD:
return RedumpSystem.SuperAudioCD;
case CommandStrings.XBOX:
case CommandStrings.XBOXSwap:
return RedumpSystem.MicrosoftXbox;
case CommandStrings.XGD2Swap:
case CommandStrings.XGD3Swap:
return RedumpSystem.MicrosoftXbox360;
default:
return null;
}
CommandStrings.Audio => (RedumpSystem?)RedumpSystem.AudioCD,
CommandStrings.CompactDisc
or CommandStrings.Data
or CommandStrings.DigitalVideoDisc
or CommandStrings.Disk
or CommandStrings.Floppy
or CommandStrings.Tape => (RedumpSystem?)RedumpSystem.IBMPCcompatible,
CommandStrings.GDROM
or CommandStrings.Swap => (RedumpSystem?)RedumpSystem.SegaDreamcast,
CommandStrings.BluRay => (RedumpSystem?)RedumpSystem.SonyPlayStation3,
CommandStrings.SACD => (RedumpSystem?)RedumpSystem.SuperAudioCD,
CommandStrings.XBOX
or CommandStrings.XBOXSwap => (RedumpSystem?)RedumpSystem.MicrosoftXbox,
CommandStrings.XGD2Swap
or CommandStrings.XGD3Swap => (RedumpSystem?)RedumpSystem.MicrosoftXbox360,
_ => null,
};
}
/// <summary>
@@ -48,41 +40,29 @@ namespace MPF.Core.Modules.DiscImageCreator
/// <param name="baseCommand">Command value to check</param>
/// <returns>MediaType if possible, null on error</returns>
/// <remarks>This takes the "safe" route by assuming the larger of any given format</remarks>
#if NET48
public static MediaType? ToMediaType(string baseCommand)
#else
public static MediaType? ToMediaType(string? baseCommand)
#endif
{
switch (baseCommand)
return baseCommand switch
{
case CommandStrings.Audio:
case CommandStrings.CompactDisc:
case CommandStrings.Data:
case CommandStrings.SACD:
return MediaType.CDROM;
case CommandStrings.GDROM:
case CommandStrings.Swap:
return MediaType.GDROM;
case CommandStrings.DigitalVideoDisc:
case CommandStrings.XBOX:
case CommandStrings.XBOXSwap:
case CommandStrings.XGD2Swap:
case CommandStrings.XGD3Swap:
return MediaType.DVD;
case CommandStrings.BluRay:
return MediaType.BluRay;
CommandStrings.Audio
or CommandStrings.CompactDisc
or CommandStrings.Data
or CommandStrings.SACD => (MediaType?)MediaType.CDROM,
CommandStrings.GDROM
or CommandStrings.Swap => (MediaType?)MediaType.GDROM,
CommandStrings.DigitalVideoDisc
or CommandStrings.XBOX
or CommandStrings.XBOXSwap
or CommandStrings.XGD2Swap
or CommandStrings.XGD3Swap => (MediaType?)MediaType.DVD,
CommandStrings.BluRay => (MediaType?)MediaType.BluRay,
// Non-optical
case CommandStrings.Floppy:
return MediaType.FloppyDisk;
case CommandStrings.Disk:
return MediaType.HardDisk;
case CommandStrings.Tape:
return MediaType.DataCartridge;
default:
return null;
}
CommandStrings.Floppy => (MediaType?)MediaType.FloppyDisk,
CommandStrings.Disk => (MediaType?)MediaType.HardDisk,
CommandStrings.Tape => (MediaType?)MediaType.DataCartridge,
_ => null,
};
}
/// <summary>
@@ -90,41 +70,29 @@ namespace MPF.Core.Modules.DiscImageCreator
/// </summary>
/// <param name="type">MediaType value to check</param>
/// <returns>Valid extension (with leading '.'), null on error</returns>
#if NET48
public static string Extension(MediaType? type)
#else
public static string? Extension(MediaType? type)
#endif
{
switch (type)
return type switch
{
case MediaType.CDROM:
case MediaType.GDROM:
case MediaType.Cartridge:
case MediaType.HardDisk:
case MediaType.CompactFlash:
case MediaType.MMC:
case MediaType.SDCard:
case MediaType.FlashDrive:
return ".bin";
case MediaType.DVD:
case MediaType.HDDVD:
case MediaType.BluRay:
case MediaType.NintendoWiiOpticalDisc:
return ".iso";
case MediaType.LaserDisc:
case MediaType.NintendoGameCubeGameDisc:
return ".raw";
case MediaType.NintendoWiiUOpticalDisc:
return ".wud";
case MediaType.FloppyDisk:
return ".img";
case MediaType.Cassette:
return ".wav";
case MediaType.NONE:
default:
return null;
}
MediaType.CDROM
or MediaType.GDROM
or MediaType.Cartridge
or MediaType.HardDisk
or MediaType.CompactFlash
or MediaType.MMC
or MediaType.SDCard
or MediaType.FlashDrive => ".bin",
MediaType.DVD
or MediaType.HDDVD
or MediaType.BluRay
or MediaType.NintendoWiiOpticalDisc => ".iso",
MediaType.LaserDisc
or MediaType.NintendoGameCubeGameDisc => ".raw",
MediaType.NintendoWiiUOpticalDisc => ".wud",
MediaType.FloppyDisk => ".img",
MediaType.Cassette => ".wav",
_ => null,
};
}
#endregion

File diff suppressed because it is too large Load Diff

View File

@@ -10,14 +10,19 @@ namespace MPF.Core.Modules.Redumper
public const string DVD = "dvd"; // Synonym for CD
public const string BluRay = "bd"; // Synonym for CD
public const string SACD = "sacd"; // Synonym for CD
public const string Rings = "rings";
public const string Dump = "dump";
public const string Info = "info";
public const string Protection = "protection";
public const string DumpNew = "dumpnew"; // Temporary command, to be removed later
public const string Refine = "refine";
public const string Split = "split";
public const string RefineNew = "refinenew"; // Temporary command, to be removed later
public const string Verify = "verify";
public const string DVDKey = "dvdkey";
public const string DVDIsoKey = "dvdisokey";
public const string Protection = "protection";
public const string Split = "split";
public const string Hash = "hash";
public const string Info = "info";
public const string Skeleton = "skeleton";
}
/// <summary>
@@ -28,6 +33,7 @@ namespace MPF.Core.Modules.Redumper
// General
public const string HelpLong = "--help";
public const string HelpShort = "-h";
public const string Version = "--version";
public const string Verbose = "--verbose";
public const string Debug = "--debug";
public const string Drive = "--drive";
@@ -46,7 +52,7 @@ namespace MPF.Core.Modules.Redumper
public const string DriveSectorOrder = "--drive-sector-order";
// Drive Specific
public const string PlextorLeadinSkip = "--plextor-leadin-skip";
public const string PlextorSkipLeadin = "--plextor-skip-leadin";
public const string PlextorLeadinRetries = "--plextor-leadin-retries";
public const string AsusSkipLeadout = "--asus-skip-leadout";
@@ -68,7 +74,10 @@ namespace MPF.Core.Modules.Redumper
public const string LBAEnd = "--lba-end";
public const string RefineSubchannel = "--refine-subchannel";
public const string Skip = "--skip";
public const string DumpWriteOffset = "--dump-write-offset";
public const string DumpReadSize = "--dump-read-size";
public const string OverreadLeadout = "--overread-leadout";
public const string LegacySubs = "--legacy-subs";
public const string DisableCDText = "--disable-cdtext";
}
}

View File

@@ -11,24 +11,16 @@ namespace MPF.Core.Modules.Redumper
/// </summary>
/// <param name="type">MediaType value to check</param>
/// <returns>Valid extension (with leading '.'), null on error</returns>
#if NET48
public static string Extension(MediaType? type)
#else
public static string? Extension(MediaType? type)
#endif
{
switch (type)
return type switch
{
case MediaType.CDROM:
return ".bin";
case MediaType.DVD:
case MediaType.HDDVD:
case MediaType.BluRay:
return ".iso";
case MediaType.NONE:
default:
return null;
}
MediaType.CDROM => ".bin",
MediaType.DVD
or MediaType.HDDVD
or MediaType.BluRay => ".iso",
_ => null,
};
}
#endregion

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,8 @@ using System.IO;
using System.Linq;
using MPF.Core.Converters;
using MPF.Core.Data;
using MPF.Core.Hashing;
using SabreTools.RedumpLib;
using SabreTools.RedumpLib.Data;
namespace MPF.Core.Modules.UmdImageCreator
@@ -21,18 +23,10 @@ namespace MPF.Core.Modules.UmdImageCreator
#endregion
/// <inheritdoc/>
#if NET48
public Parameters(string parameters) : base(parameters) { }
#else
public Parameters(string? parameters) : base(parameters) { }
#endif
/// <inheritdoc/>
#if NET48
public Parameters(RedumpSystem? system, MediaType? type, string drivePath, string filename, int? driveSpeed, Options options)
#else
public Parameters(RedumpSystem? system, MediaType? type, string? drivePath, string filename, int? driveSpeed, Options options)
#endif
: base(system, type, drivePath, filename, driveSpeed, options)
{
}
@@ -69,40 +63,33 @@ namespace MPF.Core.Modules.UmdImageCreator
}
/// <inheritdoc/>
#if NET48
public override void GenerateSubmissionInfo(SubmissionInfo info, Options options, string basePath, Drive drive, bool includeArtifacts)
#else
public override void GenerateSubmissionInfo(SubmissionInfo info, Options options, string basePath, Drive? drive, bool includeArtifacts)
#endif
{
// Ensure that required sections exist
info = SubmissionInfoTool.EnsureAllSections(info);
info = Builder.EnsureAllSections(info);
// TODO: Determine if there's a UMDImageCreator version anywhere
#if NET48
info.DumpingInfo.DumpingProgram = EnumConverter.LongName(this.InternalProgram);
#else
info.DumpingInfo!.DumpingProgram = EnumConverter.LongName(this.InternalProgram);
#endif
info.DumpingInfo.DumpingDate = InfoTool.GetFileModifiedDate(basePath + "_disc.txt")?.ToString("yyyy-MM-dd HH:mm:ss");
// Extract info based generically on MediaType
switch (this.Type)
{
case MediaType.UMD:
#if NET48
info.Extras.PVD = GetPVD(basePath + "_mainInfo.txt") ?? string.Empty;
#else
info.Extras!.PVD = GetPVD(basePath + "_mainInfo.txt") ?? string.Empty;
#endif
if (InfoTool.GetFileHashes(basePath + ".iso", out long filesize, out var crc32, out var md5, out var sha1))
if (Hasher.GetFileHashes(basePath + ".iso", out long filesize, out var crc32, out var md5, out var sha1))
{
#if NET48
info.SizeAndChecksums.Size = filesize;
#else
// Get the Datafile information
var datafile = new Datafile
{
Games = [new Game { Roms = [new Rom { Name = string.Empty, Size = filesize.ToString(), Crc = crc32, Md5 = md5, Sha1 = sha1, }] }]
};
// Fill in the hash data
info.TracksAndWriteOffsets!.ClrMameProData = InfoTool.GenerateDatfile(datafile);
info.SizeAndChecksums!.Size = filesize;
#endif
info.SizeAndChecksums.CRC32 = crc32;
info.SizeAndChecksums.MD5 = md5;
info.SizeAndChecksums.SHA1 = sha1;
@@ -110,19 +97,12 @@ namespace MPF.Core.Modules.UmdImageCreator
if (GetUMDAuxInfo(basePath + "_disc.txt", out var title, out DiscCategory? umdcat, out var umdversion, out var umdlayer, out long umdsize))
{
#if NET48
info.CommonDiscInfo.Title = title ?? string.Empty;
info.CommonDiscInfo.Category = umdcat ?? DiscCategory.Games;
info.VersionAndEditions.Version = umdversion ?? string.Empty;
info.SizeAndChecksums.Size = umdsize;
#else
info.CommonDiscInfo!.Title = title ?? string.Empty;
info.CommonDiscInfo.Category = umdcat ?? DiscCategory.Games;
info.VersionAndEditions!.Version = umdversion ?? string.Empty;
info.SizeAndChecksums!.Size = umdsize;
#endif
if (!string.IsNullOrWhiteSpace(umdlayer))
if (!string.IsNullOrEmpty(umdlayer))
info.SizeAndChecksums.Layerbreak = Int64.Parse(umdlayer ?? "-1");
}
@@ -132,11 +112,7 @@ namespace MPF.Core.Modules.UmdImageCreator
// Fill in any artifacts that exist, Base64-encoded, if we need to
if (includeArtifacts)
{
#if NET48
if (info.Artifacts == null) info.Artifacts = new Dictionary<string, string>();
#else
info.Artifacts ??= new Dictionary<string, string>();
#endif
info.Artifacts ??= [];
if (File.Exists(basePath + "_disc.txt"))
info.Artifacts["disc"] = GetBase64(GetFullFile(basePath + "_disc.txt")) ?? string.Empty;
@@ -184,38 +160,32 @@ namespace MPF.Core.Modules.UmdImageCreator
/// </summary>
/// <param name="mainInfo">_mainInfo.txt file location</param>
/// <returns>Newline-deliminated PVD if possible, null on error</returns>
#if NET48
private static string GetPVD(string mainInfo)
#else
private static string? GetPVD(string mainInfo)
#endif
{
// If the file doesn't exist, we can't get info from it
if (!File.Exists(mainInfo))
return null;
using (var sr = File.OpenText(mainInfo))
try
{
try
{
// Make sure we're in the right sector
while (sr.ReadLine()?.StartsWith("========== LBA[000016, 0x0000010]: Main Channel ==========") == false) ;
// Make sure we're in the right sector
using var sr = File.OpenText(mainInfo);
while (sr.ReadLine()?.StartsWith("========== LBA[000016, 0x0000010]: Main Channel ==========") == false) ;
// Fast forward to the PVD
while (sr.ReadLine()?.StartsWith("0310") == false) ;
// Fast forward to the PVD
while (sr.ReadLine()?.StartsWith("0310") == false) ;
// Now that we're at the PVD, read each line in and concatenate
string pvd = "";
for (int i = 0; i < 6; i++)
pvd += sr.ReadLine() + "\n"; // 320-370
// Now that we're at the PVD, read each line in and concatenate
string pvd = "";
for (int i = 0; i < 6; i++)
pvd += sr.ReadLine() + "\n"; // 320-370
return pvd;
}
catch
{
// We don't care what the exception is right now
return null;
}
return pvd;
}
catch
{
// We don't care what the exception is right now
return null;
}
}
@@ -224,11 +194,7 @@ namespace MPF.Core.Modules.UmdImageCreator
/// </summary>
/// <param name="disc">_disc.txt file location</param>
/// <returns>True on successful extraction of info, false otherwise</returns>
#if NET48
private static bool GetUMDAuxInfo(string disc, out string title, out DiscCategory? umdcat, out string umdversion, out string umdlayer, out long umdsize)
#else
private static bool GetUMDAuxInfo(string disc, out string? title, out DiscCategory? umdcat, out string? umdversion, out string? umdlayer, out long umdsize)
#endif
{
title = null; umdcat = null; umdversion = null; umdlayer = null; umdsize = -1;
@@ -236,45 +202,38 @@ namespace MPF.Core.Modules.UmdImageCreator
if (!File.Exists(disc))
return false;
using (var sr = File.OpenText(disc))
try
{
try
// Loop through everything to get the first instance of each required field
using var sr = File.OpenText(disc);
while (!sr.EndOfStream)
{
// Loop through everything to get the first instance of each required field
var line = string.Empty;
while (!sr.EndOfStream)
{
line = sr.ReadLine()?.Trim();
if (line == null)
break;
string? line = sr.ReadLine()?.Trim();
if (line == null)
break;
if (line.StartsWith("TITLE") && title == null)
#if NET48
title = line.Substring("TITLE: ".Length);
#else
title = line["TITLE: ".Length..];
#endif
else if (line.StartsWith("DISC_VERSION") && umdversion == null)
umdversion = line.Split(' ')[1];
else if (line.StartsWith("pspUmdTypes"))
umdcat = InfoTool.GetUMDCategory(line.Split(' ')[1]);
else if (line.StartsWith("L0 length"))
umdlayer = line.Split(' ')[2];
else if (line.StartsWith("FileSize:"))
umdsize = Int64.Parse(line.Split(' ')[1]);
}
// If the L0 length is the size of the full disc, there's no layerbreak
if (Int64.TryParse(umdlayer, out long umdlayerValue) && umdlayerValue * 2048 == umdsize)
umdlayer = null;
return true;
}
catch
{
// We don't care what the exception is right now
return false;
if (line.StartsWith("TITLE") && title == null)
title = line.Substring("TITLE: ".Length);
else if (line.StartsWith("DISC_VERSION") && umdversion == null)
umdversion = line.Split(' ')[1];
else if (line.StartsWith("pspUmdTypes"))
umdcat = InfoTool.GetUMDCategory(line.Split(' ')[1]);
else if (line.StartsWith("L0 length"))
umdlayer = line.Split(' ')[2];
else if (line.StartsWith("FileSize:"))
umdsize = Int64.Parse(line.Split(' ')[1]);
}
// If the L0 length is the size of the full disc, there's no layerbreak
if (Int64.TryParse(umdlayer, out long umdlayerValue) && umdlayerValue * 2048 == umdsize)
umdlayer = null;
return true;
}
catch
{
// We don't care what the exception is right now
return false;
}
}

View File

@@ -4,10 +4,10 @@ using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using BinaryObjectScanner.Protection;
using BinaryObjectScanner;
using psxt001z;
#pragma warning disable SYSLIB1045 // Convert to 'GeneratedRegexAttribute'.
namespace MPF.Core
{
public static class Protection
@@ -19,17 +19,14 @@ namespace MPF.Core
/// <param name="options">Options object that determines what to scan</param>
/// <param name="progress">Optional progress callback</param>
/// <returns>Set of all detected copy protections with an optional error string</returns>
#if NET48
public static async Task<(Dictionary<string, List<string>>, string)> RunProtectionScanOnPath(string path, Data.Options options, IProgress<ProtectionProgress> progress = null)
#else
public static async Task<(Dictionary<string, List<string>>?, string?)> RunProtectionScanOnPath(string path, Data.Options options, IProgress<ProtectionProgress>? progress = null)
#endif
public static async Task<(Dictionary<string, List<string>>?, string?)> RunProtectionScanOnPath(string path, Data.Options options, IProgress<BinaryObjectScanner.ProtectionProgress>? progress = null)
{
try
{
var found = await Task.Run(() =>
#if NET40
var found = await Task.Factory.StartNew(() =>
{
var scanner = new Scanner(
var scanner = new BinaryObjectScanner.Scanner(
options.ScanArchivesForProtection,
scanContents: true, // Hardcoded value to avoid issues
scanGameEngines: false, // Hardcoded value to avoid issues
@@ -40,14 +37,37 @@ namespace MPF.Core
return scanner.GetProtections(path);
});
#else
var found = await Task.Run(() =>
{
var scanner = new BinaryObjectScanner.Scanner(
options.ScanArchivesForProtection,
scanContents: true, // Hardcoded value to avoid issues
scanGameEngines: false, // Hardcoded value to avoid issues
options.ScanPackersForProtection,
scanPaths: true, // Hardcoded value to avoid issues
options.IncludeDebugProtectionInformation,
progress);
return scanner.GetProtections(path);
});
#endif
// If nothing was returned, return
if (found == null || !found.Any())
#if NET20 || NET35
if (found == null || found.Count == 0)
#else
if (found == null || found.IsEmpty)
#endif
return (null, null);
// Filter out any empty protections
var filteredProtections = found
.Where(kvp => kvp.Value != null && kvp.Value.Any())
#if NET20 || NET35
.Where(kvp => kvp.Value != null && kvp.Value.Count > 0)
#else
.Where(kvp => kvp.Value != null && !kvp.Value.IsEmpty)
#endif
.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value.OrderBy(s => s).ToList());
@@ -66,11 +86,7 @@ namespace MPF.Core
/// </summary>
/// <param name="protections">Dictionary of file to list of protection mappings</param>
/// <returns>Detected protections, if any</returns>
#if NET48
public static string FormatProtections(Dictionary<string, List<string>> protections)
#else
public static string? FormatProtections(Dictionary<string, List<string>>? protections)
#endif
{
// If the filtered list is empty in some way, return
if (protections == null || !protections.Any())
@@ -84,7 +100,7 @@ namespace MPF.Core
// Sanitize and join protections for writing
string protectionString = SanitizeFoundProtections(orderedDistinctProtections);
if (string.IsNullOrWhiteSpace(protectionString))
if (string.IsNullOrEmpty(protectionString))
return "None found [OMIT FROM SUBMISSION]";
return protectionString;
@@ -95,28 +111,25 @@ namespace MPF.Core
/// </summary>
/// <param name="path">Path to scan for anti-modchip strings</param>
/// <returns>Anti-modchip existence if possible, false on error</returns>
#if NET48
public static async Task<bool> GetPlayStationAntiModchipDetected(string path)
#else
public static async Task<bool> GetPlayStationAntiModchipDetected(string? path)
#endif
{
// If there is no valid path
if (string.IsNullOrEmpty(path))
return false;
return await Task.Run(() =>
#if NET40
return await Task.Factory.StartNew(() =>
{
try
{
var antiModchip = new PSXAntiModchip();
var antiModchip = new BinaryObjectScanner.Protection.PSXAntiModchip();
foreach (string file in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories))
{
try
{
byte[] fileContent = File.ReadAllBytes(file);
var protection = antiModchip.CheckContents(file, fileContent, false);
if (!string.IsNullOrWhiteSpace(protection))
if (!string.IsNullOrEmpty(protection))
return true;
}
catch { }
@@ -126,6 +139,33 @@ namespace MPF.Core
return false;
});
#else
return await Task.Run(() =>
{
try
{
var antiModchip = new BinaryObjectScanner.Protection.PSXAntiModchip();
#if NET20 || NET35
foreach (string file in Directory.GetFiles(path, "*", SearchOption.AllDirectories))
#else
foreach (string file in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories))
#endif
{
try
{
byte[] fileContent = File.ReadAllBytes(file);
var protection = antiModchip.CheckContents(file, fileContent, false);
if (!string.IsNullOrEmpty(protection))
return true;
}
catch { }
}
}
catch { }
return false;
});
#endif
}
/// <summary>
@@ -139,7 +179,7 @@ namespace MPF.Core
if (!File.Exists(sub))
return null;
return LibCrypt.CheckSubfile(sub);
return LibCrypt.DetectLibCrypt([sub]);
}
/// <summary>
@@ -152,9 +192,15 @@ namespace MPF.Core
if (foundProtections.Any(p => p.StartsWith("[Exception opening file")))
{
foundProtections = foundProtections.Where(p => !p.StartsWith("[Exception opening file"));
#if NET20 || NET35 || NET40 || NET452 || NET462
var tempList = new List<string> { "Exception occurred while scanning [RESCAN NEEDED]" };
tempList.AddRange(foundProtections);
foundProtections = tempList.OrderBy(p => p);
#else
foundProtections = foundProtections
.Prepend("Exception occurred while scanning [RESCAN NEEDED]")
.OrderBy(p => p);
#endif
}
// ActiveMARK
@@ -162,7 +208,7 @@ namespace MPF.Core
foundProtections = foundProtections.Where(p => p != "ActiveMARK");
// Cactus Data Shield
if (foundProtections.Any(p => Regex.IsMatch(p, @"Cactus Data Shield [0-9]{3} .+")) && foundProtections.Any(p => p == "Cactus Data Shield 200"))
if (foundProtections.Any(p => Regex.IsMatch(p, @"Cactus Data Shield [0-9]{3} .+", RegexOptions.Compiled)) && foundProtections.Any(p => p == "Cactus Data Shield 200"))
foundProtections = foundProtections.Where(p => p != "Cactus Data Shield 200");
// CD-Check
@@ -192,7 +238,7 @@ namespace MPF.Core
// JoWood X-Prot
if (foundProtections.Any(p => p.StartsWith("JoWood X-Prot")))
{
if (foundProtections.Any(p => Regex.IsMatch(p, @"JoWood X-Prot [0-9]\.[0-9]\.[0-9]\.[0-9]{2}")))
if (foundProtections.Any(p => Regex.IsMatch(p, @"JoWood X-Prot [0-9]\.[0-9]\.[0-9]\.[0-9]{2}", RegexOptions.Compiled)))
{
foundProtections = foundProtections.Where(p => p != "JoWood X-Prot")
.Where(p => p != "JoWood X-Prot v1.0-v1.3")
@@ -235,34 +281,43 @@ namespace MPF.Core
.Where(p => p != "Cactus Data Shield 300 (Confirm presence of other CDS-300 files)");
if (foundProtections.Any(p => !p.StartsWith("SafeDisc")))
{
#if NET20 || NET35 || NET40 || NET452 || NET462
var tempList = new List<string>();
tempList.AddRange(foundProtections);
tempList.Add("Cactus Data Shield 300");
foundProtections = tempList;
#else
foundProtections = foundProtections.Append("Cactus Data Shield 300");
#endif
}
}
// SafeDisc
if (foundProtections.Any(p => p.StartsWith("SafeDisc")))
{
if (foundProtections.Any(p => Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}")))
if (foundProtections.Any(p => Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}", RegexOptions.Compiled)))
{
foundProtections = foundProtections.Where(p => !p.StartsWith("Macrovision Protected Application"))
.Where(p => !p.StartsWith("Macrovision Protection File"))
.Where(p => !p.StartsWith("Macrovision Security Driver"))
.Where(p => p != "SafeDisc")
.Where(p => !(Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}-[0-9]\.[0-9]{2}\.[0-9]{3}")))
.Where(p => !(Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}/+")))
.Where(p => !(Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}-[0-9]\.[0-9]{2}\.[0-9]{3}", RegexOptions.Compiled)))
.Where(p => !(Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}/+", RegexOptions.Compiled)))
.Where(p => p != "SafeDisc 1/Lite")
.Where(p => p != "SafeDisc 2+");
}
else if (foundProtections.Any(p => Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}-[0-9]\.[0-9]{2}\.[0-9]{3}")))
else if (foundProtections.Any(p => Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}-[0-9]\.[0-9]{2}\.[0-9]{3}", RegexOptions.Compiled)))
{
foundProtections = foundProtections.Where(p => !p.StartsWith("Macrovision Protected Application"))
.Where(p => !p.StartsWith("Macrovision Protection File"))
.Where(p => !p.StartsWith("Macrovision Security Driver"))
.Where(p => p != "SafeDisc")
.Where(p => !(Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}/+")))
.Where(p => !(Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}/+", RegexOptions.Compiled)))
.Where(p => p != "SafeDisc 1/Lite")
.Where(p => p != "SafeDisc 2+");
}
else if (foundProtections.Any(p => Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}/+")))
else if (foundProtections.Any(p => Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}/+", RegexOptions.Compiled)))
{
foundProtections = foundProtections.Where(p => !p.StartsWith("Macrovision Protected Application"))
.Where(p => !p.StartsWith("Macrovision Protection File"))
@@ -302,7 +357,7 @@ namespace MPF.Core
// StarForce
if (foundProtections.Any(p => p.StartsWith("StarForce")))
{
if (foundProtections.Any(p => Regex.IsMatch(p, @"StarForce [0-9]+\..+")))
if (foundProtections.Any(p => Regex.IsMatch(p, @"StarForce [0-9]+\..+", RegexOptions.Compiled)))
{
foundProtections = foundProtections.Where(p => p != "StarForce")
.Where(p => p != "StarForce 3-5")
@@ -337,7 +392,7 @@ namespace MPF.Core
if (foundProtections.Any(p => p == "XCP") && foundProtections.Any(p => p.StartsWith("XCP") && p.Length > "XCP".Length))
foundProtections = foundProtections.Where(p => p != "XCP");
return string.Join(", ", foundProtections);
return string.Join(", ", foundProtections.ToArray());
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -49,16 +49,21 @@ namespace MPF.Core.UI.ComboBoxItems
}
/// <inheritdoc/>
#if NET48
public bool Equals(Element<T> other)
#else
public override bool Equals(object? obj)
{
return Equals(obj as Element<T>);
}
/// <inheritdoc/>
public bool Equals(Element<T>? other)
#endif
{
if (other == null)
return false;
return Name == other.Name;
}
/// <inheritdoc/>
public override int GetHashCode() => base.GetHashCode();
}
}

View File

@@ -10,11 +10,7 @@ namespace MPF.Core.UI.ComboBoxItems
/// </summary>
public class RedumpSystemComboBoxItem : IEquatable<RedumpSystemComboBoxItem>, IElement
{
#if NET48
private readonly object Data;
#else
private readonly object? Data;
#endif
public RedumpSystemComboBoxItem(RedumpSystem? system) => Data = system;
public RedumpSystemComboBoxItem(SystemCategory? category) => Data = category;
@@ -85,16 +81,21 @@ namespace MPF.Core.UI.ComboBoxItems
}
/// <inheritdoc/>
#if NET48
public bool Equals(RedumpSystemComboBoxItem other)
#else
public override bool Equals(object? obj)
{
return Equals(obj as RedumpSystemComboBoxItem);
}
/// <inheritdoc/>
public bool Equals(RedumpSystemComboBoxItem? other)
#endif
{
if (other == null)
return false;
return Value == other.Value;
}
/// <inheritdoc/>
public override int GetHashCode() => base.GetHashCode();
}
}

View File

@@ -0,0 +1,432 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using BinaryObjectScanner;
using MPF.Core.Data;
using MPF.Core.UI.ComboBoxItems;
using MPF.Core.Utilities;
using SabreTools.RedumpLib.Data;
namespace MPF.Core.UI.ViewModels
{
/// <summary>
/// Constructor
/// </summary>
public class CheckDumpViewModel : INotifyPropertyChanged
{
#region Fields
/// <summary>
/// Access to the current options
/// </summary>
public Data.Options Options
{
get => _options;
}
private readonly Data.Options _options;
/// <summary>
/// Indicates if SelectionChanged events can be executed
/// </summary>
public bool CanExecuteSelectionChanged { get; private set; } = false;
/// <inheritdoc/>
public event PropertyChangedEventHandler? PropertyChanged;
#endregion
#region Properties
/// <summary>
/// Currently selected system value
/// </summary>
public RedumpSystem? CurrentSystem
{
get => _currentSystem;
set
{
_currentSystem = value;
TriggerPropertyChanged(nameof(CurrentSystem));
}
}
private RedumpSystem? _currentSystem;
/// <summary>
/// Indicates the status of the system type combo box
/// </summary>
public bool SystemTypeComboBoxEnabled
{
get => _systemTypeComboBoxEnabled;
set
{
_systemTypeComboBoxEnabled = value;
TriggerPropertyChanged(nameof(SystemTypeComboBoxEnabled));
}
}
private bool _systemTypeComboBoxEnabled;
/// <summary>
/// Currently selected media type value
/// </summary>
public MediaType? CurrentMediaType
{
get => _currentMediaType;
set
{
_currentMediaType = value;
TriggerPropertyChanged(nameof(CurrentMediaType));
}
}
private MediaType? _currentMediaType;
/// <summary>
/// Indicates the status of the media type combo box
/// </summary>
public bool MediaTypeComboBoxEnabled
{
get => _mediaTypeComboBoxEnabled;
set
{
_mediaTypeComboBoxEnabled = value;
TriggerPropertyChanged(nameof(MediaTypeComboBoxEnabled));
}
}
private bool _mediaTypeComboBoxEnabled;
/// <summary>
/// Currently provided input path
/// </summary>
public string? InputPath
{
get => _inputPath;
set
{
_inputPath = value;
TriggerPropertyChanged(nameof(InputPath));
}
}
private string? _inputPath;
/// <summary>
/// Indicates the status of the input path text box
/// </summary>
public bool InputPathTextBoxEnabled
{
get => _inputPathTextBoxEnabled;
set
{
_inputPathTextBoxEnabled = value;
TriggerPropertyChanged(nameof(InputPathTextBoxEnabled));
}
}
private bool _inputPathTextBoxEnabled;
/// <summary>
/// Indicates the status of the input path browse button
/// </summary>
public bool InputPathBrowseButtonEnabled
{
get => _inputPathBrowseButtonEnabled;
set
{
_inputPathBrowseButtonEnabled = value;
TriggerPropertyChanged(nameof(InputPathBrowseButtonEnabled));
}
}
private bool _inputPathBrowseButtonEnabled;
/// <summary>
/// Currently selected dumping program
/// </summary>
public InternalProgram CurrentProgram
{
get => _currentProgram;
set
{
_currentProgram = value;
TriggerPropertyChanged(nameof(CurrentProgram));
}
}
private InternalProgram _currentProgram;
/// <summary>
/// Indicates the status of the dumping program combo box
/// </summary>
public bool DumpingProgramComboBoxEnabled
{
get => _dumpingProgramComboBoxEnabled;
set
{
_dumpingProgramComboBoxEnabled = value;
TriggerPropertyChanged(nameof(DumpingProgramComboBoxEnabled));
}
}
private bool _dumpingProgramComboBoxEnabled;
/// <summary>
/// Indicates the status of the check dump button
/// </summary>
public bool CheckDumpButtonEnabled
{
get => _checkDumpButtonEnabled;
set
{
_checkDumpButtonEnabled = value;
TriggerPropertyChanged(nameof(CheckDumpButtonEnabled));
}
}
private bool _checkDumpButtonEnabled;
#endregion
#region List Properties
/// <summary>
/// Current list of supported media types
/// </summary>
public List<Element<MediaType>>? MediaTypes
{
get => _mediaTypes;
set
{
_mediaTypes = value;
TriggerPropertyChanged(nameof(MediaTypes));
}
}
private List<Element<MediaType>>? _mediaTypes;
/// <summary>
/// Current list of supported system profiles
/// </summary>
public List<RedumpSystemComboBoxItem> Systems
{
get => _systems;
set
{
_systems = value;
TriggerPropertyChanged(nameof(Systems));
}
}
private List<RedumpSystemComboBoxItem> _systems;
/// <summary>
/// List of available internal programs
/// </summary>
public List<Element<InternalProgram>> InternalPrograms
{
get => _internalPrograms;
set
{
_internalPrograms = value;
TriggerPropertyChanged(nameof(InternalPrograms));
}
}
private List<Element<InternalProgram>> _internalPrograms;
#endregion
/// <summary>
/// Constructor for pure view model
/// </summary>
public CheckDumpViewModel()
{
_options = OptionsLoader.LoadFromConfig();
_internalPrograms = [];
_inputPath = string.Empty;
_systems = [];
SystemTypeComboBoxEnabled = true;
InputPathTextBoxEnabled = true;
InputPathBrowseButtonEnabled = true;
MediaTypeComboBoxEnabled = true;
DumpingProgramComboBoxEnabled = true;
CheckDumpButtonEnabled = false;
MediaTypes = [];
Systems = RedumpSystemComboBoxItem.GenerateElements().ToList();
InternalPrograms = [];
PopulateMediaType();
PopulateInternalPrograms();
EnableEventHandlers();
}
#region Property Updates
/// <summary>
/// Trigger a property changed event
/// </summary>
private void TriggerPropertyChanged(string propertyName)
{
// Disable event handlers temporarily
bool cachedCanExecuteSelectionChanged = CanExecuteSelectionChanged;
DisableEventHandlers();
// If the property change event is initialized
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
// Reenable event handlers, if necessary
if (cachedCanExecuteSelectionChanged) EnableEventHandlers();
}
#endregion
#region UI Commands
/// <summary>
/// Change the currently selected system
/// </summary>
public void ChangeSystem()
{
PopulateMediaType();
this.CheckDumpButtonEnabled = ShouldEnableCheckDumpButton();
}
/// <summary>
/// Change the currently selected media type
/// </summary>
public void ChangeMediaType()
{
this.CheckDumpButtonEnabled = ShouldEnableCheckDumpButton();
}
/// <summary>
/// Change the currently selected dumping program
/// </summary>
public void ChangeDumpingProgram()
{
this.CheckDumpButtonEnabled = ShouldEnableCheckDumpButton();
}
/// <summary>
/// Change the currently selected input path
/// </summary>
public void ChangeInputPath()
{
this.CheckDumpButtonEnabled = ShouldEnableCheckDumpButton();
}
#endregion
#region Population
/// <summary>
/// Populate media type according to system type
/// </summary>
private void PopulateMediaType()
{
// Disable other UI updates
bool cachedCanExecuteSelectionChanged = CanExecuteSelectionChanged;
DisableEventHandlers();
if (this.CurrentSystem != null)
{
var mediaTypeValues = this.CurrentSystem.MediaTypes();
int index = mediaTypeValues.FindIndex(m => m == this.CurrentMediaType);
MediaTypes = mediaTypeValues.Select(m => new Element<MediaType>(m ?? MediaType.NONE)).ToList();
this.MediaTypeComboBoxEnabled = MediaTypes.Count > 1;
this.CurrentMediaType = (index > -1 ? MediaTypes[index] : MediaTypes[0]);
}
else
{
this.MediaTypeComboBoxEnabled = false;
this.MediaTypes = null;
this.CurrentMediaType = null;
}
// Reenable event handlers, if necessary
if (cachedCanExecuteSelectionChanged) EnableEventHandlers();
}
/// <summary>
/// Populate media type according to system type
/// </summary>
private void PopulateInternalPrograms()
{
// Disable other UI updates
bool cachedCanExecuteSelectionChanged = CanExecuteSelectionChanged;
DisableEventHandlers();
// Get the current internal program
InternalProgram internalProgram = this.Options.InternalProgram;
// Create a static list of supported Check programs, not everything
var internalPrograms = new List<InternalProgram> { InternalProgram.Redumper, InternalProgram.DiscImageCreator, InternalProgram.Aaru, InternalProgram.CleanRip, InternalProgram.UmdImageCreator };
InternalPrograms = internalPrograms.Select(ip => new Element<InternalProgram>(ip)).ToList();
// Select the current default dumping program
int currentIndex = InternalPrograms.FindIndex(m => m == internalProgram);
this.CurrentProgram = (currentIndex > -1 ? InternalPrograms[currentIndex].Value : InternalPrograms[0].Value);
// Reenable event handlers, if necessary
if (cachedCanExecuteSelectionChanged) EnableEventHandlers();
}
#endregion
#region UI Functionality
private bool ShouldEnableCheckDumpButton()
{
return this.CurrentSystem != null && this.CurrentMediaType != null && !string.IsNullOrEmpty(this.InputPath);
}
/// <summary>
/// Enable all textbox and combobox event handlers
/// </summary>
private void EnableEventHandlers()
{
CanExecuteSelectionChanged = true;
}
/// <summary>
/// Disable all textbox and combobox event handlers
/// </summary>
private void DisableEventHandlers()
{
CanExecuteSelectionChanged = false;
}
#endregion
#region MPF.Check
/// <summary>
/// Performs MPF.Check functionality
/// </summary>
/// <returns>An error message if failed, otherwise string.Empty/null</returns>
public string? CheckDump(Func<SubmissionInfo?, (bool?, SubmissionInfo?)> processUserInfo)
{
if (string.IsNullOrEmpty(InputPath))
return "Invalid Input path";
if (!File.Exists(this.InputPath!.Trim('"')))
return "Input Path is not a valid file";
// Populate an environment
var env = new DumpEnvironment(Options, Path.GetFullPath(this.InputPath.Trim('"')), null, this.CurrentSystem, this.CurrentMediaType, this.CurrentProgram, parameters: null);
// Make new Progress objects
var resultProgress = new Progress<Result>();
resultProgress.ProgressChanged += ConsoleLogger.ProgressUpdated;
var protectionProgress = new Progress<ProtectionProgress>();
protectionProgress.ProgressChanged += ConsoleLogger.ProgressUpdated;
// Finally, attempt to do the output dance
#if NET40
var resultTask = env.VerifyAndSaveDumpOutput(resultProgress, protectionProgress);
resultTask.Wait();
var result = resultTask.Result;
#else
var result = env.VerifyAndSaveDumpOutput(resultProgress, protectionProgress, processUserInfo).ConfigureAwait(false).GetAwaiter().GetResult();
#endif
return result.Message;
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -38,11 +38,7 @@ namespace MPF.Core.UI.ViewModels
/// <summary>
/// List of Redump-supported Regions
/// </summary>
#if NET48
private static readonly List<Region> RedumpRegions = new List<Region>
#else
private static readonly List<Region> RedumpRegions = new()
#endif
{
Region.Argentina,
Region.Asia,
@@ -133,11 +129,7 @@ namespace MPF.Core.UI.ViewModels
/// <summary>
/// List of Redump-supported Languages
/// </summary>
#if NET48
private static readonly List<Language> RedumpLanguages = new List<Language>
#else
private static readonly List<Language> RedumpLanguages = new()
#endif
{
Language.Afrikaans,
Language.Albanian,
@@ -199,11 +191,7 @@ namespace MPF.Core.UI.ViewModels
/// <summary>
/// Constructor
/// </summary>
#if NET48
public DiscInformationViewModel(Options options, SubmissionInfo submissionInfo)
#else
public DiscInformationViewModel(Options options, SubmissionInfo? submissionInfo)
#endif
{
Options = options;
SubmissionInfo = submissionInfo?.Clone() as SubmissionInfo ?? new SubmissionInfo();
@@ -229,11 +217,13 @@ namespace MPF.Core.UI.ViewModels
/// TODO: Convert selected list item to binding
public void Save()
{
if (SubmissionInfo.CommonDiscInfo == null) SubmissionInfo.CommonDiscInfo = new CommonDiscInfoSection();
if (SubmissionInfo.CommonDiscInfo == null)
SubmissionInfo.CommonDiscInfo = new CommonDiscInfoSection();
SubmissionInfo.CommonDiscInfo.Languages = Languages.Where(l => l.IsChecked).Select(l => l?.Value).ToArray();
if (!SubmissionInfo.CommonDiscInfo.Languages.Any())
SubmissionInfo.CommonDiscInfo.Languages = new Language?[] { null };
SubmissionInfo.CommonDiscInfo.Languages = [null];
SubmissionInfo.CommonDiscInfo.LanguageSelection = LanguageSelections.Where(ls => ls.IsChecked).Select(ls => ls?.Value).ToArray();
SubmissionInfo.CommonDiscInfo.Title = InfoTool.NormalizeDiscTitle(SubmissionInfo.CommonDiscInfo.Title, SubmissionInfo.CommonDiscInfo.Languages);
}
/// <summary>

View File

@@ -7,8 +7,9 @@ using System.Threading.Tasks;
using BinaryObjectScanner;
using MPF.Core.Converters;
using MPF.Core.Data;
using MPF.Core.Utilities;
using MPF.Core.Modules;
using MPF.Core.UI.ComboBoxItems;
using MPF.Core.Utilities;
using SabreTools.RedumpLib.Data;
namespace MPF.Core.UI.ViewModels
@@ -37,36 +38,24 @@ namespace MPF.Core.UI.ViewModels
public bool CanExecuteSelectionChanged { get; private set; } = false;
/// <inheritdoc/>
#if NET48
public event PropertyChangedEventHandler PropertyChanged;
#else
public event PropertyChangedEventHandler? PropertyChanged;
#endif
/// <summary>
/// Action to process logging statements
/// </summary>
#if NET48
private Action<LogLevel, string> _logger;
#else
private Action<LogLevel, string>? _logger;
#endif
/// <summary>
/// Display a message to a user
/// </summary>
/// <remarks>
/// T1 - Title to display to the user
/// T1 - Message to display to the user
/// T2 - Message to display to the user
/// T3 - Number of default options to display
/// T4 - true for inquiry, false otherwise
/// TResult - true for positive, false for negative, null for neutral
/// </remarks>
#if NET48
private Func<string, string, int, bool, bool?> _displayUserMessage;
#else
private Func<string, string, int, bool, bool?>? _displayUserMessage;
#endif
/// <summary>
/// Detected media type, distinct from the selected one
@@ -76,25 +65,59 @@ namespace MPF.Core.UI.ViewModels
/// <summary>
/// Current dumping environment
/// </summary>
#if NET48
private DumpEnvironment _environment;
#else
private DumpEnvironment? _environment;
#endif
/// <summary>
/// Function to process user information
/// </summary>
#if NET48
private Func<SubmissionInfo, (bool?, SubmissionInfo)> _processUserInfo;
#else
private Func<SubmissionInfo?, (bool?, SubmissionInfo?)>? _processUserInfo;
#endif
#endregion
#region Properties
/// <summary>
/// Indicates the inability to create IRDs
/// </summary>
public bool CannotCreateIRD
{
get => _cannotCreateIRD;
set
{
_cannotCreateIRD = value;
TriggerPropertyChanged(nameof(CannotCreateIRD));
}
}
private bool _cannotCreateIRD;
/// <summary>
/// Indicates the status of the check dump menu item
/// </summary>
public bool CheckDumpMenuItemEnabled
{
get => _checkDumpMenuItemEnabled;
set
{
_checkDumpMenuItemEnabled = value;
TriggerPropertyChanged(nameof(CheckDumpMenuItemEnabled));
}
}
private bool _checkDumpMenuItemEnabled;
/// <summary>
/// Indicates the status of the create IRD menu item
/// </summary>
public bool CreateIRDMenuItemEnabled
{
get => _createIRDMenuItemEnabled;
set
{
_createIRDMenuItemEnabled = value;
TriggerPropertyChanged(nameof(CreateIRDMenuItemEnabled));
}
}
private bool _createIRDMenuItemEnabled;
/// <summary>
/// Indicates the status of the options menu item
/// </summary>
@@ -167,6 +190,7 @@ namespace MPF.Core.UI.ViewModels
/// <summary>
/// Currently provided output path
/// Not guaranteed to be a valid path
/// </summary>
public string OutputPath
{
@@ -210,11 +234,7 @@ namespace MPF.Core.UI.ViewModels
/// <summary>
/// Currently selected drive value
/// </summary>
#if NET48
public Drive CurrentDrive
#else
public Drive? CurrentDrive
#endif
{
get => _currentDrive;
set
@@ -223,11 +243,7 @@ namespace MPF.Core.UI.ViewModels
TriggerPropertyChanged(nameof(CurrentDrive));
}
}
#if NET48
private Drive _currentDrive;
#else
private Drive? _currentDrive;
#endif
/// <summary>
/// Indicates the status of the drive combo box
@@ -474,11 +490,7 @@ namespace MPF.Core.UI.ViewModels
/// <summary>
/// Current list of supported media types
/// </summary>
#if NET48
public List<Element<MediaType>> MediaTypes
#else
public List<Element<MediaType>>? MediaTypes
#endif
{
get => _mediaTypes;
set
@@ -487,11 +499,7 @@ namespace MPF.Core.UI.ViewModels
TriggerPropertyChanged(nameof(MediaTypes));
}
}
#if NET48
private List<Element<MediaType>> _mediaTypes;
#else
private List<Element<MediaType>>? _mediaTypes;
#endif
/// <summary>
/// Current list of supported system profiles
@@ -531,16 +539,24 @@ namespace MPF.Core.UI.ViewModels
_options = OptionsLoader.LoadFromConfig();
// Added to clear warnings, all are set externally
_drives = new List<Drive>();
_driveSpeeds = new List<int>();
_internalPrograms = new List<Element<InternalProgram>>();
_drives = [];
_driveSpeeds = [];
_internalPrograms = [];
_outputPath = string.Empty;
_parameters = string.Empty;
_startStopButtonText = string.Empty;
_status = string.Empty;
_systems = new List<RedumpSystemComboBoxItem>();
_systems = [];
OptionsMenuItemEnabled = true;
CheckDumpMenuItemEnabled = true;
#if NET6_0_OR_GREATER
CreateIRDMenuItemEnabled = true;
CannotCreateIRD = false;
#else
CreateIRDMenuItemEnabled = false;
CannotCreateIRD = true;
#endif
SystemTypeComboBoxEnabled = true;
MediaTypeComboBoxEnabled = true;
OutputPathTextBoxEnabled = true;
@@ -553,9 +569,9 @@ namespace MPF.Core.UI.ViewModels
EnableParametersCheckBoxEnabled = true;
LogPanelExpanded = _options.OpenLogWindowAtStartup;
MediaTypes = new List<Element<MediaType>>();
MediaTypes = [];
Systems = RedumpSystemComboBoxItem.GenerateElements().ToList();
InternalPrograms = new List<Element<InternalProgram>>();
InternalPrograms = [];
}
/// <summary>
@@ -564,11 +580,7 @@ namespace MPF.Core.UI.ViewModels
public void Init(
Action<LogLevel, string> loggerAction,
Func<string, string, int, bool, bool?> displayUserMessage,
#if NET48
Func<SubmissionInfo, (bool?, SubmissionInfo)> processUserInfo)
#else
Func<SubmissionInfo?, (bool?, SubmissionInfo?)> processUserInfo)
#endif
{
// Set the callbacks
_logger = loggerAction;
@@ -625,7 +637,7 @@ namespace MPF.Core.UI.ViewModels
if (Drives.Count > 0)
{
VerboseLogLn($"Found {Drives.Count} drives: {string.Join(", ", Drives.Select(d => d.Name))}");
VerboseLogLn($"Found {Drives.Count} drives: {string.Join(", ", Drives.Select(d => d.Name).ToArray())}");
// Check for the last selected drive, if possible
int index = -1;
@@ -709,16 +721,23 @@ namespace MPF.Core.UI.ViewModels
bool cachedCanExecuteSelectionChanged = CanExecuteSelectionChanged;
DisableEventHandlers();
// Create a static list of supported programs, not everything
InternalPrograms = Enum.GetValues(typeof(InternalProgram)).Cast<InternalProgram>().Where(ip => InternalProgramExists(ip)).Select(ip => new Element<InternalProgram>(ip)).ToList();
// Get the current internal program
InternalProgram internalProgram = this.Options.InternalProgram;
// Create a static list of supported programs, not everything
var internalPrograms = new List<InternalProgram> { InternalProgram.DiscImageCreator, InternalProgram.Aaru, InternalProgram.Redumper };
InternalPrograms = internalPrograms.Select(ip => new Element<InternalProgram>(ip)).ToList();
// Select the current default dumping program
int currentIndex = InternalPrograms.FindIndex(m => m == internalProgram);
this.CurrentProgram = (currentIndex > -1 ? InternalPrograms[currentIndex].Value : InternalPrograms[0].Value);
if (InternalPrograms.Count == 0)
{
// If no programs are found, default to InternalProgram.NONE
this.CurrentProgram = InternalProgram.NONE;
}
else
{
int currentIndex = InternalPrograms.FindIndex(m => m == internalProgram);
this.CurrentProgram = (currentIndex > -1 ? InternalPrograms[currentIndex].Value : InternalPrograms[0].Value);
}
// Reenable event handlers, if necessary
if (cachedCanExecuteSelectionChanged) EnableEventHandlers();
@@ -765,11 +784,7 @@ namespace MPF.Core.UI.ViewModels
/// <summary>
/// Check for available updates
/// </summary>
#if NET48
public (bool, string, string) CheckForUpdates()
#else
public (bool, string, string?) CheckForUpdates()
#endif
{
(bool different, string message, var url) = Tools.CheckForNewVersion();
@@ -808,7 +823,7 @@ namespace MPF.Core.UI.ViewModels
{
SchemaVersion = 1,
FullyMatchedID = 3,
PartiallyMatchedIDs = new List<int> { 0, 1, 2, 3 },
PartiallyMatchedIDs = [0, 1, 2, 3],
Added = DateTime.UtcNow,
LastModified = DateTime.UtcNow,
@@ -822,8 +837,8 @@ namespace MPF.Core.UI.ViewModels
DiscTitle = "Install Disc",
Category = DiscCategory.Games,
Region = Region.World,
Languages = new Language?[] { Language.English, Language.Spanish, Language.French },
LanguageSelection = new LanguageSelection?[] { LanguageSelection.BiosSettings },
Languages = [Language.English, Language.Spanish, Language.French],
LanguageSelection = [LanguageSelection.BiosSettings],
Serial = "Disc Serial",
Layer0MasteringRing = "L0 Mastering Ring",
Layer0MasteringSID = "L0 Mastering SID",
@@ -846,20 +861,12 @@ namespace MPF.Core.UI.ViewModels
EXEDateBuildDate = "19xx-xx-xx",
ErrorsCount = "0",
Comments = "Comment data line 1\r\nComment data line 2",
#if NET48
CommentsSpecialFields = new Dictionary<SiteCode?, string>()
#else
CommentsSpecialFields = new Dictionary<SiteCode, string>()
#endif
{
[SiteCode.ISBN] = "ISBN",
},
Contents = "Special contents 1\r\nSpecial contents 2",
#if NET48
ContentsSpecialFields = new Dictionary<SiteCode?, string>()
#else
ContentsSpecialFields = new Dictionary<SiteCode, string>()
#endif
{
[SiteCode.PlayableDemos] = "Game Demo 1",
},
@@ -869,7 +876,7 @@ namespace MPF.Core.UI.ViewModels
{
Version = "Original",
VersionDatfile = "Alt",
CommonEditions = new string[] { "Taikenban" },
CommonEditions = ["Taikenban"],
OtherEditions = "Rerelease",
},
@@ -907,7 +914,7 @@ namespace MPF.Core.UI.ViewModels
DumpersAndStatus = new DumpersAndStatusSection()
{
Status = DumpStatus.TwoOrMoreGreen,
Dumpers = new string[] { "Dumper1", "Dumper2" },
Dumpers = ["Dumper1", "Dumper2"],
OtherDumpers = "Dumper3",
},
@@ -915,7 +922,7 @@ namespace MPF.Core.UI.ViewModels
{
ClrMameProData = "Datfile",
Cuesheet = "Cuesheet",
CommonWriteOffsets = new int[] { 0, 12, -12 },
CommonWriteOffsets = [0, 12, -12],
OtherWriteOffsets = "-2",
},
@@ -982,18 +989,13 @@ namespace MPF.Core.UI.ViewModels
/// </summary>
/// <param name="savedSettings">Indicates if the settings were saved or not</param>
/// <param name="newOptions">Options representing the new, saved values</param>
#if NET48
public void UpdateOptions(bool savedSettings, Data.Options newOptions)
#else
public void UpdateOptions(bool savedSettings, Data.Options? newOptions)
#endif
{
// Get which options to save
var optionsToSave = savedSettings ? newOptions : Options;
// Ensure the first run flag is unset
var continuingOptions = new Data.Options(optionsToSave);
continuingOptions.FirstRun = false;
var continuingOptions = new Data.Options(optionsToSave) { FirstRun = false };
this.Options = continuingOptions;
// If settings were changed, reinitialize the UI
@@ -1246,7 +1248,7 @@ namespace MPF.Core.UI.ViewModels
{
return new DumpEnvironment(
this.Options,
this.OutputPath,
EvaluateOutputPath(this.OutputPath),
this.CurrentDrive,
this.CurrentSystem,
this.CurrentMediaType,
@@ -1294,6 +1296,10 @@ namespace MPF.Core.UI.ViewModels
public void DisableAllUIElements()
{
OptionsMenuItemEnabled = false;
CheckDumpMenuItemEnabled = false;
#if NET6_0_OR_GREATER
CreateIRDMenuItemEnabled = false;
#endif
SystemTypeComboBoxEnabled = false;
MediaTypeComboBoxEnabled = false;
OutputPathTextBoxEnabled = false;
@@ -1314,6 +1320,10 @@ namespace MPF.Core.UI.ViewModels
public void EnableAllUIElements()
{
OptionsMenuItemEnabled = true;
CheckDumpMenuItemEnabled = true;
#if NET6_0_OR_GREATER
CreateIRDMenuItemEnabled = true;
#endif
SystemTypeComboBoxEnabled = true;
MediaTypeComboBoxEnabled = true;
OutputPathTextBoxEnabled = true;
@@ -1338,7 +1348,10 @@ namespace MPF.Core.UI.ViewModels
// Get the status to write out
Result result = Tools.GetSupportStatus(_environment.System, _environment.Type);
this.Status = result.Message;
if (this.CurrentProgram == InternalProgram.NONE || _environment.Parameters == null)
this.Status = "No dumping program found";
else
this.Status = result.Message;
// Enable or disable the button
this.StartStopButtonEnabled = result && ShouldEnableDumpingButton();
@@ -1355,6 +1368,49 @@ namespace MPF.Core.UI.ViewModels
}
}
/// <summary>
/// Replaces %-delimited variables inside a path string with their values
/// </summary>
/// <param name="outputPath">Path to be evaluated</param>
/// <returns>String with %-delimited variables evaluated</returns>
public string EvaluateOutputPath(string outputPath)
{
string systemLong = this._currentSystem.LongName() ?? "Unknown System";
if (string.IsNullOrEmpty(systemLong))
systemLong = "Unknown System";
string systemShort = this._currentSystem.ShortName() ?? "unknown";
if (string.IsNullOrEmpty(systemShort))
systemShort = "unknown";
string mediaLong = this._currentMediaType.LongName() ?? "Unknown Media";
if (string.IsNullOrEmpty(mediaLong))
mediaLong = "Unknown Media";
string program = this._currentProgram.ToString() ?? "Unknown Program";
if (string.IsNullOrEmpty(program))
program = "Unknown Program";
string programShort = program == "DiscImageCreator" ? "DIC" : program;
if (string.IsNullOrEmpty(programShort))
programShort = "Unknown Program";
string label = this._currentDrive?.FormattedVolumeLabel ?? "track";
if (string.IsNullOrEmpty(label))
label = "track";
string date = DateTime.Today.ToString("yyyyMMdd");
if (string.IsNullOrEmpty(date))
date = "UNKNOWN";
string datetime = DateTime.Now.ToString("yyyyMMdd-HHmmss");
if (string.IsNullOrEmpty(datetime))
datetime = "UNKNOWN";
return outputPath
.Replace("%SYSTEM%", systemLong)
.Replace("%SYS%", systemShort)
.Replace("%MEDIA%", mediaLong)
.Replace("%PROGRAM%", program)
.Replace("%PROG%", programShort)
.Replace("%LABEL%", label)
.Replace("%DATE%", date)
.Replace("%DATETIME%", datetime);
}
/// <summary>
/// Get the default output directory name from the currently selected drive
/// </summary>
@@ -1382,7 +1438,11 @@ namespace MPF.Core.UI.ViewModels
directory = Path.GetDirectoryName(directory);
if (directory != null && label != null)
#if NET20 || NET35
this.OutputPath = Path.Combine(Path.Combine(directory, label), filename);
#else
this.OutputPath = Path.Combine(directory, label, filename);
#endif
else
this.OutputPath = filename;
}
@@ -1405,7 +1465,11 @@ namespace MPF.Core.UI.ViewModels
directory = Path.GetDirectoryName(directory);
if (directory != null && label != null)
#if NET20 || NET35
this.OutputPath = Path.Combine(Path.Combine(directory, label), filename);
#else
this.OutputPath = Path.Combine(directory, label, filename);
#endif
else
this.OutputPath = filename;
}
@@ -1467,19 +1531,14 @@ namespace MPF.Core.UI.ViewModels
/// <summary>
/// Scan and show copy protection for the current disc
/// </summary>
#if NET48
public async Task<(string, string)> ScanAndShowProtection()
#if NET40
public (string?, string?) ScanAndShowProtection()
#else
public async Task<(string?, string?)> ScanAndShowProtection()
#endif
{
// Determine current environment, just in case
#if NET48
if (_environment == null)
_environment = DetermineEnvironment();
#else
_environment ??= DetermineEnvironment();
#endif
// If we don't have a valid drive
if (this.CurrentDrive?.Name == null)
@@ -1496,7 +1555,13 @@ namespace MPF.Core.UI.ViewModels
var progress = new Progress<ProtectionProgress>();
progress.ProgressChanged += ProgressUpdated;
#if NET40
var protectionTask = Protection.RunProtectionScanOnPath(this.CurrentDrive.Name, this.Options, progress);
protectionTask.Wait();
var (protections, error) = protectionTask.Result;
#else
var (protections, error) = await Protection.RunProtectionScanOnPath(this.CurrentDrive.Name, this.Options, progress);
#endif
var output = Protection.FormatProtections(protections);
// If SmartE is detected on the current disc, remove `/sf` from the flags for DIC only -- Disabled until further notice
@@ -1560,31 +1625,29 @@ namespace MPF.Core.UI.ViewModels
{
// Set the drive speed list that's appropriate
this.DriveSpeeds = (List<int>)Interface.GetSpeedsForMediaType(CurrentMediaType);
VerboseLogLn($"Supported media speeds: {string.Join(", ", this.DriveSpeeds)}");
VerboseLogLn($"Supported media speeds: {string.Join(", ", this.DriveSpeeds.Select(ds => ds.ToString()).ToArray())}");
// Set the selected speed
int speed;
switch (this.CurrentMediaType)
int speed = (CurrentMediaType) switch
{
case MediaType.CDROM:
case MediaType.GDROM:
speed = this.Options.PreferredDumpSpeedCD;
break;
case MediaType.DVD:
case MediaType.NintendoGameCubeGameDisc:
case MediaType.NintendoWiiOpticalDisc:
speed = this.Options.PreferredDumpSpeedDVD;
break;
case MediaType.HDDVD:
speed = this.Options.PreferredDumpSpeedHDDVD;
break;
case MediaType.BluRay:
speed = this.Options.PreferredDumpSpeedBD;
break;
default:
speed = this.Options.PreferredDumpSpeedCD;
break;
}
// CD dump speed
MediaType.CDROM => Options.PreferredDumpSpeedCD,
MediaType.GDROM => Options.PreferredDumpSpeedCD,
// DVD dump speed
MediaType.DVD => Options.PreferredDumpSpeedDVD,
MediaType.NintendoGameCubeGameDisc => Options.PreferredDumpSpeedDVD,
MediaType.NintendoWiiOpticalDisc => Options.PreferredDumpSpeedDVD,
// HD-DVD dump speed
MediaType.HDDVD => Options.PreferredDumpSpeedHDDVD,
// BD dump speed
MediaType.BluRay => Options.PreferredDumpSpeedBD,
// Default
_ => Options.PreferredDumpSpeedCD,
};
VerboseLogLn($"Setting drive speed to: {speed}");
this.DriveSpeed = speed;
@@ -1645,8 +1708,8 @@ namespace MPF.Core.UI.ViewModels
_environment.Drive?.RefreshDrive();
// Output to the label and log
this.Status = "Starting dumping process... Please wait!";
LogLn("Starting dumping process... Please wait!");
this.Status = "Starting dumping process... please wait!";
LogLn("Starting dumping process... please wait!");
if (this.Options.ToolsInSeparateWindow)
LogLn("Look for the separate command window for more details");
else
@@ -1660,7 +1723,11 @@ namespace MPF.Core.UI.ViewModels
_environment.ReportStatus += ProgressUpdated;
// Run the program with the parameters
#if NET40
Result result = _environment.Run(resultProgress);
#else
Result result = await _environment.Run(resultProgress);
#endif
// If we didn't execute a dumping command we cannot get submission output
if (_environment.Parameters?.IsDumpingCommand() != true)
@@ -1735,8 +1802,11 @@ namespace MPF.Core.UI.ViewModels
/// <returns>True if dumping should start, false otherwise</returns>
private bool ValidateBeforeDumping()
{
if (Parameters == null)
return false;
// Validate that we have an output path of any sort
if (string.IsNullOrWhiteSpace(_environment?.OutputPath))
if (string.IsNullOrEmpty(_environment?.OutputPath))
{
if (_displayUserMessage != null)
_ = _displayUserMessage("Missing Path", "No output path was provided so dumping cannot continue.", 1, false);
@@ -1745,10 +1815,10 @@ namespace MPF.Core.UI.ViewModels
}
// Validate that the user explicitly wants an inactive drive to be considered for dumping
if (_environment.Drive?.MarkedActive != true && _displayUserMessage != null)
if (_environment?.Drive?.MarkedActive != true && _displayUserMessage != null)
{
string message = "The currently selected drive does not appear to contain a disc! "
+ (!_environment.System.DetectedByWindows() ? $"This is normal for {_environment.System.LongName()} as the discs may not be readable on Windows. " : string.Empty)
+ (!_environment!.System.DetectedByWindows() ? $"This is normal for {_environment.System.LongName()} as the discs may not be readable on Windows. " : string.Empty)
+ "Do you want to continue?";
bool? mbresult = _displayUserMessage("No Disc Detected", message, 2, false);
@@ -1760,11 +1830,11 @@ namespace MPF.Core.UI.ViewModels
}
// Pre-split the output path
var outputDirectory = Path.GetDirectoryName(_environment.OutputPath);
var outputDirectory = Path.GetDirectoryName(_environment!.OutputPath);
string outputFilename = Path.GetFileName(_environment.OutputPath);
// If a complete dump already exists
(bool foundFiles, List<string> _) = InfoTool.FoundAllFiles(outputDirectory, outputFilename, _environment.Parameters, true);
(bool foundFiles, List<string> _) = _environment.Parameters.FoundAllFiles(outputDirectory, outputFilename, true);
if (foundFiles && _displayUserMessage != null)
{
bool? mbresult = _displayUserMessage("Overwrite?", "A complete dump already exists! Are you sure you want to overwrite?", 2, true);
@@ -1774,11 +1844,58 @@ namespace MPF.Core.UI.ViewModels
return false;
}
}
else
{
// If a complete dump exists from a different program
InternalProgram? programFound = null;
if (programFound == null && _environment.InternalProgram != InternalProgram.Aaru)
{
Modules.Aaru.Parameters parameters = new("")
{
Type = _environment.Type,
System = _environment.System
};
(bool foundOtherFiles, _) = parameters.FoundAllFiles(outputDirectory, outputFilename, true);
if (foundOtherFiles)
programFound = InternalProgram.Aaru;
}
if (programFound == null && _environment.InternalProgram != InternalProgram.DiscImageCreator)
{
Modules.DiscImageCreator.Parameters parameters = new("")
{
Type = _environment.Type,
System = _environment.System
};
(bool foundOtherFiles, _) = parameters.FoundAllFiles(outputDirectory, outputFilename, true);
if (foundOtherFiles)
programFound = InternalProgram.DiscImageCreator;
}
if (programFound == null && _environment.InternalProgram != InternalProgram.Redumper)
{
Modules.Redumper.Parameters parameters = new("")
{
Type = _environment.Type,
System = _environment.System
};
(bool foundOtherFiles, _) = parameters.FoundAllFiles(outputDirectory, outputFilename, true);
if (foundOtherFiles)
programFound = InternalProgram.Redumper;
}
if (programFound != null && _displayUserMessage != null)
{
bool? mbresult = _displayUserMessage("Overwrite?", $"A complete dump from {programFound} already exists! Dumping here may cause issues. Are you sure you want to overwrite?", 2, true);
if (mbresult != true)
{
LogLn("Dumping aborted!");
return false;
}
}
}
// Validate that at least some space exists
// TODO: Tie this to the size of the disc, type of disc, etc.
string fullPath;
if (string.IsNullOrWhiteSpace(outputDirectory))
if (string.IsNullOrEmpty(outputDirectory))
fullPath = Path.GetFullPath(_environment.OutputPath);
else
fullPath = Path.GetFullPath(outputDirectory);
@@ -1798,27 +1915,51 @@ namespace MPF.Core.UI.ViewModels
return true;
}
#endregion
/// <summary>
/// Checks whether a internal program is found in its path
/// </summary>
/// <param name="program">Program to check for</param>
/// <returns>True if the program is found, false otherwise</returns>
private bool InternalProgramExists(InternalProgram program)
{
try
{
return program switch
{
InternalProgram.Redumper => File.Exists(this.Options.RedumperPath),
InternalProgram.Aaru => File.Exists(this.Options.AaruPath),
InternalProgram.DiscImageCreator => File.Exists(this.Options.DiscImageCreatorPath),
_ => false,
};
}
catch
{
return false;
}
}
#endregion
#region Progress Reporting
/// <summary>
/// Handler for Result ProgressChanged event
/// </summary>
#if NET48
private void ProgressUpdated(object sender, string value)
#if NET20 || NET35 || NET40
private void ProgressUpdated(object? sender, BaseParameters.StringEventArgs value)
#else
private void ProgressUpdated(object? sender, string value)
#endif
{
try
{
#if NET48
value = value ?? string.Empty;
#if NET20 || NET35 || NET40
value.Value ??= string.Empty;
LogLn(value.Value);
#else
value ??= string.Empty;
#endif
LogLn(value);
#endif
}
catch { }
}
@@ -1826,20 +1967,12 @@ namespace MPF.Core.UI.ViewModels
/// <summary>
/// Handler for Result ProgressChanged event
/// </summary>
#if NET48
private void ProgressUpdated(object sender, Result value)
#else
private void ProgressUpdated(object? sender, Result value)
#endif
{
var message = value?.Message;
// Update the label with only the first line of output
#if NET48
if (message != null && message.Contains("\n"))
#else
if (message != null && message.Contains('\n'))
#endif
this.Status = value?.Message?.Split('\n')[0] + " (See log output)";
else
this.Status = value?.Message ?? string.Empty;
@@ -1854,17 +1987,13 @@ namespace MPF.Core.UI.ViewModels
/// <summary>
/// Handler for ProtectionProgress ProgressChanged event
/// </summary>
#if NET48
private void ProgressUpdated(object sender, ProtectionProgress value)
#else
private void ProgressUpdated(object? sender, ProtectionProgress value)
#endif
{
string message = $"{value.Percentage * 100:N2}%: {value.Filename} - {value.Protection}";
this.Status = message;
VerboseLogLn(message);
}
#endregion
#endregion
}
}

View File

@@ -8,6 +8,9 @@ using SabreTools.RedumpLib.Web;
namespace MPF.Core.UI.ViewModels
{
/// <summary>
/// Constructor
/// </summary>
public class OptionsViewModel : INotifyPropertyChanged
{
#region Fields
@@ -15,11 +18,7 @@ namespace MPF.Core.UI.ViewModels
/// <summary>
/// Title for the window
/// </summary>
#if NET48
public string Title
#else
public string? Title
#endif
{
get => _title;
set
@@ -28,11 +27,7 @@ namespace MPF.Core.UI.ViewModels
TriggerPropertyChanged(nameof(Title));
}
}
#if NET48
private string _title;
#else
private string? _title;
#endif
/// <summary>
/// Current set of options
@@ -45,30 +40,47 @@ namespace MPF.Core.UI.ViewModels
public bool SavedSettings { get; set; }
/// <inheritdoc/>
#if NET48
public event PropertyChangedEventHandler PropertyChanged;
#else
public event PropertyChangedEventHandler? PropertyChanged;
#endif
#endregion
/// <summary>
/// Used to indicate whether LibIRD is supported
/// Whether compiled with .NET Core 6 or greater
/// </summary>
public static bool CreateIRDSupported
{
#if NET6_0_OR_GREATER
get { return true; }
#else
get { return false; }
#endif
}
#endregion
#region Lists
/// <summary>
/// List of available internal programs
/// </summary>
public List<Element<InternalProgram>> InternalPrograms => PopulateInternalPrograms();
public static List<Element<InternalProgram>> InternalPrograms => PopulateInternalPrograms();
/// <summary>
/// Current list of supported system profiles
/// </summary>
public List<RedumpSystemComboBoxItem> Systems => RedumpSystemComboBoxItem.GenerateElements().ToList();
public static List<RedumpSystemComboBoxItem> Systems => RedumpSystemComboBoxItem.GenerateElements().ToList();
#endregion
/// <summary>
/// Constructor
/// Constructor for pure view model
/// </summary>
public OptionsViewModel()
{
Options = new Options();
}
/// <summary>
/// Constructor for in-code
/// </summary>
public OptionsViewModel(Options baseOptions)
{
@@ -82,7 +94,7 @@ namespace MPF.Core.UI.ViewModels
/// </summary>
private static List<Element<InternalProgram>> PopulateInternalPrograms()
{
var internalPrograms = new List<InternalProgram> { InternalProgram.DiscImageCreator, InternalProgram.Aaru, InternalProgram.Redumper };
var internalPrograms = new List<InternalProgram> { InternalProgram.Redumper, InternalProgram.DiscImageCreator, InternalProgram.Aaru };
return internalPrograms.Select(ip => new Element<InternalProgram>(ip)).ToList();
}
@@ -93,14 +105,16 @@ namespace MPF.Core.UI.ViewModels
/// <summary>
/// Test Redump login credentials
/// </summary>
#if NET48
public (bool?, string) TestRedumpLogin(string username, string password)
#if NET40
public static Task<(bool?, string?)> TestRedumpLogin(string username, string password)
#else
public async Task<(bool?, string?)> TestRedumpLogin(string username, string password)
public static async Task<(bool?, string?)> TestRedumpLogin(string username, string password)
#endif
{
#if NET48
return RedumpWebClient.ValidateCredentials(username, password);
#if NET40
return Task.Factory.StartNew(() => RedumpWebClient.ValidateCredentials(username, password));
#elif NETFRAMEWORK
return await Task.Run(() => RedumpWebClient.ValidateCredentials(username, password));
#else
return await RedumpHttpClient.ValidateCredentials(username, password);
#endif

View File

@@ -8,35 +8,6 @@ namespace MPF.Core.Utilities
{
public static class EnumExtensions
{
/// <summary>
/// Determine if a system is okay if it's not detected by Windows
/// </summary>
/// <param name="system">RedumpSystem value to check</param>
/// <returns>True if Windows show see a disc when dumping, false otherwise</returns>
public static bool DetectedByWindows(this RedumpSystem? system)
{
switch (system)
{
case RedumpSystem.AmericanLaserGames3DO:
case RedumpSystem.AppleMacintosh:
case RedumpSystem.Atari3DO:
case RedumpSystem.AtariJaguarCDInteractiveMultimediaSystem:
case RedumpSystem.NewJatreCDi:
case RedumpSystem.NintendoGameCube:
case RedumpSystem.NintendoWii:
case RedumpSystem.NintendoWiiU:
case RedumpSystem.PhilipsCDi:
case RedumpSystem.PhilipsCDiDigitalVideo:
case RedumpSystem.Panasonic3DOInteractiveMultiplayer:
case RedumpSystem.PanasonicM2:
case RedumpSystem.PioneerLaserActive:
case RedumpSystem.SuperAudioCD:
return false;
default:
return true;
}
}
/// <summary>
/// Determine if the media supports drive speeds
/// </summary>
@@ -44,88 +15,17 @@ namespace MPF.Core.Utilities
/// <returns>True if the media has variable dumping speeds, false otherwise</returns>
public static bool DoesSupportDriveSpeed(this MediaType? type)
{
switch (type)
return type switch
{
case MediaType.CDROM:
case MediaType.DVD:
case MediaType.GDROM:
case MediaType.HDDVD:
case MediaType.BluRay:
case MediaType.NintendoGameCubeGameDisc:
case MediaType.NintendoWiiOpticalDisc:
return true;
default:
return false;
}
}
/// <summary>
/// Determine if a system has reversed ringcodes
/// </summary>
/// <param name="system">RedumpSystem value to check</param>
/// <returns>True if the system has reversed ringcodes, false otherwise</returns>
public static bool HasReversedRingcodes(this RedumpSystem? system)
{
switch (system)
{
case RedumpSystem.SonyPlayStation2:
case RedumpSystem.SonyPlayStation3:
case RedumpSystem.SonyPlayStation4:
//case RedumpSystem.SonyPlayStation5:
case RedumpSystem.SonyPlayStationPortable:
return true;
default:
return false;
}
}
/// <summary>
/// Determine if a system is considered audio-only
/// </summary>
/// <param name="system">RedumpSystem value to check</param>
/// <returns>True if the system is audio-only, false otherwise</returns>
/// <remarks>
/// Philips CD-i should NOT be in this list. It's being included until there's a
/// reasonable distinction between CD-i and CD-i ready on the database side.
/// </remarks>
public static bool IsAudio(this RedumpSystem? system)
{
switch (system)
{
case RedumpSystem.AtariJaguarCDInteractiveMultimediaSystem:
case RedumpSystem.AudioCD:
case RedumpSystem.DVDAudio:
case RedumpSystem.HasbroiONEducationalGamingSystem:
case RedumpSystem.HasbroVideoNow:
case RedumpSystem.HasbroVideoNowColor:
case RedumpSystem.HasbroVideoNowJr:
case RedumpSystem.HasbroVideoNowXP:
case RedumpSystem.PhilipsCDi:
case RedumpSystem.PlayStationGameSharkUpdates:
case RedumpSystem.SuperAudioCD:
return true;
default:
return false;
}
}
/// <summary>
/// Determine if a system is considered XGD
/// </summary>
/// <param name="system">RedumpSystem value to check</param>
/// <returns>True if the system is XGD, false otherwise</returns>
public static bool IsXGD(this RedumpSystem? system)
{
switch (system)
{
case RedumpSystem.MicrosoftXbox:
case RedumpSystem.MicrosoftXbox360:
case RedumpSystem.MicrosoftXboxOne:
case RedumpSystem.MicrosoftXboxSeriesXS:
return true;
default:
return false;
}
MediaType.CDROM
or MediaType.DVD
or MediaType.GDROM
or MediaType.HDDVD
or MediaType.BluRay
or MediaType.NintendoGameCubeGameDisc
or MediaType.NintendoWiiOpticalDisc => true,
_ => false,
};
}
/// <summary>
@@ -137,7 +37,7 @@ namespace MPF.Core.Utilities
foreach (var val in Enum.GetValues(typeof(InternalProgram)))
{
if (((InternalProgram)val) == InternalProgram.NONE)
if (((InternalProgram)val!) == InternalProgram.NONE)
continue;
programs.Add($"{((InternalProgram?)val).LongName()}");

View File

@@ -14,8 +14,10 @@ namespace MPF.Core.Utilities
/// <param name="reader">TextReader representing the input</param>
/// <param name="baseClass">Invoking class, passed on to the event handler</param>
/// <param name="handler">Event handler to be invoked to write to log</param>
#if NET48
public static async Task OutputToLog(TextReader reader, object baseClass, EventHandler<string> handler)
#if NET20 || NET35
public static async Task OutputToLog(TextReader reader, object baseClass, EventHandler<Modules.BaseParameters.StringEventArgs>? handler)
#elif NET40
public static void OutputToLog(TextReader reader, object baseClass, EventHandler<Modules.BaseParameters.StringEventArgs>? handler)
#else
public static async Task OutputToLog(TextReader reader, object baseClass, EventHandler<string>? handler)
#endif
@@ -30,7 +32,15 @@ namespace MPF.Core.Utilities
while (true)
{
// Try to read the next chunk of characters
#if NET20 || NET35
read = await Task.Run(() => reader.Read(buffer, 0, buffer.Length));
#elif NET40
var readTask = Task.Factory.StartNew(() => reader.Read(buffer, 0, buffer.Length));
readTask.Wait();
read = readTask.Result;
#else
read = await reader.ReadAsync(buffer, 0, buffer.Length);
#endif
if (read == 0)
{
Thread.Sleep(10);
@@ -38,25 +48,41 @@ namespace MPF.Core.Utilities
}
// Convert the characters into a string
string line = new string(buffer, 0, read);
string line = new(buffer, 0, read);
// If we have no newline characters, store in the string builder
#if NETFRAMEWORK
if (!line.Contains("\r") && !line.Contains("\n"))
#else
if (!line.Contains('\r') && !line.Contains('\n'))
#endif
sb.Append(line);
// If we have a newline, append and log
#if NETFRAMEWORK
else if (line.Contains("\n") || line.Contains("\r\n"))
#else
else if (line.Contains('\n') || line.Contains("\r\n"))
#endif
ProcessNewLines(sb, line, baseClass, handler);
// If we have a carriage return only, append and log first and last instances
#if NETFRAMEWORK
else if (line.Contains("\r"))
#else
else if (line.Contains('\r'))
#endif
ProcessCarriageReturns(sb, line, baseClass, handler);
}
}
catch { }
finally
{
#if NET20 || NET35 || NET40
handler?.Invoke(baseClass, new Modules.BaseParameters.StringEventArgs { Value = sb.ToString() });
#else
handler?.Invoke(baseClass, sb.ToString());
#endif
}
}
@@ -67,8 +93,8 @@ namespace MPF.Core.Utilities
/// <param name="line">Current line to process</param>
/// <param name="baseClass">Invoking class, passed on to the event handler</param>
/// <param name="handler">Event handler to be invoked to write to log</param>
#if NET48
private static void ProcessNewLines(StringBuilder sb, string line, object baseClass, EventHandler<string> handler)
#if NET20 || NET35 || NET40
private static void ProcessNewLines(StringBuilder sb, string line, object baseClass, EventHandler<Modules.BaseParameters.StringEventArgs>? handler)
#else
private static void ProcessNewLines(StringBuilder sb, string line, object baseClass, EventHandler<string>? handler)
#endif
@@ -78,7 +104,11 @@ namespace MPF.Core.Utilities
for (int i = 0; i < split.Length; i++)
{
// If the chunk contains a carriage return, handle it like a separate line
#if NETFRAMEWORK
if (split[i].Contains("\r"))
#else
if (split[i].Contains('\r'))
#endif
{
ProcessCarriageReturns(sb, split[i], baseClass, handler);
continue;
@@ -88,8 +118,13 @@ namespace MPF.Core.Utilities
if (i == 0)
{
sb.Append(split[i]);
#if NET20 || NET35 || NET40
handler?.Invoke(baseClass, new Modules.BaseParameters.StringEventArgs { Value = sb.ToString() });
sb = new();
#else
handler?.Invoke(baseClass, sb.ToString());
sb.Clear();
#endif
}
// For the last item, just append so it's dealt with the next time
@@ -101,7 +136,11 @@ namespace MPF.Core.Utilities
// For everything else, directly write out
else
{
#if NET20 || NET35 || NET40
handler?.Invoke(baseClass, new Modules.BaseParameters.StringEventArgs { Value = split[i] });
#else
handler?.Invoke(baseClass, split[i]);
#endif
}
}
}
@@ -113,8 +152,8 @@ namespace MPF.Core.Utilities
/// <param name="line">Current line to process</param>
/// <param name="baseClass">Invoking class, passed on to the event handler</param>
/// <param name="handler">Event handler to be invoked to write to log</param>
#if NET48
private static void ProcessCarriageReturns(StringBuilder sb, string line, object baseClass, EventHandler<string> handler)
#if NET20 || NET35 || NET40
private static void ProcessCarriageReturns(StringBuilder sb, string line, object baseClass, EventHandler<Modules.BaseParameters.StringEventArgs>? handler)
#else
private static void ProcessCarriageReturns(StringBuilder sb, string line, object baseClass, EventHandler<string>? handler)
#endif
@@ -123,10 +162,15 @@ namespace MPF.Core.Utilities
// Append and log the first
sb.Append(split[0]);
#if NET20 || NET35 || NET40
handler?.Invoke(baseClass, new Modules.BaseParameters.StringEventArgs { Value = sb.ToString() });
sb = new();
#else
handler?.Invoke(baseClass, sb.ToString());
sb.Clear();
#endif
// Append the last
sb.Clear();
sb.Append($"\r{split[split.Length - 1]}");
}
}

View File

@@ -4,6 +4,7 @@ using System.IO;
using MPF.Core.Converters;
using MPF.Core.Data;
using Newtonsoft.Json;
using SabreTools.RedumpLib;
using SabreTools.RedumpLib.Data;
namespace MPF.Core.Utilities
@@ -63,11 +64,7 @@ namespace MPF.Core.Utilities
/// Process common arguments for all functionality
/// </summary>
/// <returns>True if all arguments pass, false otherwise</returns>
#if NET48
public static (bool, MediaType, RedumpSystem?, string) ProcessCommonArguments(string[] args)
#else
public static (bool, MediaType, RedumpSystem?, string?) ProcessCommonArguments(string[] args)
#endif
{
// All other use requires at least 3 arguments
if (args.Length < 3)
@@ -89,11 +86,7 @@ namespace MPF.Core.Utilities
/// <summary>
/// Load the current set of options from application arguments
/// </summary>
#if NET48
public static (Options, SubmissionInfo, string, int) LoadFromArguments(string[] args, int startIndex = 0)
#else
public static (Options, SubmissionInfo?, string?, int) LoadFromArguments(string[] args, int startIndex = 0)
#endif
{
// Create the output values with defaults
var options = new Options()
@@ -108,16 +101,11 @@ namespace MPF.Core.Utilities
};
// Create the submission info to return, if necessary
#if NET48
SubmissionInfo info = null;
string parsedPath = null;
#else
SubmissionInfo? info = null;
string? parsedPath = null;
#endif
// These values require multiple parts to be active
bool scan = false, protectFile = false;
bool scan = false, protectFile = false, hideDriveLetters = false;
// If we have no arguments, just return
if (args == null || args.Length == 0)
@@ -186,16 +174,22 @@ namespace MPF.Core.Utilities
protectFile = true;
}
// Hide drive letters from scan output (requires --protect-file)
else if (args[startIndex].Equals("-g") || args[startIndex].Equals("--hide-drive-letters"))
{
hideDriveLetters = true;
}
// Include seed info file
else if (args[startIndex].StartsWith("-l=") || args[startIndex].StartsWith("--load-seed="))
{
string seedInfo = args[startIndex].Split('=')[1];
info = SubmissionInfoTool.CreateFromFile(seedInfo);
info = Builder.CreateFromFile(seedInfo);
}
else if (args[startIndex] == "-l" || args[startIndex] == "--load-seed")
{
string seedInfo = args[startIndex + 1];
info = SubmissionInfoTool.CreateFromFile(seedInfo);
info = Builder.CreateFromFile(seedInfo);
startIndex++;
}
@@ -231,8 +225,9 @@ namespace MPF.Core.Utilities
}
// Now deal with the complex options
options.ScanForProtection = scan && !string.IsNullOrWhiteSpace(parsedPath);
options.OutputSeparateProtectionFile = scan && protectFile && !string.IsNullOrWhiteSpace(parsedPath);
options.ScanForProtection = scan && !string.IsNullOrEmpty(parsedPath);
options.OutputSeparateProtectionFile = scan && protectFile && !string.IsNullOrEmpty(parsedPath);
options.HideDriveLetters = hideDriveLetters && scan && protectFile && !string.IsNullOrEmpty(parsedPath);
return (options, info, parsedPath, startIndex);
}
@@ -250,6 +245,7 @@ namespace MPF.Core.Utilities
"-p, --path <drivepath> Physical drive path for additional checks",
"-s, --scan Enable copy protection scan (requires --path)",
"-f, --protect-file Output protection to separate file (requires --scan)",
"-g, --hide-drive-letters Hide drive letters from scan output (requires --protect-file)",
"-l, --load-seed <path> Load a seed submission JSON for user information",
"-x, --suffix Enable adding filename suffix",
"-j, --json Enable submission JSON output",
@@ -260,7 +256,7 @@ namespace MPF.Core.Utilities
return supportedArguments;
}
#endregion
#endregion
#region Configuration
@@ -277,11 +273,7 @@ namespace MPF.Core.Utilities
var serializer = JsonSerializer.Create();
var reader = new StreamReader(ConfigurationPath);
#if NET48
var settings = serializer.Deserialize(reader, typeof(Dictionary<string, string>)) as Dictionary<string, string>;
#else
var settings = serializer.Deserialize(reader, typeof(Dictionary<string, string?>)) as Dictionary<string, string?>;
#endif
return new Options(settings);
}

View File

@@ -1,4 +1,6 @@
using System;
using System.IO;
using System.Net;
using System.Reflection;
using MPF.Core.Data;
using Newtonsoft.Json.Linq;
@@ -70,6 +72,36 @@ namespace MPF.Core.Utilities
return true;
}
/// <summary>
/// Converts a hex string into a byte array
/// </summary>
/// <param name="hex">Hex string</param>
/// <returns>Converted byte array, or null if invalid hex string</returns>
public static byte[]? HexStringToByteArray(string? hexString)
{
// Valid hex string must be an even number of characters
if (string.IsNullOrEmpty(hexString) || hexString!.Length % 2 == 1)
return null;
// Convert ASCII to byte via lookup table
int[] hexLookup = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F];
byte[] byteArray = new byte[hexString.Length / 2];
for (int i = 0; i < hexString.Length; i += 2)
{
// Convert next two chars to ASCII value relative to '0'
int a = Char.ToUpper(hexString[i]) - '0';
int b = Char.ToUpper(hexString[i + 1]) - '0';
// Ensure hex string only has '0' through '9' and 'A' through 'F' (case insensitive)
if ((a < 0 || b < 0 || a > 22 || b > 22) || (a > 10 && a < 17) || (b > 10 && b < 17))
return null;
byteArray[i / 2] = (byte)(hexLookup[a] << 4 | hexLookup[b]);
}
return byteArray;
}
#endregion
#region Support
@@ -84,40 +116,6 @@ namespace MPF.Core.Utilities
return Result.Failure("Please select a valid system");
// If we're on an unsupported type, update the status accordingly
#if NET48
switch (type)
{
// Fully supported types
case MediaType.BluRay:
case MediaType.CDROM:
case MediaType.DVD:
case MediaType.FloppyDisk:
case MediaType.HardDisk:
case MediaType.CompactFlash:
case MediaType.SDCard:
case MediaType.FlashDrive:
case MediaType.HDDVD:
return Result.Success($"{type.LongName()} ready to dump");
// Partially supported types
case MediaType.GDROM:
case MediaType.NintendoGameCubeGameDisc:
case MediaType.NintendoWiiOpticalDisc:
return Result.Success($"{type.LongName()} partially supported for dumping");
// Special case for other supported tools
case MediaType.UMD:
return Result.Failure($"{type.LongName()} supported for submission info parsing");
// Specifically unknown type
case MediaType.NONE:
return Result.Failure($"Please select a valid media type");
// Undumpable but recognized types
default:
return Result.Failure($"{type.LongName()} media are not supported for dumping");
}
#else
return type switch
{
// Fully supported types
@@ -145,9 +143,8 @@ namespace MPF.Core.Utilities
// Undumpable but recognized types
_ => Result.Failure($"{type.LongName()} media are not supported for dumping"),
};
#endif
}
/// <summary>
/// Returns false if a given InternalProgram does not support a given MediaType
/// </summary>
@@ -157,88 +154,46 @@ namespace MPF.Core.Utilities
if (type == null || type == MediaType.NONE)
return false;
#if NET48
switch (program)
return (program) switch
{
case InternalProgram.Redumper:
switch (type)
{
// Formats considered at least partially dumpable by Redumper
case MediaType.CDROM:
case MediaType.DVD:
case MediaType.GDROM:
return true;
// Aaru
InternalProgram.Aaru when type == MediaType.BluRay => true,
InternalProgram.Aaru when type == MediaType.CDROM => true,
InternalProgram.Aaru when type == MediaType.CompactFlash => true,
InternalProgram.Aaru when type == MediaType.DVD => true,
InternalProgram.Aaru when type == MediaType.GDROM => true,
InternalProgram.Aaru when type == MediaType.FlashDrive => true,
InternalProgram.Aaru when type == MediaType.FloppyDisk => true,
InternalProgram.Aaru when type == MediaType.HardDisk => true,
InternalProgram.Aaru when type == MediaType.HDDVD => true,
InternalProgram.Aaru when type == MediaType.NintendoGameCubeGameDisc => true,
InternalProgram.Aaru when type == MediaType.NintendoWiiOpticalDisc => true,
InternalProgram.Aaru when type == MediaType.SDCard => true,
// All other formats considered unsupported
default:
return false;
}
case InternalProgram.Aaru:
case InternalProgram.DiscImageCreator:
switch (type)
{
// Formats considered at least partially supported
case MediaType.BluRay:
case MediaType.CDROM:
case MediaType.DVD:
case MediaType.FloppyDisk:
case MediaType.HardDisk:
case MediaType.CompactFlash:
case MediaType.SDCard:
case MediaType.FlashDrive:
case MediaType.HDDVD:
case MediaType.NintendoGameCubeGameDisc:
case MediaType.NintendoWiiOpticalDisc:
return true;
// DiscImageCreator
InternalProgram.DiscImageCreator when type == MediaType.BluRay => true,
InternalProgram.DiscImageCreator when type == MediaType.CDROM => true,
InternalProgram.DiscImageCreator when type == MediaType.CompactFlash => true,
InternalProgram.DiscImageCreator when type == MediaType.DVD => true,
InternalProgram.DiscImageCreator when type == MediaType.GDROM => true,
InternalProgram.DiscImageCreator when type == MediaType.FlashDrive => true,
InternalProgram.DiscImageCreator when type == MediaType.FloppyDisk => true,
InternalProgram.DiscImageCreator when type == MediaType.HardDisk => true,
InternalProgram.DiscImageCreator when type == MediaType.HDDVD => true,
InternalProgram.DiscImageCreator when type == MediaType.NintendoGameCubeGameDisc => true,
InternalProgram.DiscImageCreator when type == MediaType.NintendoWiiOpticalDisc => true,
InternalProgram.DiscImageCreator when type == MediaType.SDCard => true,
// All other formats considered unsupported
default:
return false;
}
// All other InternalPrograms are not used for dumping
default:
return false;
}
#else
switch (program)
{
case InternalProgram.Redumper:
return type switch
{
// Formats considered at least partially supported by Redumper
MediaType.CDROM
or MediaType.DVD
or MediaType.GDROM => true,
// Redumper
InternalProgram.Redumper when type == MediaType.BluRay => true,
InternalProgram.Redumper when type == MediaType.CDROM => true,
InternalProgram.Redumper when type == MediaType.DVD => true,
InternalProgram.Redumper when type == MediaType.GDROM => true,
InternalProgram.Redumper when type == MediaType.HDDVD => true,
// All other formats considered unsupported
_ => false,
};
case InternalProgram.Aaru:
case InternalProgram.DiscImageCreator:
return type switch
{
// Formats considered at least partially supported by MPF
MediaType.BluRay
or MediaType.CDROM
or MediaType.DVD
or MediaType.GDROM
or MediaType.FloppyDisk
or MediaType.CompactFlash
or MediaType.SDCard
or MediaType.FlashDrive
or MediaType.HardDisk
or MediaType.HDDVD
or MediaType.NintendoGameCubeGameDisc
or MediaType.NintendoWiiOpticalDisc => true,
// All other formats considered unsupported
_ => false,
};
// All other InternalPrograms are not supported for dumping
default:
return false;
}
#endif
// Default
_ => false,
};
}
#endregion
@@ -253,11 +208,7 @@ namespace MPF.Core.Utilities
/// String representing the message to display the the user.
/// String representing the new release URL.
/// </returns>
#if NET48
public static (bool different, string message, string url) CheckForNewVersion()
#else
public static (bool different, string message, string? url) CheckForNewVersion()
#endif
{
try
{
@@ -270,7 +221,7 @@ namespace MPF.Core.Utilities
// Get the latest tag from GitHub
var (tag, url) = GetRemoteVersionAndUrl();
bool different = version != tag;
bool different = version != tag && tag != null;
string message = $"Local version: {version}"
+ $"{Environment.NewLine}Remote version: {tag}"
@@ -289,11 +240,7 @@ namespace MPF.Core.Utilities
/// <summary>
/// Get the current informational version formatted as a string
/// </summary>
#if NET48
public static string GetCurrentVersion()
#else
public static string? GetCurrentVersion()
#endif
{
try
{
@@ -313,47 +260,358 @@ namespace MPF.Core.Utilities
/// <summary>
/// Get the latest version of MPF from GitHub and the release URL
/// </summary>
#if NET48
private static (string tag, string url) GetRemoteVersionAndUrl()
#else
private static (string? tag, string? url) GetRemoteVersionAndUrl()
#endif
{
#if NET48
using (var wc = new System.Net.WebClient())
{
wc.Headers["User-Agent"] = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:64.0) Gecko/20100101 Firefox/64.0";
// TODO: Figure out a better way than having this hardcoded...
string url = "https://api.github.com/repos/SabreTools/MPF/releases/latest";
string latestReleaseJsonString = wc.DownloadString(url);
var latestReleaseJson = JObject.Parse(latestReleaseJsonString);
string latestTag = latestReleaseJson["tag_name"].ToString();
string releaseUrl = latestReleaseJson["html_url"].ToString();
return (latestTag, releaseUrl);
}
#if NET20 || NET35 || NET40
// Not supported in .NET Frameworks 2.0, 3.5, or 4.0
return (null, null);
#else
using (var hc = new System.Net.Http.HttpClient())
{
// TODO: Figure out a better way than having this hardcoded...
string url = "https://api.github.com/repos/SabreTools/MPF/releases/latest";
var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, url);
message.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:64.0) Gecko/20100101 Firefox/64.0");
var latestReleaseJsonString = hc.Send(message)?.Content?.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult();
if (latestReleaseJsonString == null)
return (null, null);
var latestReleaseJson = JObject.Parse(latestReleaseJsonString);
if (latestReleaseJson == null)
return (null, null);
var latestTag = latestReleaseJson["tag_name"]?.ToString();
var releaseUrl = latestReleaseJson["html_url"]?.ToString();
return (latestTag, releaseUrl);
}
using var hc = new System.Net.Http.HttpClient();
#if NET452
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
#endif
// TODO: Figure out a better way than having this hardcoded...
string url = "https://api.github.com/repos/SabreTools/MPF/releases/latest";
var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, url);
message.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:64.0) Gecko/20100101 Firefox/64.0");
var latestReleaseJsonString = hc.SendAsync(message)?.ConfigureAwait(false).GetAwaiter().GetResult()
.Content?.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult();
if (latestReleaseJsonString == null)
return (null, null);
var latestReleaseJson = JObject.Parse(latestReleaseJsonString);
if (latestReleaseJson == null)
return (null, null);
var latestTag = latestReleaseJson["tag_name"]?.ToString();
var releaseUrl = latestReleaseJson["html_url"]?.ToString();
return (latestTag, releaseUrl);
#endif
}
#endregion
#region PlayStation 3
/// <summary>
/// Validates a getkey log to check for presence of valid PS3 key
/// </summary>
/// <param name="logPath">Path to getkey log file</param>
/// <param name="key">Output 16 byte key, null if not valid</param>
/// <returns>True if path to log file contains valid key, false otherwise</returns>
public static bool ParseGetKeyLog(string? logPath, out byte[]? key, out byte[]? id, out byte[]? pic)
{
key = null;
id = null;
pic = null;
if (string.IsNullOrEmpty(logPath))
return false;
try
{
if (!File.Exists(logPath))
return false;
// Protect from attempting to read from really long files
FileInfo logFile = new(logPath);
if (logFile.Length > 65536)
return false;
// Read from .getkey.log file
using StreamReader sr = File.OpenText(logPath);
// Determine whether GetKey was successful
string? line;
while ((line = sr.ReadLine()) != null && line.Trim().StartsWith("get_dec_key succeeded!") == false) ;
if (line == null)
return false;
// Look for Disc Key in log
while ((line = sr.ReadLine()) != null && line.Trim().StartsWith("disc_key = ") == false) ;
// If end of file reached, no key found
if (line == null)
return false;
// Get Disc Key from log
string discKeyStr = line.Substring("disc_key = ".Length);
// Validate Disc Key from log
if (discKeyStr.Length != 32)
return false;
// Convert Disc Key to byte array
key = Tools.HexStringToByteArray(discKeyStr);
if (key == null)
return false;
// Read Disc ID
while ((line = sr.ReadLine()) != null && line.Trim().StartsWith("disc_id = ") == false) ;
// If end of file reached, no ID found
if (line == null)
return false;
// Get Disc ID from log
string discIDStr = line.Substring("disc_id = ".Length);
// Validate Disc ID from log
if (discIDStr.Length != 32)
return false;
// Replace X's in Disc ID with 00000001
discIDStr = discIDStr.Substring(0, 24) + "00000001";
// Convert Disc ID to byte array
id = Tools.HexStringToByteArray(discIDStr);
if (id == null)
return false;
// Look for PIC in log
while ((line = sr.ReadLine()) != null && line.Trim().StartsWith("PIC:") == false) ;
// If end of file reached, no PIC found
if (line == null)
return false;
// Get PIC from log
string discPICStr = "";
for (int i = 0; i < 8; i++)
discPICStr += sr.ReadLine();
if (discPICStr == null)
return false;
// Validate PIC from log
if (discPICStr.Length != 256)
return false;
// Convert PIC to byte array
pic = Tools.HexStringToByteArray(discPICStr.Substring(0, 230));
if (pic == null)
return false;
// Double check for warnings in .getkey.log
while ((line = sr.ReadLine()) != null)
{
string t = line.Trim();
if (t.StartsWith("WARNING"))
return false;
else if (t.StartsWith("SUCCESS"))
return true;
}
}
catch
{
// We are not concerned with the error
return false;
}
return true;
}
/// <summary>
/// Validates a hexadecimal disc ID
/// </summary>
/// <param name="discID">String representing hexadecimal disc ID</param>
/// <returns>True if string is a valid disc ID, false otherwise</returns>
public static byte[]? ParseDiscID(string? discID)
{
if (string.IsNullOrEmpty(discID))
return null;
string cleandiscID = discID!.Trim().Replace("\n", string.Empty);
if (discID!.Length != 32)
return null;
// Censor last 4 bytes by replacing with 0x00000001
cleandiscID = cleandiscID.Substring(0, 24) + "00000001";
// Convert to byte array, null if invalid hex string
byte[]? id = Tools.HexStringToByteArray(cleandiscID);
return id;
}
/// <summary>
/// Validates a key file to check for presence of valid PS3 key
/// </summary>
/// <param name="keyPath">Path to key file</param>
/// <returns>Output 16 byte key, null if not valid</returns>
public static byte[]? ParseKeyFile(string? keyPath)
{
if (string.IsNullOrEmpty(keyPath))
return null;
// Try read from key file
try
{
if (!File.Exists(keyPath))
return null;
// Key file must be exactly 16 bytes long
FileInfo keyFile = new(keyPath);
if (keyFile.Length != 16)
return null;
byte[] key = new byte[16];
// Read 16 bytes from Key file
using FileStream fs = new(keyPath, FileMode.Open, FileAccess.Read);
using BinaryReader reader = new(fs);
int numBytes = reader.Read(key, 0, 16);
if (numBytes != 16)
return null;
return key;
}
catch
{
// Not concerned with error
return null;
}
}
/// <summary>
/// Validates a hexadecimal key
/// </summary>
/// <param name="hexKey">String representing hexadecimal key</param>
/// <returns>Output 16 byte key, null if not valid</returns>
public static byte[]? ParseHexKey(string? hexKey)
{
if (string.IsNullOrEmpty(hexKey))
return null;
string cleanHexKey = hexKey!.Trim().Replace("\n", string.Empty);
if (cleanHexKey.Length != 32)
return null;
// Convert to byte array, null if invalid hex string
byte[]? key = Tools.HexStringToByteArray(cleanHexKey);
return key;
}
/// <summary>
/// Validates a PIC file path
/// </summary>
/// <param name="picPath">Path to PIC file</param>
/// <returns>Output PIC byte array, null if not valid</returns>
public static byte[]? ParsePICFile(string? picPath)
{
if (string.IsNullOrEmpty(picPath))
return null;
// Try read from PIC file
try
{
if (!File.Exists(picPath))
return null;
// PIC file must be at least 115 bytes long
FileInfo picFile = new(picPath);
if (picFile.Length < 115)
return null;
byte[] pic = new byte[115];
// Read 115 bytes from PIC file
using FileStream fs = new(picPath, FileMode.Open, FileAccess.Read);
using BinaryReader reader = new(fs);
int numBytes = reader.Read(pic, 0, 115);
if (numBytes != 115)
return null;
// Validate that a PIC was read by checking first 6 bytes
if (pic[0] != 0x10 ||
pic[1] != 0x02 ||
pic[2] != 0x00 ||
pic[3] != 0x00 ||
pic[4] != 0x44 ||
pic[5] != 0x49)
return null;
return pic;
}
catch
{
// Not concerned with error
return null;
}
}
/// <summary>
/// Validates a PIC
/// </summary>
/// <param name="inputPIC">String representing PIC</param>
/// <returns>Output PIC byte array, null if not valid</returns>
public static byte[]? ParsePIC(string? inputPIC)
{
if (string.IsNullOrEmpty(inputPIC))
return null;
string cleanPIC = inputPIC!.Trim().Replace("\n", string.Empty);
if (cleanPIC.Length < 230)
return null;
// Convert to byte array, null if invalid hex string
byte[]? pic = Tools.HexStringToByteArray(cleanPIC.Substring(0, 230));
return pic;
}
/// <summary>
/// Validates a string representing a layerbreak value (in sectors)
/// </summary>
/// <param name="inputLayerbreak">String representing layerbreak value</param>
/// <param name="layerbreak">Output layerbreak value, null if not valid</param>
/// <returns>True if layerbreak is valid, false otherwise</returns>
public static long? ParseLayerbreak(string? inputLayerbreak)
{
if (string.IsNullOrEmpty(inputLayerbreak))
return null;
if (!long.TryParse(inputLayerbreak, out long layerbreak))
return null;
return ParseLayerbreak(layerbreak);
}
/// <summary>
/// Validates a layerbreak value (in sectors)
/// </summary>
/// <param name="inputLayerbreak">Number representing layerbreak value</param>
/// <param name="layerbreak">Output layerbreak value, null if not valid</param>
/// <returns>True if layerbreak is valid, false otherwise</returns>
public static long? ParseLayerbreak(long? layerbreak)
{
// Check that layerbreak is positive number and smaller than largest disc size (in sectors)
if (layerbreak <= 0 || layerbreak > 24438784)
return null;
return layerbreak;
}
/// <summary>
/// Converts a CRC32 hash hex string into uint32 representation
/// </summary>
/// <param name="inputLayerbreak">Hex string representing CRC32 hash</param>
/// <param name="layerbreak">Output CRC32 value, null if not valid</param>
/// <returns>True if CRC32 hash string is valid, false otherwise</returns>
public static uint? ParseCRC32(string? inputCRC32)
{
if (string.IsNullOrEmpty(inputCRC32))
return null;
byte[]? crc32 = Tools.HexStringToByteArray(inputCRC32);
if (crc32 == null || crc32.Length != 4)
return null;
return (uint)(0x01000000 * crc32[0] + 0x00010000 * crc32[1] + 0x00000100 * crc32[2] + 0x00000001 * crc32[3]);
}
#endregion

View File

@@ -15,12 +15,12 @@ namespace MPF.Test.Core.Converters
/// <summary>
/// DiscType values that map to InternalDriveType
/// </summary>
private static readonly DriveType[] _mappableDriveTypes = new DriveType[]
{
private static readonly DriveType[] _mappableDriveTypes =
[
DriveType.CDRom,
DriveType.Fixed,
DriveType.Removable,
};
];
/// <summary>
/// Check that every supported DriveType maps to an InternalDriveType
@@ -43,37 +43,21 @@ namespace MPF.Test.Core.Converters
/// Generate a test set of DriveType values
/// </summary>
/// <returns>MemberData-compatible list of DriveType values</returns>
#if NET48
public static List<object[]> GenerateDriveTypeMappingTestData()
#else
public static List<object?[]> GenerateDriveTypeMappingTestData()
#endif
{
#if NET48
var testData = new List<object[]>() { new object[] { null, true } };
#else
var testData = new List<object?[]>() { new object?[] { null, true } };
#endif
foreach (DriveType driveType in Enum.GetValues(typeof(DriveType)))
{
if (_mappableDriveTypes.Contains(driveType))
#if NET48
testData.Add(new object[] { driveType, false });
#else
testData.Add(new object?[] { driveType, false });
#endif
testData.Add([driveType, false]);
else
#if NET48
testData.Add(new object[] { driveType, true });
#else
testData.Add(new object?[] { driveType, true });
#endif
testData.Add([driveType, true]);
}
return testData;
}
#endregion
#endregion
#region Convert to Long Name
@@ -95,30 +79,18 @@ namespace MPF.Test.Core.Converters
/// Generate a test set of InternalProgram values
/// </summary>
/// <returns>MemberData-compatible list of InternalProgram values</returns>
#if NET48
public static List<object[]> GenerateInternalProgramTestData()
#else
public static List<object?[]> GenerateInternalProgramTestData()
#endif
{
#if NET48
var testData = new List<object[]>() { new object[] { null } };
#else
var testData = new List<object?[]>() { new object?[] { null } };
#endif
foreach (InternalProgram? internalProgram in Enum.GetValues(typeof(InternalProgram)))
{
#if NET48
testData.Add(new object[] { internalProgram });
#else
testData.Add(new object?[] { internalProgram });
#endif
testData.Add([internalProgram]);
}
return testData;
}
#endregion
#endregion
// TODO: Add from-string tests
}

View File

@@ -12,8 +12,8 @@ namespace MPF.Test.Core.Utilities
/// <summary>
/// MediaType values that support drive speeds
/// </summary>
private static readonly MediaType?[] _supportDriveSpeeds = new MediaType?[]
{
private static readonly MediaType?[] _supportDriveSpeeds =
[
MediaType.CDROM,
MediaType.DVD,
MediaType.GDROM,
@@ -21,13 +21,13 @@ namespace MPF.Test.Core.Utilities
MediaType.BluRay,
MediaType.NintendoGameCubeGameDisc,
MediaType.NintendoWiiOpticalDisc,
};
];
/// <summary>
/// RedumpSystem values that are considered Audio
/// </summary>
private static readonly RedumpSystem?[] _audioSystems = new RedumpSystem?[]
{
private static readonly RedumpSystem?[] _audioSystems =
[
RedumpSystem.AtariJaguarCDInteractiveMultimediaSystem,
RedumpSystem.AudioCD,
RedumpSystem.DVDAudio,
@@ -39,40 +39,41 @@ namespace MPF.Test.Core.Utilities
RedumpSystem.PlayStationGameSharkUpdates,
RedumpSystem.PhilipsCDi,
RedumpSystem.SuperAudioCD,
};
];
/// <summary>
/// RedumpSystem values that are considered markers
/// </summary>
private static readonly RedumpSystem?[] _markerSystems = new RedumpSystem?[]
{
private static readonly RedumpSystem?[] _markerSystems =
[
RedumpSystem.MarkerArcadeEnd,
RedumpSystem.MarkerComputerEnd,
RedumpSystem.MarkerDiscBasedConsoleEnd,
RedumpSystem.MarkerOtherEnd,
};
];
/// <summary>
/// RedumpSystem values that are have reversed ringcodes
/// </summary>
private static readonly RedumpSystem?[] _reverseRingcodeSystems = new RedumpSystem?[]
{
private static readonly RedumpSystem?[] _reverseRingcodeSystems =
[
RedumpSystem.SonyPlayStation2,
RedumpSystem.SonyPlayStation3,
RedumpSystem.SonyPlayStation4,
RedumpSystem.SonyPlayStation5,
RedumpSystem.SonyPlayStationPortable,
};
];
/// <summary>
/// RedumpSystem values that are considered XGD
/// </summary>
private static readonly RedumpSystem?[] _xgdSystems = new RedumpSystem?[]
{
private static readonly RedumpSystem?[] _xgdSystems =
[
RedumpSystem.MicrosoftXbox,
RedumpSystem.MicrosoftXbox360,
RedumpSystem.MicrosoftXboxOne,
RedumpSystem.MicrosoftXboxSeriesXS,
};
];
/// <summary>
/// Check that all optical media support drive speeds
@@ -143,31 +144,15 @@ namespace MPF.Test.Core.Utilities
/// Generate a test set of MediaType values that support drive speeds
/// </summary>
/// <returns>MemberData-compatible list of MediaType values</returns>
#if NET48
public static List<object[]> GenerateSupportDriveSpeedsTestData()
#else
public static List<object?[]> GenerateSupportDriveSpeedsTestData()
#endif
{
#if NET48
var testData = new List<object[]>() { new object[] { null, false } };
#else
var testData = new List<object?[]>() { new object?[] { null, false } };
#endif
foreach (MediaType mediaType in Enum.GetValues(typeof(MediaType)))
{
if (_supportDriveSpeeds.Contains(mediaType))
#if NET48
testData.Add(new object[] { mediaType, true });
#else
testData.Add(new object?[] { mediaType, true });
#endif
testData.Add([mediaType, true]);
else
#if NET48
testData.Add(new object[] { mediaType, false });
#else
testData.Add(new object?[] { mediaType, false });
#endif
testData.Add([mediaType, false]);
}
return testData;
@@ -177,31 +162,15 @@ namespace MPF.Test.Core.Utilities
/// Generate a test set of RedumpSystem values that are considered Audio
/// </summary>
/// <returns>MemberData-compatible list of RedumpSystem values</returns>
#if NET48
public static List<object[]> GenerateAudioSystemsTestData()
#else
public static List<object?[]> GenerateAudioSystemsTestData()
#endif
{
#if NET48
var testData = new List<object[]>() { new object[] { null, false } };
#else
var testData = new List<object?[]>() { new object?[] { null, false } };
#endif
foreach (RedumpSystem redumpSystem in Enum.GetValues(typeof(RedumpSystem)))
{
if (_audioSystems.Contains(redumpSystem))
#if NET48
testData.Add(new object[] { redumpSystem, true });
#else
testData.Add(new object?[] { redumpSystem, true });
#endif
testData.Add([redumpSystem, true]);
else
#if NET48
testData.Add(new object[] { redumpSystem, false });
#else
testData.Add(new object?[] { redumpSystem, false });
#endif
testData.Add([redumpSystem, false]);
}
return testData;
@@ -211,31 +180,15 @@ namespace MPF.Test.Core.Utilities
/// Generate a test set of RedumpSystem values that are considered markers
/// </summary>
/// <returns>MemberData-compatible list of RedumpSystem values</returns>
#if NET48
public static List<object[]> GenerateMarkerSystemsTestData()
#else
public static List<object?[]> GenerateMarkerSystemsTestData()
#endif
{
#if NET48
var testData = new List<object[]>() { new object[] { null, false } };
#else
var testData = new List<object?[]>() { new object?[] { null, false } };
#endif
foreach (RedumpSystem redumpSystem in Enum.GetValues(typeof(RedumpSystem)))
{
if (_markerSystems.Contains(redumpSystem))
#if NET48
testData.Add(new object[] { redumpSystem, true });
#else
testData.Add(new object?[] { redumpSystem, true });
#endif
testData.Add([redumpSystem, true]);
else
#if NET48
testData.Add(new object[] { redumpSystem, false });
#else
testData.Add(new object?[] { redumpSystem, false });
#endif
testData.Add([redumpSystem, false]);
}
return testData;
@@ -245,31 +198,15 @@ namespace MPF.Test.Core.Utilities
/// Generate a test set of RedumpSystem values that are considered markers
/// </summary>
/// <returns>MemberData-compatible list of RedumpSystem values</returns>
#if NET48
public static List<object[]> GenerateReversedRingcodeSystemsTestData()
#else
public static List<object?[]> GenerateReversedRingcodeSystemsTestData()
#endif
{
#if NET48
var testData = new List<object[]>() { new object[] { null, false } };
#else
var testData = new List<object?[]>() { new object?[] { null, false } };
#endif
foreach (RedumpSystem redumpSystem in Enum.GetValues(typeof(RedumpSystem)))
{
if (_reverseRingcodeSystems.Contains(redumpSystem))
#if NET48
testData.Add(new object[] { redumpSystem, true });
#else
testData.Add(new object?[] { redumpSystem, true });
#endif
testData.Add([redumpSystem, true]);
else
#if NET48
testData.Add(new object[] { redumpSystem, false });
#else
testData.Add(new object?[] { redumpSystem, false });
#endif
testData.Add([redumpSystem, false]);
}
return testData;
@@ -279,31 +216,15 @@ namespace MPF.Test.Core.Utilities
/// Generate a test set of RedumpSystem values that are considered XGD
/// </summary>
/// <returns>MemberData-compatible list of RedumpSystem values</returns>
#if NET48
public static List<object[]> GenerateXGDSystemsTestData()
#else
public static List<object?[]> GenerateXGDSystemsTestData()
#endif
{
#if NET48
var testData = new List<object[]>() { new object[] { null, false } };
#else
var testData = new List<object?[]>() { new object?[] { null, false } };
#endif
foreach (RedumpSystem redumpSystem in Enum.GetValues(typeof(RedumpSystem)))
{
if (_xgdSystems.Contains(redumpSystem))
#if NET48
testData.Add(new object[] { redumpSystem, true });
#else
testData.Add(new object?[] { redumpSystem, true });
#endif
testData.Add([redumpSystem, true]);
else
#if NET48
testData.Add(new object[] { redumpSystem, false });
#else
testData.Add(new object?[] { redumpSystem, false });
#endif
testData.Add([redumpSystem, false]);
}
return testData;

View File

@@ -14,7 +14,7 @@ namespace MPF.Test.Library
[InlineData("fd A test.img", 'A', true, MediaType.FloppyDisk, true)]
[InlineData("dvd X test.iso 8 /raw", 'X', false, MediaType.FloppyDisk, false)]
[InlineData("stop D", 'D', false, MediaType.DVD, true)]
public void ParametersValidTest(string parameters, char letter, bool isFloppy, MediaType? mediaType, bool expected)
public void ParametersValidTest(string? parameters, char letter, bool isFloppy, MediaType? mediaType, bool expected)
{
var options = new Options() { InternalProgram = InternalProgram.DiscImageCreator };

View File

@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.IO;
using MPF.Core;
using SabreTools.RedumpLib;
using SabreTools.RedumpLib.Data;
using Xunit;
@@ -8,67 +9,18 @@ namespace MPF.Test.Library
{
public class InfoToolTests
{
[Theory]
[InlineData(null, 0, 0, 0, 0, null)]
[InlineData(null, 12345, 0, 0, 0, null)]
[InlineData(null, 12345, 1, 0, 0, null)]
[InlineData(null, 12345, 1, 2, 0, null)]
[InlineData(null, 12345, 1, 2, 3, null)]
[InlineData(MediaType.CDROM, 0, 0, 0, 0, "CD-ROM")]
[InlineData(MediaType.CDROM, 12345, 0, 0, 0, "CD-ROM")]
[InlineData(MediaType.CDROM, 12345, 1, 0, 0, "CD-ROM")]
[InlineData(MediaType.CDROM, 12345, 1, 2, 0, "CD-ROM")]
[InlineData(MediaType.CDROM, 12345, 1, 2, 3, "CD-ROM")]
[InlineData(MediaType.DVD, 0, 0, 0, 0, "DVD-ROM-5")]
[InlineData(MediaType.DVD, 12345, 0, 0, 0, "DVD-ROM-5")]
[InlineData(MediaType.DVD, 12345, 1, 0, 0, "DVD-ROM-9")]
[InlineData(MediaType.DVD, 12345, 1, 2, 0, "DVD-ROM-9")]
[InlineData(MediaType.DVD, 12345, 1, 2, 3, "DVD-ROM-9")]
[InlineData(MediaType.BluRay, 0, 0, 0, 0, "BD-ROM-25")]
[InlineData(MediaType.BluRay, 12345, 0, 0, 0, "BD-ROM-25")]
[InlineData(MediaType.BluRay, 26_843_531_857, 0, 0, 0, "BD-ROM-33")]
[InlineData(MediaType.BluRay, 12345, 1, 0, 0, "BD-ROM-50")]
[InlineData(MediaType.BluRay, 53_687_063_713, 1, 0, 0, "BD-ROM-66")]
[InlineData(MediaType.BluRay, 12345, 1, 2, 0, "BD-ROM-100")]
[InlineData(MediaType.BluRay, 12345, 1, 2, 3, "BD-ROM-128")]
[InlineData(MediaType.UMD, 0, 0, 0, 0, "UMD-SL")]
[InlineData(MediaType.UMD, 12345, 0, 0, 0, "UMD-SL")]
[InlineData(MediaType.UMD, 12345, 1, 0, 0, "UMD-DL")]
[InlineData(MediaType.UMD, 12345, 1, 2, 0, "UMD-DL")]
[InlineData(MediaType.UMD, 12345, 1, 2, 3, "UMD-DL")]
public void GetFixedMediaTypeTest(
MediaType? mediaType,
long size,
long layerbreak,
long layerbreak2,
long layerbreak3,
#if NET48
string expected)
#else
string? expected)
#endif
{
// TODO: Add tests around BDU
var actual = InfoTool.GetFixedMediaType(mediaType, null, size, layerbreak, layerbreak2, layerbreak3);
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(null, "")]
[InlineData(" ", "")]
[InlineData(" ", " ")]
[InlineData("super\\blah.bin", "super\\blah.bin")]
[InlineData("super\\hero\\blah.bin", "super\\hero\\blah.bin")]
[InlineData("super.hero\\blah.bin", "super.hero\\blah.bin")]
[InlineData("superhero\\blah.rev.bin", "superhero\\blah.rev.bin")]
[InlineData("super&hero\\blah.bin", "super&hero\\blah.bin")]
[InlineData("superhero\\blah&foo.bin", "superhero\\blah&foo.bin")]
#if NET48
public void NormalizeOutputPathsTest(string outputPath, string expectedPath)
#else
public void NormalizeOutputPathsTest(string? outputPath, string? expectedPath)
#endif
{
if (!string.IsNullOrWhiteSpace(expectedPath))
if (!string.IsNullOrEmpty(expectedPath))
expectedPath = Path.GetFullPath(expectedPath);
string actualPath = InfoTool.NormalizeOutputPaths(outputPath, true);
@@ -84,21 +36,13 @@ namespace MPF.Test.Library
CommonDiscInfo = new CommonDiscInfoSection()
{
Comments = "This is a comments line\n[T:ISBN] ISBN Value",
#if NET48
CommentsSpecialFields = new Dictionary<SiteCode?, string>()
#else
CommentsSpecialFields = new Dictionary<SiteCode, string>()
#endif
{
[SiteCode.VolumeLabel] = "VOLUME_LABEL",
},
Contents = "This is a contents line\n[T:GF] Game Footage",
#if NET48
ContentsSpecialFields = new Dictionary<SiteCode?, string>()
#else
ContentsSpecialFields = new Dictionary<SiteCode, string>()
#endif
{
[SiteCode.Patches] = "1.04 patch",
},
@@ -106,7 +50,7 @@ namespace MPF.Test.Library
};
// Process the special fields
InfoTool.ProcessSpecialFields(info);
Formatter.ProcessSpecialFields(info);
// Validate the basics
Assert.NotNull(info.CommonDiscInfo.Comments);
@@ -133,7 +77,7 @@ namespace MPF.Test.Library
};
// Process the special fields
InfoTool.ProcessSpecialFields(info);
Formatter.ProcessSpecialFields(info);
// Validate
Assert.Null(info.CommonDiscInfo);
@@ -148,21 +92,13 @@ namespace MPF.Test.Library
CommonDiscInfo = new CommonDiscInfoSection()
{
Comments = null,
#if NET48
CommentsSpecialFields = new Dictionary<SiteCode?, string>()
#else
CommentsSpecialFields = new Dictionary<SiteCode, string>()
#endif
{
[SiteCode.VolumeLabel] = "VOLUME_LABEL",
},
Contents = null,
#if NET48
ContentsSpecialFields = new Dictionary<SiteCode?, string>()
#else
ContentsSpecialFields = new Dictionary<SiteCode, string>()
#endif
{
[SiteCode.Patches] = "1.04 patch",
},
@@ -170,7 +106,7 @@ namespace MPF.Test.Library
};
// Process the special fields
InfoTool.ProcessSpecialFields(info);
Formatter.ProcessSpecialFields(info);
// Validate the basics
Assert.NotNull(info.CommonDiscInfo.Comments);
@@ -204,7 +140,7 @@ namespace MPF.Test.Library
};
// Process the special fields
InfoTool.ProcessSpecialFields(info);
Formatter.ProcessSpecialFields(info);
// Validate the basics
Assert.NotNull(info.CommonDiscInfo.Comments);

View File

@@ -11,11 +11,11 @@ namespace MPF.Test.Library
[Fact]
public void SanitizeFoundProtectionsActiveMARKTest()
{
List<string> protections = new List<string>()
{
List<string> protections =
[
"ActiveMARK",
"ActiveMARK 5",
};
];
string sanitized = Protection.SanitizeFoundProtections(protections);
Assert.Equal("ActiveMARK 5", sanitized);
@@ -24,11 +24,11 @@ namespace MPF.Test.Library
[Fact]
public void SanitizeFoundProtectionsCactusDataShieldTest()
{
List<string> protections = new List<string>()
{
List<string> protections =
[
"Cactus Data Shield 200",
"Cactus Data Shield 200 (Build 3.0.100a)",
};
];
string sanitized = Protection.SanitizeFoundProtections(protections);
Assert.Equal("Cactus Data Shield 200 (Build 3.0.100a)", sanitized);
@@ -37,11 +37,11 @@ namespace MPF.Test.Library
[Fact]
public void SanitizeFoundProtectionsCDCheckTest()
{
List<string> protections = new List<string>()
{
List<string> protections =
[
"Anything Else Protection",
"Executable-Based CD Check",
};
];
string sanitized = Protection.SanitizeFoundProtections(protections);
Assert.Equal("Anything Else Protection", sanitized);
@@ -50,11 +50,11 @@ namespace MPF.Test.Library
[Fact]
public void SanitizeFoundProtectionsCDCopsTest()
{
List<string> protections = new List<string>()
{
List<string> protections =
[
"CD-Cops",
"CD-Cops v1.2.0",
};
];
string sanitized = Protection.SanitizeFoundProtections(protections);
Assert.Equal("CD-Cops v1.2.0", sanitized);
@@ -63,11 +63,11 @@ namespace MPF.Test.Library
[Fact]
public void SanitizeFoundProtectionsCDKeyTest()
{
List<string> protections = new List<string>()
{
List<string> protections =
[
"Anything Else Protection",
"CD-Key / Serial",
};
];
string sanitized = Protection.SanitizeFoundProtections(protections);
Assert.Equal("Anything Else Protection", sanitized);
@@ -76,11 +76,11 @@ namespace MPF.Test.Library
[Fact]
public void SanitizeFoundProtectionsEACdKeyTest()
{
List<string> protections = new List<string>()
{
List<string> protections =
[
"EA CdKey Registration Module",
"EA CdKey Registration Module v1.2.0",
};
];
string sanitized = Protection.SanitizeFoundProtections(protections);
Assert.Equal("EA CdKey Registration Module v1.2.0", sanitized);
@@ -89,11 +89,11 @@ namespace MPF.Test.Library
[Fact]
public void SanitizeFoundProtectionsEADRMTest()
{
List<string> protections = new List<string>()
{
List<string> protections =
[
"EA DRM Protection",
"EA DRM Protection v1.2.0",
};
];
string sanitized = Protection.SanitizeFoundProtections(protections);
Assert.Equal("EA DRM Protection v1.2.0", sanitized);
@@ -102,11 +102,11 @@ namespace MPF.Test.Library
[Fact]
public void SanitizeFoundProtectionsGFWLTest()
{
List<string> protections = new List<string>()
{
List<string> protections =
[
"Games for Windows LIVE",
"Games for Windows LIVE v1.2.0",
};
];
string sanitized = Protection.SanitizeFoundProtections(protections);
Assert.Equal("Games for Windows LIVE v1.2.0", sanitized);
@@ -115,11 +115,11 @@ namespace MPF.Test.Library
[Fact]
public void SanitizeFoundProtectionsGFWLZDPPTest()
{
List<string> protections = new List<string>()
{
List<string> protections =
[
"Games for Windows LIVE",
"Games for Windows LIVE Zero Day Piracy Protection",
};
];
string sanitized = Protection.SanitizeFoundProtections(protections);
Assert.Equal("Games for Windows LIVE, Games for Windows LIVE Zero Day Piracy Protection", sanitized);
@@ -128,11 +128,11 @@ namespace MPF.Test.Library
[Fact]
public void SanitizeFoundProtectionsImpulseReactorTest()
{
List<string> protections = new List<string>()
{
List<string> protections =
[
"Impulse Reactor",
"Impulse Reactor Core Module v1.2.0",
};
];
string sanitized = Protection.SanitizeFoundProtections(protections);
Assert.Equal("Impulse Reactor Core Module v1.2.0", sanitized);
@@ -145,14 +145,14 @@ namespace MPF.Test.Library
[InlineData(3)]
public void SanitizeFoundProtectionsJoWoodXProtTest(int skip)
{
List<string> protections = new List<string>()
{
List<string> protections =
[
"JoWood X-Prot 1.2.0.00",
"JoWood X-Prot v2",
"JoWood X-Prot v1.4+",
"JoWood X-Prot v1.0-v1.3",
"JoWood X-Prot",
};
];
// Safeguard for the future
if (skip >= protections.Count)
@@ -168,11 +168,11 @@ namespace MPF.Test.Library
[Fact]
public void SanitizeFoundProtectionsOnlineRegistrationTest()
{
List<string> protections = new List<string>()
{
List<string> protections =
[
"Anything Else Protection",
"Executable-Based Online Registration",
};
];
string sanitized = Protection.SanitizeFoundProtections(protections);
Assert.Equal("Anything Else Protection", sanitized);
@@ -185,14 +185,14 @@ namespace MPF.Test.Library
[InlineData(3)]
public void SanitizeFoundProtectionStarForceTest(int skip)
{
List<string> protections = new List<string>()
{
List<string> protections =
[
"StarForce 1.20.000.000",
"StarForce 5 [Protected Module]",
"StarForce 5",
"StarForce 3-5",
"StarForce",
};
];
// Safeguard for the future
if (skip >= protections.Count)
@@ -208,11 +208,11 @@ namespace MPF.Test.Library
[Fact]
public void SanitizeFoundProtectionsSysiphusTest()
{
List<string> protections = new List<string>()
{
List<string> protections =
[
"Sysiphus",
"Sysiphus v1.2.0",
};
];
string sanitized = Protection.SanitizeFoundProtections(protections);
Assert.Equal("Sysiphus v1.2.0", sanitized);
@@ -221,11 +221,11 @@ namespace MPF.Test.Library
[Fact]
public void SanitizeFoundProtectionsXCPTest()
{
List<string> protections = new List<string>()
{
List<string> protections =
[
"XCP",
"XCP v1.2.0",
};
];
string sanitized = Protection.SanitizeFoundProtections(protections);
Assert.Equal("XCP v1.2.0", sanitized);

View File

@@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net48;net6.0-windows;net7.0-windows</TargetFrameworks>
<IsPackable>false</IsPackable>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)'!='net48'">
<TargetFrameworks>net6.0-windows;net8.0-windows</TargetFrameworks>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
@@ -14,23 +14,22 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeCoverage" Version="17.7.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="Microsoft.CodeCoverage" Version="17.9.0-preview-23531-01" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0-preview-23531-01" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="psxt001z" Version="0.21.0-beta1" />
<PackageReference Include="SabreTools.RedumpLib" Version="1.1.1" />
<PackageReference Include="xunit" Version="2.5.3" />
<PackageReference Include="SabreTools.RedumpLib" Version="1.3.2" />
<PackageReference Include="xunit" Version="2.6.2" />
<PackageReference Include="xunit.abstractions" Version="2.0.3" />
<PackageReference Include="xunit.analyzers" Version="1.4.0" />
<PackageReference Include="xunit.assert" Version="2.5.3" />
<PackageReference Include="xunit.core" Version="2.5.3" />
<PackageReference Include="xunit.extensibility.core" Version="2.5.3" />
<PackageReference Include="xunit.extensibility.execution" Version="2.5.3" />
<PackageReference Include="xunit.runner.console" Version="2.5.3">
<PackageReference Include="xunit.analyzers" Version="1.6.0" />
<PackageReference Include="xunit.assert" Version="2.6.2" />
<PackageReference Include="xunit.core" Version="2.6.2" />
<PackageReference Include="xunit.extensibility.core" Version="2.6.2" />
<PackageReference Include="xunit.extensibility.execution" Version="2.6.2" />
<PackageReference Include="xunit.runner.console" Version="2.6.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@@ -18,7 +18,7 @@ namespace MPF.Test.Modules
[InlineData(RedumpSystem.SonyPlayStation3, MediaType.BluRay, CommandStrings.BluRay)]
[InlineData(RedumpSystem.AppleMacintosh, MediaType.FloppyDisk, CommandStrings.Floppy)]
[InlineData(RedumpSystem.RawThrillsVarious, MediaType.GDROM, null)]
public void ParametersFromSystemAndTypeTest(RedumpSystem? knownSystem, MediaType? mediaType, string expected)
public void ParametersFromSystemAndTypeTest(RedumpSystem? knownSystem, MediaType? mediaType, string? expected)
{
var options = new Options();
var actual = new Parameters(knownSystem, mediaType, "D:\\", "disc.bin", 16, options);
@@ -29,7 +29,7 @@ namespace MPF.Test.Modules
[InlineData(RedumpSystem.AppleMacintosh, MediaType.LaserDisc, null)] // Deliberately unsupported
[InlineData(RedumpSystem.IBMPCcompatible, MediaType.CDROM, new string[] { FlagStrings.C2Opcode, FlagStrings.NoFixSubQSecuROM, FlagStrings.ScanFileProtect })]
[InlineData(RedumpSystem.NintendoGameCube, MediaType.NintendoGameCubeGameDisc, new string[] { FlagStrings.Raw })]
public void ParametersFromOptionsSpecialDefaultTest(RedumpSystem? knownSystem, MediaType? mediaType,string[] expected)
public void ParametersFromOptionsSpecialDefaultTest(RedumpSystem? knownSystem, MediaType? mediaType, string[]? expected)
{
var options = new Options();
var actual = new Parameters(knownSystem, mediaType, "D:\\", "disc.bin", 16, options);
@@ -147,7 +147,7 @@ namespace MPF.Test.Modules
[InlineData("bd D longer\\path_test.iso 16", true)]
[InlineData("stop D", true)]
[InlineData("ls", false)]
public void ValidateParametersTest(string parameters, bool expected)
public void ValidateParametersTest(string? parameters, bool expected)
{
var actual = new Parameters(parameters);
Assert.Equal(expected, actual.IsValid());
@@ -161,11 +161,7 @@ namespace MPF.Test.Modules
[InlineData(MediaType.FloppyDisk, ".img")]
[InlineData(MediaType.Cassette, ".wav")]
[InlineData(MediaType.NONE, null)]
#if NET48
public void MediaTypeToExtensionTest(MediaType? mediaType, string expected)
#else
public void MediaTypeToExtensionTest(MediaType? mediaType, string? expected)
#endif
{
var actual = Converters.Extension(mediaType);
Assert.Equal(expected, actual);
@@ -219,7 +215,7 @@ namespace MPF.Test.Modules
Assert.Equal(expected, actual);
}
#endregion
#endregion
[Fact]
public void DiscImageCreatorAudioParametersTest()

View File

@@ -95,31 +95,15 @@ namespace MPF.Test.RedumpLib
/// Generate a test set of DiscType values
/// </summary>
/// <returns>MemberData-compatible list of DiscType values</returns>
#if NET48
public static List<object[]> GenerateDiscTypeMappingTestData()
#else
public static List<object?[]> GenerateDiscTypeMappingTestData()
#endif
{
#if NET48
var testData = new List<object[]>() { new object[] { null, true } };
#else
var testData = new List<object?[]>() { new object?[] { null, true } };
#endif
foreach (DiscType? discType in Enum.GetValues(typeof(DiscType)))
{
if (_mappableDiscTypes.Contains(discType))
#if NET48
testData.Add(new object[] { discType, false });
#else
testData.Add(new object?[] { discType, false });
#endif
else
#if NET48
testData.Add(new object[] { discType, true });
#else
testData.Add(new object?[] { discType, true });
#endif
}
return testData;
@@ -129,24 +113,12 @@ namespace MPF.Test.RedumpLib
/// Generate a test set of RedumpSystem values
/// </summary>
/// <returns>MemberData-compatible list of RedumpSystem values</returns>
#if NET48
public static List<object[]> GenerateRedumpSystemMappingTestData()
#else
public static List<object?[]> GenerateRedumpSystemMappingTestData()
#endif
{
#if NET48
var testData = new List<object[]>() { new object[] { null } };
#else
var testData = new List<object?[]>() { new object?[] { null } };
#endif
foreach (RedumpSystem? redumpSystem in Enum.GetValues(typeof(RedumpSystem)))
{
#if NET48
testData.Add(new object[] { redumpSystem });
#else
testData.Add(new object?[] { redumpSystem });
#endif
}
return testData;
@@ -156,32 +128,16 @@ namespace MPF.Test.RedumpLib
/// Generate a test set of mappable media types
/// </summary>
/// <returns>MemberData-compatible list of MediaTypes</returns>
#if NET48
public static List<object[]> GenerateMediaTypeMappingTestData()
#else
public static List<object?[]> GenerateMediaTypeMappingTestData()
#endif
{
#if NET48
var testData = new List<object[]>() { new object[] { null, true } };
#else
var testData = new List<object?[]>() { new object?[] { null, true } };
#endif
foreach (MediaType? mediaType in Enum.GetValues(typeof(MediaType)))
{
if (_mappableMediaTypes.Contains(mediaType))
#if NET48
testData.Add(new object[] { mediaType, false });
#else
testData.Add(new object?[] { mediaType, false });
#endif
else
#if NET48
testData.Add(new object[] { mediaType, true });
#else
testData.Add(new object?[] { mediaType, true });
#endif
}
return testData;
@@ -212,24 +168,12 @@ namespace MPF.Test.RedumpLib
/// Generate a test set of DiscCategory values
/// </summary>
/// <returns>MemberData-compatible list of DiscCategory values</returns>
#if NET48
public static List<object[]> GenerateDiscCategoryTestData()
#else
public static List<object?[]> GenerateDiscCategoryTestData()
#endif
{
#if NET48
var testData = new List<object[]>() { new object[] { null, true } };
#else
var testData = new List<object?[]>() { new object?[] { null, true } };
#endif
foreach (DiscCategory? discCategory in Enum.GetValues(typeof(DiscCategory)))
{
#if NET48
testData.Add(new object[] { discCategory, false });
#else
testData.Add(new object?[] { discCategory, false });
#endif
}
return testData;
@@ -260,31 +204,15 @@ namespace MPF.Test.RedumpLib
/// Generate a test set of DiscType values
/// </summary>
/// <returns>MemberData-compatible list of DiscType values</returns>
#if NET48
public static List<object[]> GenerateDiscTypeTestData()
#else
public static List<object?[]> GenerateDiscTypeTestData()
#endif
{
#if NET48
var testData = new List<object[]>() { new object[] { null, true } };
#else
var testData = new List<object?[]>() { new object?[] { null, true } };
#endif
foreach (DiscType? discType in Enum.GetValues(typeof(DiscType)))
{
if (discType == DiscType.NONE)
#if NET48
testData.Add(new object[] { discType, true });
#else
testData.Add(new object?[] { discType, true });
#endif
else
#if NET48
testData.Add(new object[] { discType, false });
#else
testData.Add(new object?[] { discType, false });
#endif
}
return testData;
@@ -341,7 +269,7 @@ namespace MPF.Test.RedumpLib
foreach (Language? language in fullLanguages)
{
var code = language.TwoLetterCode();
if (string.IsNullOrWhiteSpace(code))
if (string.IsNullOrEmpty(code))
continue;
// Throw if the code already exists
@@ -368,7 +296,7 @@ namespace MPF.Test.RedumpLib
foreach (Language? language in fullLanguages)
{
var code = language.ThreeLetterCode();
if (string.IsNullOrWhiteSpace(code))
if (string.IsNullOrEmpty(code))
continue;
// Throw if the code already exists
@@ -395,7 +323,7 @@ namespace MPF.Test.RedumpLib
foreach (Language? language in fullLanguages)
{
var code = language.ThreeLetterCodeAlt();
if (string.IsNullOrWhiteSpace(code))
if (string.IsNullOrEmpty(code))
continue;
// Throw if the code already exists
@@ -413,24 +341,12 @@ namespace MPF.Test.RedumpLib
/// Generate a test set of Language values
/// </summary>
/// <returns>MemberData-compatible list of Language values</returns>
#if NET48
public static List<object[]> GenerateLanguageTestData()
#else
public static List<object?[]> GenerateLanguageTestData()
#endif
{
#if NET48
var testData = new List<object[]>() { new object[] { null, true } };
#else
var testData = new List<object?[]>() { new object?[] { null, true } };
#endif
foreach (Language? language in Enum.GetValues(typeof(Language)))
{
#if NET48
testData.Add(new object[] { language, false });
#else
testData.Add(new object?[] { language, false });
#endif
}
return testData;
@@ -461,24 +377,12 @@ namespace MPF.Test.RedumpLib
/// Generate a test set of LanguageSelection values
/// </summary>
/// <returns>MemberData-compatible list of LanguageSelection values</returns>
#if NET48
public static List<object[]> GenerateLanguageSelectionTestData()
#else
public static List<object?[]> GenerateLanguageSelectionTestData()
#endif
{
#if NET48
var testData = new List<object[]>() { new object[] { null, true } };
#else
var testData = new List<object?[]>() { new object?[] { null, true } };
#endif
foreach (LanguageSelection? languageSelection in Enum.GetValues(typeof(LanguageSelection)))
{
#if NET48
testData.Add(new object[] { languageSelection, false });
#else
testData.Add(new object?[] { languageSelection, false });
#endif
}
return testData;
@@ -526,24 +430,12 @@ namespace MPF.Test.RedumpLib
/// Generate a test set of MediaType values
/// </summary>
/// <returns>MemberData-compatible list of MediaType values</returns>
#if NET48
public static List<object[]> GenerateMediaTypeTestData()
#else
public static List<object?[]> GenerateMediaTypeTestData()
#endif
{
#if NET48
var testData = new List<object[]>() { new object[] { null, true } };
#else
var testData = new List<object?[]>() { new object?[] { null, true } };
#endif
foreach (MediaType? mediaType in Enum.GetValues(typeof(MediaType)))
{
#if NET48
testData.Add(new object[] { mediaType, false });
#else
testData.Add(new object?[] { mediaType, false });
#endif
}
return testData;
@@ -600,7 +492,7 @@ namespace MPF.Test.RedumpLib
foreach (Region? region in fullRegions)
{
var code = region.ShortName();
if (string.IsNullOrWhiteSpace(code))
if (string.IsNullOrEmpty(code))
continue;
// Throw if the code already exists
@@ -618,24 +510,12 @@ namespace MPF.Test.RedumpLib
/// Generate a test set of Region values
/// </summary>
/// <returns>MemberData-compatible list of Region values</returns>
#if NET48
public static List<object[]> GenerateRegionTestData()
#else
public static List<object?[]> GenerateRegionTestData()
#endif
{
#if NET48
var testData = new List<object[]>() { new object[] { null, true } };
#else
var testData = new List<object?[]>() { new object?[] { null, true } };
#endif
foreach (Region? region in Enum.GetValues(typeof(Region)))
{
#if NET48
testData.Add(new object[] { region, false });
#else
testData.Add(new object?[] { region, false });
#endif
}
return testData;
@@ -683,24 +563,12 @@ namespace MPF.Test.RedumpLib
/// Generate a test set of SiteCode values
/// </summary>
/// <returns>MemberData-compatible list of SiteCode values</returns>
#if NET48
public static List<object[]> GenerateSiteCodeTestData()
#else
public static List<object?[]> GenerateSiteCodeTestData()
#endif
{
#if NET48
var testData = new List<object[]>() { new object[] { null, true } };
#else
var testData = new List<object?[]>() { new object?[] { null, true } };
#endif
foreach (SiteCode? siteCode in Enum.GetValues(typeof(SiteCode)))
{
#if NET48
testData.Add(new object[] { siteCode, false });
#else
testData.Add(new object?[] { siteCode, false });
#endif
}
return testData;
@@ -754,28 +622,16 @@ namespace MPF.Test.RedumpLib
/// Generate a test set of RedumpSystem values
/// </summary>
/// <returns>MemberData-compatible list of RedumpSystem values</returns>
#if NET48
public static List<object[]> GenerateRedumpSystemTestData()
#else
public static List<object?[]> GenerateRedumpSystemTestData()
#endif
{
#if NET48
var testData = new List<object[]>() { new object[] { null, true } };
#else
var testData = new List<object?[]>() { new object?[] { null, true } };
#endif
foreach (RedumpSystem? redumpSystem in Enum.GetValues(typeof(RedumpSystem)))
{
// We want to skip all markers for this
if (redumpSystem.IsMarker())
continue;
#if NET48
testData.Add(new object[] { redumpSystem, false });
#else
testData.Add(new object?[] { redumpSystem, false });
#endif
}
return testData;
@@ -806,31 +662,15 @@ namespace MPF.Test.RedumpLib
/// Generate a test set of SystemCategory values
/// </summary>
/// <returns>MemberData-compatible list of SystemCategory values</returns>
#if NET48
public static List<object[]> GenerateSystemCategoryTestData()
#else
public static List<object?[]> GenerateSystemCategoryTestData()
#endif
{
#if NET48
var testData = new List<object[]>() { new object[] { null, true } };
#else
var testData = new List<object?[]>() { new object?[] { null, true } };
#endif
foreach (SystemCategory? systemCategory in Enum.GetValues(typeof(SystemCategory)))
{
if (systemCategory == SystemCategory.NONE)
#if NET48
testData.Add(new object[] { systemCategory, true });
#else
testData.Add(new object?[] { systemCategory, true });
#endif
else
#if NET48
testData.Add(new object[] { systemCategory, false });
#else
testData.Add(new object?[] { systemCategory, false });
#endif
}
return testData;
@@ -861,24 +701,12 @@ namespace MPF.Test.RedumpLib
/// Generate a test set of YesNo values
/// </summary>
/// <returns>MemberData-compatible list of YesNo values</returns>
#if NET48
public static List<object[]> GenerateYesNoTestData()
#else
public static List<object?[]> GenerateYesNoTestData()
#endif
{
#if NET48
var testData = new List<object[]>() { new object[] { null, false } };
#else
var testData = new List<object?[]>() { new object?[] { null, false } };
#endif
foreach (YesNo? yesNo in Enum.GetValues(typeof(YesNo)))
{
#if NET48
testData.Add(new object[] { yesNo, false });
#else
testData.Add(new object?[] { yesNo, false });
#endif
}
return testData;

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using psxt001z;
using SabreTools.RedumpLib.Data;
using Xunit;
@@ -82,20 +81,12 @@ namespace MPF.Test.RedumpLib
EXEDateBuildDate = "19xx-xx-xx",
ErrorsCount = "0",
Comments = "Comment data line 1\r\nComment data line 2",
#if NET48
CommentsSpecialFields = new Dictionary<SiteCode?, string>()
#else
CommentsSpecialFields = new Dictionary<SiteCode, string>()
#endif
{
[SiteCode.ISBN] = "ISBN",
},
Contents = "Special contents 1\r\nSpecial contents 2",
#if NET48
ContentsSpecialFields = new Dictionary<SiteCode?, string>()
#else
ContentsSpecialFields = new Dictionary<SiteCode, string>()
#endif
{
[SiteCode.PlayableDemos] = "Game Demo 1",
},

View File

@@ -1,5 +1,4 @@
using System.Linq;
using MPF.Core.Data;
using MPF.Core.Data;
using SabreTools.RedumpLib.Data;
using Xunit;
@@ -16,7 +15,7 @@ namespace MPF.Test.Data
public void GetAllowedDriveSpeedForMediaTypeTest(MediaType? mediaType, int maxExpected)
{
var actual = Interface.GetSpeedsForMediaType(mediaType);
Assert.Equal(maxExpected, actual.Last());
Assert.Equal(maxExpected, actual[actual.Count - 1]);
}
}
}

View File

@@ -16,7 +16,12 @@ namespace MPF.UI.Core
public static DoubleCollection SpeedsForDVDAsCollection { get; } = GetDoubleCollectionFromIntList(DVD);
public static DoubleCollection SpeedsForHDDVDAsCollection { get; } = GetDoubleCollectionFromIntList(HDDVD);
public static DoubleCollection SpeedsForBDAsCollection { get; } = GetDoubleCollectionFromIntList(BD);
#if NET20 || NET35 || NET40
private static DoubleCollection GetDoubleCollectionFromIntList(IList<int> list)
#else
private static DoubleCollection GetDoubleCollectionFromIntList(IReadOnlyList<int> list)
=> new DoubleCollection(list.Select(i => Convert.ToDouble(i)).ToList());
#endif
=> new(list.Select(i => Convert.ToDouble(i)).ToList());
}
}

View File

@@ -11,54 +11,34 @@ namespace MPF.UI.Core
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
switch (value)
return value switch
{
case DiscCategory discCategory:
return new Element<DiscCategory>(discCategory);
case InternalProgram internalProgram:
return new Element<InternalProgram>(internalProgram);
case MediaType mediaType:
return new Element<MediaType>(mediaType);
case RedumpSystem redumpSystem:
return new RedumpSystemComboBoxItem(redumpSystem);
case Region region:
return new Element<Region>(region);
DiscCategory discCategory => new Element<DiscCategory>(discCategory),
InternalProgram internalProgram => new Element<InternalProgram>(internalProgram),
MediaType mediaType => new Element<MediaType>(mediaType),
RedumpSystem redumpSystem => new RedumpSystemComboBoxItem(redumpSystem),
Region region => new Element<Region>(region),
// Null values are treated as a system value
default:
return new RedumpSystemComboBoxItem((RedumpSystem?)null);
}
_ => new RedumpSystemComboBoxItem((RedumpSystem?)null),
};
}
#if NET48
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
#else
public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
#endif
{
// If it's an IElement but ends up null
#if NET48
if (!(value is IElement element))
#else
if (value is not IElement element)
#endif
return null;
switch (element)
return element switch
{
case Element<DiscCategory> dcElement:
return dcElement.Value;
case Element<InternalProgram> ipElement:
return ipElement.Value;
case Element<MediaType> mtElement:
return mtElement.Value;
case RedumpSystemComboBoxItem rsElement:
return rsElement.Value;
case Element<Region> reValue:
return reValue.Value;
default: return null;
}
Element<DiscCategory> dcElement => dcElement.Value,
Element<InternalProgram> ipElement => ipElement.Value,
Element<MediaType> mtElement => mtElement.Value,
RedumpSystemComboBoxItem rsElement => rsElement.Value,
Element<Region> reValue => reValue.Value,
_ => null,
};
}
}
}

View File

@@ -173,11 +173,7 @@ namespace WPFCustomMessageBox
/// </param>
/// <param name="timeoutResult">If the message box closes automatically due to the <paramref name="timeout"/> being exceeded, this result is returned.</param>
/// <returns>A System.Windows.MessageBoxResult value that specifies which message box button is clicked by the user.</returns>
#if NET48
public static MessageBoxResult Show(Window owner, string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon, int? timeout = null, MessageBoxResult timeoutResult = MessageBoxResult.None)
#else
public static MessageBoxResult Show(Window owner, string? messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon, int? timeout = null, MessageBoxResult timeoutResult = MessageBoxResult.None)
#endif
{
switch (button)
{
@@ -519,11 +515,7 @@ namespace WPFCustomMessageBox
/// <param name="timeout">The message box will close automatically after given milliseconds</param>
/// <param name="timeoutResult">If the message box closes automatically due to <paramref name="timeout"/> being exceeded, this result is returned.</param>
/// <returns>A System.Windows.MessageBoxResult value that specifies which message box button is clicked by the user.</returns>
#if NET48
private static MessageBoxResult ShowOKMessage(Window owner, string messageBoxText, string caption, string okButtonText, string cancelButtonText, MessageBoxImage? icon, int? timeout = null, MessageBoxResult timeoutResult = MessageBoxResult.None)
#else
private static MessageBoxResult ShowOKMessage(Window? owner, string? messageBoxText, string caption, string? okButtonText, string? cancelButtonText, MessageBoxImage? icon, int? timeout = null, MessageBoxResult timeoutResult = MessageBoxResult.None)
#endif
{
MessageBoxButton buttonLayout = string.IsNullOrEmpty(cancelButtonText) ? MessageBoxButton.OK : MessageBoxButton.OKCancel;
@@ -553,11 +545,7 @@ namespace WPFCustomMessageBox
/// <param name="timeout">The message box will close automatically after given milliseconds</param>
/// <param name="timeoutResult">If the message box closes automatically due to <paramref name="timeout"/> being exceeded, this result is returned.</param>
/// <returns>A System.Windows.MessageBoxResult value that specifies which message box button is clicked by the user.</returns>
#if NET48
private static MessageBoxResult ShowYesNoMessage(Window owner, string messageBoxText, string caption, string yesButtonText, string noButtonText, string cancelButtonText, MessageBoxImage? icon, int? timeout = null, MessageBoxResult timeoutResult = MessageBoxResult.None)
#else
private static MessageBoxResult ShowYesNoMessage(Window? owner, string? messageBoxText, string caption, string? yesButtonText, string? noButtonText, string? cancelButtonText, MessageBoxImage? icon, int? timeout = null, MessageBoxResult timeoutResult = MessageBoxResult.None)
#endif
{
MessageBoxButton buttonLayout = string.IsNullOrEmpty(cancelButtonText) ? MessageBoxButton.YesNo : MessageBoxButton.YesNoCancel;

View File

@@ -5,14 +5,9 @@
WindowStartupLocation="CenterScreen"
WindowStyle="None"
ResizeMode="NoResize" SizeToContent="WidthAndHeight"
TextOptions.TextFormattingMode="Display" TextOptions.TextRenderingMode="ClearType" UseLayoutRounding="True"
Title="" MinHeight="155" MaxWidth="470" MinWidth="154"
BorderBrush="DarkGray" BorderThickness="2">
<WindowChrome.WindowChrome>
<WindowChrome CaptionHeight="0" ResizeBorderThickness="0" />
</WindowChrome.WindowChrome>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />

View File

@@ -1,7 +1,11 @@
using System;
using System.Drawing;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using MPF.UI.Core;
#pragma warning disable IDE1006 // Naming Styles
namespace WPFCustomMessageBox
{
@@ -12,11 +16,7 @@ namespace WPFCustomMessageBox
{
private readonly bool _removeTitleBarIcon = true;
#if NET48
public string Caption
#else
public string? Caption
#endif
{
get
{
@@ -28,102 +28,150 @@ namespace WPFCustomMessageBox
}
}
#if NET48
public string Message
#else
public string? Message
#endif
{
get
{
#if NET35
return _TextBlock_Message!.Text;
#else
return TextBlock_Message.Text;
#endif
}
set
{
#if NET35
_TextBlock_Message!.Text = value;
#else
TextBlock_Message.Text = value;
#endif
}
}
#if NET48
public string OkButtonText
#else
public string? OkButtonText
#endif
{
get
{
#if NET35
return _Label_Ok!.Content.ToString();
#else
return Label_Ok.Content.ToString();
#endif
}
set
{
#if NET35
_Label_Ok!.Content = value.TryAddKeyboardAccellerator();
#else
Label_Ok.Content = value.TryAddKeyboardAccellerator();
#endif
}
}
#if NET48
public string CancelButtonText
#else
public string? CancelButtonText
#endif
{
get
{
#if NET35
return _Label_Cancel!.Content.ToString();
#else
return Label_Cancel.Content.ToString();
#endif
}
set
{
#if NET35
_Label_Cancel!.Content = value.TryAddKeyboardAccellerator();
#else
Label_Cancel.Content = value.TryAddKeyboardAccellerator();
#endif
}
}
#if NET48
public string YesButtonText
#else
public string? YesButtonText
#endif
{
get
{
#if NET35
return _Label_Yes!.Content.ToString();
#else
return Label_Yes.Content.ToString();
#endif
}
set
{
#if NET35
_Label_Yes!.Content = value.TryAddKeyboardAccellerator();
#else
Label_Yes.Content = value.TryAddKeyboardAccellerator();
#endif
}
}
#if NET48
public string NoButtonText
#else
public string? NoButtonText
#endif
{
get
{
#if NET35
return _Label_No!.Content.ToString();
#else
return Label_No.Content.ToString();
#endif
}
set
{
#if NET35
_Label_No!.Content = value.TryAddKeyboardAccellerator();
#else
Label_No.Content = value.TryAddKeyboardAccellerator();
#endif
}
}
public MessageBoxResult Result { get; set; }
#if NET48
internal CustomMessageBoxWindow(Window owner, string message, string caption = null, MessageBoxButton? button = null, MessageBoxImage? image = null, bool removeTitleBarIcon = true)
#else
internal CustomMessageBoxWindow(Window? owner, string? message, string? caption = null, MessageBoxButton? button = null, MessageBoxImage? image = null, bool removeTitleBarIcon = true)
#if NET35
private Button? _Button_Cancel => ItemHelper.FindChild<Button>(this, "Button_Cancel");
private Button? _Button_No => ItemHelper.FindChild<Button>(this, "Button_No");
private Button? _Button_OK => ItemHelper.FindChild<Button>(this, "Button_OK");
private Button? _Button_Yes => ItemHelper.FindChild<Button>(this, "Button_Yes");
private System.Windows.Controls.Image? _Image_MessageBox => ItemHelper.FindChild<System.Windows.Controls.Image>(this, "Image_MessageBox");
private Label? _Label_Cancel => ItemHelper.FindChild<Label>(this, "Label_Cancel");
private Label? _Label_No => ItemHelper.FindChild<Label>(this, "Label_No");
private Label? _Label_Ok => ItemHelper.FindChild<Label>(this, "Label_Ok");
private Label? _Label_Yes => ItemHelper.FindChild<Label>(this, "Label_Yes");
private TextBlock? _TextBlock_Message => ItemHelper.FindChild<TextBlock>(this, "TextBlock_Message");
#endif
internal CustomMessageBoxWindow(Window? owner, string? message, string? caption = null, MessageBoxButton? button = null, MessageBoxImage? image = null, bool removeTitleBarIcon = true)
{
#if NET40_OR_GREATER || NETCOREAPP
System.Windows.Media.TextOptions.SetTextFormattingMode(this, System.Windows.Media.TextFormattingMode.Display);
System.Windows.Media.TextOptions.SetTextRenderingMode(this, System.Windows.Media.TextRenderingMode.ClearType);
UseLayoutRounding = true;
#endif
#if NET452_OR_GREATER || NETCOREAPP
var chrome = new System.Windows.Shell.WindowChrome
{
CaptionHeight = 0,
ResizeBorderThickness = new Thickness(0),
};
System.Windows.Shell.WindowChrome.SetWindowChrome(this, chrome);
#endif
#if NET40_OR_GREATER || NETCOREAPP
InitializeComponent();
#endif
_removeTitleBarIcon = removeTitleBarIcon;
Focusable = true;
ShowActivated = true;
ShowInTaskbar = true;
if (owner != null)
if (owner != null && owner.IsLoaded)
{
Owner = owner;
WindowStartupLocation = WindowStartupLocation.CenterOwner;
@@ -137,7 +185,11 @@ namespace WPFCustomMessageBox
if (image.HasValue)
DisplayImage(image.Value);
else
#if NET35
_Image_MessageBox!.Visibility = Visibility.Collapsed;
#else
Image_MessageBox.Visibility = Visibility.Collapsed;
#endif
}
protected override void OnSourceInitialized(EventArgs e)
@@ -154,39 +206,75 @@ namespace WPFCustomMessageBox
{
case MessageBoxButton.OKCancel:
// Hide all but OK, Cancel
#if NET35
_Button_OK!.Visibility = Visibility.Visible;
_Button_OK.Focus();
_Button_Cancel!.Visibility = Visibility.Visible;
_Button_Yes!.Visibility = Visibility.Collapsed;
_Button_No!.Visibility = Visibility.Collapsed;
#else
Button_OK.Visibility = Visibility.Visible;
Button_OK.Focus();
Button_Cancel.Visibility = Visibility.Visible;
Button_Yes.Visibility = Visibility.Collapsed;
Button_No.Visibility = Visibility.Collapsed;
#endif
break;
case MessageBoxButton.YesNo:
// Hide all but Yes, No
#if NET35
_Button_Yes!.Visibility = Visibility.Visible;
_Button_Yes.Focus();
_Button_No!.Visibility = Visibility.Visible;
_Button_OK!.Visibility = Visibility.Collapsed;
_Button_Cancel!.Visibility = Visibility.Collapsed;
#else
Button_Yes.Visibility = Visibility.Visible;
Button_Yes.Focus();
Button_No.Visibility = Visibility.Visible;
Button_OK.Visibility = Visibility.Collapsed;
Button_Cancel.Visibility = Visibility.Collapsed;
#endif
break;
case MessageBoxButton.YesNoCancel:
// Hide only OK
#if NET35
_Button_Yes!.Visibility = Visibility.Visible;
_Button_Yes.Focus();
_Button_No!.Visibility = Visibility.Visible;
_Button_Cancel!.Visibility = Visibility.Visible;
_Button_OK!.Visibility = Visibility.Collapsed;
#else
Button_Yes.Visibility = Visibility.Visible;
Button_Yes.Focus();
Button_No.Visibility = Visibility.Visible;
Button_Cancel.Visibility = Visibility.Visible;
Button_OK.Visibility = Visibility.Collapsed;
#endif
break;
default:
// Hide all but OK
#if NET35
_Button_OK!.Visibility = Visibility.Visible;
_Button_OK.Focus();
_Button_Yes!.Visibility = Visibility.Collapsed;
_Button_No!.Visibility = Visibility.Collapsed;
_Button_Cancel!.Visibility = Visibility.Collapsed;
#else
Button_OK.Visibility = Visibility.Visible;
Button_OK.Focus();
Button_Yes.Visibility = Visibility.Collapsed;
Button_No.Visibility = Visibility.Collapsed;
Button_Cancel.Visibility = Visibility.Collapsed;
#endif
break;
}
}
@@ -214,8 +302,13 @@ namespace WPFCustomMessageBox
break;
}
#if NET35
_Image_MessageBox!.Source = icon.ToImageSource();
_Image_MessageBox.Visibility = Visibility.Visible;
#else
Image_MessageBox.Source = icon.ToImageSource();
Image_MessageBox.Visibility = Visibility.Visible;
#endif
}
private void Button_OK_Click(object sender, RoutedEventArgs e)

View File

@@ -60,11 +60,7 @@ namespace WPFCustomMessageBox
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
#if NET48
internal static string TryAddKeyboardAccellerator(this string input)
#else
internal static string? TryAddKeyboardAccellerator(this string? input)
#endif
{
if (input == null)
return input;

124
MPF.UI.Core/ItemHelper.cs Normal file
View File

@@ -0,0 +1,124 @@
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows;
namespace MPF.UI.Core
{
internal static class ItemHelper
{
/// <summary>
/// Finds a Child of a given item in the visual tree.
/// </summary>
/// <param name="parent">A direct parent of the queried item.</param>
/// <typeparam name="T">The type of the queried item.</typeparam>
/// <param name="childName">x:Name or Name of child. </param>
/// <returns>The first parent item that matches the submitted type parameter.
/// If not matching item can be found,
/// a null parent is being returned.</returns>
public static T? FindChild<T>(DependencyObject? parent, string childName) where T : DependencyObject
{
// Confirm parent and childName are valid.
if (parent == null) return null;
T? foundChild = null;
if (parent is ItemsControl itemsControl && itemsControl.Items != null)
{
int childrenCount = itemsControl.Items.Count;
for (int i = 0; i < childrenCount; i++)
{
var child = itemsControl.Items[i] as DependencyObject;
// If the child is not of the request child type child
if (child is not T)
{
// recursively drill down the tree
foundChild = FindChild<T>(child, childName);
// If the child is found, break so we do not overwrite the found child.
if (foundChild != null)
break;
}
else if (!string.IsNullOrEmpty(childName))
{
// If the child's name is set for search
if (child is FrameworkElement frameworkElement && frameworkElement.Name == childName)
{
// if the child's name is of the request name
foundChild = (T)child;
break;
}
}
else
{
// child element found.
foundChild = (T)child;
break;
}
}
}
else if (parent is ContentControl contentControl && contentControl.Content != null)
{
var child = contentControl.Content as DependencyObject;
// If the child is not of the request child type child
if (child is not T)
{
// recursively drill down the tree
foundChild = FindChild<T>(child, childName);
}
else if (!string.IsNullOrEmpty(childName))
{
// If the child's name is set for search
if (child is FrameworkElement frameworkElement && frameworkElement.Name == childName)
{
// if the child's name is of the request name
foundChild = (T)child;
}
}
else
{
// child element found.
foundChild = (T)child;
}
}
else if (parent is Visual)
{
int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
// If the child is not of the request child type child
if (child is not T)
{
// recursively drill down the tree
foundChild = FindChild<T>(child, childName);
// If the child is found, break so we do not overwrite the found child.
if (foundChild != null)
break;
}
else if (!string.IsNullOrEmpty(childName))
{
// If the child's name is set for search
if (child is FrameworkElement frameworkElement && frameworkElement.Name == childName)
{
// if the child's name is of the request name
foundChild = (T)child;
break;
}
}
else
{
// child element found.
foundChild = (T)child;
break;
}
}
}
return foundChild;
}
}
}

View File

@@ -1,17 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<PropertyGroup>
<TargetFrameworks>net48;net6.0-windows;net7.0-windows</TargetFrameworks>
<RuntimeIdentifiers>win-x64</RuntimeIdentifiers>
<!-- Assembly Properties -->
<TargetFrameworks>net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0-windows;net6.0-windows;net7.0-windows;net8.0-windows</TargetFrameworks>
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<ImportFrameworkWinFXTargets Condition="$(TargetFramework.StartsWith(`net3`))">true</ImportFrameworkWinFXTargets>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<VersionPrefix>3.1.1</VersionPrefix>
<UseWindowsForms>true</UseWindowsForms>
<UseWPF>true</UseWPF>
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
<Copyright>Copyright (c)2019-2023</Copyright>
<VersionPrefix>2.7.3</VersionPrefix>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)'!='net48'">
<Nullable>enable</Nullable>
<!-- Package Properties -->
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
<Description>Common code for all MPF UI implementations</Description>
<Copyright>Copyright (c) Matt Nadareski 2019-2024</Copyright>
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
<RepositoryUrl>https://github.com/SabreTools/MPF</RepositoryUrl>
<RepositoryType>git</RepositoryType>
</PropertyGroup>
<ItemGroup>
@@ -23,12 +34,16 @@
<ProjectReference Include="..\MPF.Core\MPF.Core.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net48'">
<Reference Include="PresentationFramework.Aero" />
<!-- Support for old .NET versions -->
<ItemGroup Condition="$(TargetFramework.StartsWith(`net3`))">
<Reference Include="PresentationBuildTasks" HintPath="$(ProgramFiles)\Reference Assemblies\Microsoft\Framework\v3.0\PresentationBuildTasks.dll" />
<Reference Include="PresentationCore" HintPath="$(ProgramFiles)\Reference Assemblies\Microsoft\Framework\v3.0\PresentationCore.dll" />
<Reference Include="PresentationFramework" HintPath="$(ProgramFiles)\Reference Assemblies\Microsoft\Framework\v3.0\PresentationFramework.dll" />
<Reference Include="WindowsBase" HintPath="$(ProgramFiles)\Reference Assemblies\Microsoft\Framework\v3.0\WindowsBase.dll" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SabreTools.RedumpLib" Version="1.1.1" />
<PackageReference Include="SabreTools.RedumpLib" Version="1.3.2" />
</ItemGroup>
<ItemGroup>

View File

@@ -13,56 +13,32 @@ namespace MPF.UI.Core
/// <summary>
/// SolidColorBrush used to paint the active window's border.
/// </summary>
#if NET48
public SolidColorBrush ActiveBorderBrush { get; protected set; }
#else
public SolidColorBrush? ActiveBorderBrush { get; protected set; }
#endif
/// <summary>
/// SolidColorBrush that paints the face of a three-dimensional display element.
/// </summary>
#if NET48
public SolidColorBrush ControlBrush { get; protected set; }
#else
public SolidColorBrush? ControlBrush { get; protected set; }
#endif
/// <summary>
/// SolidColorBrush that paints text in a three-dimensional display element.
/// </summary>
#if NET48
public SolidColorBrush ControlTextBrush { get; protected set; }
#else
public SolidColorBrush? ControlTextBrush { get; protected set; }
#endif
/// <summary>
/// SolidColorBrush that paints disabled text.
/// </summary>
#if NET48
public SolidColorBrush GrayTextBrush { get; protected set; }
#else
public SolidColorBrush? GrayTextBrush { get; protected set; }
#endif
/// <summary>
/// SolidColorBrush that paints the background of a window's client area.
/// </summary>
#if NET48
public SolidColorBrush WindowBrush { get; protected set; }
#else
public SolidColorBrush? WindowBrush { get; protected set; }
#endif
/// <summary>
/// SolidColorBrush that paints the text in the client area of a window.
/// </summary>
#if NET48
public SolidColorBrush WindowTextBrush { get; protected set; }
#else
public SolidColorBrush? WindowTextBrush { get; protected set; }
#endif
#endregion
@@ -71,38 +47,22 @@ namespace MPF.UI.Core
/// <summary>
/// Brush for the Button.Disabled.Background resource
/// </summary>
#if NET48
public Brush Button_Disabled_Background { get; protected set; }
#else
public Brush? Button_Disabled_Background { get; protected set; }
#endif
/// <summary>
/// Brush for the Button.MouseOver.Background resource
/// </summary>
#if NET48
public Brush Button_MouseOver_Background { get; protected set; }
#else
public Brush? Button_MouseOver_Background { get; protected set; }
#endif
/// <summary>
/// Brush for the Button.Pressed.Background resource
/// </summary>
#if NET48
public Brush Button_Pressed_Background { get; protected set; }
#else
public Brush? Button_Pressed_Background { get; protected set; }
#endif
/// <summary>
/// Brush for the Button.Static.Background resource
/// </summary>
#if NET48
public Brush Button_Static_Background { get; protected set; }
#else
public Brush? Button_Static_Background { get; protected set; }
#endif
#endregion
@@ -111,110 +71,62 @@ namespace MPF.UI.Core
/// <summary>
/// Brush for the ComboBox.Disabled.Background resource
/// </summary>
#if NET48
public Brush ComboBox_Disabled_Background { get; protected set; }
#else
public Brush? ComboBox_Disabled_Background { get; protected set; }
#endif
/// <summary>
/// Brush for the ComboBox.Disabled.Editable.Background resource
/// </summary>
#if NET48
public Brush ComboBox_Disabled_Editable_Background { get; protected set; }
#else
public Brush? ComboBox_Disabled_Editable_Background { get; protected set; }
#endif
/// <summary>
/// Brush for the ComboBox.Disabled.Editable.Button.Background resource
/// </summary>
#if NET48
public Brush ComboBox_Disabled_Editable_Button_Background { get; protected set; }
#else
public Brush? ComboBox_Disabled_Editable_Button_Background { get; protected set; }
#endif
/// <summary>
/// Brush for the ComboBox.MouseOver.Background resource
/// </summary>
#if NET48
public Brush ComboBox_MouseOver_Background { get; protected set; }
#else
public Brush? ComboBox_MouseOver_Background { get; protected set; }
#endif
/// <summary>
/// Brush for the ComboBox.MouseOver.Editable.Background resource
/// </summary>
#if NET48
public Brush ComboBox_MouseOver_Editable_Background { get; protected set; }
#else
public Brush? ComboBox_MouseOver_Editable_Background { get; protected set; }
#endif
/// <summary>
/// Brush for the ComboBox.MouseOver.Editable.Button.Background resource
/// </summary>
#if NET48
public Brush ComboBox_MouseOver_Editable_Button_Background { get; protected set; }
#else
public Brush? ComboBox_MouseOver_Editable_Button_Background { get; protected set; }
#endif
/// <summary>
/// Brush for the ComboBox.Pressed.Background resource
/// </summary>
#if NET48
public Brush ComboBox_Pressed_Background { get; protected set; }
#else
public Brush? ComboBox_Pressed_Background { get; protected set; }
#endif
/// <summary>
/// Brush for the ComboBox.Pressed.Editable.Background resource
/// </summary>
#if NET48
public Brush ComboBox_Pressed_Editable_Background { get; protected set; }
#else
public Brush? ComboBox_Pressed_Editable_Background { get; protected set; }
#endif
/// <summary>
/// Brush for the ComboBox.Pressed.Editable.Button.Background resource
/// </summary>
#if NET48
public Brush ComboBox_Pressed_Editable_Button_Background { get; protected set; }
#else
public Brush? ComboBox_Pressed_Editable_Button_Background { get; protected set; }
#endif
/// <summary>
/// Brush for the ComboBox.Static.Background resource
/// </summary>
#if NET48
public Brush ComboBox_Static_Background { get; protected set; }
#else
public Brush? ComboBox_Static_Background { get; protected set; }
#endif
/// <summary>
/// Brush for the ComboBox.Static.Editable.Background resource
/// </summary>
#if NET48
public Brush ComboBox_Static_Editable_Background { get; protected set; }
#else
public Brush? ComboBox_Static_Editable_Background { get; protected set; }
#endif
/// <summary>
/// Brush for the ComboBox.Static.Editable.Button.Background resource
/// </summary>
#if NET48
public Brush ComboBox_Static_Editable_Button_Background { get; protected set; }
#else
public Brush? ComboBox_Static_Editable_Button_Background { get; protected set; }
#endif
#endregion
@@ -223,11 +135,7 @@ namespace MPF.UI.Core
/// <summary>
/// Brush for the CustomMessageBox.Static.Background resource
/// </summary>
#if NET48
public Brush CustomMessageBox_Static_Background { get; protected set; }
#else
public Brush? CustomMessageBox_Static_Background { get; protected set; }
#endif
#endregion
@@ -236,20 +144,12 @@ namespace MPF.UI.Core
/// <summary>
/// Brush for the MenuItem.SubMenu.Background resource
/// </summary>
#if NET48
public Brush MenuItem_SubMenu_Background { get; protected set; }
#else
public Brush? MenuItem_SubMenu_Background { get; protected set; }
#endif
/// <summary>
/// Brush for the MenuItem.SubMenu.Border resource
/// </summary>
#if NET48
public Brush MenuItem_SubMenu_Border { get; protected set; }
#else
public Brush? MenuItem_SubMenu_Border { get; protected set; }
#endif
#endregion
@@ -258,11 +158,7 @@ namespace MPF.UI.Core
/// <summary>
/// Brush for the ProgressBar.Background resource
/// </summary>
#if NET48
public Brush ProgressBar_Background { get; protected set; }
#else
public Brush? ProgressBar_Background { get; protected set; }
#endif
#endregion
@@ -271,11 +167,7 @@ namespace MPF.UI.Core
/// <summary>
/// Brush for the ScrollViewer.ScrollBar.Background resource
/// </summary>
#if NET48
public Brush ScrollViewer_ScrollBar_Background { get; protected set; }
#else
public Brush? ScrollViewer_ScrollBar_Background { get; protected set; }
#endif
#endregion
@@ -284,29 +176,17 @@ namespace MPF.UI.Core
/// <summary>
/// Brush for the TabItem.Selected.Background resource
/// </summary>
#if NET48
public Brush TabItem_Selected_Background { get; protected set; }
#else
public Brush? TabItem_Selected_Background { get; protected set; }
#endif
/// <summary>
/// Brush for the TabItem.Static.Background resource
/// </summary>
#if NET48
public Brush TabItem_Static_Background { get; protected set; }
#else
public Brush? TabItem_Static_Background { get; protected set; }
#endif
/// <summary>
/// Brush for the TabItem.Static.Border resource
/// </summary>
#if NET48
public Brush TabItem_Static_Border { get; protected set; }
#else
public Brush? TabItem_Static_Border { get; protected set; }
#endif
#endregion
@@ -315,11 +195,7 @@ namespace MPF.UI.Core
/// <summary>
/// Brush for the TextBox.Static.Background resource
/// </summary>
#if NET48
public Brush TextBox_Static_Background { get; protected set; }
#else
public Brush? TextBox_Static_Background { get; protected set; }
#endif
#endregion

View File

@@ -7,6 +7,8 @@ using System.Windows.Media;
using System.Windows.Threading;
using MPF.Core.Data;
#pragma warning disable IDE1006 // Naming Styles
namespace MPF.UI.Core.UserControls
{
public partial class LogOutput : UserControl
@@ -29,15 +31,22 @@ namespace MPF.UI.Core.UserControls
/// <summary>
/// Cached value of the last line written
/// </summary>
#if NET48
private Run lastLine = null;
#else
private Run? lastLine = null;
#if NET35
private Button? _ClearButton => ItemHelper.FindChild<Button>(this, "ClearButton");
private RichTextBox? _Output => ItemHelper.FindChild<RichTextBox>(this, "Output");
private ScrollViewer? _OutputViewer => ItemHelper.FindChild<ScrollViewer>(this, "OutputViewer");
private Button? _SaveButton => ItemHelper.FindChild<Button>(this, "SaveButton");
#endif
public LogOutput()
{
#if NET40_OR_GREATER || NETCOREAPP
InitializeComponent();
#endif
// Update the internal state
Document = new FlowDocument()
@@ -51,13 +60,24 @@ namespace MPF.UI.Core.UserControls
LogQueue = new ProcessingQueue<LogLine>(ProcessLogLine);
// Add handlers
#if NET35
_OutputViewer!.SizeChanged += OutputViewerSizeChanged;
_Output!.TextChanged += OnTextChanged;
_ClearButton!.Click += OnClearButton;
_SaveButton!.Click += OnSaveButton;
#else
OutputViewer.SizeChanged += OutputViewerSizeChanged;
Output.TextChanged += OnTextChanged;
ClearButton.Click += OnClearButton;
SaveButton.Click += OnSaveButton;
#endif
// Update the internal state
#if NET35
_Output.Document = Document;
#else
Output.Document = Document;
#endif
}
#region Logging
@@ -97,18 +117,13 @@ namespace MPF.UI.Core.UserControls
/// <returns>Brush representing the color</returns>
public Brush GetForegroundColor()
{
switch (this.LogLevel)
return this.LogLevel switch
{
case LogLevel.SECRET:
return Brushes.Blue;
case LogLevel.ERROR:
return Brushes.Red;
case LogLevel.VERBOSE:
return Brushes.Yellow;
case LogLevel.USER:
default:
return Brushes.White;
}
LogLevel.SECRET => Brushes.Blue,
LogLevel.ERROR => Brushes.Red,
LogLevel.VERBOSE => Brushes.Yellow,
_ => Brushes.White,
};
}
/// <summary>
@@ -168,17 +183,13 @@ namespace MPF.UI.Core.UserControls
{
Dispatcher.Invoke(() =>
{
#if NET48
if (lastLine == null) lastLine = new Run();
#else
lastLine ??= new Run();
#endif
lastLine.Text = logLine.Text;
lastLine.Foreground = logLine.GetForegroundColor();
});
}
#endregion
#endregion
#region Helpers
@@ -192,20 +203,22 @@ namespace MPF.UI.Core.UserControls
/// </summary>
private void SaveInlines()
{
using (var sw = new StreamWriter(File.OpenWrite("console.log")))
using var sw = new StreamWriter(File.OpenWrite("console.log"));
foreach (var inline in _paragraph.Inlines)
{
foreach (var inline in _paragraph.Inlines)
{
if (inline is Run run)
sw.Write(run.Text);
}
if (inline is Run run)
sw.Write(run.Text);
}
}
/// <summary>
/// Scroll the current view to the bottom
/// </summary>
#if NET35
public void ScrollToBottom() => _OutputViewer!.ScrollToBottom();
#else
public void ScrollToBottom() => OutputViewer.ScrollToBottom();
#endif
#endregion

View File

@@ -39,7 +39,7 @@ namespace MPF.UI.Core.UserControls
public static readonly DependencyProperty VerticalScrollBarVisibilityProperty =
DependencyProperty.Register("VerticalScrollBarVisibility", typeof(ScrollBarVisibility), typeof(UserInput));
#endregion
#region Properties
@@ -118,7 +118,9 @@ namespace MPF.UI.Core.UserControls
HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden;
VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
#if NET40_OR_GREATER || NETCOREAPP
InitializeComponent();
#endif
}
}
}

View File

@@ -0,0 +1,127 @@
<coreWindows:WindowBase x:Class="MPF.UI.Core.Windows.CheckDumpWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:core="clr-namespace:MPF.UI.Core"
xmlns:coreWindows="clr-namespace:MPF.UI.Core.Windows"
xmlns:viewModels="clr-namespace:MPF.Core.UI.ViewModels;assembly=MPF.Core"
mc:Ignorable="d"
Title="Check Existing Dump" Width="600" WindowStyle="None"
WindowStartupLocation="CenterOwner" ResizeMode="CanMinimize" SizeToContent="Height"
BorderBrush="DarkGray" BorderThickness="2">
<Window.DataContext>
<viewModels:CheckDumpViewModel/>
</Window.DataContext>
<Window.Resources>
<core:ElementConverter x:Key="ElementConverter" />
</Window.Resources>
<Grid>
<StackPanel Orientation="Vertical">
<Grid Margin="0,2,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="25"/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition Width="50"/>
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Source="/Images/Icon.ico" Height="20" Width="20" Margin="1" />
<Label Grid.Column="1" Grid.ColumnSpan="4" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" MouseDown="TitleMouseDown">
<Label.Content>
<Run FontWeight="Bold" Text="Check Existing Dump" />
</Label.Content>
<Label.ContextMenu>
<ContextMenu Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
Foreground="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"
Style="{DynamicResource CustomContextMenuStyle}">
<MenuItem Header="Minimize" Click="MinimizeButtonClick" Width="185"
Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
Foreground="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"
Template="{DynamicResource CustomMenuItemTemplate}"/>
<MenuItem Header="Close" Click="CloseButtonClick" Width="185"
Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
Foreground="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"
Template="{DynamicResource CustomMenuItemTemplate}"/>
</ContextMenu>
</Label.ContextMenu>
</Label>
<Grid Grid.Column="5">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button x:Name="MinimizeButton" Grid.Column="0" BorderThickness="0" Background="Transparent" Style="{DynamicResource CustomButtonStyle}" Click="MinimizeButtonClick">
<Path Data="M 0,0 L 10,0" Stroke="{Binding Path=Foreground,RelativeSource={RelativeSource AncestorType={x:Type Button}}}" StrokeThickness="1"/>
</Button>
<Button x:Name="CloseButton" Grid.Column="1" BorderThickness="0" Background="Transparent" Style="{DynamicResource CustomButtonStyle}" Click="CloseButtonClick">
<Path Data="M 0,0 L 12,12 M 0,12 L 12,0" Stroke="{Binding Path=Foreground,RelativeSource={RelativeSource AncestorType={x:Type Button}}}" StrokeThickness="1"/>
</Button>
</Grid>
</Grid>
<GroupBox Margin="5,5,5,5" HorizontalAlignment="Stretch" Header="Settings">
<Grid Margin="5,5,5,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="2.5*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Label x:Name="InputPathLabel" Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Content="Input Path"/>
<TextBox x:Name="InputPathTextBox" Grid.Row="0" Grid.Column="1" Height="22" Width="345" HorizontalAlignment="Left" VerticalContentAlignment="Center"
Text="{Binding InputPath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding InputPathTextBoxEnabled}" />
<Button x:Name="InputPathBrowseButton" Grid.Row="0" Grid.Column="1" Height="22" Width="50" HorizontalAlignment="Right" Content="Browse"
IsEnabled="{Binding InputPathBrowseButtonEnabled}" Style="{DynamicResource CustomButtonStyle}"/>
<Label x:Name="SystemMediaTypeLabel" Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Content="System/Media Type" />
<ComboBox x:Name="SystemTypeComboBox" Grid.Row="1" Grid.Column="1" Height="22" Width="250" HorizontalAlignment="Left"
ItemsSource="{Binding Systems}" SelectedItem="{Binding Path=CurrentSystem, Converter={StaticResource ElementConverter}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding SystemTypeComboBoxEnabled}" Style="{DynamicResource CustomComboBoxStyle}">
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsHeader}" Value="True">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
<ComboBox x:Name="MediaTypeComboBox" Grid.Row="1" Grid.Column="1" Height="22" Width="140" HorizontalAlignment="Right"
ItemsSource="{Binding MediaTypes}" SelectedItem="{Binding Path=CurrentMediaType, Converter={StaticResource ElementConverter}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding MediaTypeComboBoxEnabled}" Style="{DynamicResource CustomComboBoxStyle}" />
<Label x:Name="DumpingProgramLabel" Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Content="Dumping Program"/>
<ComboBox x:Name="DumpingProgramComboBox" Grid.Row="2" Grid.Column="1" Height="22" Width="250" HorizontalAlignment="Left"
ItemsSource="{Binding InternalPrograms}" SelectedItem="{Binding Path=CurrentProgram, Converter={StaticResource ElementConverter}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding DumpingProgramComboBoxEnabled}" Style="{DynamicResource CustomComboBoxStyle}" />
</Grid>
</GroupBox>
<!-- Check Dump / Cancel -->
<GroupBox Margin="5,5,5,5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<UniformGrid Columns="4" Margin="5,5,5,5" Height="28">
<Label/>
<!-- Empty label for padding -->
<Button Name="CheckDumpButton" Height="25" Width="80" IsDefault="True" Content="Check Dump"
IsEnabled="{Binding CheckDumpButtonEnabled}" Style="{DynamicResource CustomButtonStyle}" />
<Button Name="CancelButton" Height="25" Width="80" IsCancel="True" Content="Cancel"
Style="{DynamicResource CustomButtonStyle}" />
<Label/>
<!-- Empty label for padding -->
</UniformGrid>
</GroupBox>
</StackPanel>
</Grid>
</coreWindows:WindowBase>

View File

@@ -0,0 +1,283 @@
using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using MPF.Core.UI.ViewModels;
using SabreTools.RedumpLib.Data;
using WPFCustomMessageBox;
using WinForms = System.Windows.Forms;
#pragma warning disable IDE1006 // Naming Styles
namespace MPF.UI.Core.Windows
{
/// <summary>
/// Interaction logic for CheckDumpWindow.xaml
/// </summary>
public partial class CheckDumpWindow : WindowBase
{
/// <summary>
/// Read-only access to the current check dump view model
/// </summary>
public CheckDumpViewModel CheckDumpViewModel => DataContext as CheckDumpViewModel ?? new CheckDumpViewModel();
#if NET35
#region Settings
private ComboBox? _DumpingProgramComboBox => ItemHelper.FindChild<ComboBox>(this, "DumpingProgramComboBox");
private Button? _InputPathBrowseButton => ItemHelper.FindChild<Button>(this, "InputPathBrowseButton");
private TextBox? _InputPathTextBox => ItemHelper.FindChild<TextBox>(this, "InputPathTextBox");
private ComboBox? _MediaTypeComboBox => ItemHelper.FindChild<ComboBox>(this, "MediaTypeComboBox");
private ComboBox? _SystemTypeComboBox => ItemHelper.FindChild<ComboBox>(this, "SystemTypeComboBox");
#endregion
#region Controls
private System.Windows.Controls.Button? _CheckDumpButton => ItemHelper.FindChild<System.Windows.Controls.Button>(this, "CheckDumpButton");
private System.Windows.Controls.Button? _CancelButton => ItemHelper.FindChild<System.Windows.Controls.Button>(this, "CancelButton");
#endregion
#endif
/// <summary>
/// Constructor
/// </summary>
public CheckDumpWindow(MainWindow parent)
{
#if NET40_OR_GREATER || NETCOREAPP
InitializeComponent();
#endif
#if NET452_OR_GREATER || NETCOREAPP
var chrome = new System.Windows.Shell.WindowChrome
{
CaptionHeight = 0,
ResizeBorderThickness = new Thickness(0),
};
System.Windows.Shell.WindowChrome.SetWindowChrome(this, chrome);
#endif
}
/// <summary>
/// Handler for CheckDumpWindow OnContentRendered event
/// </summary>
protected override void OnContentRendered(EventArgs e)
{
base.OnContentRendered(e);
// Add the click handlers to the UI
AddEventHandlers();
}
#region UI Functionality
/// <summary>
/// Add all event handlers
/// </summary>
public void AddEventHandlers()
{
// Main buttons
#if NET35
_CheckDumpButton!.Click += OnCheckDumpClick;
_CancelButton!.Click += OnCancelClick;
#else
CheckDumpButton.Click += OnCheckDumpClick;
CancelButton.Click += OnCancelClick;
#endif
// User Area Click
#if NET35
_InputPathBrowseButton!.Click += InputPathBrowseButtonClick;
#else
InputPathBrowseButton.Click += InputPathBrowseButtonClick;
#endif
// User Area SelectionChanged
#if NET35
_SystemTypeComboBox!.SelectionChanged += SystemTypeComboBoxSelectionChanged;
_MediaTypeComboBox!.SelectionChanged += MediaTypeComboBoxSelectionChanged;
_DumpingProgramComboBox!.SelectionChanged += DumpingProgramComboBoxSelectionChanged;
#else
SystemTypeComboBox.SelectionChanged += SystemTypeComboBoxSelectionChanged;
MediaTypeComboBox.SelectionChanged += MediaTypeComboBoxSelectionChanged;
DumpingProgramComboBox.SelectionChanged += DumpingProgramComboBoxSelectionChanged;
#endif
// User Area TextChanged
#if NET35
_InputPathTextBox!.TextChanged += InputPathTextBoxTextChanged;
#else
InputPathTextBox.TextChanged += InputPathTextBoxTextChanged;
#endif
}
/// <summary>
/// Browse for an input file path
/// </summary>
public void BrowseFile()
{
// Get the current path, if possible
string? currentPath = CheckDumpViewModel.InputPath;
if (string.IsNullOrEmpty(currentPath) && !string.IsNullOrEmpty(CheckDumpViewModel.Options.DefaultOutputPath))
currentPath = CheckDumpViewModel.Options.DefaultOutputPath!;
if (string.IsNullOrEmpty(currentPath))
currentPath = AppDomain.CurrentDomain.BaseDirectory!;
// Get the full directory
var directory = Path.GetDirectoryName(Path.GetFullPath(currentPath));
WinForms.FileDialog fileDialog = new WinForms.OpenFileDialog
{
InitialDirectory = directory,
Filter = "Disc Images|*.iso;*.cue;*.aaruf|All Files|*.*",
};
WinForms.DialogResult result = fileDialog.ShowDialog();
if (result == WinForms.DialogResult.OK)
{
CheckDumpViewModel.InputPath = fileDialog.FileName;
}
}
/// <summary>
/// Display a user message using a CustomMessageBox
/// </summary>
/// <param name="title">Title to display to the user</param>
/// <param name="message">Message to display to the user</param>
/// <param name="optionCount">Number of options to display</param>
/// <param name="flag">true for inquiry, false otherwise</param>
/// <returns>true for positive, false for negative, null for neutral</returns>
public bool? DisplayUserMessage(string title, string message, int optionCount, bool flag)
{
// Set the correct button style
var button = optionCount switch
{
1 => MessageBoxButton.OK,
2 => MessageBoxButton.YesNo,
3 => MessageBoxButton.YesNoCancel,
// This should not happen, but default to "OK"
_ => MessageBoxButton.OK,
};
// Set the correct icon
MessageBoxImage image = flag ? MessageBoxImage.Question : MessageBoxImage.Exclamation;
// Display and get the result
MessageBoxResult result = CustomMessageBox.Show(this, message, title, button, image);
return result switch
{
MessageBoxResult.OK or MessageBoxResult.Yes => true,
MessageBoxResult.No => false,
_ => null,
};
}
/// <summary>
/// Show the disc information window
/// </summary>
/// <param name="submissionInfo">SubmissionInfo object to display and possibly change</param>
/// <returns>Dialog open result</returns>
public (bool?, SubmissionInfo?) ShowDiscInformationWindow(SubmissionInfo? submissionInfo)
{
var discInformationWindow = new DiscInformationWindow(CheckDumpViewModel.Options, submissionInfo)
{
Focusable = true,
Owner = this,
ShowActivated = true,
ShowInTaskbar = true,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
};
discInformationWindow.Closed += delegate { this.Activate(); };
bool? result = discInformationWindow.ShowDialog();
// Copy back the submission info changes, if necessary
if (result == true)
submissionInfo = (discInformationWindow.DiscInformationViewModel.SubmissionInfo.Clone() as SubmissionInfo)!;
return (result, submissionInfo!);
}
#endregion
#region Event Handlers
/// <summary>
/// Handler for CheckDumpButton Click event
/// </summary>
private void OnCheckDumpClick(object sender, EventArgs e)
{
string? errorMessage = CheckDumpViewModel.CheckDump(ShowDiscInformationWindow);
if (string.IsNullOrEmpty(errorMessage))
{
bool? checkAgain = DisplayUserMessage("Check Complete", "The dump has been processed successfully! Would you like to check another dump?", 2, false);
if (checkAgain == false)
Close();
}
else
{
DisplayUserMessage("Check Failed", errorMessage!, 1, false);
}
}
/// <summary>
/// Handler for CancelButtom Click event
/// </summary>
private void OnCancelClick(object sender, EventArgs e)
{
Close();
}
/// <summary>
/// Handler for DumpingProgramComboBox SelectionChanged event
/// </summary>
public void DumpingProgramComboBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (CheckDumpViewModel.CanExecuteSelectionChanged)
CheckDumpViewModel.ChangeDumpingProgram();
}
/// <summary>
/// Handler for InputPathBrowseButton Click event
/// </summary>
public void InputPathBrowseButtonClick(object sender, RoutedEventArgs e)
{
BrowseFile();
if (CheckDumpViewModel.CanExecuteSelectionChanged)
CheckDumpViewModel.ChangeInputPath();
}
/// <summary>
/// Handler for InputPathTextBox TextChanged event
/// </summary>
public void InputPathTextBoxTextChanged(object sender, TextChangedEventArgs e)
{
if (CheckDumpViewModel.CanExecuteSelectionChanged)
CheckDumpViewModel.ChangeInputPath();
}
/// <summary>
/// Handler for MediaTypeComboBox SelectionChanged event
/// </summary>
public void MediaTypeComboBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (CheckDumpViewModel.CanExecuteSelectionChanged)
CheckDumpViewModel.ChangeMediaType();
}
/// <summary>
/// Handler for SystemTypeComboBox SelectionChanged event
/// </summary>
public void SystemTypeComboBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (CheckDumpViewModel.CanExecuteSelectionChanged)
CheckDumpViewModel.ChangeSystem();
}
#endregion
}
}

View File

@@ -0,0 +1,209 @@
<coreWindows:WindowBase x:Class="MPF.UI.Core.Windows.CreateIRDWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:core="clr-namespace:MPF.UI.Core"
xmlns:coreWindows="clr-namespace:MPF.UI.Core.Windows"
xmlns:viewModels="clr-namespace:MPF.Core.UI.ViewModels;assembly=MPF.Core"
mc:Ignorable="d"
Title="Create PS3 IRD" Width="600" WindowStyle="None"
WindowStartupLocation="CenterOwner" ResizeMode="CanMinimize" SizeToContent="Height"
BorderBrush="DarkGray" BorderThickness="2">
<Window.DataContext>
<viewModels:CreateIRDViewModel/>
</Window.DataContext>
<Window.Resources>
<core:ElementConverter x:Key="ElementConverter" />
</Window.Resources>
<Grid>
<StackPanel Orientation="Vertical">
<Grid Margin="0,2,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="25"/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition Width="50"/>
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Source="/Images/Icon.ico" Height="20" Width="20" Margin="1" />
<Label Grid.Column="1" Grid.ColumnSpan="4" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" MouseDown="TitleMouseDown">
<Label.Content>
<Run FontWeight="Bold" Text="Create PS3 IRD" />
</Label.Content>
<Label.ContextMenu>
<ContextMenu Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
Foreground="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"
Style="{DynamicResource CustomContextMenuStyle}">
<MenuItem Header="Minimize" Click="MinimizeButtonClick" Width="185"
Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
Foreground="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"
Template="{DynamicResource CustomMenuItemTemplate}"/>
<MenuItem Header="Close" Click="CloseButtonClick" Width="185"
Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
Foreground="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"
Template="{DynamicResource CustomMenuItemTemplate}"/>
</ContextMenu>
</Label.ContextMenu>
</Label>
<Grid Grid.Column="5">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button x:Name="MinimizeButton" Grid.Column="0" BorderThickness="0" Background="Transparent" Style="{DynamicResource CustomButtonStyle}" Click="MinimizeButtonClick">
<Path Data="M 0,0 L 10,0" Stroke="{Binding Path=Foreground,RelativeSource={RelativeSource AncestorType={x:Type Button}}}" StrokeThickness="1"/>
</Button>
<Button x:Name="CloseButton" Grid.Column="1" BorderThickness="0" Background="Transparent" Style="{DynamicResource CustomButtonStyle}" Click="CloseButtonClick">
<Path Data="M 0,0 L 12,12 M 0,12 L 12,0" Stroke="{Binding Path=Foreground,RelativeSource={RelativeSource AncestorType={x:Type Button}}}" StrokeThickness="1"/>
</Button>
</Grid>
</Grid>
<GroupBox Margin="5,5,5,5" HorizontalAlignment="Stretch" Header="Input">
<Grid Margin="5,5,5,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="2.5*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Label x:Name="InputPathLabel" Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Content="PS3 ISO Path"/>
<TextBox x:Name="InputPathTextBox" Grid.Row="0" Grid.Column="1" Height="22" Width="345" HorizontalAlignment="Left" VerticalContentAlignment="Center"
Text="{Binding InputPath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding InputPathTextBoxEnabled}" />
<Button x:Name="InputPathBrowseButton" Grid.Row="0" Grid.Column="1" Height="22" Width="50" HorizontalAlignment="Right" Content="Browse"
IsEnabled="{Binding InputPathBrowseButtonEnabled}" Style="{DynamicResource CustomButtonStyle}"/>
<Label x:Name="LogPathLabel" Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Content="*.getkey.log File Path"/>
<TextBox x:Name="LogPathTextBox" Grid.Row="1" Grid.Column="1" Height="22" Width="345" HorizontalAlignment="Left" VerticalContentAlignment="Center"
Text="{Binding LogPath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding LogPathTextBoxEnabled}" />
<Button x:Name="LogPathBrowseButton" Grid.Row="1" Grid.Column="1" Height="22" Width="50" HorizontalAlignment="Right" Content="Browse"
IsEnabled="{Binding LogPathBrowseButtonEnabled}" Style="{DynamicResource CustomButtonStyle}"/>
</Grid>
</GroupBox>
<Expander Name="KeyExpander" BorderThickness="1" BorderBrush="#D5DFE5" Margin="5,5,5,5" HorizontalAlignment="Stretch"
IsEnabled="{Binding LogPathNotProvided}" IsExpanded="False" Header="Manually define PS3 Disc Encryption Key">
<Grid Margin="5,5,5,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="2.5*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Label x:Name="KeyLabel" Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Content="Hexadecimal Key"/>
<TextBox x:Name="KeyTextBox" Grid.Row="0" Grid.Column="1" Height="22" Width="345" HorizontalAlignment="Left" VerticalContentAlignment="Center"
Text="{Binding HexKey, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding HexKeyTextBoxEnabled}" />
<Label x:Name="KeyPathLabel" Grid.Row="1" Grid.Column="0" VerticalAlignment="Center"
ToolTip="Typically a *.key file" Content="Key File Path"/>
<TextBox x:Name="KeyPathTextBox" Grid.Row="1" Grid.Column="1" Height="22" Width="345" HorizontalAlignment="Left" VerticalContentAlignment="Center"
Text="{Binding KeyPath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding KeyPathTextBoxEnabled}" />
<Button x:Name="KeyPathBrowseButton" Grid.Row="1" Grid.Column="1" Height="22" Width="50" HorizontalAlignment="Right" Content="Browse"
IsEnabled="{Binding KeyPathBrowseButtonEnabled}" Style="{DynamicResource CustomButtonStyle}"/>
<Label x:Name="KeyStatusLabel" Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Content="Key Status"/>
<Label x:Name="KeyStatusText" Grid.Row="2" Grid.Column="1" VerticalAlignment="Center" Content="{Binding KeyStatus}"/>
</Grid>
</Expander>
<Expander Name="DiscIDExpander" BorderThickness="1" BorderBrush="#D5DFE5" Margin="5,5,5,5" HorizontalAlignment="Stretch"
IsEnabled="{Binding LogPathNotProvided}" IsExpanded="False" Header="Manually define PS3 Disc ID">
<Grid Margin="5,5,5,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="2.5*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Label x:Name="DiscIDLabel" Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Content="Disc ID"/>
<TextBox x:Name="DiscIDTextBox" Grid.Row="0" Grid.Column="1" Height="22" Width="345" HorizontalAlignment="Left" VerticalContentAlignment="Center"
Text="{Binding DiscIDString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding DiscIDTextBoxEnabled}" />
<Label x:Name="DiscIDStatusLabel" Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Content="DiscID Status"/>
<Label x:Name="DiscIDStatusText" Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" Content="{Binding DiscIDStatus}"/>
</Grid>
</Expander>
<Expander Name="PICExpander" BorderThickness="1" BorderBrush="#D5DFE5" Margin="5,5,5,5" HorizontalAlignment="Stretch"
IsEnabled="{Binding LogPathNotProvided}" IsExpanded="False" Header="Manually define Permanent Information &#x26; Control (PIC)">
<Grid Margin="5,5,5,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="2.5*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Label x:Name="LayerbreakLabel" Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Content="Layerbreak (sectors)"/>
<TextBox x:Name="LayerbreakTextBox" Grid.Row="0" Grid.Column="1" Height="22" Width="345" HorizontalAlignment="Left" VerticalContentAlignment="Center"
Text="{Binding LayerbreakString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding LayerbreakTextBoxEnabled}" />
<Label x:Name="PICLabel" Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Content="PIC"/>
<TextBox x:Name="PICTextBox" Grid.Row="1" Grid.Column="1" Width="345" HorizontalAlignment="Left" VerticalContentAlignment="Top"
ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Auto"
MinHeight="55" MaxHeight="146" AcceptsReturn="True"
Text="{Binding PICString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding PICTextBoxEnabled}" />
<Label x:Name="PICPathLabel" Grid.Row="2" Grid.Column="0" VerticalAlignment="Center"
ToolTip="Typically a *.physical or *_PIC.bin file" Content="PIC File Path"/>
<TextBox x:Name="PICPathTextBox" Grid.Row="2" Grid.Column="1" Height="22" Width="345" HorizontalAlignment="Left" VerticalContentAlignment="Center"
Text="{Binding PICPath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding PICPathTextBoxEnabled}" />
<Button x:Name="PICPathBrowseButton" Grid.Row="2" Grid.Column="1" Height="22" Width="50" HorizontalAlignment="Right" Content="Browse"
IsEnabled="{Binding PICPathBrowseButtonEnabled}" Style="{DynamicResource CustomButtonStyle}"/>
<Label x:Name="PICStatusLabel" Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Content="PIC Status"/>
<Label x:Name="PICStatusText" Grid.Row="3" Grid.Column="1" VerticalAlignment="Center" Content="{Binding PICStatus}"/>
</Grid>
</Expander>
<GroupBox Margin="5,5,5,5" HorizontalAlignment="Stretch" Header="Status">
<UniformGrid Margin="5,5,5,5" Grid.ColumnSpan="2">
<TextBlock x:Name="StatusLabel" VerticalAlignment="Center" HorizontalAlignment="Center"
Text="{Binding CreateIRDStatus, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</UniformGrid>
</GroupBox>
<!-- Create IRD / Cancel -->
<GroupBox Margin="5,5,5,5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<UniformGrid Columns="4" Margin="5,5,5,5" Height="28">
<Label/>
<!-- Empty label for padding -->
<Button Name="CreateIRDButton" Height="25" Width="80" IsDefault="True" Content="Create IRD"
IsEnabled="{Binding CreateIRDButtonEnabled}" Style="{DynamicResource CustomButtonStyle}" />
<Button Name="CancelButton" Height="25" Width="80" IsCancel="True" Content="Cancel"
IsEnabled="{Binding CancelButtonEnabled}" Style="{DynamicResource CustomButtonStyle}" />
<Label/>
<!-- Empty label for padding -->
</UniformGrid>
</GroupBox>
</StackPanel>
</Grid>
</coreWindows:WindowBase>

View File

@@ -0,0 +1,489 @@
using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using MPF.Core.UI.ViewModels;
using WPFCustomMessageBox;
using WinForms = System.Windows.Forms;
#pragma warning disable IDE1006 // Naming Styles
namespace MPF.UI.Core.Windows
{
/// <summary>
/// Interaction logic for CreateIRDWindow.xaml
/// </summary>
public partial class CreateIRDWindow : WindowBase
{
/// <summary>
/// Read-only access to the current check dump view model
/// </summary>
public CreateIRDViewModel CreateIRDViewModel => DataContext as CreateIRDViewModel ?? new CreateIRDViewModel();
#if NET35
#region Settings
private Button? _InputPathBrowseButton => ItemHelper.FindChild<Button>(this, "InputPathBrowseButton");
private TextBox? _InputPathTextBox => ItemHelper.FindChild<TextBox>(this, "InputPathTextBox");
private Button? _LogPathBrowseButton => ItemHelper.FindChild<Button>(this, "LogPathBrowseButton");
private TextBox? _LogPathTextBox => ItemHelper.FindChild<TextBox>(this, "LogPathTextBox");
private Button? _KeyPathBrowseButton => ItemHelper.FindChild<Button>(this, "KeyPathBrowseButton");
private TextBox? _KeyPathTextBox => ItemHelper.FindChild<TextBox>(this, "KeyPathTextBox");
private TextBox? _KeyTextBox => ItemHelper.FindChild<TextBox>(this, "KeyTextBox");
private TextBox? _DiscIDTextBox => ItemHelper.FindChild<TextBox>(this, "DiscIDTextBox");
private Button? _PICPathBrowseButton => ItemHelper.FindChild<Button>(this, "PICPathBrowseButton");
private TextBox? _PICPathTextBox => ItemHelper.FindChild<TextBox>(this, "PICPathTextBox");
private TextBox? _PICTextBox => ItemHelper.FindChild<TextBox>(this, "PICTextBox");
private TextBox? _LayerbreakTextBox => ItemHelper.FindChild<TextBox>(this, "LayerbreakTextBox");
#endregion
#region Controls
private System.Windows.Controls.Button? _CreateIRDButton => ItemHelper.FindChild<System.Windows.Controls.Button>(this, "CreateIRDButton");
private System.Windows.Controls.Button? _CancelButton => ItemHelper.FindChild<System.Windows.Controls.Button>(this, "CancelButton");
#endregion
#region Expanders
private Expander? _KeyExpander => ItemHelper.FindChild<Expander>(this, "KeyExpander");
private Expander? _DiscIDExpander => ItemHelper.FindChild<Expander>(this, "DiscIDExpander");
private Expander? _PICExpander => ItemHelper.FindChild<Expander>(this, "PICExpander");
#endregion
#endif
/// <summary>
/// Constructor
/// </summary>
public CreateIRDWindow(MainWindow parent)
{
#if NET40_OR_GREATER || NETCOREAPP
InitializeComponent();
#endif
#if NET452_OR_GREATER || NETCOREAPP
var chrome = new System.Windows.Shell.WindowChrome
{
CaptionHeight = 0,
ResizeBorderThickness = new Thickness(0),
};
System.Windows.Shell.WindowChrome.SetWindowChrome(this, chrome);
#endif
}
/// <summary>
/// Handler for CheckDumpWindow OnContentRendered event
/// </summary>
protected override void OnContentRendered(EventArgs e)
{
base.OnContentRendered(e);
// Add the click handlers to the UI
AddEventHandlers();
}
#region UI Functionality
/// <summary>
/// Add all event handlers
/// </summary>
public void AddEventHandlers()
{
// Main buttons
#if NET35
_CreateIRDButton!.Click += OnCreateIRDClick;
_CancelButton!.Click += OnCancelClick;
#else
CreateIRDButton.Click += OnCreateIRDClick;
CancelButton.Click += OnCancelClick;
#endif
// User Area Click
#if NET35
_InputPathBrowseButton!.Click += InputPathBrowseButtonClick;
_LogPathBrowseButton!.Click += LogPathBrowseButtonClick;
_KeyPathBrowseButton!.Click += KeyPathBrowseButtonClick;
_PICPathBrowseButton!.Click += PICPathBrowseButtonClick;
#else
InputPathBrowseButton.Click += InputPathBrowseButtonClick;
LogPathBrowseButton.Click += LogPathBrowseButtonClick;
KeyPathBrowseButton.Click += KeyPathBrowseButtonClick;
PICPathBrowseButton.Click += PICPathBrowseButtonClick;
#endif
// User Area TextChanged
#if NET35
_InputPathTextBox!.TextChanged += InputPathTextBoxTextChanged;
_LogPathTextBox!.TextChanged += LogPathTextBoxTextChanged;
_KeyPathTextBox!.TextChanged += KeyPathTextBoxTextChanged;
_KeyTextBox!.TextChanged += KeyTextBoxTextChanged;
_DiscIDTextBox!.TextChanged += DiscIDTextBoxTextChanged;
_PICPathTextBox!.TextChanged += PICPathTextBoxTextChanged;
_PICTextBox!.TextChanged += PICTextBoxTextChanged;
_LayerbreakTextBox!.TextChanged += LayerbreakTextBoxTextChanged;
#else
InputPathTextBox.TextChanged += InputPathTextBoxTextChanged;
LogPathTextBox.TextChanged += LogPathTextBoxTextChanged;
KeyPathTextBox.TextChanged += KeyPathTextBoxTextChanged;
KeyTextBox.TextChanged += KeyTextBoxTextChanged;
DiscIDTextBox.TextChanged += DiscIDTextBoxTextChanged;
PICPathTextBox.TextChanged += PICPathTextBoxTextChanged;
PICTextBox.TextChanged += PICTextBoxTextChanged;
LayerbreakTextBox.TextChanged += LayerbreakTextBoxTextChanged;
#endif
}
/// <summary>
/// Browse for an input ISO file path
/// </summary>
public void BrowseISOFile()
{
// Get the current path, if possible
string? currentPath = CreateIRDViewModel.InputPath;
if (string.IsNullOrEmpty(currentPath) && !string.IsNullOrEmpty(CreateIRDViewModel.Options.DefaultOutputPath))
currentPath = CreateIRDViewModel.Options.DefaultOutputPath!;
if (string.IsNullOrEmpty(currentPath))
currentPath = AppDomain.CurrentDomain.BaseDirectory!;
// Get the full directory
var directory = Path.GetDirectoryName(Path.GetFullPath(currentPath));
WinForms.FileDialog fileDialog = new WinForms.OpenFileDialog
{
InitialDirectory = directory,
Filter = "ISO|*.iso|All Files|*.*",
};
WinForms.DialogResult result = fileDialog.ShowDialog();
if (result == WinForms.DialogResult.OK)
{
CreateIRDViewModel.InputPath = fileDialog.FileName;
}
}
/// <summary>
/// Browse for an .getkey.log file path
/// </summary>
public void BrowseLogFile()
{
// Get the current path, if possible
string? currentPath = CreateIRDViewModel.LogPath;
if (string.IsNullOrEmpty(currentPath) && !string.IsNullOrEmpty(CreateIRDViewModel.Options.DefaultOutputPath))
currentPath = CreateIRDViewModel.Options.DefaultOutputPath!;
if (string.IsNullOrEmpty(currentPath))
currentPath = AppDomain.CurrentDomain.BaseDirectory!;
// Get the full directory
var directory = Path.GetDirectoryName(Path.GetFullPath(currentPath));
WinForms.FileDialog fileDialog = new WinForms.OpenFileDialog
{
InitialDirectory = directory,
Filter = "GetKey Log|*.getkey.log|All Files|*.*",
};
WinForms.DialogResult result = fileDialog.ShowDialog();
if (result == WinForms.DialogResult.OK)
{
CreateIRDViewModel.LogPath = fileDialog.FileName;
}
}
/// <summary>
/// Browse for an key file path
/// </summary>
public void BrowseKeyFile()
{
// Get the current path, if possible
string? currentPath = CreateIRDViewModel.LogPath;
if (string.IsNullOrEmpty(currentPath) && !string.IsNullOrEmpty(CreateIRDViewModel.Options.DefaultOutputPath))
currentPath = CreateIRDViewModel.Options.DefaultOutputPath!;
if (string.IsNullOrEmpty(currentPath))
currentPath = AppDomain.CurrentDomain.BaseDirectory!;
// Get the full directory
var directory = Path.GetDirectoryName(Path.GetFullPath(currentPath));
WinForms.FileDialog fileDialog = new WinForms.OpenFileDialog
{
InitialDirectory = directory,
Filter = "Key|*.key|All Files|*.*",
};
WinForms.DialogResult result = fileDialog.ShowDialog();
if (result == WinForms.DialogResult.OK)
{
CreateIRDViewModel.KeyPath = fileDialog.FileName;
}
}
/// <summary>
/// Browse for an IRD output path
/// </summary>
/// <returns>Output path if provided, else null</returns>
public string? BrowseOutputFile()
{
// Get the current path, if possible
string? currentPath = CreateIRDViewModel.InputPath;
if (string.IsNullOrEmpty(currentPath) && !string.IsNullOrEmpty(CreateIRDViewModel.Options.DefaultOutputPath))
currentPath = Path.Combine(CreateIRDViewModel.Options.DefaultOutputPath, "game.ird");
else if (string.IsNullOrEmpty(currentPath))
currentPath = "game.ird";
if (string.IsNullOrEmpty(currentPath))
currentPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory!, "game.ird");
// Get the full path
currentPath = Path.GetFullPath(currentPath);
// Get the directory
var directory = Path.GetDirectoryName(currentPath);
// Get the filename
string filename = Path.ChangeExtension(Path.GetFileName(currentPath), ".ird");
WinForms.FileDialog fileDialog = new WinForms.SaveFileDialog
{
FileName = filename,
InitialDirectory = directory,
Filter = "IRD File|*.ird|All Files|*.*"
};
WinForms.DialogResult result = fileDialog.ShowDialog();
if (result == WinForms.DialogResult.OK)
return fileDialog.FileName;
else
return null;
}
/// <summary>
/// Browse for an PIC file path
/// </summary>
public void BrowsePICFile()
{
// Get the current path, if possible
string? currentPath = CreateIRDViewModel.LogPath;
if (string.IsNullOrEmpty(currentPath) && !string.IsNullOrEmpty(CreateIRDViewModel.Options.DefaultOutputPath))
currentPath = CreateIRDViewModel.Options.DefaultOutputPath!;
if (string.IsNullOrEmpty(currentPath))
currentPath = AppDomain.CurrentDomain.BaseDirectory!;
// Get the full directory
var directory = Path.GetDirectoryName(Path.GetFullPath(currentPath));
WinForms.FileDialog fileDialog = new WinForms.OpenFileDialog
{
InitialDirectory = directory,
Filter = "PIC|*.physical;*_PIC.bin;*.PIC|All Files|*.*",
};
WinForms.DialogResult result = fileDialog.ShowDialog();
if (result == WinForms.DialogResult.OK)
{
CreateIRDViewModel.PICPath = fileDialog.FileName;
}
}
/// <summary>
/// Display a user message using a CustomMessageBox
/// </summary>
/// <param name="title">Title to display to the user</param>
/// <param name="message">Message to display to the user</param>
/// <param name="optionCount">Number of options to display</param>
/// <param name="flag">true for inquiry, false otherwise</param>
/// <returns>true for positive, false for negative, null for neutral</returns>
public bool? DisplayUserMessage(string title, string message, int optionCount, bool flag)
{
// Set the correct button style
var button = optionCount switch
{
1 => MessageBoxButton.OK,
2 => MessageBoxButton.YesNo,
3 => MessageBoxButton.YesNoCancel,
// This should not happen, but default to "OK"
_ => MessageBoxButton.OK,
};
// Set the correct icon
MessageBoxImage image = flag ? MessageBoxImage.Question : MessageBoxImage.Exclamation;
// Display and get the result
MessageBoxResult result = CustomMessageBox.Show(this, message, title, button, image);
return result switch
{
MessageBoxResult.OK or MessageBoxResult.Yes => true,
MessageBoxResult.No => false,
_ => null,
};
}
#endregion
#region Event Handlers
/// <summary>
/// Handler for CreateIRDButton Click event
/// </summary>
private void OnCreateIRDClick(object sender, EventArgs e)
{
#if NET35
if (_KeyExpander != null) _KeyExpander.IsExpanded = false;
if (_DiscIDExpander != null) _DiscIDExpander.IsExpanded = false;
if (_PICExpander != null) _PICExpander.IsExpanded = false;
#else
KeyExpander.IsExpanded = false;
DiscIDExpander.IsExpanded = false;
PICExpander.IsExpanded = false;
#endif
string tempStatus = CreateIRDViewModel.CreateIRDStatus;
bool[] enabledFields = CreateIRDViewModel.DisableUIFields();
CreateIRDViewModel.CreateIRDStatus = "Creating IRD... Please Wait";
string? outputPath = BrowseOutputFile();
string? errorMessage = "Please provide an output path";
if (outputPath != null)
{
errorMessage = CreateIRDViewModel.CreateIRD(outputPath);
}
if (string.IsNullOrEmpty(errorMessage))
{
bool? checkAgain = DisplayUserMessage("IRD Create", "An IRD has been created successfully! Would you like to create another IRD?", 2, false);
if (checkAgain == false)
Close();
else
CreateIRDViewModel.ResetFields();
}
else
{
DisplayUserMessage("Failed to create IRD", errorMessage!, 1, false);
CreateIRDViewModel.ReenableUIFields(enabledFields);
CreateIRDViewModel.CreateIRDStatus = tempStatus;
}
}
/// <summary>
/// Handler for CancelButtom Click event
/// </summary>
private void OnCancelClick(object sender, EventArgs e)
{
Close();
}
/// <summary>
/// Handler for DiscIDTextBox TextChanged event
/// </summary>
public void DiscIDTextBoxTextChanged(object sender, TextChangedEventArgs e)
{
if (CreateIRDViewModel.CanExecuteSelectionChanged)
CreateIRDViewModel.ChangeDiscID();
}
/// <summary>
/// Handler for InputPathBrowseButton Click event
/// </summary>
public void InputPathBrowseButtonClick(object sender, RoutedEventArgs e)
{
BrowseISOFile();
if (CreateIRDViewModel.CanExecuteSelectionChanged)
CreateIRDViewModel.ChangeInputPath();
}
/// <summary>
/// Handler for InputPathTextBox TextChanged event
/// </summary>
public void InputPathTextBoxTextChanged(object sender, TextChangedEventArgs e)
{
if (CreateIRDViewModel.CanExecuteSelectionChanged)
CreateIRDViewModel.ChangeInputPath();
}
/// <summary>
/// Handler for LogPathBrowseButton Click event
/// </summary>
public void LogPathBrowseButtonClick(object sender, RoutedEventArgs e)
{
BrowseLogFile();
if (CreateIRDViewModel.CanExecuteSelectionChanged)
CreateIRDViewModel.ChangeLogPath();
}
/// <summary>
/// Handler for LogPathTextBox TextChanged event
/// </summary>
public void LogPathTextBoxTextChanged(object sender, TextChangedEventArgs e)
{
if (CreateIRDViewModel.CanExecuteSelectionChanged)
CreateIRDViewModel.ChangeLogPath();
}
/// <summary>
/// Handler for KeyPathBrowseButton Click event
/// </summary>
public void KeyPathBrowseButtonClick(object sender, RoutedEventArgs e)
{
BrowseKeyFile();
if (CreateIRDViewModel.CanExecuteSelectionChanged)
CreateIRDViewModel.ChangeKeyPath();
}
/// <summary>
/// Handler for KeyPathTextBox TextChanged event
/// </summary>
public void KeyPathTextBoxTextChanged(object sender, TextChangedEventArgs e)
{
if (CreateIRDViewModel.CanExecuteSelectionChanged)
CreateIRDViewModel.ChangeKeyPath();
}
/// <summary>
/// Handler for KeyTextBox TextChanged event
/// </summary>
public void KeyTextBoxTextChanged(object sender, TextChangedEventArgs e)
{
if (CreateIRDViewModel.CanExecuteSelectionChanged)
CreateIRDViewModel.ChangeKey();
}
/// <summary>
/// Handler for PICPathBrowseButton Click event
/// </summary>
public void PICPathBrowseButtonClick(object sender, RoutedEventArgs e)
{
BrowsePICFile();
if (CreateIRDViewModel.CanExecuteSelectionChanged)
CreateIRDViewModel.ChangePICPath();
}
/// <summary>
/// Handler for PICPathTextBox TextChanged event
/// </summary>
public void PICPathTextBoxTextChanged(object sender, TextChangedEventArgs e)
{
if (CreateIRDViewModel.CanExecuteSelectionChanged)
CreateIRDViewModel.ChangePICPath();
}
/// <summary>
/// Handler for PICTextBox TextChanged event
/// </summary>
public void PICTextBoxTextChanged(object sender, TextChangedEventArgs e)
{
if (CreateIRDViewModel.CanExecuteSelectionChanged)
CreateIRDViewModel.ChangePIC();
}
/// <summary>
/// Handler for LayerbreakTextBox TextChanged event
/// </summary>
public void LayerbreakTextBoxTextChanged(object sender, TextChangedEventArgs e)
{
if (CreateIRDViewModel.CanExecuteSelectionChanged)
CreateIRDViewModel.ChangeLayerbreak();
}
#endregion
}
}

View File

@@ -15,10 +15,7 @@
<Window.Resources>
<core:ElementConverter x:Key="ElementConverter" />
</Window.Resources>
<WindowChrome.WindowChrome>
<WindowChrome CaptionHeight="0" ResizeBorderThickness="0" />
</WindowChrome.WindowChrome>
<Grid>
<StackPanel Orientation="Vertical" Width="500" MaxHeight="650">
<Grid Margin="0,10,0,0">
@@ -34,7 +31,7 @@
<Image Grid.Column="0" Source="/Images/Icon.ico" Height="20" Width="20" Margin="1" />
<Label Grid.Column="1" Grid.ColumnSpan="4" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" MouseDown="TitleMouseDown">
<Label.Content>
<TextBlock TextAlignment="Center"><Bold>Disc Information</Bold></TextBlock>
<Run FontWeight="Bold" Text="Disc Information" />
</Label.Content>
<Label.ContextMenu>
<ContextMenu Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
@@ -175,6 +172,10 @@
<controls:UserInput x:Name="CommentsTextBox" Label="Comments"
Text="{Binding SubmissionInfo.CommonDiscInfo.Comments, Mode=TwoWay}" TextHeight="50"
Enter="True" TextWrapping="Wrap" VerticalContentAlignmentValue="Top" />
<controls:UserInput x:Name="DiscKeyTextBox" Label="Disc Key" Visibility="Collapsed"
Text="{Binding Path=SubmissionInfo.Extras.DiscKey, Mode=TwoWay}"/>
<controls:UserInput x:Name="DiscIDTextBox" Label="Disc ID" Visibility="Collapsed"
Text="{Binding Path=SubmissionInfo.Extras.DiscID, Mode=TwoWay}"/>
<controls:UserInput x:Name="GenreTextBox" Label="Genre"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)Genre], Mode=TwoWay}"/>
<controls:UserInput x:Name="ProtectionTextBox" Label="Protection" ToolTip="CAUTION: Only edit if you know what you are doing!"

View File

@@ -1,10 +1,14 @@
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using MPF.Core.Data;
using MPF.Core.UI.ViewModels;
using MPF.Core.Utilities;
using MPF.UI.Core.UserControls;
using SabreTools.RedumpLib.Data;
#pragma warning disable IDE1006 // Naming Styles
namespace MPF.UI.Core.Windows
{
/// <summary>
@@ -12,6 +16,105 @@ namespace MPF.UI.Core.Windows
/// </summary>
public partial class DiscInformationWindow : WindowBase
{
#if NET35
#region Common Info
private Grid? _LanguageSelectionGrid => ItemHelper.FindChild<Grid>(this, "LanguageSelectionGrid");
#endregion
#region Additional Info
private UserInput? _CommentsTextBox => ItemHelper.FindChild<UserInput>(this, "CommentsTextBox");
private UserInput? _DiscIDTextBox => ItemHelper.FindChild<UserInput>(this, "DiscIDTextBox");
private UserInput? _DiscKeyTextBox => ItemHelper.FindChild<UserInput>(this, "DiscKeyTextBox");
#endregion
#region Contents
private UserInput? _ExtrasTextBox => ItemHelper.FindChild<UserInput>(this, "ExtrasTextBox");
private UserInput? _GameFootageTextBox => ItemHelper.FindChild<UserInput>(this, "GameFootageTextBox");
private UserInput? _GamesTextBox => ItemHelper.FindChild<UserInput>(this, "GamesTextBox");
private UserInput? _GeneralContent => ItemHelper.FindChild<UserInput>(this, "GeneralContent");
private UserInput? _NetYarozeGamesTextBox => ItemHelper.FindChild<UserInput>(this, "NetYarozeGamesTextBox");
private UserInput? _PatchesTextBox => ItemHelper.FindChild<UserInput>(this, "PatchesTextBox");
private UserInput? _PlayableDemosTextBox => ItemHelper.FindChild<UserInput>(this, "PlayableDemosTextBox");
private UserInput? _RollingDemosTextBox => ItemHelper.FindChild<UserInput>(this, "RollingDemosTextBox");
private UserInput? _SavegamesTextBox => ItemHelper.FindChild<UserInput>(this, "SavegamesTextBox");
private UserInput? _TechDemosTextBox => ItemHelper.FindChild<UserInput>(this, "TechDemosTextBox");
private UserInput? _VideosTextBox => ItemHelper.FindChild<UserInput>(this, "VideosTextBox");
#endregion
#region Ringcodes
private GroupBox? _L0Info => ItemHelper.FindChild<GroupBox>(this, "L0Info");
private UserInput? _L0MasteringRing => ItemHelper.FindChild<UserInput>(this, "L0MasteringRing");
private UserInput? _L0MasteringSID => ItemHelper.FindChild<UserInput>(this, "L0MasteringSID");
private UserInput? _L0Toolstamp => ItemHelper.FindChild<UserInput>(this, "L0Toolstamp");
private UserInput? _L0MouldSID => ItemHelper.FindChild<UserInput>(this, "L0MouldSID");
private UserInput? _L0AdditionalMould => ItemHelper.FindChild<UserInput>(this, "L0AdditionalMould");
private GroupBox? _L1Info => ItemHelper.FindChild<GroupBox>(this, "L1Info");
private UserInput? _L1MasteringRing => ItemHelper.FindChild<UserInput>(this, "L1MasteringRing");
private UserInput? _L1MasteringSID => ItemHelper.FindChild<UserInput>(this, "L1MasteringSID");
private UserInput? _L1Toolstamp => ItemHelper.FindChild<UserInput>(this, "L1Toolstamp");
private UserInput? _L1MouldSID => ItemHelper.FindChild<UserInput>(this, "L1MouldSID");
private UserInput? _L1AdditionalMould => ItemHelper.FindChild<UserInput>(this, "L1AdditionalMould");
private GroupBox? _L2Info => ItemHelper.FindChild<GroupBox>(this, "L2Info");
private UserInput? _L2MasteringRing => ItemHelper.FindChild<UserInput>(this, "L2MasteringRing");
private UserInput? _L2MasteringSID => ItemHelper.FindChild<UserInput>(this, "L2MasteringSID");
private UserInput? _L2Toolstamp => ItemHelper.FindChild<UserInput>(this, "L2Toolstamp");
private GroupBox? _L3Info => ItemHelper.FindChild<GroupBox>(this, "L3Info");
private UserInput? _L3MasteringRing => ItemHelper.FindChild<UserInput>(this, "L3MasteringRing");
private UserInput? _L3MasteringSID => ItemHelper.FindChild<UserInput>(this, "L3MasteringSID");
private UserInput? _L3Toolstamp => ItemHelper.FindChild<UserInput>(this, "L3Toolstamp");
#endregion
#region Read-Only Info
private UserInput? _FullyMatchedID => ItemHelper.FindChild<UserInput>(this, "FullyMatchedID");
private UserInput? _PartiallyMatchedIDs => ItemHelper.FindChild<UserInput>(this, "PartiallyMatchedIDs");
private UserInput? _AntiModchip => ItemHelper.FindChild<UserInput>(this, "AntiModchip");
private UserInput? _DiscOffset => ItemHelper.FindChild<UserInput>(this, "DiscOffset");
private UserInput? _DMIHash => ItemHelper.FindChild<UserInput>(this, "DMIHash");
private UserInput? _EDC => ItemHelper.FindChild<UserInput>(this, "EDC");
private UserInput? _ErrorsCount => ItemHelper.FindChild<UserInput>(this, "ErrorsCount");
private UserInput? _EXEDateBuildDate => ItemHelper.FindChild<UserInput>(this, "EXEDateBuildDate");
private UserInput? _Filename => ItemHelper.FindChild<UserInput>(this, "Filename");
private UserInput? _Header => ItemHelper.FindChild<UserInput>(this, "Header");
private UserInput? _InternalName => ItemHelper.FindChild<UserInput>(this, "InternalName");
private UserInput? _InternalSerialName => ItemHelper.FindChild<UserInput>(this, "InternalSerialName");
private UserInput? _Multisession => ItemHelper.FindChild<UserInput>(this, "Multisession");
private UserInput? _LibCrypt => ItemHelper.FindChild<UserInput>(this, "LibCrypt");
private UserInput? _LibCryptData => ItemHelper.FindChild<UserInput>(this, "LibCryptData");
private UserInput? _PFIHash => ItemHelper.FindChild<UserInput>(this, "PFIHash");
private UserInput? _PIC => ItemHelper.FindChild<UserInput>(this, "PIC");
private UserInput? _PVD => ItemHelper.FindChild<UserInput>(this, "PVD");
private UserInput? _RingNonZeroDataStart => ItemHelper.FindChild<UserInput>(this, "RingNonZeroDataStart");
private UserInput? _SecuROMData => ItemHelper.FindChild<UserInput>(this, "SecuROMData");
private UserInput? _SSHash => ItemHelper.FindChild<UserInput>(this, "SSHash");
private UserInput? _SecuritySectorRanges => ItemHelper.FindChild<UserInput>(this, "SecuritySectorRanges");
private UserInput? _SSVersion => ItemHelper.FindChild<UserInput>(this, "SSVersion");
private UserInput? _UniversalHash => ItemHelper.FindChild<UserInput>(this, "UniversalHash");
private UserInput? _VolumeLabel => ItemHelper.FindChild<UserInput>(this, "VolumeLabel");
private UserInput? _XeMID => ItemHelper.FindChild<UserInput>(this, "XeMID");
private UserInput? _XMID => ItemHelper.FindChild<UserInput>(this, "XMID");
#endregion
#region Accept / Cancel
private Button? _AcceptButton => ItemHelper.FindChild<Button>(this, "AcceptButton");
private Button? _CancelButton => ItemHelper.FindChild<Button>(this, "CancelButton");
private Button? _RingCodeGuideButton => ItemHelper.FindChild<Button>(this, "RingCodeGuideButton");
#endregion
#endif
/// <summary>
/// Read-only access to the current disc information view model
/// </summary>
@@ -20,13 +123,21 @@ namespace MPF.UI.Core.Windows
/// <summary>
/// Constructor
/// </summary>
#if NET48
public DiscInformationWindow(Options options, SubmissionInfo submissionInfo)
#else
public DiscInformationWindow(Options options, SubmissionInfo? submissionInfo)
#endif
{
#if NET40_OR_GREATER || NETCOREAPP
InitializeComponent();
#endif
#if NET452_OR_GREATER || NETCOREAPP
var chrome = new System.Windows.Shell.WindowChrome
{
CaptionHeight = 0,
ResizeBorderThickness = new Thickness(0),
};
System.Windows.Shell.WindowChrome.SetWindowChrome(this, chrome);
#endif
DataContext = new DiscInformationViewModel(options, submissionInfo);
DiscInformationViewModel.Load();
@@ -38,9 +149,15 @@ namespace MPF.UI.Core.Windows
}
// Add handlers
#if NET35
_AcceptButton!.Click += OnAcceptClick;
_CancelButton!.Click += OnCancelClick;
_RingCodeGuideButton!.Click += OnRingCodeGuideClick;
#else
AcceptButton.Click += OnAcceptClick;
CancelButton.Click += OnCancelClick;
RingCodeGuideButton.Click += OnRingCodeGuideClick;
#endif
// Update UI with new values
ManipulateFields(options, submissionInfo);
@@ -51,11 +168,7 @@ namespace MPF.UI.Core.Windows
/// <summary>
/// Manipulate fields based on the current disc
/// </summary>
#if NET48
private void ManipulateFields(Options options, SubmissionInfo submissionInfo)
#else
private void ManipulateFields(Options options, SubmissionInfo? submissionInfo)
#endif
{
// Enable tabs in all fields, if required
if (options.EnableTabsInInputFields)
@@ -78,9 +191,26 @@ namespace MPF.UI.Core.Windows
private void EnableTabsInInputFields()
{
// Additional Information
#if NET35
_CommentsTextBox!.Tab = true;
#else
CommentsTextBox.Tab = true;
#endif
// Contents
#if NET35
_GeneralContent!.Tab = true;
_GamesTextBox!.Tab = true;
_NetYarozeGamesTextBox!.Tab = true;
_PlayableDemosTextBox!.Tab = true;
_RollingDemosTextBox!.Tab = true;
_TechDemosTextBox!.Tab = true;
_GameFootageTextBox!.Tab = true;
_VideosTextBox!.Tab = true;
_PatchesTextBox!.Tab = true;
_SavegamesTextBox!.Tab = true;
_ExtrasTextBox!.Tab = true;
#else
GeneralContent.Tab = true;
GamesTextBox.Tab = true;
NetYarozeGamesTextBox.Tab = true;
@@ -92,30 +222,59 @@ namespace MPF.UI.Core.Windows
PatchesTextBox.Tab = true;
SavegamesTextBox.Tab = true;
ExtrasTextBox.Tab = true;
#endif
// L0
#if NET35
_L0MasteringRing!.Tab = true;
_L0MasteringSID!.Tab = true;
_L0Toolstamp!.Tab = true;
_L0MouldSID!.Tab = true;
_L0AdditionalMould!.Tab = true;
#else
L0MasteringRing.Tab = true;
L0MasteringSID.Tab = true;
L0Toolstamp.Tab = true;
L0MouldSID.Tab = true;
L0AdditionalMould.Tab = true;
#endif
// L1
#if NET35
_L1MasteringRing!.Tab = true;
_L1MasteringSID!.Tab = true;
_L1Toolstamp!.Tab = true;
_L1MouldSID!.Tab = true;
_L1AdditionalMould!.Tab = true;
#else
L1MasteringRing.Tab = true;
L1MasteringSID.Tab = true;
L1Toolstamp.Tab = true;
L1MouldSID.Tab = true;
L1AdditionalMould.Tab = true;
#endif
// L2
#if NET35
_L2MasteringRing!.Tab = true;
_L2MasteringSID!.Tab = true;
_L2Toolstamp!.Tab = true;
#else
L2MasteringRing.Tab = true;
L2MasteringSID.Tab = true;
L2Toolstamp.Tab = true;
#endif
// L3
#if NET35
_L3MasteringRing!.Tab = true;
_L3MasteringSID!.Tab = true;
_L3Toolstamp!.Tab = true;
#else
L3MasteringRing.Tab = true;
L3MasteringSID.Tab = true;
L3Toolstamp.Tab = true;
#endif
}
/// <summary>
@@ -123,22 +282,76 @@ namespace MPF.UI.Core.Windows
/// </summary>
/// TODO: Figure out how to bind the PartiallyMatchedIDs array to a text box
/// TODO: Convert visibility to a binding
#if NET48
private void HideReadOnlyFields(SubmissionInfo submissionInfo)
#else
private void HideReadOnlyFields(SubmissionInfo? submissionInfo)
#endif
{
// If there's no submission information
if (submissionInfo == null)
return;
#if NET35
if (submissionInfo.FullyMatchedID == null)
_FullyMatchedID!.Visibility = Visibility.Collapsed;
if (submissionInfo.PartiallyMatchedIDs == null)
_PartiallyMatchedIDs!.Visibility = Visibility.Collapsed;
else
_PartiallyMatchedIDs!.Text = string.Join(", ", submissionInfo.PartiallyMatchedIDs.Select(i => i.ToString()).ToArray());
if (submissionInfo.CopyProtection?.AntiModchip == null)
_AntiModchip!.Visibility = Visibility.Collapsed;
if (submissionInfo.TracksAndWriteOffsets?.OtherWriteOffsets == null)
_DiscOffset!.Visibility = Visibility.Collapsed;
if (submissionInfo.CommonDiscInfo?.CommentsSpecialFields?.Keys?.Contains(SiteCode.DMIHash) != true)
_DMIHash!.Visibility = Visibility.Collapsed;
if (submissionInfo.EDC?.EDC == null)
_EDC!.Visibility = Visibility.Collapsed;
if (string.IsNullOrEmpty(submissionInfo.CommonDiscInfo?.ErrorsCount))
_ErrorsCount!.Visibility = Visibility.Collapsed;
if (string.IsNullOrEmpty(submissionInfo.CommonDiscInfo?.EXEDateBuildDate))
_EXEDateBuildDate!.Visibility = Visibility.Collapsed;
if (submissionInfo.CommonDiscInfo?.CommentsSpecialFields?.ContainsKey(SiteCode.Filename) != true)
_Filename!.Visibility = Visibility.Collapsed;
if (string.IsNullOrEmpty(submissionInfo.Extras?.Header))
_Header!.Visibility = Visibility.Collapsed;
if (submissionInfo.CommonDiscInfo?.CommentsSpecialFields?.ContainsKey(SiteCode.InternalName) != true)
_InternalName!.Visibility = Visibility.Collapsed;
if (submissionInfo.CommonDiscInfo?.CommentsSpecialFields?.ContainsKey(SiteCode.InternalSerialName) != true)
_InternalSerialName!.Visibility = Visibility.Collapsed;
if (submissionInfo.CommonDiscInfo?.CommentsSpecialFields?.ContainsKey(SiteCode.Multisession) != true)
_Multisession!.Visibility = Visibility.Collapsed;
if (submissionInfo.CopyProtection?.LibCrypt == null)
_LibCrypt!.Visibility = Visibility.Collapsed;
if (string.IsNullOrEmpty(submissionInfo.CopyProtection?.LibCryptData))
_LibCryptData!.Visibility = Visibility.Collapsed;
if (submissionInfo.CommonDiscInfo?.CommentsSpecialFields?.ContainsKey(SiteCode.PFIHash) != true)
_PFIHash!.Visibility = Visibility.Collapsed;
if (string.IsNullOrEmpty(submissionInfo.Extras?.PIC))
_PIC!.Visibility = Visibility.Collapsed;
if (string.IsNullOrEmpty(submissionInfo.Extras?.PVD))
_PVD!.Visibility = Visibility.Collapsed;
if (submissionInfo.CommonDiscInfo?.CommentsSpecialFields?.ContainsKey(SiteCode.RingNonZeroDataStart) != true)
_RingNonZeroDataStart!.Visibility = Visibility.Collapsed;
if (string.IsNullOrEmpty(submissionInfo.CopyProtection?.SecuROMData))
_SecuROMData!.Visibility = Visibility.Collapsed;
if (submissionInfo.CommonDiscInfo?.CommentsSpecialFields?.ContainsKey(SiteCode.SSHash) != true)
_SSHash!.Visibility = Visibility.Collapsed;
if (string.IsNullOrEmpty(submissionInfo.Extras?.SecuritySectorRanges))
_SecuritySectorRanges!.Visibility = Visibility.Collapsed;
if (submissionInfo.CommonDiscInfo?.CommentsSpecialFields?.ContainsKey(SiteCode.SSVersion) != true)
_SSVersion!.Visibility = Visibility.Collapsed;
if (submissionInfo.CommonDiscInfo?.CommentsSpecialFields?.ContainsKey(SiteCode.UniversalHash) != true)
_UniversalHash!.Visibility = Visibility.Collapsed;
if (submissionInfo.CommonDiscInfo?.CommentsSpecialFields?.ContainsKey(SiteCode.VolumeLabel) != true)
_VolumeLabel!.Visibility = Visibility.Collapsed;
if (submissionInfo.CommonDiscInfo?.CommentsSpecialFields?.ContainsKey(SiteCode.XeMID) != true)
_XeMID!.Visibility = Visibility.Collapsed;
if (submissionInfo.CommonDiscInfo?.CommentsSpecialFields?.ContainsKey(SiteCode.XMID) != true)
_XMID!.Visibility = Visibility.Collapsed;
#else
if (submissionInfo.FullyMatchedID == null)
FullyMatchedID.Visibility = Visibility.Collapsed;
if (submissionInfo.PartiallyMatchedIDs == null)
PartiallyMatchedIDs.Visibility = Visibility.Collapsed;
else
PartiallyMatchedIDs.Text = string.Join(", ", submissionInfo.PartiallyMatchedIDs);
PartiallyMatchedIDs.Text = string.Join(", ", submissionInfo.PartiallyMatchedIDs.Select(i => i.ToString()).ToArray());
if (submissionInfo.CopyProtection?.AntiModchip == null)
AntiModchip.Visibility = Visibility.Collapsed;
if (submissionInfo.TracksAndWriteOffsets?.OtherWriteOffsets == null)
@@ -147,13 +360,13 @@ namespace MPF.UI.Core.Windows
DMIHash.Visibility = Visibility.Collapsed;
if (submissionInfo.EDC?.EDC == null)
EDC.Visibility = Visibility.Collapsed;
if (string.IsNullOrWhiteSpace(submissionInfo.CommonDiscInfo?.ErrorsCount))
if (string.IsNullOrEmpty(submissionInfo.CommonDiscInfo?.ErrorsCount))
ErrorsCount.Visibility = Visibility.Collapsed;
if (string.IsNullOrWhiteSpace(submissionInfo.CommonDiscInfo?.EXEDateBuildDate))
if (string.IsNullOrEmpty(submissionInfo.CommonDiscInfo?.EXEDateBuildDate))
EXEDateBuildDate.Visibility = Visibility.Collapsed;
if (submissionInfo.CommonDiscInfo?.CommentsSpecialFields?.ContainsKey(SiteCode.Filename) != true)
Filename.Visibility = Visibility.Collapsed;
if (string.IsNullOrWhiteSpace(submissionInfo.Extras?.Header))
if (string.IsNullOrEmpty(submissionInfo.Extras?.Header))
Header.Visibility = Visibility.Collapsed;
if (submissionInfo.CommonDiscInfo?.CommentsSpecialFields?.ContainsKey(SiteCode.InternalName) != true)
InternalName.Visibility = Visibility.Collapsed;
@@ -163,21 +376,21 @@ namespace MPF.UI.Core.Windows
Multisession.Visibility = Visibility.Collapsed;
if (submissionInfo.CopyProtection?.LibCrypt == null)
LibCrypt.Visibility = Visibility.Collapsed;
if (string.IsNullOrWhiteSpace(submissionInfo.CopyProtection?.LibCryptData))
if (string.IsNullOrEmpty(submissionInfo.CopyProtection?.LibCryptData))
LibCryptData.Visibility = Visibility.Collapsed;
if (submissionInfo.CommonDiscInfo?.CommentsSpecialFields?.ContainsKey(SiteCode.PFIHash) != true)
PFIHash.Visibility = Visibility.Collapsed;
if (string.IsNullOrWhiteSpace(submissionInfo.Extras?.PIC))
if (string.IsNullOrEmpty(submissionInfo.Extras?.PIC))
PIC.Visibility = Visibility.Collapsed;
if (string.IsNullOrWhiteSpace(submissionInfo.Extras?.PVD))
if (string.IsNullOrEmpty(submissionInfo.Extras?.PVD))
PVD.Visibility = Visibility.Collapsed;
if (submissionInfo.CommonDiscInfo?.CommentsSpecialFields?.ContainsKey(SiteCode.RingNonZeroDataStart) != true)
RingNonZeroDataStart.Visibility = Visibility.Collapsed;
if (string.IsNullOrWhiteSpace(submissionInfo.CopyProtection?.SecuROMData))
if (string.IsNullOrEmpty(submissionInfo.CopyProtection?.SecuROMData))
SecuROMData.Visibility = Visibility.Collapsed;
if (submissionInfo.CommonDiscInfo?.CommentsSpecialFields?.ContainsKey(SiteCode.SSHash) != true)
SSHash.Visibility = Visibility.Collapsed;
if (string.IsNullOrWhiteSpace(submissionInfo.Extras?.SecuritySectorRanges))
if (string.IsNullOrEmpty(submissionInfo.Extras?.SecuritySectorRanges))
SecuritySectorRanges.Visibility = Visibility.Collapsed;
if (submissionInfo.CommonDiscInfo?.CommentsSpecialFields?.ContainsKey(SiteCode.SSVersion) != true)
SSVersion.Visibility = Visibility.Collapsed;
@@ -189,17 +402,14 @@ namespace MPF.UI.Core.Windows
XeMID.Visibility = Visibility.Collapsed;
if (submissionInfo.CommonDiscInfo?.CommentsSpecialFields?.ContainsKey(SiteCode.XMID) != true)
XMID.Visibility = Visibility.Collapsed;
#endif
}
/// <summary>
/// Update visible fields and sections based on the media type
/// </summary>
/// TODO: See if these can be done by binding
#if NET48
private void UpdateFromDiscType(SubmissionInfo submissionInfo)
#else
private void UpdateFromDiscType(SubmissionInfo? submissionInfo)
#endif
{
// Sony-printed discs have layers in the opposite order
var system = submissionInfo?.CommonDiscInfo?.System;
@@ -209,19 +419,37 @@ namespace MPF.UI.Core.Windows
{
case DiscType.CD:
case DiscType.GDROM:
#if NET35
_L0Info!.Header = "Data Side";
_L0MasteringRing!.Label = "Mastering Ring";
_L0MasteringSID!.Label = "Mastering SID";
_L0Toolstamp!.Label = "Toolstamp/Mastering Code";
_L0MouldSID!.Label = "Mould SID";
_L0AdditionalMould!.Label = "Additional Mould";
#else
L0Info.Header = "Data Side";
L0MasteringRing.Label = "Mastering Ring";
L0MasteringSID.Label = "Mastering SID";
L0Toolstamp.Label = "Toolstamp/Mastering Code";
L0MouldSID.Label = "Mould SID";
L0AdditionalMould.Label = "Additional Mould";
#endif
#if NET35
_L1Info!.Header = "Label Side";
_L1MasteringRing!.Visibility = Visibility.Collapsed;
_L1MasteringSID!.Visibility = Visibility.Collapsed;
_L1Toolstamp!.Visibility = Visibility.Collapsed;
_L1MouldSID!.Label = "Mould SID";
_L1AdditionalMould!.Label = "Additional Mould";
#else
L1Info.Header = "Label Side";
L1MasteringRing.Visibility = Visibility.Collapsed;
L1MasteringSID.Visibility = Visibility.Collapsed;
L1Toolstamp.Visibility = Visibility.Collapsed;
L1MouldSID.Label = "Mould SID";
L1AdditionalMould.Label = "Additional Mould";
#endif
break;
case DiscType.DVD5:
@@ -238,106 +466,217 @@ namespace MPF.UI.Core.Windows
case DiscType.NintendoWiiOpticalDiscSL:
case DiscType.NintendoWiiOpticalDiscDL:
case DiscType.NintendoWiiUOpticalDiscSL:
case DiscType.UMDSL:
case DiscType.UMDDL:
// Quad-layer discs
if (submissionInfo?.SizeAndChecksums?.Layerbreak3 != default(long))
{
#if NET35
_L2Info!.Visibility = Visibility.Visible;
_L3Info!.Visibility = Visibility.Visible;
#else
L2Info.Visibility = Visibility.Visible;
L3Info.Visibility = Visibility.Visible;
#endif
#if NET35
_L0Info!.Header = reverseOrder ? "Layer 0 (Outer)" : "Layer 0 (Inner)";
_L0MasteringRing!.Label = "Mastering Ring";
_L0MasteringSID!.Label = "Mastering SID";
_L0Toolstamp!.Label = "Toolstamp/Mastering Code";
_L0MouldSID!.Label = "Data Side Mould SID";
_L0AdditionalMould!.Label = "Data Side Additional Mould";
#else
L0Info.Header = reverseOrder ? "Layer 0 (Outer)" : "Layer 0 (Inner)";
L0MasteringRing.Label = "Mastering Ring";
L0MasteringSID.Label = "Mastering SID";
L0Toolstamp.Label = "Toolstamp/Mastering Code";
L0MouldSID.Label = "Data Side Mould SID";
L0AdditionalMould.Label = "Data Side Additional Mould";
#endif
#if NET35
_L1Info!.Header = "Layer 1";
_L1MasteringRing!.Label = "Mastering Ring";
_L1MasteringSID!.Label = "Mastering SID";
_L1Toolstamp!.Label = "Toolstamp/Mastering Code";
_L1MouldSID!.Label = "Label Side Mould SID";
_L1AdditionalMould!.Label = "Label Side Additional Mould";
#else
L1Info.Header = "Layer 1";
L1MasteringRing.Label = "Mastering Ring";
L1MasteringSID.Label = "Mastering SID";
L1Toolstamp.Label = "Toolstamp/Mastering Code";
L1MouldSID.Label = "Label Side Mould SID";
L1AdditionalMould.Label = "Label Side Additional Mould";
#endif
#if NET35
_L2Info!.Header = "Layer 2";
_L2MasteringRing!.Label = "Mastering Ring";
_L2MasteringSID!.Label = "Mastering SID";
_L2Toolstamp!.Label = "Toolstamp/Mastering Code";
#else
L2Info.Header = "Layer 2";
L2MasteringRing.Label = "Mastering Ring";
L2MasteringSID.Label = "Mastering SID";
L2Toolstamp.Label = "Toolstamp/Mastering Code";
#endif
#if NET35
_L3Info!.Header = reverseOrder ? "Layer 3 (Inner)" : "Layer 3 (Outer)";
_L3MasteringRing!.Label = "Mastering Ring";
_L3MasteringSID!.Label = "Mastering SID";
_L3Toolstamp!.Label = "Toolstamp/Mastering Code";
#else
L3Info.Header = reverseOrder ? "Layer 3 (Inner)" : "Layer 3 (Outer)";
L3MasteringRing.Label = "Mastering Ring";
L3MasteringSID.Label = "Mastering SID";
L3Toolstamp.Label = "Toolstamp/Mastering Code";
#endif
}
// Triple-layer discs
else if (submissionInfo?.SizeAndChecksums?.Layerbreak2 != default(long))
{
#if NET35
_L2Info!.Visibility = Visibility.Visible;
#else
L2Info.Visibility = Visibility.Visible;
#endif
#if NET35
_L0Info!.Header = reverseOrder ? "Layer 0 (Outer)" : "Layer 0 (Inner)";
_L0MasteringRing!.Label = "Mastering Ring";
_L0MasteringSID!.Label = "Mastering SID";
_L0Toolstamp!.Label = "Toolstamp/Mastering Code";
_L0MouldSID!.Label = "Data Side Mould SID";
_L0AdditionalMould!.Label = "Data Side Additional Mould";
#else
L0Info.Header = reverseOrder ? "Layer 0 (Outer)" : "Layer 0 (Inner)";
L0MasteringRing.Label = "Mastering Ring";
L0MasteringSID.Label = "Mastering SID";
L0Toolstamp.Label = "Toolstamp/Mastering Code";
L0MouldSID.Label = "Data Side Mould SID";
L0AdditionalMould.Label = "Data Side Additional Mould";
#endif
#if NET35
_L1Info!.Header = "Layer 1";
_L1MasteringRing!.Label = "Mastering Ring";
_L1MasteringSID!.Label = "Mastering SID";
_L1Toolstamp!.Label = "Toolstamp/Mastering Code";
_L1MouldSID!.Label = "Label Side Mould SID";
_L1AdditionalMould!.Label = "Label Side Additional Mould";
#else
L1Info.Header = "Layer 1";
L1MasteringRing.Label = "Mastering Ring";
L1MasteringSID.Label = "Mastering SID";
L1Toolstamp.Label = "Toolstamp/Mastering Code";
L1MouldSID.Label = "Label Side Mould SID";
L1AdditionalMould.Label = "Label Side Additional Mould";
#endif
#if NET35
_L2Info!.Header = reverseOrder ? "Layer 2 (Inner)" : "Layer 2 (Outer)";
_L2MasteringRing!.Label = "Mastering Ring";
_L2MasteringSID!.Label = "Mastering SID";
_L2Toolstamp!.Label = "Toolstamp/Mastering Code";
#else
L2Info.Header = reverseOrder ? "Layer 2 (Inner)" : "Layer 2 (Outer)";
L2MasteringRing.Label = "Mastering Ring";
L2MasteringSID.Label = "Mastering SID";
L2Toolstamp.Label = "Toolstamp/Mastering Code";
#endif
}
// Double-layer discs
else if (submissionInfo?.SizeAndChecksums?.Layerbreak != default(long))
{
#if NET35
_L0Info!.Header = reverseOrder ? "Layer 0 (Outer)" : "Layer 0 (Inner)";
_L0MasteringRing!.Label = "Mastering Ring";
_L0MasteringSID!.Label = "Mastering SID";
_L0Toolstamp!.Label = "Toolstamp/Mastering Code";
_L0MouldSID!.Label = "Data Side Mould SID";
_L0AdditionalMould!.Label = "Data Side Additional Mould";
#else
L0Info.Header = reverseOrder ? "Layer 0 (Outer)" : "Layer 0 (Inner)";
L0MasteringRing.Label = "Mastering Ring";
L0MasteringSID.Label = "Mastering SID";
L0Toolstamp.Label = "Toolstamp/Mastering Code";
L0MouldSID.Label = "Data Side Mould SID";
L0AdditionalMould.Label = "Data Side Additional Mould";
#endif
#if NET35
_L1Info!.Header = reverseOrder ? "Layer 1 (Inner)" : "Layer 1 (Outer)";
_L1MasteringRing!.Label = "Mastering Ring";
_L1MasteringSID!.Label = "Mastering SID";
_L1Toolstamp!.Label = "Toolstamp/Mastering Code";
_L1MouldSID!.Label = "Label Side Mould SID";
_L1AdditionalMould!.Label = "Label Side Additional Mould";
#else
L1Info.Header = reverseOrder ? "Layer 1 (Inner)" : "Layer 1 (Outer)";
L1MasteringRing.Label = "Mastering Ring";
L1MasteringSID.Label = "Mastering SID";
L1Toolstamp.Label = "Toolstamp/Mastering Code";
L1MouldSID.Label = "Label Side Mould SID";
L1AdditionalMould.Label = "Label Side Additional Mould";
#endif
}
// Single-layer discs
else
{
#if NET35
_L0Info!.Header = "Data Side";
_L0MasteringRing!.Label = "Mastering Ring";
_L0MasteringSID!.Label = "Mastering SID";
_L0Toolstamp!.Label = "Toolstamp/Mastering Code";
_L0MouldSID!.Label = "Mould SID";
_L0AdditionalMould!.Label = "Additional Mould";
#else
L0Info.Header = "Data Side";
L0MasteringRing.Label = "Mastering Ring";
L0MasteringSID.Label = "Mastering SID";
L0Toolstamp.Label = "Toolstamp/Mastering Code";
L0MouldSID.Label = "Mould SID";
L0AdditionalMould.Label = "Additional Mould";
#endif
#if NET35
_L1Info!.Header = "Label Side";
_L1MasteringRing!.Visibility = Visibility.Collapsed;
_L1MasteringSID!.Visibility = Visibility.Collapsed;
_L1Toolstamp!.Visibility = Visibility.Collapsed;
_L1MouldSID!.Label = "Mould SID";
_L1AdditionalMould!.Label = "Additional Mould";
#else
L1Info.Header = "Label Side";
L1MasteringRing.Label = "Mastering Ring";
L1MasteringSID.Label = "Mastering SID";
L1Toolstamp.Label = "Toolstamp/Mastering Code";
L1MasteringRing.Visibility = Visibility.Collapsed;
L1MasteringSID.Visibility = Visibility.Collapsed;
L1Toolstamp.Visibility = Visibility.Collapsed;
L1MouldSID.Label = "Mould SID";
L1AdditionalMould.Label = "Additional Mould";
#endif
}
break;
// All other media we assume to have no rings
default:
#if NET35
_L0Info!.Visibility = Visibility.Collapsed;
_L1Info!.Visibility = Visibility.Collapsed;
_L2Info!.Visibility = Visibility.Collapsed;
_L3Info!.Visibility = Visibility.Collapsed;
#else
L0Info.Visibility = Visibility.Collapsed;
L1Info.Visibility = Visibility.Collapsed;
L2Info.Visibility = Visibility.Collapsed;
L3Info.Visibility = Visibility.Collapsed;
#endif
break;
}
}
@@ -346,22 +685,40 @@ namespace MPF.UI.Core.Windows
/// Update visible fields and sections based on the system type
/// </summary>
/// TODO: See if these can be done by binding
#if NET48
private void UpdateFromSystemType(SubmissionInfo submissionInfo)
#else
private void UpdateFromSystemType(SubmissionInfo? submissionInfo)
#endif
{
var system = submissionInfo?.CommonDiscInfo?.System;
switch (system)
{
case RedumpSystem.NintendoWiiU:
#if NET35
_DiscKeyTextBox!.Visibility = Visibility.Visible;
#else
DiscKeyTextBox.Visibility = Visibility.Visible;
#endif
break;
case RedumpSystem.SonyPlayStation2:
#if NET35
_LanguageSelectionGrid!.Visibility = Visibility.Visible;
#else
LanguageSelectionGrid.Visibility = Visibility.Visible;
#endif
break;
case RedumpSystem.SonyPlayStation3:
#if NET35
_DiscKeyTextBox!.Visibility = Visibility.Visible;
_DiscIDTextBox!.Visibility = Visibility.Visible;
#else
DiscKeyTextBox.Visibility = Visibility.Visible;
DiscIDTextBox.Visibility = Visibility.Visible;
#endif
break;
}
}
#endregion
#endregion
#region Event Handlers

View File

@@ -18,9 +18,6 @@
<Window.Resources>
<core:ElementConverter x:Key="ElementConverter" />
</Window.Resources>
<WindowChrome.WindowChrome>
<WindowChrome CaptionHeight="0" ResizeBorderThickness="0" />
</WindowChrome.WindowChrome>
<Grid>
<StackPanel Orientation="Vertical">
@@ -52,6 +49,20 @@
Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
Foreground="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"
Template="{DynamicResource CustomMenuItemTemplate}">
<MenuItem x:Name="CheckDumpMenuItem" Header="_Check Dump" HorizontalAlignment="Left" Width="185"
Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
Foreground="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"
Template="{DynamicResource CustomMenuItemTemplate}"
IsEnabled="{Binding CheckDumpMenuItemEnabled}"/>
<MenuItem x:Name="CreateIRDMenuItem" Header="_Create PS3 IRD" HorizontalAlignment="Left" Width="185"
Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
Foreground="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"
Template="{DynamicResource CustomMenuItemTemplate}"
ToolTip="IRD Creation is only available on .NET Core 6 or newer."
ToolTipService.InitialShowDelay="0"
ToolTipService.IsEnabled="{Binding CannotCreateIRD}"
ToolTipService.ShowOnDisabled="True"
IsEnabled="{Binding CreateIRDMenuItemEnabled}"/>
<MenuItem x:Name="OptionsMenuItem" Header="_Options" HorizontalAlignment="Left" Width="185"
Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
Foreground="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"
@@ -79,7 +90,7 @@
</StackPanel>
<Label Panel.ZIndex="0" Grid.Column="1" Grid.ColumnSpan="4" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" MouseDown="TitleMouseDown">
<Label.Content>
<TextBlock TextAlignment="Center"><Bold>Media Preservation Frontend</Bold></TextBlock>
<TextBlock TextAlignment="Center"><Run FontWeight="Bold" Text="Media Preservation Frontend" /></TextBlock>
</Label.Content>
<Label.ContextMenu>
<ContextMenu Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"

View File

@@ -2,12 +2,15 @@ using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using MPF.Core;
using MPF.Core.UI.ViewModels;
using MPF.UI.Core.UserControls;
using SabreTools.RedumpLib;
using SabreTools.RedumpLib.Data;
using WPFCustomMessageBox;
using WinForms = System.Windows.Forms;
#pragma warning disable IDE1006 // Naming Styles
namespace MPF.UI.Core.Windows
{
public partial class MainWindow : WindowBase
@@ -17,10 +20,68 @@ namespace MPF.UI.Core.Windows
/// </summary>
public MainViewModel MainViewModel => DataContext as MainViewModel ?? new MainViewModel();
#if NET35
#region Top Menu Bar
private MenuItem? _AboutMenuItem => ItemHelper.FindChild<MenuItem>(this, "AboutMenuItem");
private MenuItem? _AppExitMenuItem => ItemHelper.FindChild<MenuItem>(this, "AppExitMenuItem");
private MenuItem? _CheckForUpdatesMenuItem => ItemHelper.FindChild<MenuItem>(this, "CheckForUpdatesMenuItem");
private MenuItem? _DebugViewMenuItem => ItemHelper.FindChild<MenuItem>(this, "DebugViewMenuItem");
private MenuItem? _CheckDumpMenuItem => ItemHelper.FindChild<MenuItem>(this, "CheckDumpMenuItem");
private MenuItem? _CreateIRDMenuItem => ItemHelper.FindChild<MenuItem>(this, "CreateIRDMenuItem");
private MenuItem? _OptionsMenuItem => ItemHelper.FindChild<MenuItem>(this, "OptionsMenuItem");
#endregion
#region Settings
private ComboBox? _DriveLetterComboBox => ItemHelper.FindChild<ComboBox>(this, "DriveLetterComboBox");
private ComboBox? _DriveSpeedComboBox => ItemHelper.FindChild<ComboBox>(this, "DriveSpeedComboBox");
private ComboBox? _DumpingProgramComboBox => ItemHelper.FindChild<ComboBox>(this, "DumpingProgramComboBox");
private CheckBox? _EnableParametersCheckBox => ItemHelper.FindChild<CheckBox>(this, "EnableParametersCheckBox");
private ComboBox? _MediaTypeComboBox => ItemHelper.FindChild<ComboBox>(this, "MediaTypeComboBox");
private Button? _OutputPathBrowseButton => ItemHelper.FindChild<Button>(this, "OutputPathBrowseButton");
private TextBox? _OutputPathTextBox => ItemHelper.FindChild<TextBox>(this, "OutputPathTextBox");
private ComboBox? _SystemTypeComboBox => ItemHelper.FindChild<ComboBox>(this, "SystemTypeComboBox");
#endregion
#region Controls
private Button? _CopyProtectScanButton => ItemHelper.FindChild<Button>(this, "CopyProtectScanButton");
private Button? _MediaScanButton => ItemHelper.FindChild<Button>(this, "MediaScanButton");
private Button? _StartStopButton => ItemHelper.FindChild<Button>(this, "StartStopButton");
private Button? _UpdateVolumeLabel => ItemHelper.FindChild<Button>(this, "UpdateVolumeLabel");
#endregion
#region Status
private LogOutput? _LogOutput => ItemHelper.FindChild<LogOutput>(this, "LogOutput");
#endregion
#endif
/// <summary>
/// Constructor
/// </summary>
public MainWindow() => InitializeComponent();
public MainWindow()
{
#if NET40_OR_GREATER || NETCOREAPP
InitializeComponent();
#endif
#if NET452_OR_GREATER || NETCOREAPP
var chrome = new System.Windows.Shell.WindowChrome
{
CaptionHeight = 0,
ResizeBorderThickness = new Thickness(0),
};
System.Windows.Shell.WindowChrome.SetWindowChrome(this, chrome);
#endif
}
/// <summary>
/// Handler for MainWindow OnContentRendered event
@@ -40,9 +101,17 @@ namespace MPF.UI.Core.Windows
// Display the debug option in the menu, if necessary
if (MainViewModel.Options.ShowDebugViewMenuItem)
#if NET35
_DebugViewMenuItem!.Visibility = Visibility.Visible;
#else
DebugViewMenuItem.Visibility = Visibility.Visible;
#endif
#if NET35
MainViewModel.Init(_LogOutput!.EnqueueLog, DisplayUserMessage, ShowDiscInformationWindow);
#else
MainViewModel.Init(LogOutput.EnqueueLog, DisplayUserMessage, ShowDiscInformationWindow);
#endif
// Set the UI color scheme according to the options
ApplyTheme();
@@ -67,29 +136,62 @@ namespace MPF.UI.Core.Windows
public void AddEventHandlers()
{
// Menu Bar Click
#if NET35
_AboutMenuItem!.Click += AboutClick;
_AppExitMenuItem!.Click += AppExitClick;
_CheckForUpdatesMenuItem!.Click += CheckForUpdatesClick;
_DebugViewMenuItem!.Click += DebugViewClick;
_CheckDumpMenuItem!.Click += CheckDumpMenuItemClick;
_CreateIRDMenuItem!.Click += CreateIRDMenuItemClick;
_OptionsMenuItem!.Click += OptionsMenuItemClick;
#else
AboutMenuItem.Click += AboutClick;
AppExitMenuItem.Click += AppExitClick;
CheckForUpdatesMenuItem.Click += CheckForUpdatesClick;
DebugViewMenuItem.Click += DebugViewClick;
CheckDumpMenuItem.Click += CheckDumpMenuItemClick;
CreateIRDMenuItem.Click += CreateIRDMenuItemClick;
OptionsMenuItem.Click += OptionsMenuItemClick;
#endif
// User Area Click
#if NET35
_CopyProtectScanButton!.Click += CopyProtectScanButtonClick;
_EnableParametersCheckBox!.Click += EnableParametersCheckBoxClick;
_MediaScanButton!.Click += MediaScanButtonClick;
_UpdateVolumeLabel!.Click += UpdateVolumeLabelClick;
_OutputPathBrowseButton!.Click += OutputPathBrowseButtonClick;
_StartStopButton!.Click += StartStopButtonClick;
#else
CopyProtectScanButton.Click += CopyProtectScanButtonClick;
EnableParametersCheckBox.Click += EnableParametersCheckBoxClick;
MediaScanButton.Click += MediaScanButtonClick;
UpdateVolumeLabel.Click += UpdateVolumeLabelClick;
OutputPathBrowseButton.Click += OutputPathBrowseButtonClick;
StartStopButton.Click += StartStopButtonClick;
#endif
// User Area SelectionChanged
#if NET35
_SystemTypeComboBox!.SelectionChanged += SystemTypeComboBoxSelectionChanged;
_MediaTypeComboBox!.SelectionChanged += MediaTypeComboBoxSelectionChanged;
_DriveLetterComboBox!.SelectionChanged += DriveLetterComboBoxSelectionChanged;
_DriveSpeedComboBox!.SelectionChanged += DriveSpeedComboBoxSelectionChanged;
_DumpingProgramComboBox!.SelectionChanged += DumpingProgramComboBoxSelectionChanged;
#else
SystemTypeComboBox.SelectionChanged += SystemTypeComboBoxSelectionChanged;
MediaTypeComboBox.SelectionChanged += MediaTypeComboBoxSelectionChanged;
DriveLetterComboBox.SelectionChanged += DriveLetterComboBoxSelectionChanged;
DriveSpeedComboBox.SelectionChanged += DriveSpeedComboBoxSelectionChanged;
DumpingProgramComboBox.SelectionChanged += DumpingProgramComboBoxSelectionChanged;
#endif
// User Area TextChanged
#if NET35
_OutputPathTextBox!.TextChanged += OutputPathTextBoxTextChanged;
#else
OutputPathTextBox.TextChanged += OutputPathTextBoxTextChanged;
#endif
}
/// <summary>
@@ -99,12 +201,12 @@ namespace MPF.UI.Core.Windows
{
// Get the current path, if possible
string currentPath = MainViewModel.OutputPath;
if (string.IsNullOrWhiteSpace(currentPath) && !string.IsNullOrWhiteSpace(MainViewModel.Options.DefaultOutputPath))
if (string.IsNullOrEmpty(currentPath) && !string.IsNullOrEmpty(MainViewModel.Options.DefaultOutputPath))
currentPath = Path.Combine(MainViewModel.Options.DefaultOutputPath, "track.bin");
else if (string.IsNullOrWhiteSpace(currentPath))
else if (string.IsNullOrEmpty(currentPath))
currentPath = "track.bin";
if (string.IsNullOrWhiteSpace(currentPath))
currentPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "track.bin");
if (string.IsNullOrEmpty(currentPath))
currentPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory!, "track.bin");
// Get the full path
currentPath = Path.GetFullPath(currentPath);
@@ -137,7 +239,7 @@ namespace MPF.UI.Core.Windows
(bool different, string message, var url) = MainViewModel.CheckForUpdates();
// If we have a new version, put it in the clipboard
if (different)
if (different && !string.IsNullOrEmpty(url))
Clipboard.SetText(url);
if (showIfSame || different)
@@ -155,44 +257,27 @@ namespace MPF.UI.Core.Windows
public bool? DisplayUserMessage(string title, string message, int optionCount, bool flag)
{
// Set the correct button style
MessageBoxButton button;
switch (optionCount)
var button = optionCount switch
{
case 1:
button = MessageBoxButton.OK;
break;
case 2:
button = MessageBoxButton.YesNo;
break;
case 3:
button = MessageBoxButton.YesNoCancel;
break;
1 => MessageBoxButton.OK,
2 => MessageBoxButton.YesNo,
3 => MessageBoxButton.YesNoCancel,
// This should not happen, but default to "OK"
default:
button = MessageBoxButton.OK;
break;
}
_ => MessageBoxButton.OK,
};
// Set the correct icon
MessageBoxImage image = flag ? MessageBoxImage.Question : MessageBoxImage.Exclamation;
// Display and get the result
MessageBoxResult result = CustomMessageBox.Show(this, message, title, button, image);
switch (result)
return result switch
{
case MessageBoxResult.OK:
case MessageBoxResult.Yes:
return true;
case MessageBoxResult.No:
return false;
case MessageBoxResult.Cancel:
case MessageBoxResult.None:
default:
return null;
}
MessageBoxResult.OK or MessageBoxResult.Yes => true,
MessageBoxResult.No => false,
_ => null,
};
}
/// <summary>
@@ -202,7 +287,7 @@ namespace MPF.UI.Core.Windows
{
var submissionInfo = MainViewModel.CreateDebugSubmissionInfo();
var result = ShowDiscInformationWindow(submissionInfo);
InfoTool.ProcessSpecialFields(result.Item2);
Formatter.ProcessSpecialFields(result.Item2);
}
/// <summary>
@@ -210,11 +295,7 @@ namespace MPF.UI.Core.Windows
/// </summary>
/// <param name="submissionInfo">SubmissionInfo object to display and possibly change</param>
/// <returns>Dialog open result</returns>
#if NET48
public (bool?, SubmissionInfo) ShowDiscInformationWindow(SubmissionInfo submissionInfo)
#else
public (bool?, SubmissionInfo?) ShowDiscInformationWindow(SubmissionInfo? submissionInfo)
#endif
{
if (MainViewModel.Options.ShowDiscEjectReminder)
CustomMessageBox.Show(this, "It is now safe to eject the disc", "Eject", MessageBoxButton.OK, MessageBoxImage.Information);
@@ -227,31 +308,71 @@ namespace MPF.UI.Core.Windows
ShowInTaskbar = true,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
};
discInformationWindow.Closed += delegate { this.Activate(); };
bool? result = discInformationWindow.ShowDialog();
// Copy back the submission info changes, if necessary
if (result == true)
#if NET48
submissionInfo = discInformationWindow.DiscInformationViewModel.SubmissionInfo.Clone() as SubmissionInfo;
#else
submissionInfo = (discInformationWindow.DiscInformationViewModel.SubmissionInfo.Clone() as SubmissionInfo)!;
#endif
#if NET48
return (result, submissionInfo);
#else
return (result, submissionInfo!);
#endif
}
/// <summary>
/// Show the Check Dump window
/// </summary>
public void ShowCheckDumpWindow()
{
// Hide MainWindow while Check GUI is open
this.Hide();
var checkDumpWindow = new CheckDumpWindow(this)
{
Focusable = true,
Owner = this,
ShowActivated = true,
ShowInTaskbar = true,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
};
checkDumpWindow.Closed += delegate {
// Unhide Main window after Check window has been closed
this.Show();
this.Activate();
};
checkDumpWindow.Show();
}
/// <summary>
/// Show the Create IRD window
/// </summary>
public void ShowCreateIRDWindow()
{
// Hide MainWindow while Create IRD UI is open
this.Hide();
var createIRDWindow = new CreateIRDWindow(this)
{
Focusable = true,
Owner = this,
ShowActivated = true,
ShowInTaskbar = true,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
};
createIRDWindow.Closed += delegate {
// Unhide Main window after Create IRD window has been closed
this.Show();
this.Activate();
};
createIRDWindow.Show();
}
/// <summary>
/// Show the Options window
/// </summary>
#if NET48
public void ShowOptionsWindow(string title = null)
#else
public void ShowOptionsWindow(string? title = null)
#endif
{
var optionsWindow = new OptionsWindow(MainViewModel.Options)
{
@@ -263,6 +384,7 @@ namespace MPF.UI.Core.Windows
WindowStartupLocation = WindowStartupLocation.CenterOwner,
};
optionsWindow.Closed += delegate { this.Activate(); };
optionsWindow.Closed += OnOptionsUpdated;
optionsWindow.Show();
}
@@ -281,18 +403,14 @@ namespace MPF.UI.Core.Windows
theme.Apply();
}
#endregion
#endregion
#region Event Handlers
/// <summary>
/// Handler for OptionsWindow OnUpdated event
/// </summary>
#if NET48
public void OnOptionsUpdated(object sender, EventArgs e)
#else
public void OnOptionsUpdated(object? sender, EventArgs e)
#endif
{
// Get the options window
var optionsWindow = (sender as OptionsWindow);
@@ -324,6 +442,18 @@ namespace MPF.UI.Core.Windows
public void AppExitClick(object sender, RoutedEventArgs e) =>
Application.Current.Shutdown();
/// <summary>
/// Handler for CheckDumpMenuItem Click event
/// </summary>
public void CheckDumpMenuItemClick(object sender, RoutedEventArgs e) =>
ShowCheckDumpWindow();
/// <summary>
/// Handler for CreateIRDMenuItem Click event
/// </summary>
public void CreateIRDMenuItemClick(object sender, RoutedEventArgs e) =>
ShowCreateIRDWindow();
/// <summary>
/// Handler for CheckForUpdatesMenuItem Click event
/// </summary>
@@ -349,9 +479,17 @@ namespace MPF.UI.Core.Windows
/// <summary>
/// Handler for CopyProtectScanButton Click event
/// </summary>
#if NET40
public void CopyProtectScanButtonClick(object sender, RoutedEventArgs e)
#else
public async void CopyProtectScanButtonClick(object sender, RoutedEventArgs e)
#endif
{
#if NET40
var (output, error) = MainViewModel.ScanAndShowProtection();
#else
var (output, error) = await MainViewModel.ScanAndShowProtection();
#endif
if (!MainViewModel.LogPanelExpanded)
{

View File

@@ -5,17 +5,18 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:core="clr-namespace:MPF.UI.Core"
xmlns:coreWindows="clr-namespace:MPF.UI.Core.Windows"
xmlns:viewModels="clr-namespace:MPF.Core.UI.ViewModels;assembly=MPF.Core"
mc:Ignorable="d"
Width="515.132" WindowStyle="None"
WindowStartupLocation="CenterOwner" ResizeMode="CanMinimize" SizeToContent="Height"
BorderBrush="DarkGray" BorderThickness="2">
<Window.DataContext>
<viewModels:OptionsViewModel/>
</Window.DataContext>
<Window.Resources>
<core:ElementConverter x:Key="ElementConverter" />
</Window.Resources>
<WindowChrome.WindowChrome>
<WindowChrome CaptionHeight="0" ResizeBorderThickness="0" />
</WindowChrome.WindowChrome>
<Grid>
<StackPanel Orientation="Vertical">
@@ -30,14 +31,8 @@
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Source="/Images/Icon.ico" Height="20" Width="20" Margin="1" />
<Label Grid.Column="1" Grid.ColumnSpan="4" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" MouseDown="TitleMouseDown">
<Label.Content>
<TextBlock>
<TextBlock.Inlines>
<Run FontWeight="Bold" Text="{Binding Title, Mode=OneWay}"/>
</TextBlock.Inlines>
</TextBlock>
</Label.Content>
<Label Grid.Column="1" Grid.ColumnSpan="4" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" MouseDown="TitleMouseDown"
Content="{Binding Path=Title, Mode=OneWay}" FontWeight="Bold">
<Label.ContextMenu>
<ContextMenu Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
Foreground="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"
@@ -186,7 +181,8 @@
<Label Grid.Row="4" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Right" Content="Default Output Path" />
<TextBox x:Name="DefaultOutputPathTextBox" Grid.Row="4" Grid.Column="1" Height="22" HorizontalAlignment="Stretch"
Text="{Binding Options.DefaultOutputPath}" VerticalContentAlignment="Center" />
Text="{Binding Options.DefaultOutputPath}" VerticalContentAlignment="Center"
ToolTip="Variables allowed:&#x0a; &#37;SYSTEM&#37;&#9;(System name, long)&#x0a; &#37;SYS&#37;&#9;&#9;(System name, short)&#x0a; &#37;MEDIA&#37;&#9;(Media type)&#x0a; &#37;PROGRAM&#37;&#9;(Program name, long)&#x0a; &#37;PROG&#37;&#9;(Program name, short)&#x0a; &#37;LABEL&#37;&#9;(Volume label)&#x0a; &#37;DATE&#37;&#9;(Current date)&#x0a; &#37;DATETIME&#37;&#9;(Current date and time)"/>
<Button x:Name="DefaultOutputPathButton" Grid.Row="4" Grid.Column="2" Height="22" Width="22" Content="..."
Style="{DynamicResource CustomButtonStyle}" />
</Grid>
@@ -195,7 +191,7 @@
<TabItem Header="Dumping" Style="{DynamicResource CustomTabItemStyle}">
<StackPanel>
<GroupBox Margin="5,5,5,5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Header="Dumping">
<UniformGrid Columns="2" Rows="7">
<UniformGrid Columns="2" Rows="8">
<CheckBox VerticalAlignment="Center" Content="Show Separate Window"
IsChecked="{Binding Options.ToolsInSeparateWindow}"
ToolTip="Show program output in separate command window instead of in the log. Enable this if you have a weaker system as there is an increased processing load otherwise." Margin="0,4"
@@ -265,11 +261,16 @@
IsChecked="{Binding Options.DeleteUnnecessaryFiles}"
ToolTip="Delete unnecesary output files to reduce space" Margin="0,4"
/>
<CheckBox VerticalAlignment="Center" Content="Create PS3 IRD After Dumping"
IsChecked="{Binding Options.CreateIRDAfterDumping}" IsEnabled="{Binding CreateIRDSupported}"
ToolTip="Automatically creates an IRD file after dumping a PS3 disc" Margin="0,4"
/>
</UniformGrid>
</GroupBox>
<GroupBox Margin="5,5,5,5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Header="Protection">
<UniformGrid Columns="2" Rows="2">
<UniformGrid Columns="2" Rows="3">
<CheckBox VerticalAlignment="Center" Content="Enable Protection Scan"
IsChecked="{Binding Options.ScanForProtection}"
ToolTip="Enable automatic checking for copy protection on dumped media" Margin="0,4,0,0"
@@ -289,6 +290,11 @@
IsChecked="{Binding Options.IncludeDebugProtectionInformation}"
ToolTip="Include debug information during protection scans" Margin="0,4"
/>
<CheckBox VerticalAlignment="Center" Content="Hide Drive Letters"
IsChecked="{Binding Options.HideDriveLetters}"
ToolTip="Remove drive letters from protection scan output" Margin="0,4"
/>
</UniformGrid>
</GroupBox>
@@ -311,28 +317,28 @@
<Slider x:Name="DumpSpeedCDSlider" Grid.Row="0" Grid.Column="1" Minimum="1" Maximum="72" IsSnapToTickEnabled="True" TickPlacement="BottomRight"
Ticks="{Binding Source={x:Static core:Constants.SpeedsForCDAsCollection}}"
Value="{Binding Options.PreferredDumpSpeedCD}" />
<TextBox x:Name="DumpSpeedCDTextBox" Grid.Row="0" Grid.Column="2" Width="22" Height="22" TextAlignment="Center" IsReadOnly="True" IsReadOnlyCaretVisible="False" VerticalAlignment="Center"
<TextBox x:Name="DumpSpeedCDTextBox" Grid.Row="0" Grid.Column="2" Width="22" Height="22" TextAlignment="Center" IsReadOnly="True" VerticalAlignment="Center"
Text="{Binding ElementName=DumpSpeedCDSlider, Path=Value, UpdateSourceTrigger=PropertyChanged}" Background="LightGray" Foreground="Gray"/>
<Label Grid.Row="1" Grid.Column="0" Content="DVD" />
<Slider x:Name="DumpSpeedDVDSlider" Grid.Row="1" Grid.Column="1" Minimum="1" Maximum="24" IsSnapToTickEnabled="True" TickPlacement="BottomRight"
Ticks="{Binding Source={x:Static core:Constants.SpeedsForDVDAsCollection}}"
Value="{Binding Options.PreferredDumpSpeedDVD}" />
<TextBox x:Name="DumpSpeedDVDTextBox" Grid.Row="1" Grid.Column="2" Width="22" Height="22" TextAlignment="Center" IsReadOnly="True" IsReadOnlyCaretVisible="False" VerticalAlignment="Center"
<TextBox x:Name="DumpSpeedDVDTextBox" Grid.Row="1" Grid.Column="2" Width="22" Height="22" TextAlignment="Center" IsReadOnly="True" VerticalAlignment="Center"
Text="{Binding ElementName=DumpSpeedDVDSlider, Path=Value, UpdateSourceTrigger=PropertyChanged}" Background="LightGray" Foreground="Gray"/>
<Label Grid.Row="2" Grid.Column="0" Content="HD-DVD" />
<Slider x:Name="DumpSpeedHDDVDSlider" Grid.Row="2" Grid.Column="1" Minimum="1" Maximum="24" IsSnapToTickEnabled="True" TickPlacement="BottomRight"
Ticks="{Binding Source={x:Static core:Constants.SpeedsForHDDVDAsCollection}}"
Value="{Binding Options.PreferredDumpSpeedHDDVD}" />
<TextBox x:Name="DumpSpeedHDDVDTextBox" Grid.Row="2" Grid.Column="2" Width="22" Height="22" TextAlignment="Center" IsReadOnly="True" IsReadOnlyCaretVisible="False" VerticalAlignment="Center"
<TextBox x:Name="DumpSpeedHDDVDTextBox" Grid.Row="2" Grid.Column="2" Width="22" Height="22" TextAlignment="Center" IsReadOnly="True" VerticalAlignment="Center"
Text="{Binding ElementName=DumpSpeedHDDVDSlider, Path=Value, UpdateSourceTrigger=PropertyChanged}" Background="LightGray" Foreground="Gray"/>
<Label Grid.Row="3" Grid.Column="0" Content="BD" />
<Slider x:Name="DumpSpeedBDSlider" Grid.Row="3" Grid.Column="1" Minimum="1" Maximum="16" IsSnapToTickEnabled="True" TickPlacement="BottomRight"
Ticks="{Binding Source={x:Static core:Constants.SpeedsForBDAsCollection}}"
Value="{Binding Options.PreferredDumpSpeedBD}" />
<TextBox x:Name="DumpSpeedBDTextBox" Grid.Row="3" Grid.Column="2" Width="22" Height="22" TextAlignment="Center" IsReadOnly="True" IsReadOnlyCaretVisible="False" VerticalAlignment="Center"
<TextBox x:Name="DumpSpeedBDTextBox" Grid.Row="3" Grid.Column="2" Width="22" Height="22" TextAlignment="Center" IsReadOnly="True" VerticalAlignment="Center"
Text="{Binding ElementName=DumpSpeedBDSlider, Path=Value, UpdateSourceTrigger=PropertyChanged}" Background="LightGray" Foreground="Gray"/>
</Grid>
</GroupBox>
@@ -469,7 +475,10 @@
<Label>
<Label.Content>
<TextBlock TextWrapping="Wrap"><Bold Foreground="Red">WARNING:</Bold> If you choose to enable validation and information retrieval, you are responsible for ensuring that all data populated matches your actual media. Some information may be marked to check for validity as a reminder, but all information should be subject to the same scrutiny.</TextBlock>
<TextBlock TextWrapping="Wrap">
<Run FontWeight="Bold" Foreground="Red" Text="WARNING:" />
<Run Text="If you choose to enable validation and information retrieval, you are responsible for ensuring that all data populated matches your actual media. Some information may be marked to check for validity as a reminder, but all information should be subject to the same scrutiny." />
</TextBlock>
</Label.Content>
</Label>
</StackPanel>

View File

@@ -2,11 +2,14 @@
using System.IO;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Forms;
using MPF.Core.Data;
using MPF.Core.UI.ViewModels;
using WPFCustomMessageBox;
#pragma warning disable IDE1006 // Naming Styles
namespace MPF.UI.Core.Windows
{
/// <summary>
@@ -19,26 +22,75 @@ namespace MPF.UI.Core.Windows
/// </summary>
public OptionsViewModel OptionsViewModel => DataContext as OptionsViewModel ?? new OptionsViewModel(new Options());
#if NET35
private System.Windows.Controls.Button? _AaruPathButton => ItemHelper.FindChild<System.Windows.Controls.Button>(this, "AaruPathButton");
private System.Windows.Controls.Button? _AcceptButton => ItemHelper.FindChild<System.Windows.Controls.Button>(this, "AcceptButton");
private System.Windows.Controls.Button? _CancelButton => ItemHelper.FindChild<System.Windows.Controls.Button>(this, "CancelButton");
private System.Windows.Controls.Button? _DefaultOutputPathButton => ItemHelper.FindChild<System.Windows.Controls.Button>(this, "DefaultOutputPathButton");
private System.Windows.Controls.Button? _DiscImageCreatorPathButton => ItemHelper.FindChild<System.Windows.Controls.Button>(this, "DiscImageCreatorPathButton");
private System.Windows.Controls.Button? _RedumperPathButton => ItemHelper.FindChild<System.Windows.Controls.Button>(this, "RedumperPathButton");
private System.Windows.Controls.Button? _RedumpLoginTestButton => ItemHelper.FindChild<System.Windows.Controls.Button>(this, "RedumpLoginTestButton");
private PasswordBox? _RedumpPasswordBox => ItemHelper.FindChild<PasswordBox>(this, "RedumpPasswordBox");
private System.Windows.Controls.TextBox? _RedumpUsernameTextBox => ItemHelper.FindChild<System.Windows.Controls.TextBox>(this, "RedumpUsernameTextBox");
#endif
/// <summary>
/// Constructor
/// </summary>
public OptionsWindow(Options options)
{
#if NET40_OR_GREATER || NETCOREAPP
InitializeComponent();
#endif
#if NET40_OR_GREATER || NETCOREAPP
DumpSpeedCDTextBox.IsReadOnlyCaretVisible = false;
DumpSpeedDVDTextBox.IsReadOnlyCaretVisible = false;
DumpSpeedHDDVDTextBox.IsReadOnlyCaretVisible = false;
DumpSpeedBDTextBox.IsReadOnlyCaretVisible = false;
#endif
#if NET452_OR_GREATER || NETCOREAPP
var chrome = new System.Windows.Shell.WindowChrome
{
CaptionHeight = 0,
ResizeBorderThickness = new Thickness(0),
};
System.Windows.Shell.WindowChrome.SetWindowChrome(this, chrome);
#endif
DataContext = new OptionsViewModel(options);
// Set initial value for binding
#if NET35
_RedumpPasswordBox!.Password = options.RedumpPassword;
#else
RedumpPasswordBox.Password = options.RedumpPassword;
#endif
// Add handlers
#if NET35
_AaruPathButton!.Click += BrowseForPathClick;
_DiscImageCreatorPathButton!.Click += BrowseForPathClick;
_RedumperPathButton!.Click += BrowseForPathClick;
_DefaultOutputPathButton!.Click += BrowseForPathClick;
_AcceptButton!.Click += OnAcceptClick;
_CancelButton!.Click += OnCancelClick;
_RedumpPasswordBox!.PasswordChanged += OnPasswordChanged;
_RedumpLoginTestButton!.Click += OnRedumpTestClick;
#else
AaruPathButton.Click += BrowseForPathClick;
DiscImageCreatorPathButton.Click += BrowseForPathClick;
RedumperPathButton.Click += BrowseForPathClick;
DefaultOutputPathButton.Click += BrowseForPathClick;
AcceptButton.Click += OnAcceptClick;
CancelButton.Click += OnCancelClick;
RedumpPasswordBox.PasswordChanged += OnPasswordChanged;
RedumpLoginTestButton.Click += OnRedumpTestClick;
#endif
}
/// <summary>
@@ -57,22 +109,14 @@ namespace MPF.UI.Core.Windows
/// <summary>
/// Browse and set a path based on the invoking button
/// </summary>
#if NET48
private void BrowseForPath(Window parent, System.Windows.Controls.Button button)
#else
private void BrowseForPath(Window parent, System.Windows.Controls.Button? button)
#endif
{
// If the button is null, we can't do anything
if (button == null)
return;
// Strips button prefix to obtain the setting name
#if NET48
string pathSettingName = button.Name.Substring(0, button.Name.IndexOf("Button"));
#else
string pathSettingName = button.Name[..button.Name.IndexOf("Button")];
#endif
// TODO: hack for now, then we'll see
bool shouldBrowseForPath = pathSettingName == "DefaultOutputPath";
@@ -129,30 +173,18 @@ namespace MPF.UI.Core.Windows
/// </summary>
/// <param name="name">Setting name to find</param>
/// <returns>TextBox for that setting</returns>
#if NET48
private static System.Windows.Controls.TextBox TextBoxForPathSetting(Window parent, string name) =>
#else
private static System.Windows.Controls.TextBox? TextBoxForPathSetting(Window parent, string name) =>
#endif
parent.FindName(name + "TextBox") as System.Windows.Controls.TextBox;
/// <summary>
/// Create an open folder dialog box
/// </summary>
#if NET48
private static FolderBrowserDialog CreateFolderBrowserDialog() => new FolderBrowserDialog();
#else
private static FolderBrowserDialog CreateFolderBrowserDialog() => new();
#endif
/// <summary>
/// Create an open file dialog box
/// </summary>
#if NET48
private static OpenFileDialog CreateOpenFileDialog(string initialDirectory)
#else
private static OpenFileDialog CreateOpenFileDialog(string? initialDirectory)
#endif
{
return new OpenFileDialog()
{
@@ -166,14 +198,10 @@ namespace MPF.UI.Core.Windows
/// <summary>
/// Test Redump credentials for validity
/// </summary>
#if NET48
private void ValidateRedumpCredentials()
#else
private async Task ValidateRedumpCredentials()
#endif
{
#if NET48
(bool? success, string message) = OptionsViewModel.TestRedumpLogin(RedumpUsernameTextBox.Text, RedumpPasswordBox.Password);
#if NET35
(bool? success, string? message) = await OptionsViewModel.TestRedumpLogin(_RedumpUsernameTextBox!.Text, _RedumpPasswordBox!.Password);
#else
(bool? success, string? message) = await OptionsViewModel.TestRedumpLogin(RedumpUsernameTextBox.Text, RedumpPasswordBox.Password);
#endif
@@ -219,18 +247,26 @@ namespace MPF.UI.Core.Windows
/// </summary>
private void OnPasswordChanged(object sender, EventArgs e)
{
#if NET35
OptionsViewModel.Options.RedumpPassword = _RedumpPasswordBox!.Password;
#else
OptionsViewModel.Options.RedumpPassword = RedumpPasswordBox.Password;
#endif
}
/// <summary>
/// Test Redump credentials for validity
/// </summary>
#if NET48
private void OnRedumpTestClick(object sender, EventArgs e) => ValidateRedumpCredentials();
#if NET40
private void OnRedumpTestClick(object sender, EventArgs e)
{
var validateTask = ValidateRedumpCredentials();
validateTask.Wait();
}
#else
private async void OnRedumpTestClick(object sender, EventArgs e) => await ValidateRedumpCredentials();
#endif
#endregion
#endregion
}
}

View File

@@ -10,10 +10,6 @@
WindowStartupLocation="CenterOwner" ResizeMode="CanMinimize" SizeToContent="Height"
BorderBrush="DarkGray" BorderThickness="2">
<WindowChrome.WindowChrome>
<WindowChrome CaptionHeight="0" ResizeBorderThickness="0" />
</WindowChrome.WindowChrome>
<Grid>
<StackPanel Orientation="Vertical">
<Grid Margin="0,10,0,0">
@@ -29,7 +25,7 @@
<Image Grid.Column="0" Source="/Images/Icon.ico" Height="20" Width="20" Margin="1" />
<Label Grid.Column="1" Grid.ColumnSpan="4" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" MouseDown="TitleMouseDown">
<Label.Content>
<TextBlock TextAlignment="Center"><Bold>Ring Code Guide</Bold></TextBlock>
<Run FontWeight="Bold" BaselineAlignment="Center" Text="Ring Code Guide" />
</Label.Content>
<Label.ContextMenu>
<ContextMenu Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
@@ -85,22 +81,34 @@
RenderOptions.BitmapScalingMode="HighQuality" />
<Label Grid.Row="1">
<Label.Content>
<TextBlock><Bold Foreground="Red">1. Mastering Ring:</Bold> Sony DADC&lt;tab&gt;A0100368905-0101&lt;tab&gt;13</TextBlock>
<TextBlock>
<Run FontWeight="Bold" Foreground="Red" Text="1. Mastering Ring:" />
<Run Text=" Sony DADC&lt;tab&gt;A0100368905-0101&lt;tab&gt;13" />
</TextBlock>
</Label.Content>
</Label>
<Label Grid.Row="2">
<Label.Content>
<TextBlock><Bold Foreground="Green">2. Mastering SID:</Bold> IFPI L553</TextBlock>
<TextBlock>
<Run FontWeight="Bold" Foreground="Green" Text="2. Mastering SID:" />
<Run Text=" IFPI L553" />
</TextBlock>
</Label.Content>
</Label>
<Label Grid.Row="3">
<Label.Content>
<TextBlock><Bold Foreground="Purple">3. Toolstamp/Mastering Code:</Bold> A2</TextBlock>
<TextBlock>
<Run FontWeight="Bold" Foreground="Purple" Text="3. Toolstamp/Mastering Code:" />
<Run Text=" A2" />
</TextBlock>
</Label.Content>
</Label>
<Label Grid.Row="4">
<Label.Content>
<TextBlock><Bold Foreground="LightBlue">4. Mould SID:</Bold> IFPI 94V1</TextBlock>
<TextBlock>
<Run FontWeight="Bold" Foreground="LightBlue" Text="4. Mould SID:" />
<Run Text=" IFPI 94V1" />
</TextBlock>
</Label.Content>
</Label>
</Grid>
@@ -126,27 +134,42 @@
RenderOptions.BitmapScalingMode="HighQuality" />
<Label Grid.Row="1">
<Label.Content>
<TextBlock><Bold Foreground="Red">1. Outer Mastering Ring:</Bold> IM01501A-L1&lt;tab&gt;03 +&lt;tab&gt;+</TextBlock>
<TextBlock>
<Run FontWeight="Bold" Foreground="Red" Text="1. Outer Mastering Ring:" />
<Run Text=" IM01501A-L1&lt;tab&gt;03 +&lt;tab&gt;+" />
</TextBlock>
</Label.Content>
</Label>
<Label Grid.Row="2">
<Label.Content>
<TextBlock><Bold Foreground="Green">2. Inner Mastering Ring:</Bold> IM01501A-L0&lt;tab&gt;01 +&lt;tab&gt;+</TextBlock>
<TextBlock>
<Run FontWeight="Bold" Foreground="Green" Text="2. Inner Mastering Ring:" />
<Run Text=" IM01501A-L0&lt;tab&gt;01 +&lt;tab&gt;+" />
</TextBlock>
</Label.Content>
</Label>
<Label Grid.Row="3">
<Label.Content>
<TextBlock><Bold Foreground="Purple">3. Outer Mastering SID:</Bold> IFPI LB48</TextBlock>
<TextBlock>
<Run FontWeight="Bold" Foreground="Purple" Text="3. Outer Mastering SID:" />
<Run Text=" IFPI LB48" />
</TextBlock>
</Label.Content>
</Label>
<Label Grid.Row="4">
<Label.Content>
<TextBlock><Bold Foreground="LightBlue">4. Inner Mastering SID:</Bold> IFPI LB48</TextBlock>
<TextBlock>
<Run FontWeight="Bold" Foreground="LightBlue" Text="4. Inner Mastering SID:" />
<Run Text=" IFPI LB48" />
</TextBlock>
</Label.Content>
</Label>
<Label Grid.Row="5">
<Label.Content>
<TextBlock><Bold>Note:</Bold> See the 1-Layer guide for more details on additional fields</TextBlock>
<TextBlock>
<Run FontWeight="Bold" Text="Note:" />
<Run Text=" See the 1-Layer guide for more details on additional fields" />
</TextBlock>
</Label.Content>
</Label>
</Grid>

View File

@@ -5,6 +5,20 @@
/// </summary>
public partial class RingCodeGuideWindow : WindowBase
{
public RingCodeGuideWindow() => InitializeComponent();
public RingCodeGuideWindow()
{
#if NET40_OR_GREATER || NETCOREAPP
InitializeComponent();
#endif
#if NET452_OR_GREATER || NETCOREAPP
var chrome = new System.Windows.Shell.WindowChrome
{
CaptionHeight = 0,
ResizeBorderThickness = new System.Windows.Thickness(0),
};
System.Windows.Shell.WindowChrome.SetWindowChrome(this, chrome);
#endif
}
}
}

20
MPF.sln
View File

@@ -13,11 +13,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ProjectSection(SolutionItems) = preProject
appveyor.yml = appveyor.yml
CHANGELIST.md = CHANGELIST.md
.github\ISSUE_TEMPLATE\feature-request.md = .github\ISSUE_TEMPLATE\feature-request.md
.github\ISSUE_TEMPLATE\informational.md = .github\ISSUE_TEMPLATE\informational.md
.github\ISSUE_TEMPLATE\issue-report.md = .github\ISSUE_TEMPLATE\issue-report.md
publish-nix.sh = publish-nix.sh
publish-win.bat = publish-win.bat
publish-win.ps1 = publish-win.ps1
README.md = README.md
EndProjectSection
EndProject
@@ -27,6 +24,19 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MPF.UI.Core", "MPF.UI.Core\
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MPF", "MPF", "{4160167D-681D-480B-ABC6-06AC869E5769}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ISSUE_TEMPLATE", "ISSUE_TEMPLATE", "{ADC5FDD7-F43F-4F9C-B222-19AA1D64D3D4}"
ProjectSection(SolutionItems) = preProject
.github\ISSUE_TEMPLATE\feature-request.md = .github\ISSUE_TEMPLATE\feature-request.md
.github\ISSUE_TEMPLATE\informational.md = .github\ISSUE_TEMPLATE\informational.md
.github\ISSUE_TEMPLATE\issue-report.md = .github\ISSUE_TEMPLATE\issue-report.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{440776E0-E6E2-46C6-8E85-09E64A8FC7D6}"
ProjectSection(SolutionItems) = preProject
.github\workflows\build_check.yml = .github\workflows\build_check.yml
.github\workflows\build_ui.yml = .github\workflows\build_ui.yml
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -63,6 +73,8 @@ Global
{8CFDE289-E171-4D49-A40D-5293265C1253} = {4160167D-681D-480B-ABC6-06AC869E5769}
{70B1265D-FE49-472A-A83D-0B462152D37A} = {4160167D-681D-480B-ABC6-06AC869E5769}
{EA3768DB-694A-4653-82E4-9FF71B8963F3} = {4160167D-681D-480B-ABC6-06AC869E5769}
{ADC5FDD7-F43F-4F9C-B222-19AA1D64D3D4} = {4D1DCF5A-F0B0-4E81-A05B-F1A7D37C9D9D}
{440776E0-E6E2-46C6-8E85-09E64A8FC7D6} = {4D1DCF5A-F0B0-4E81-A05B-F1A7D37C9D9D}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {73C62E6A-6584-4D93-83B5-ECB1FBDB469B}

View File

@@ -1,7 +1,6 @@
<Application
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
xmlns:windows="clr-namespace:MPF.UI.Core.Windows;assembly=MPF.UI.Core"
x:Class="MPF.App">
<Application.MainWindow>
@@ -205,140 +204,7 @@
</Setter.Value>
</Setter>
</Style>
<ControlTemplate x:Key="ComboBoxTemplate" TargetType="{x:Type ComboBox}">
<Grid x:Name="templateRoot" SnapsToDevicePixels="true">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition MinWidth="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}" Width="0"/>
</Grid.ColumnDefinitions>
<Popup x:Name="PART_Popup" AllowsTransparency="true" Grid.ColumnSpan="2" IsOpen="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Margin="1" PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}" Placement="Bottom">
<Themes:SystemDropShadowChrome x:Name="shadow" Color="Transparent" MaxHeight="{TemplateBinding MaxDropDownHeight}" MinWidth="{Binding ActualWidth, ElementName=templateRoot}">
<Border x:Name="dropDownBorder" BorderBrush="{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}">
<ScrollViewer x:Name="DropDownScrollViewer">
<Grid x:Name="grid" RenderOptions.ClearTypeHint="Enabled">
<Canvas x:Name="canvas" HorizontalAlignment="Left" Height="0" VerticalAlignment="Top" Width="0">
<Rectangle x:Name="opaqueRect" Fill="{Binding Background, ElementName=dropDownBorder}" Height="{Binding ActualHeight, ElementName=dropDownBorder}" Width="{Binding ActualWidth, ElementName=dropDownBorder}"/>
</Canvas>
<ItemsPresenter x:Name="ItemsPresenter" KeyboardNavigation.DirectionalNavigation="Contained" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Grid>
</ScrollViewer>
</Border>
</Themes:SystemDropShadowChrome>
</Popup>
<ToggleButton x:Name="toggleButton" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.ColumnSpan="2" IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Style="{DynamicResource ComboBoxToggleButton}"/>
<ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}" ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}" Content="{TemplateBinding SelectionBoxItem}" ContentStringFormat="{TemplateBinding SelectionBoxItemStringFormat}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" IsHitTestVisible="false" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="HasDropShadow" SourceName="PART_Popup" Value="true">
<Setter Property="Margin" TargetName="shadow" Value="0,0,5,5"/>
<Setter Property="Color" TargetName="shadow" Value="#71000000"/>
</Trigger>
<Trigger Property="HasItems" Value="false">
<Setter Property="Height" TargetName="dropDownBorder" Value="95"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsGrouping" Value="true"/>
<Condition Property="VirtualizingPanel.IsVirtualizingWhenGrouping" Value="false"/>
</MultiTrigger.Conditions>
<Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
</MultiTrigger>
<Trigger Property="ScrollViewer.CanContentScroll" SourceName="DropDownScrollViewer" Value="false">
<Setter Property="Canvas.Top" TargetName="opaqueRect" Value="{Binding VerticalOffset, ElementName=DropDownScrollViewer}"/>
<Setter Property="Canvas.Left" TargetName="opaqueRect" Value="{Binding HorizontalOffset, ElementName=DropDownScrollViewer}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<SolidColorBrush x:Key="TextBox.Static.Background" Color="#FFFFFFFF"/>
<Style x:Key="ComboBoxEditableTextBox" TargetType="{x:Type TextBox}">
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="AllowDrop" Value="true"/>
<Setter Property="MinWidth" Value="0"/>
<Setter Property="MinHeight" Value="0"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/>
<Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<ScrollViewer x:Name="PART_ContentHost" Background="Transparent" Focusable="false" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ControlTemplate x:Key="ComboBoxEditableTemplate" TargetType="{x:Type ComboBox}">
<Grid x:Name="templateRoot" SnapsToDevicePixels="true">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition MinWidth="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}" Width="0"/>
</Grid.ColumnDefinitions>
<Popup x:Name="PART_Popup" AllowsTransparency="true" Grid.ColumnSpan="2" IsOpen="{Binding IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}}" PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}" Placement="Bottom">
<Themes:SystemDropShadowChrome x:Name="shadow" Color="Transparent" MaxHeight="{TemplateBinding MaxDropDownHeight}" MinWidth="{Binding ActualWidth, ElementName=templateRoot}">
<Border x:Name="dropDownBorder" BorderBrush="{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}">
<ScrollViewer x:Name="DropDownScrollViewer">
<Grid x:Name="grid" RenderOptions.ClearTypeHint="Enabled">
<Canvas x:Name="canvas" HorizontalAlignment="Left" Height="0" VerticalAlignment="Top" Width="0">
<Rectangle x:Name="opaqueRect" Fill="{Binding Background, ElementName=dropDownBorder}" Height="{Binding ActualHeight, ElementName=dropDownBorder}" Width="{Binding ActualWidth, ElementName=dropDownBorder}"/>
</Canvas>
<ItemsPresenter x:Name="ItemsPresenter" KeyboardNavigation.DirectionalNavigation="Contained" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Grid>
</ScrollViewer>
</Border>
</Themes:SystemDropShadowChrome>
</Popup>
<ToggleButton x:Name="toggleButton" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.ColumnSpan="2" IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Style="{DynamicResource ComboBoxToggleButton}"/>
<Border x:Name="border" Background="{DynamicResource TextBox.Static.Background}" Margin="{TemplateBinding BorderThickness}">
<TextBox x:Name="PART_EditableTextBox" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" IsReadOnly="{Binding IsReadOnly, RelativeSource={RelativeSource TemplatedParent}}" Margin="{TemplateBinding Padding}" Style="{DynamicResource ComboBoxEditableTextBox}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Opacity" TargetName="border" Value="0.56"/>
</Trigger>
<Trigger Property="IsKeyboardFocusWithin" Value="true">
<Setter Property="Foreground" Value="Black"/>
</Trigger>
<Trigger Property="HasDropShadow" SourceName="PART_Popup" Value="true">
<Setter Property="Margin" TargetName="shadow" Value="0,0,5,5"/>
<Setter Property="Color" TargetName="shadow" Value="#71000000"/>
</Trigger>
<Trigger Property="HasItems" Value="false">
<Setter Property="Height" TargetName="dropDownBorder" Value="95"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsGrouping" Value="true"/>
<Condition Property="VirtualizingPanel.IsVirtualizingWhenGrouping" Value="false"/>
</MultiTrigger.Conditions>
<Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
</MultiTrigger>
<Trigger Property="ScrollViewer.CanContentScroll" SourceName="DropDownScrollViewer" Value="false">
<Setter Property="Canvas.Top" TargetName="opaqueRect" Value="{Binding VerticalOffset, ElementName=DropDownScrollViewer}"/>
<Setter Property="Canvas.Left" TargetName="opaqueRect" Value="{Binding HorizontalOffset, ElementName=DropDownScrollViewer}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style x:Key="CustomComboBoxStyle" TargetType="{x:Type ComboBox}">
<Setter Property="FocusVisualStyle" Value="{DynamicResource FocusVisual}"/>
<Setter Property="Background" Value="{DynamicResource ComboBox.Static.Background}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ComboBox.Static.Border}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="Padding" Value="6,3,5,3"/>
<Setter Property="ScrollViewer.CanContentScroll" Value="true"/>
<Setter Property="ScrollViewer.PanningMode" Value="Both"/>
<Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
<Setter Property="Template" Value="{DynamicResource ComboBoxTemplate}"/>
<Style.Triggers>
<Trigger Property="IsEditable" Value="true">
<Setter Property="IsTabStop" Value="false"/>
<Setter Property="Padding" Value="2"/>
<Setter Property="Template" Value="{DynamicResource ComboBoxEditableTemplate}"/>
</Trigger>
</Style.Triggers>
</Style>
<!-- ContextMenu -->
<SolidColorBrush x:Key="ContextMenu.Static.Border" Color="#FF888888"/>
@@ -384,7 +250,8 @@
<Popup x:Name="PART_Popup" AllowsTransparency="True" Focusable="False" IsOpen="{Binding IsSubmenuOpen, RelativeSource={RelativeSource TemplatedParent}}" PopupAnimation="{DynamicResource {x:Static SystemParameters.MenuPopupAnimationKey}}" Placement="Bottom">
<Border x:Name="SubMenuBorder" BorderBrush="{DynamicResource MenuItem.SubMenu.Border}" BorderThickness="1" Background="{DynamicResource MenuItem.SubMenu.Background}" Padding="2">
<ScrollViewer x:Name="SubMenuScrollViewer" Style="{DynamicResource {ComponentResourceKey ResourceId=MenuScrollViewer, TypeInTargetAssembly={x:Type FrameworkElement}}}">
<Grid RenderOptions.ClearTypeHint="Enabled">
<!--<Grid RenderOptions.ClearTypeHint="Enabled">--> <!-- Not supported in .NET Framework 3.5 -->
<Grid> <!-- Not supported in .NET Framework 3.5 -->
<Canvas HorizontalAlignment="Left" Height="0" VerticalAlignment="Top" Width="0">
<Rectangle x:Name="OpaqueRect" Fill="{Binding Background, ElementName=SubMenuBorder}" Height="{Binding ActualHeight, ElementName=SubMenuBorder}" Width="{Binding ActualWidth, ElementName=SubMenuBorder}"/>
</Canvas>
@@ -426,66 +293,6 @@
<SolidColorBrush x:Key="ProgressBar.Progress" Color="#FF06B025"/>
<SolidColorBrush x:Key="ProgressBar.Background" Color="#FFE6E6E6"/>
<SolidColorBrush x:Key="ProgressBar.Border" Color="#FFBCBCBC"/>
<Style x:Key="CustomProgressBarStyle" TargetType="{x:Type ProgressBar}">
<Setter Property="Foreground" Value="{DynamicResource ProgressBar.Progress}"/>
<Setter Property="Background" Value="{DynamicResource ProgressBar.Background}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ProgressBar.Border}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ProgressBar}">
<Grid x:Name="TemplateRoot">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Determinate"/>
<VisualState x:Name="Indeterminate">
<Storyboard RepeatBehavior="Forever">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)" Storyboard.TargetName="Animation">
<EasingDoubleKeyFrame KeyTime="0" Value="0.25"/>
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="0.25"/>
<EasingDoubleKeyFrame KeyTime="0:0:2" Value="0.25"/>
</DoubleAnimationUsingKeyFrames>
<PointAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransformOrigin)" Storyboard.TargetName="Animation">
<EasingPointKeyFrame KeyTime="0" Value="-0.5,0.5"/>
<EasingPointKeyFrame KeyTime="0:0:1" Value="0.5,0.5"/>
<EasingPointKeyFrame KeyTime="0:0:2" Value="1.5,0.5"/>
</PointAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}"/>
<Rectangle x:Name="PART_Track"/>
<Grid x:Name="PART_Indicator" ClipToBounds="true" HorizontalAlignment="Left">
<Rectangle x:Name="Indicator" Fill="{TemplateBinding Foreground}"/>
<Rectangle x:Name="Animation" Fill="{TemplateBinding Foreground}" RenderTransformOrigin="0.5,0.5">
<Rectangle.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
</Grid>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="Orientation" Value="Vertical">
<Setter Property="LayoutTransform" TargetName="TemplateRoot">
<Setter.Value>
<RotateTransform Angle="-90"/>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsIndeterminate" Value="true">
<Setter Property="Visibility" TargetName="Indicator" Value="Collapsed"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- ScrollViewer -->
<SolidColorBrush x:Key="ScrollViewer.ScrollBar.Background" Color="LightGray"/>

View File

@@ -1,7 +1,496 @@
namespace MPF
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
namespace MPF
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : System.Windows.Application { }
public partial class App : Application
{
#if NET35_OR_GREATER || NETCOREAPP
#region ControlTemplates
/// <summary>
/// ComboBoxTemplate ControlTemplate XAML (.NET Framework 4.0 and above)
/// </summary>
private const string _comboBoxTemplateDefault = @"<ControlTemplate TargetType=""{x:Type ComboBox}"">
<Grid x:Name=""templateRoot"" SnapsToDevicePixels=""true"">
<Grid.ColumnDefinitions>
<ColumnDefinition Width=""*""/>
<ColumnDefinition MinWidth=""{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}"" Width=""0""/>
</Grid.ColumnDefinitions>
<Popup x:Name=""PART_Popup"" AllowsTransparency=""true"" Grid.ColumnSpan=""2"" IsOpen=""{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"" Margin=""1"" PopupAnimation=""{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}"" Placement=""Bottom"">
<themes:SystemDropShadowChrome x:Name=""shadow"" Color=""Transparent"" MaxHeight=""{TemplateBinding MaxDropDownHeight}"" MinWidth=""{Binding ActualWidth, ElementName=templateRoot}"">
<Border x:Name=""dropDownBorder"" BorderBrush=""{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}"" BorderThickness=""1"" Background=""{DynamicResource {x:Static SystemColors.WindowBrushKey}}"">
<ScrollViewer x:Name=""DropDownScrollViewer"">
<Grid x:Name=""grid"" RenderOptions.ClearTypeHint=""Enabled"">
<Canvas x:Name=""canvas"" HorizontalAlignment=""Left"" Height=""0"" VerticalAlignment=""Top"" Width=""0"">
<Rectangle x:Name=""opaqueRect"" Fill=""{Binding Background, ElementName=dropDownBorder}"" Height=""{Binding ActualHeight, ElementName=dropDownBorder}"" Width=""{Binding ActualWidth, ElementName=dropDownBorder}""/>
</Canvas>
<ItemsPresenter x:Name=""ItemsPresenter"" KeyboardNavigation.DirectionalNavigation=""Contained"" SnapsToDevicePixels=""{TemplateBinding SnapsToDevicePixels}""/>
</Grid>
</ScrollViewer>
</Border>
</themes:SystemDropShadowChrome>
</Popup>
<ToggleButton x:Name=""toggleButton"" BorderBrush=""{TemplateBinding BorderBrush}"" BorderThickness=""{TemplateBinding BorderThickness}"" Background=""{TemplateBinding Background}"" Grid.ColumnSpan=""2"" IsChecked=""{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"" Style=""{DynamicResource ComboBoxToggleButton}""/>
<ContentPresenter x:Name=""contentPresenter"" ContentTemplate=""{TemplateBinding SelectionBoxItemTemplate}"" ContentTemplateSelector=""{TemplateBinding ItemTemplateSelector}"" Content=""{TemplateBinding SelectionBoxItem}"" ContentStringFormat=""{TemplateBinding SelectionBoxItemStringFormat}"" HorizontalAlignment=""{TemplateBinding HorizontalContentAlignment}"" IsHitTestVisible=""false"" Margin=""{TemplateBinding Padding}"" SnapsToDevicePixels=""{TemplateBinding SnapsToDevicePixels}"" VerticalAlignment=""{TemplateBinding VerticalContentAlignment}""/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property=""HasDropShadow"" SourceName=""PART_Popup"" Value=""true"">
<Setter Property=""Margin"" TargetName=""shadow"" Value=""0,0,5,5""/>
<Setter Property=""Color"" TargetName=""shadow"" Value=""#71000000""/>
</Trigger>
<Trigger Property=""HasItems"" Value=""false"">
<Setter Property=""Height"" TargetName=""dropDownBorder"" Value=""95""/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property=""IsGrouping"" Value=""true""/>
<Condition Property=""VirtualizingPanel.IsVirtualizingWhenGrouping"" Value=""false""/>
</MultiTrigger.Conditions>
<Setter Property=""ScrollViewer.CanContentScroll"" Value=""false""/>
</MultiTrigger>
<Trigger Property=""ScrollViewer.CanContentScroll"" SourceName=""DropDownScrollViewer"" Value=""false"">
<Setter Property=""Canvas.Top"" TargetName=""opaqueRect"" Value=""{Binding VerticalOffset, ElementName=DropDownScrollViewer}""/>
<Setter Property=""Canvas.Left"" TargetName=""opaqueRect"" Value=""{Binding HorizontalOffset, ElementName=DropDownScrollViewer}""/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>";
/// <summary>
/// ComboBoxTemplate ControlTemplate XAML (.NET Framework 3.5)
/// </summary>
private const string _comboBoxTemplateNet35 = @"<ControlTemplate TargetType=""{x:Type ComboBox}"">
<Grid x:Name=""templateRoot"" SnapsToDevicePixels=""true"">
<Grid.ColumnDefinitions>
<ColumnDefinition Width=""*""/>
<ColumnDefinition MinWidth=""{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}"" Width=""0""/>
</Grid.ColumnDefinitions>
<Popup x:Name=""PART_Popup"" AllowsTransparency=""true"" Grid.ColumnSpan=""2"" IsOpen=""{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"" Margin=""1"" PopupAnimation=""{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}"" Placement=""Bottom"">
<Border x:Name=""dropDownBorder"" BorderBrush=""{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}"" BorderThickness=""1"" Background=""{DynamicResource {x:Static SystemColors.WindowBrushKey}}"">
<ScrollViewer x:Name=""DropDownScrollViewer"">
<Grid x:Name=""grid"">
<Canvas x:Name=""canvas"" HorizontalAlignment=""Left"" Height=""0"" VerticalAlignment=""Top"" Width=""0"">
<Rectangle x:Name=""opaqueRect"" Fill=""{Binding Background, ElementName=dropDownBorder}"" Height=""{Binding ActualHeight, ElementName=dropDownBorder}"" Width=""{Binding ActualWidth, ElementName=dropDownBorder}""/>
</Canvas>
<ItemsPresenter x:Name=""ItemsPresenter"" KeyboardNavigation.DirectionalNavigation=""Contained"" SnapsToDevicePixels=""{TemplateBinding SnapsToDevicePixels}""/>
</Grid>
</ScrollViewer>
</Border>
</Popup>
<ToggleButton x:Name=""toggleButton"" BorderBrush=""{TemplateBinding BorderBrush}"" BorderThickness=""{TemplateBinding BorderThickness}"" Background=""{TemplateBinding Background}"" Grid.ColumnSpan=""2"" IsChecked=""{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"" Style=""{DynamicResource ComboBoxToggleButton}""/>
<ContentPresenter x:Name=""contentPresenter"" ContentTemplate=""{TemplateBinding SelectionBoxItemTemplate}"" ContentTemplateSelector=""{TemplateBinding ItemTemplateSelector}"" Content=""{TemplateBinding SelectionBoxItem}"" ContentStringFormat=""{TemplateBinding SelectionBoxItemStringFormat}"" HorizontalAlignment=""{TemplateBinding HorizontalContentAlignment}"" IsHitTestVisible=""false"" Margin=""{TemplateBinding Padding}"" SnapsToDevicePixels=""{TemplateBinding SnapsToDevicePixels}"" VerticalAlignment=""{TemplateBinding VerticalContentAlignment}""/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property=""HasItems"" Value=""false"">
<Setter Property=""Height"" TargetName=""dropDownBorder"" Value=""95""/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property=""IsGrouping"" Value=""true""/>
</MultiTrigger.Conditions>
<Setter Property=""ScrollViewer.CanContentScroll"" Value=""false""/>
</MultiTrigger>
<Trigger Property=""ScrollViewer.CanContentScroll"" SourceName=""DropDownScrollViewer"" Value=""false"">
<Setter Property=""Canvas.Top"" TargetName=""opaqueRect"" Value=""{Binding VerticalOffset, ElementName=DropDownScrollViewer}""/>
<Setter Property=""Canvas.Left"" TargetName=""opaqueRect"" Value=""{Binding HorizontalOffset, ElementName=DropDownScrollViewer}""/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>";
/// <summary>
/// ComboBoxEditableTemplate ControlTemplate XAML (.NET Framework 4.0 and above)
/// </summary>
private const string _comboBoxEditableTemplateDefault = @"<ControlTemplate TargetType=""{x:Type ComboBox}"">
<Grid x:Name=""templateRoot"" SnapsToDevicePixels=""true"">
<Grid.ColumnDefinitions>
<ColumnDefinition Width=""*""/>
<ColumnDefinition MinWidth=""{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}"" Width=""0""/>
</Grid.ColumnDefinitions>
<Popup x:Name=""PART_Popup"" AllowsTransparency=""true"" Grid.ColumnSpan=""2"" IsOpen=""{Binding IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}}"" PopupAnimation=""{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}"" Placement=""Bottom"">
<themes:SystemDropShadowChrome x:Name=""shadow"" Color=""Transparent"" MaxHeight=""{TemplateBinding MaxDropDownHeight}"" MinWidth=""{Binding ActualWidth, ElementName=templateRoot}"">
<Border x:Name=""dropDownBorder"" BorderBrush=""{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}"" BorderThickness=""1"" Background=""{DynamicResource {x:Static SystemColors.WindowBrushKey}}"">
<ScrollViewer x:Name=""DropDownScrollViewer"">
<Grid x:Name=""grid"" RenderOptions.ClearTypeHint=""Enabled"">
<Canvas x:Name=""canvas"" HorizontalAlignment=""Left"" Height=""0"" VerticalAlignment=""Top"" Width=""0"">
<Rectangle x:Name=""opaqueRect"" Fill=""{Binding Background, ElementName=dropDownBorder}"" Height=""{Binding ActualHeight, ElementName=dropDownBorder}"" Width=""{Binding ActualWidth, ElementName=dropDownBorder}""/>
</Canvas>
<ItemsPresenter x:Name=""ItemsPresenter"" KeyboardNavigation.DirectionalNavigation=""Contained"" SnapsToDevicePixels=""{TemplateBinding SnapsToDevicePixels}""/>
</Grid>
</ScrollViewer>
</Border>
</themes:SystemDropShadowChrome>
</Popup>
<ToggleButton x:Name=""toggleButton"" BorderBrush=""{TemplateBinding BorderBrush}"" BorderThickness=""{TemplateBinding BorderThickness}"" Background=""{TemplateBinding Background}"" Grid.ColumnSpan=""2"" IsChecked=""{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"" Style=""{DynamicResource ComboBoxToggleButton}""/>
<Border x:Name=""border"" Background=""{DynamicResource TextBox.Static.Background}"" Margin=""{TemplateBinding BorderThickness}"">
<TextBox x:Name=""PART_EditableTextBox"" HorizontalContentAlignment=""{TemplateBinding HorizontalContentAlignment}"" IsReadOnly=""{Binding IsReadOnly, RelativeSource={RelativeSource TemplatedParent}}"" Margin=""{TemplateBinding Padding}"" Style=""{DynamicResource ComboBoxEditableTextBox}"" VerticalContentAlignment=""{TemplateBinding VerticalContentAlignment}""/>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property=""IsEnabled"" Value=""false"">
<Setter Property=""Opacity"" TargetName=""border"" Value=""0.56""/>
</Trigger>
<Trigger Property=""IsKeyboardFocusWithin"" Value=""true"">
<Setter Property=""Foreground"" Value=""Black""/>
</Trigger>
<Trigger Property=""HasDropShadow"" SourceName=""PART_Popup"" Value=""true"">
<Setter Property=""Margin"" TargetName=""shadow"" Value=""0,0,5,5""/>
<Setter Property=""Color"" TargetName=""shadow"" Value=""#71000000""/>
</Trigger>
<Trigger Property=""HasItems"" Value=""false"">
<Setter Property=""Height"" TargetName=""dropDownBorder"" Value=""95""/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property=""IsGrouping"" Value=""true""/>
<Condition Property=""VirtualizingPanel.IsVirtualizingWhenGrouping"" Value=""false""/>
</MultiTrigger.Conditions>
<Setter Property=""ScrollViewer.CanContentScroll"" Value=""false""/>
</MultiTrigger>
<Trigger Property=""ScrollViewer.CanContentScroll"" SourceName=""DropDownScrollViewer"" Value=""false"">
<Setter Property=""Canvas.Top"" TargetName=""opaqueRect"" Value=""{Binding VerticalOffset, ElementName=DropDownScrollViewer}""/>
<Setter Property=""Canvas.Left"" TargetName=""opaqueRect"" Value=""{Binding HorizontalOffset, ElementName=DropDownScrollViewer}""/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>";
/// <summary>
/// ComboBoxEditableTemplate ControlTemplate XAML (.NET Framework 3.5)
/// </summary>
private const string _comboBoxEditableTemplateNet35 = @"<ControlTemplate TargetType=""{x:Type ComboBox}"">
<Grid x:Name=""templateRoot"" SnapsToDevicePixels=""true"">
<Grid.ColumnDefinitions>
<ColumnDefinition Width=""*""/>
<ColumnDefinition MinWidth=""{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}"" Width=""0""/>
</Grid.ColumnDefinitions>
<Popup x:Name=""PART_Popup"" AllowsTransparency=""true"" Grid.ColumnSpan=""2"" IsOpen=""{Binding IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}}"" PopupAnimation=""{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}"" Placement=""Bottom"">
<Border x:Name=""dropDownBorder"" BorderBrush=""{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}"" BorderThickness=""1"" Background=""{DynamicResource {x:Static SystemColors.WindowBrushKey}}"">
<ScrollViewer x:Name=""DropDownScrollViewer"">
<Grid x:Name=""grid"">
<Canvas x:Name=""canvas"" HorizontalAlignment=""Left"" Height=""0"" VerticalAlignment=""Top"" Width=""0"">
<Rectangle x:Name=""opaqueRect"" Fill=""{Binding Background, ElementName=dropDownBorder}"" Height=""{Binding ActualHeight, ElementName=dropDownBorder}"" Width=""{Binding ActualWidth, ElementName=dropDownBorder}""/>
</Canvas>
<ItemsPresenter x:Name=""ItemsPresenter"" KeyboardNavigation.DirectionalNavigation=""Contained"" SnapsToDevicePixels=""{TemplateBinding SnapsToDevicePixels}""/>
</Grid>
</ScrollViewer>
</Border>
</Popup>
<ToggleButton x:Name=""toggleButton"" BorderBrush=""{TemplateBinding BorderBrush}"" BorderThickness=""{TemplateBinding BorderThickness}"" Background=""{TemplateBinding Background}"" Grid.ColumnSpan=""2"" IsChecked=""{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"" Style=""{DynamicResource ComboBoxToggleButton}""/>
<Border x:Name=""border"" Background=""{DynamicResource TextBox.Static.Background}"" Margin=""{TemplateBinding BorderThickness}"">
<TextBox x:Name=""PART_EditableTextBox"" HorizontalContentAlignment=""{TemplateBinding HorizontalContentAlignment}"" IsReadOnly=""{Binding IsReadOnly, RelativeSource={RelativeSource TemplatedParent}}"" Margin=""{TemplateBinding Padding}"" Style=""{DynamicResource ComboBoxEditableTextBox}"" VerticalContentAlignment=""{TemplateBinding VerticalContentAlignment}""/>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property=""IsEnabled"" Value=""false"">
<Setter Property=""Opacity"" TargetName=""border"" Value=""0.56""/>
</Trigger>
<Trigger Property=""IsKeyboardFocusWithin"" Value=""true"">
<Setter Property=""Foreground"" Value=""Black""/>
</Trigger>
<Trigger Property=""HasItems"" Value=""false"">
<Setter Property=""Height"" TargetName=""dropDownBorder"" Value=""95""/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property=""IsGrouping"" Value=""true""/>
</MultiTrigger.Conditions>
<Setter Property=""ScrollViewer.CanContentScroll"" Value=""false""/>
</MultiTrigger>
<Trigger Property=""ScrollViewer.CanContentScroll"" SourceName=""DropDownScrollViewer"" Value=""false"">
<Setter Property=""Canvas.Top"" TargetName=""opaqueRect"" Value=""{Binding VerticalOffset, ElementName=DropDownScrollViewer}""/>
<Setter Property=""Canvas.Left"" TargetName=""opaqueRect"" Value=""{Binding HorizontalOffset, ElementName=DropDownScrollViewer}""/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>";
#endregion
#region Styles
/// <summary>
/// ComboBoxEditableTextBox Style XAML (.NET Framework 4.0 and above)
/// </summary>
private const string _comboBoxEditableTextBoxStyleDefault = @"<Style TargetType=""{x:Type TextBox}"">
<Setter Property=""OverridesDefaultStyle"" Value=""true""/>
<Setter Property=""AllowDrop"" Value=""true""/>
<Setter Property=""MinWidth"" Value=""0""/>
<Setter Property=""MinHeight"" Value=""0""/>
<Setter Property=""FocusVisualStyle"" Value=""{x:Null}""/>
<Setter Property=""ScrollViewer.PanningMode"" Value=""VerticalFirst""/>
<Setter Property=""Stylus.IsFlicksEnabled"" Value=""False""/>
<Setter Property=""Template"">
<Setter.Value>
<ControlTemplate TargetType=""{x:Type TextBox}"">
<ScrollViewer x:Name=""PART_ContentHost"" Background=""Transparent"" Focusable=""false"" HorizontalScrollBarVisibility=""Hidden"" VerticalScrollBarVisibility=""Hidden""/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>";
/// <summary>
/// ComboBoxEditableTextBox Style XAML (.NET Framework 3.5)
/// </summary>
private const string _comboBoxEditableTextBoxStyleNet35 = @"<Style TargetType=""{x:Type TextBox}"">
<Setter Property=""OverridesDefaultStyle"" Value=""true""/>
<Setter Property=""AllowDrop"" Value=""true""/>
<Setter Property=""MinWidth"" Value=""0""/>
<Setter Property=""MinHeight"" Value=""0""/>
<Setter Property=""FocusVisualStyle"" Value=""{x:Null}""/>
<Setter Property=""Stylus.IsFlicksEnabled"" Value=""False""/>
<Setter Property=""Template"">
<Setter.Value>
<ControlTemplate TargetType=""{x:Type TextBox}"">
<ScrollViewer x:Name=""PART_ContentHost"" Background=""Transparent"" Focusable=""false"" HorizontalScrollBarVisibility=""Hidden"" VerticalScrollBarVisibility=""Hidden""/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>";
/// <summary>
/// CustomComboBoxStyle Style XAML (.NET Framework 4.0 and above)
/// </summary>
private const string _customComboBoxStyleDefault = @"<Style x:Key=""CustomComboBoxStyle"" TargetType=""{x:Type ComboBox}"">
<Setter Property=""FocusVisualStyle"" Value=""{DynamicResource FocusVisual}""/>
<Setter Property=""Background"" Value=""{DynamicResource ComboBox.Static.Background}""/>
<Setter Property=""BorderBrush"" Value=""{DynamicResource ComboBox.Static.Border}""/>
<Setter Property=""Foreground"" Value=""{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}""/>
<Setter Property=""BorderThickness"" Value=""1""/>
<Setter Property=""ScrollViewer.HorizontalScrollBarVisibility"" Value=""Auto""/>
<Setter Property=""ScrollViewer.VerticalScrollBarVisibility"" Value=""Auto""/>
<Setter Property=""Padding"" Value=""6,3,5,3""/>
<Setter Property=""ScrollViewer.CanContentScroll"" Value=""true""/>
<Setter Property=""ScrollViewer.PanningMode"" Value=""VerticalFirst""/>
<Setter Property=""Stylus.IsFlicksEnabled"" Value=""False""/>
<Setter Property=""Template"" Value=""{DynamicResource ComboBoxTemplate}""/>
<Style.Triggers>
<Trigger Property=""IsEditable"" Value=""true"">
<Setter Property=""IsTabStop"" Value=""false""/>
<Setter Property=""Padding"" Value=""2""/>
<Setter Property=""Template"" Value=""{DynamicResource ComboBoxEditableTemplate}""/>
</Trigger>
</Style.Triggers>
</Style>";
/// <summary>
/// CustomComboBoxStyle Style XAML (.NET Framework 3.5)
/// </summary>
private const string _customComboBoxStyleNet35 = @"<Style TargetType=""{x:Type ComboBox}"">
<Setter Property=""FocusVisualStyle"" Value=""{DynamicResource FocusVisual}""/>
<Setter Property=""Background"" Value=""{DynamicResource ComboBox.Static.Background}""/>
<Setter Property=""BorderBrush"" Value=""{DynamicResource ComboBox.Static.Border}""/>
<Setter Property=""Foreground"" Value=""{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}""/>
<Setter Property=""BorderThickness"" Value=""1""/>
<Setter Property=""ScrollViewer.HorizontalScrollBarVisibility"" Value=""Auto""/>
<Setter Property=""ScrollViewer.VerticalScrollBarVisibility"" Value=""Auto""/>
<Setter Property=""Padding"" Value=""6,3,5,3""/>
<Setter Property=""ScrollViewer.CanContentScroll"" Value=""true""/>
<Setter Property=""Stylus.IsFlicksEnabled"" Value=""False""/>
<Setter Property=""Template"" Value=""{DynamicResource ComboBoxTemplate}""/>
<Style.Triggers>
<Trigger Property=""IsEditable"" Value=""true"">
<Setter Property=""IsTabStop"" Value=""false""/>
<Setter Property=""Padding"" Value=""2""/>
<Setter Property=""Template"" Value=""{DynamicResource ComboBoxEditableTemplate}""/>
</Trigger>
</Style.Triggers>
</Style>";
/// <summary>
/// CustomProgressBarStyle Style XAML (.NET Framework 4.0 and above)
/// </summary>
private const string _customProgressBarStyleDefault = @"<Style x:Key=""CustomProgressBarStyle"" TargetType=""{x:Type ProgressBar}"">
<Setter Property=""Foreground"" Value=""{DynamicResource ProgressBar.Progress}""/>
<Setter Property=""Background"" Value=""{DynamicResource ProgressBar.Background}""/>
<Setter Property=""BorderBrush"" Value=""{DynamicResource ProgressBar.Border}""/>
<Setter Property=""BorderThickness"" Value=""1""/>
<Setter Property=""Template"">
<Setter.Value>
<ControlTemplate TargetType=""{x:Type ProgressBar}"">
<Grid x:Name=""TemplateRoot"">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name=""CommonStates"">
<VisualState x:Name=""Determinate""/>
<VisualState x:Name=""Indeterminate"">
<Storyboard RepeatBehavior=""Forever"">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=""(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"" Storyboard.TargetName=""Animation"">
<EasingDoubleKeyFrame KeyTime=""0"" Value=""0.25""/>
<EasingDoubleKeyFrame KeyTime=""0:0:1"" Value=""0.25""/>
<EasingDoubleKeyFrame KeyTime=""0:0:2"" Value=""0.25""/>
</DoubleAnimationUsingKeyFrames>
<PointAnimationUsingKeyFrames Storyboard.TargetProperty=""(UIElement.RenderTransformOrigin)"" Storyboard.TargetName=""Animation"">
<EasingPointKeyFrame KeyTime=""0"" Value=""-0.5,0.5""/>
<EasingPointKeyFrame KeyTime=""0:0:1"" Value=""0.5,0.5""/>
<EasingPointKeyFrame KeyTime=""0:0:2"" Value=""1.5,0.5""/>
</PointAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border BorderBrush=""{TemplateBinding BorderBrush}"" BorderThickness=""{TemplateBinding BorderThickness}"" Background=""{TemplateBinding Background}""/>
<Rectangle x:Name=""PART_Track""/>
<Grid x:Name=""PART_Indicator"" ClipToBounds=""true"" HorizontalAlignment=""Left"">
<Rectangle x:Name=""Indicator"" Fill=""{TemplateBinding Foreground}""/>
<Rectangle x:Name=""Animation"" Fill=""{TemplateBinding Foreground}"" RenderTransformOrigin=""0.5,0.5"">
<Rectangle.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
</Grid>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property=""Orientation"" Value=""Vertical"">
<Setter Property=""LayoutTransform"" TargetName=""TemplateRoot"">
<Setter.Value>
<RotateTransform Angle=""-90""/>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property=""IsIndeterminate"" Value=""true"">
<Setter Property=""Visibility"" TargetName=""Indicator"" Value=""Collapsed""/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>";
/// <summary>
/// CustomProgressBarStyle Style XAML (.NET Framework 3.5)
/// </summary>
private const string _customProgressBarStyleNet35 = @"<Style TargetType=""{x:Type ProgressBar}"">
<Setter Property=""Foreground"" Value=""{DynamicResource ProgressBar.Progress}""/>
<Setter Property=""Background"" Value=""{DynamicResource ProgressBar.Background}""/>
<Setter Property=""BorderBrush"" Value=""{DynamicResource ProgressBar.Border}""/>
<Setter Property=""BorderThickness"" Value=""1""/>
<Setter Property=""Template"">
<Setter.Value>
<ControlTemplate TargetType=""{x:Type ProgressBar}"">
<Grid x:Name=""TemplateRoot"">
<Border BorderBrush=""{TemplateBinding BorderBrush}"" BorderThickness=""{TemplateBinding BorderThickness}"" Background=""{TemplateBinding Background}""/>
<Rectangle x:Name=""PART_Track""/>
<Grid x:Name=""PART_Indicator"" ClipToBounds=""true"" HorizontalAlignment=""Left"">
<Rectangle x:Name=""Indicator"" Fill=""{TemplateBinding Foreground}""/>
<Rectangle x:Name=""Animation"" Fill=""{TemplateBinding Foreground}"" RenderTransformOrigin=""0.5,0.5"">
<Rectangle.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
</Grid>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property=""Orientation"" Value=""Vertical"">
<Setter Property=""LayoutTransform"" TargetName=""TemplateRoot"">
<Setter.Value>
<RotateTransform Angle=""-90""/>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property=""IsIndeterminate"" Value=""true"">
<Setter Property=""Visibility"" TargetName=""Indicator"" Value=""Collapsed""/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>";
#endregion
public App()
{
#if NET40_OR_GREATER || NETCOREAPP
InitializeComponent();
#endif
// Create control templates
CreateControlTemplate("ComboBoxTemplate");
CreateControlTemplate("ComboBoxEditableTemplate");
// Create styles
CreateStyle("ComboBoxEditableTextBox");
CreateStyle("CustomComboBoxStyle");
CreateStyle("CustomProgressBarStyle");
}
/// <summary>
/// Create an XAML parser context with the required namespaces
/// </summary>
private ParserContext CreateParserContext()
{
var context = new ParserContext();
context.XmlnsDictionary[""] = "http://schemas.microsoft.com/winfx/2006/xaml/presentation";
context.XmlnsDictionary["x"] = "http://schemas.microsoft.com/winfx/2006/xaml";
#if NETFRAMEWORK
context.XmlnsDictionary["themes"] = "clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero";
#else
context.XmlnsDictionary["themes"] = "clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero2";
#endif
context.XamlTypeMapper = new XamlTypeMapper([]);
return context;
}
/// <summary>
/// Create a named control template and add it to the current set of resources
/// </summary>
private void CreateControlTemplate(string resourceName)
{
var parserContext = CreateParserContext();
var controlTemplate = resourceName switch
{
#if NET35
"ComboBoxTemplate" => XamlReader.Parse(_comboBoxTemplateNet35, parserContext) as ControlTemplate,
"ComboBoxEditableTemplate" => XamlReader.Parse(_comboBoxEditableTemplateNet35, parserContext) as ControlTemplate,
#else
"ComboBoxTemplate" => XamlReader.Parse(_comboBoxTemplateDefault, parserContext) as ControlTemplate,
"ComboBoxEditableTemplate" => XamlReader.Parse(_comboBoxEditableTemplateDefault, parserContext) as ControlTemplate,
#endif
_ => throw new ArgumentException($"'{resourceName}' is not a recognized control template", nameof(resourceName)),
};
// Add the control template
Resources[resourceName] = controlTemplate;
}
/// <summary>
/// Create a named style and add it to the current set of resources
/// </summary>
private void CreateStyle(string resourceName)
{
var parserContext = CreateParserContext();
var style = resourceName switch
{
#if NET35
"ComboBoxEditableTextBox" => XamlReader.Parse(_comboBoxEditableTextBoxStyleNet35, parserContext) as Style,
"CustomComboBoxStyle" => XamlReader.Parse(_customComboBoxStyleNet35, parserContext) as Style,
"CustomProgressBarStyle" => XamlReader.Parse(_customProgressBarStyleNet35, parserContext) as Style,
#else
"ComboBoxEditableTextBox" => XamlReader.Parse(_comboBoxEditableTextBoxStyleDefault, parserContext) as Style,
"CustomComboBoxStyle" => XamlReader.Parse(_customComboBoxStyleDefault, parserContext) as Style,
"CustomProgressBarStyle" => XamlReader.Parse(_customProgressBarStyleDefault, parserContext) as Style,
#endif
_ => throw new ArgumentException($"'{resourceName}' is not a recognized style", nameof(resourceName)),
};
// Add the style
Resources[resourceName] = style;
}
#endif
}
}

View File

@@ -1,23 +1,53 @@
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<PropertyGroup>
<TargetFrameworks>net48;net6.0-windows;net7.0-windows</TargetFrameworks>
<RuntimeIdentifiers>win-x64</RuntimeIdentifiers>
<!-- Assembly Properties -->
<TargetFrameworks>net40;net452;net462;net472;net48;netcoreapp3.1;net5.0-windows;net6.0-windows;net7.0-windows;net8.0-windows</TargetFrameworks>
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
<OutputType>WinExe</OutputType>
<ApplicationIcon>Images\Icon.ico</ApplicationIcon>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<ImportFrameworkWinFXTargets Condition="$(TargetFramework.StartsWith(`net3`))">true</ImportFrameworkWinFXTargets>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<VersionPrefix>3.1.1</VersionPrefix>
<UseWindowsForms>true</UseWindowsForms>
<UseWPF>true</UseWPF>
<ApplicationIcon>Images\Icon.ico</ApplicationIcon>
<Title>MPF</Title>
<Description>Frontend for various dumping programs</Description>
<!-- Package Properties -->
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
<Copyright>Copyright (c)2019-2023</Copyright>
<VersionPrefix>2.7.3</VersionPrefix>
<InternalsVisibleTo>MPF.Test</InternalsVisibleTo>
<Description>Frontend for various dumping programs</Description>
<Copyright>Copyright (c) Matt Nadareski 2019-2024</Copyright>
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
<RepositoryUrl>https://github.com/SabreTools/MPF</RepositoryUrl>
<RepositoryType>git</RepositoryType>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)'!='net48'">
<Nullable>enable</Nullable>
<!-- Special handling for Aero2 on non-Framework .NET -->
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net4`))">
<ReferenceWpfAeroTheme>true</ReferenceWpfAeroTheme>
<ReferenceWpfAero2Theme>false</ReferenceWpfAero2Theme>
<ReferenceWpfAeroLiteTheme>false</ReferenceWpfAeroLiteTheme>
<ReferenceWpfClassicTheme>false</ReferenceWpfClassicTheme>
<ReferenceWpfLunaTheme>false</ReferenceWpfLunaTheme>
<ReferenceWpfRoyaleTheme>false</ReferenceWpfRoyaleTheme>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`)) OR $(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`))">
<ReferenceWpfAeroTheme>false</ReferenceWpfAeroTheme>
<ReferenceWpfAero2Theme>true</ReferenceWpfAero2Theme>
<ReferenceWpfAeroLiteTheme>false</ReferenceWpfAeroLiteTheme>
<ReferenceWpfClassicTheme>false</ReferenceWpfClassicTheme>
<ReferenceWpfLunaTheme>false</ReferenceWpfLunaTheme>
<ReferenceWpfRoyaleTheme>false</ReferenceWpfRoyaleTheme>
</PropertyGroup>
<ItemGroup>
<InternalsVisibleTo Include="MPF.Test" />
</ItemGroup>
<ItemGroup>
<Resource Include="Images\Icon.ico" />
@@ -28,19 +58,59 @@
<ProjectReference Include="..\MPF.UI.Core\MPF.UI.Core.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net48'">
<!-- Support for old .NET versions -->
<ItemGroup Condition="$(TargetFramework.StartsWith(`net3`))">
<Reference Include="PresentationBuildTasks" HintPath="$(ProgramFiles)\Reference Assemblies\Microsoft\Framework\v3.0\PresentationBuildTasks.dll" />
<Reference Include="PresentationCore" HintPath="$(ProgramFiles)\Reference Assemblies\Microsoft\Framework\v3.0\PresentationCore.dll" />
<Reference Include="PresentationFramework" HintPath="$(ProgramFiles)\Reference Assemblies\Microsoft\Framework\v3.0\PresentationFramework.dll" />
<Reference Include="WindowsBase" HintPath="$(ProgramFiles)\Reference Assemblies\Microsoft\Framework\v3.0\WindowsBase.dll" />
</ItemGroup>
<ItemGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net4`))">
<Reference Include="PresentationFramework.Aero" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BurnOutSharp" PrivateAssets="build; analyzers" ExcludeAssets="contentFiles" Version="2.9.0" GeneratePathProperty="true">
<PackageReference Include="BinaryObjectScanner" PrivateAssets="build; analyzers" ExcludeAssets="contentFiles" Version="3.0.2" GeneratePathProperty="true">
<IncludeAssets>runtime; compile; build; native; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="SabreTools.RedumpLib" Version="1.1.1" />
<PackageReference Include="SabreTools.RedumpLib" Version="1.3.2" />
</ItemGroup>
<ItemGroup>
<Content Include="$(PkgBurnOutSharp)\content\**" PackagePath="contentFiles\any\any;content" CopyToOutputDirectory="Always" PackageCopyToOutput="true" />
<Content Include="$(PkgBinaryObjectScanner)\content\**" PackagePath="contentFiles\any\any;content" CopyToOutputDirectory="Always" PackageCopyToOutput="true" />
</ItemGroup>
<!-- Special handling for Aero2 on non-Framework .NET -->
<Target Name="SelectWpfThemeAssembly" AfterTargets="ResolveAssemblyReferences" Condition="'$(ReferenceWpfAeroTheme)' == 'true' OR '$(ReferenceWpfAero2Theme)' == 'true' OR '$(ReferenceWpfAeroLiteTheme)' == 'true' OR '$(ReferenceWpfClassicTheme)' == 'true' OR '$(ReferenceWpfLunaTheme)' == 'true' OR '$(ReferenceWpfRoyaleTheme)' == 'true'">
<ItemGroup>
<_WpfThemeAssemblies Include="@(ReferencePath)" Condition="'%(ReferencePath.NuGetPackageId)'=='Microsoft.WindowsDesktop.App.Ref' AND '%(ReferencePath.FileName)'=='PresentationFramework.Aero'">
<Aliases Condition="'$(WpfAeroThemeAliases)'!=''">$(WpfAeroThemeAliases)</Aliases>
</_WpfThemeAssemblies>
<_WpfThemeAssemblies Include="@(ReferencePath)" Condition="'%(ReferencePath.NuGetPackageId)'=='Microsoft.WindowsDesktop.App.Ref' AND '%(ReferencePath.FileName)'=='PresentationFramework.Aero2'">
<Aliases Condition="'$(WpfAero2ThemeAliases)'!=''">$(WpfAero2ThemeAliases)</Aliases>
</_WpfThemeAssemblies>
<_WpfThemeAssemblies Include="@(ReferencePath)" Condition="'%(ReferencePath.NuGetPackageId)'=='Microsoft.WindowsDesktop.App.Ref' AND '%(ReferencePath.FileName)'=='PresentationFramework.AeroLite'">
<Aliases Condition="'$(WpfAeroLiteThemeAliases)'!=''">$(WpfAeroLiteThemeAliases)</Aliases>
</_WpfThemeAssemblies>
<_WpfThemeAssemblies Include="@(ReferencePath)" Condition="'%(ReferencePath.NuGetPackageId)'=='Microsoft.WindowsDesktop.App.Ref' AND '%(ReferencePath.FileName)'=='PresentationFramework.Classic'">
<Aliases Condition="'$(WpfClassicThemeAliases)'!=''">$(WpfClassicThemeAliases)</Aliases>
</_WpfThemeAssemblies>
<_WpfThemeAssemblies Include="@(ReferencePath)" Condition="'%(ReferencePath.NuGetPackageId)'=='Microsoft.WindowsDesktop.App.Ref' AND '%(ReferencePath.FileName)'=='PresentationFramework.Luna'">
<Aliases Condition="'$(WpfLunaThemeAliases)'!=''">$(WpfLunaThemeAliases)</Aliases>
</_WpfThemeAssemblies>
<_WpfThemeAssemblies Include="@(ReferencePath)" Condition="'%(ReferencePath.NuGetPackageId)'=='Microsoft.WindowsDesktop.App.Ref' AND '%(ReferencePath.FileName)'=='PresentationFramework.Royale'">
<Aliases Condition="'$(WpfRoyaleThemeAliases)'!=''">$(WpfRoyaleThemeAliases)</Aliases>
</_WpfThemeAssemblies>
<ReferencePath Remove="@(_WpfThemeAssemblies)" />
<ReferencePath Include="@(_WpfThemeAssemblies)" Condition="('%(_WpfThemeAssemblies.FileName)'=='PresentationFramework.Aero' AND'$(ReferenceWpfAeroTheme)'=='true') OR ('%(_WpfThemeAssemblies.FileName)'=='PresentationFramework.Aero2' AND '$(ReferenceWpfAero2Theme)'=='true') OR ('%(_WpfThemeAssemblies.FileName)'=='PresentationFramework.AeroLite' AND '$(ReferenceWpfAeroLiteTheme)'=='true') OR ('%(_WpfThemeAssemblies.FileName)'=='PresentationFramework.Classic' AND '$(ReferenceWpfClassicTheme)'=='true') OR ('%(_WpfThemeAssemblies.FileName)'=='PresentationFramework.Luna' AND '$(ReferenceWpfLunaTheme)'=='true') OR ('%(_WpfThemeAssemblies.FileName)'=='PresentationFramework.Royale' AND '$(ReferenceWpfRoyaleTheme)'=='true')" />
</ItemGroup>
</Target>
</Project>

View File

@@ -3,71 +3,69 @@
Redumper/Aaru/DiscImageCreator UI in C#
[![Build status](https://ci.appveyor.com/api/projects/status/3ldav3v0c373jeqa?svg=true)](https://ci.appveyor.com/project/mnadareski/MPF/build/artifacts)
[![UI Build](https://github.com/SabreTools/MPF/actions/workflows/build_ui.yml/badge.svg)](https://github.com/SabreTools/MPF/actions/workflows/build_ui.yml)
[![Check Build](https://github.com/SabreTools/MPF/actions/workflows/build_check.yml/badge.svg)](https://github.com/SabreTools/MPF/actions/workflows/build_check.yml)
This is a community project, so if you have some time and knowledge to give, we'll be glad to add you as a contributor to this project. If you have any suggestions, issues, bugs, or crashes, please look at the [Issues](https://github.com/SabreTools/MPF/issues) page first to see if it has been reported before and try out the latest AppVeyor WIP build below to see if it has already been addressed. If it hasn't, please open an issue that's as descriptive as you can be. Help me make this a better program for everyone :)
## Releases
For those who would rather use the most recent stable build, download the latest release here:
[Releases Page](https://github.com/SabreTools/MPF/releases)
For the most recent stable build, download the latest release here: [Releases Page](https://github.com/SabreTools/MPF/releases)
For those who like to test the newest features, download the latest AppVeyor WIP build here: [AppVeyor](https://ci.appveyor.com/project/mnadareski/MPF/build/artifacts)
For the latest GitHub Actions WIP build here: [GitHub Actions](https://github.com/SabreTools/MPF/actions)
## Media Preservation Frontend (MPF)
## Media Preservation Frontend UI (MPF)
MPF is the main, UI-centric application of the MPF suite. This program allows users to use Redumper, Aaru, or DiscImageCreator in a more user-friendly way. Each backend dumping program is supported as fully as possible to ensure that all information is captured on output. There are many customization options and quality of life settings that can be access through the Options menu.
### System Requirements
- Windows 8.1 (x64) or newer
- Users who wish to use MPF on Windows 7 need to disable strong name validation due to `Microsoft.Management.Infrastructure` being unsigned. Add the following registry keys (accurate at time of writing):
```
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\StrongName\Verification\*,31bf3856ad364e35]
[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\StrongName\Verification\*,31bf3856ad364e35]
```
- Alternatively, look at this [StackOverflow question](https://stackoverflow.com/questions/403731/strong-name-validation-failed) for more information.
- .NET Framework 4.8, .NET 6.0, or .NET 7.0 Runtimes
- As much hard drive space as the amount of discs you will be dumping (20+ GB recommended)
Ensure that your operating system is as up-to-date as possible, since some features may rely on those updates.
### Support Limitations
The main UI has some known limitations that are documented in code and in some prior support tickets:
- Windows-only due to reliance on WPF and Winforms
- MAUI is not a viable alternative due to lack of out-of-box support for Linux
- Avalonia is being heavily considered as an alternative
### Build Instructions
To build for .NET Framework 4.8, .NET 6.0, or .NET 7.0 (all Windows only), ensure that the .NET 7.0 SDK (or later) is installed and included in your PATH. Then, run the following commands from command prompt, Powershell, or Terminal:
```
dotnet build MPF\MPF.csproj --framework [net48|net6.0-windows|net7.0-windows] --runtime win-x64 --self-contained
```
You may also run `publish-win.bat` (on Windows) or `publish-nix.sh` (on Linux) to build and package all variants MPF and MPF.Check at once. The Windows script additionally requires 7-zip commandline, Git for Windows, and .NET Framework 4.8 SDK to be installed and in PATH. The Linux script additionally requires `zip` and Git to be installed and in PATH.
- MAUI is not a viable alternative due to lack of out-of-box support for Linux
- Avalonia is being heavily considered as an alternative
- For those who need .NET Framework 4.8, there is an official fork: [MPF Legacy](https://github.com/Deterous/MPF-Legacy)
## Media Preservation Frontend Checker (MPF.Check)
MPF.Check is a commandline-only program that allows users to generate submission information from their personal rips. This program supports the outputs from Redumper, Aaru, DiscImageCreator, Cleanrip, and UmdImageCreator. Running this program without any parameters will display the help text, including all supported parameters.
### System Requirements
## System Requirements
- Windows 8.1 (x64) or newer, GNU/Linux x64, or OSX x64
- .NET Framework 4.8 (Windows or `mono` only), .NET 6.0, or .NET 7.0 Runtimes
Both MPF UI and MPF.Check have the same system requirements for running, with the exception that MPF UI is Windows-only.
### Build Instructions
- [Supported OS versions for .NET 8](https://github.com/dotnet/core/blob/main/release-notes/8.0/supported-os.md)
- Requires [.NET 8.0 Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) if built without bundled runtime
To build for .NET Framework 4.8 (Windows only), .NET 6.0, and .NET 7.0 (both all supported OSes), ensure that the .NET 7.0 SDK (or later) is installed and included in your PATH. Then, run the following commands from command prompt, Powershell, Terminal, or shell:
Ensure that your operating system and runtimes are as up-to-date as possible, since some features may rely on those updates.
```
dotnet build MPF.Check\MPF.Check.csproj --framework [net48|net6.0|net7.0] --runtime [win-x64|linux-x64|osx-x64] --self-contained
## Build Instructions
To build for .NET 8.0, ensure that the [.NET 8.0 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) (or later) is installed and included in your `PATH`. Then, run the following commands from command prompt, Powershell, Terminal, or shell:
**MPF UI (Windows only):**
```bash
dotnet build MPF/MPF.csproj --framework net8.0-windows --runtime [win-x86|win-x64]
```
Choose one of `[win-x64|linux-x64|osx-x64]` depending on the machine you are targeting.
**MPF.Check (Windows, OSX, Linux):**
```bash
dotnet build MPF.Check/MPF.Check.csproj --framework net8.0 --runtime [win-x86|win-x64|linux-x64|osx-x64]
```
Choose one of `win-x64`, `linux-x64`, or `osx-x64` depending on the machine you are targeting.
### Build Scripts
Windows users may run `publish-win.ps1` and Linux users may run `publish-nix.sh` to perform a full release build.
- `publish-win.ps1` will build and package all variants of MPF UI and MPF.Check
- The script requires [7-zip commandline](https://www.7-zip.org/download.html) and [Git for Windows](https://git-scm.com/downloads) to be installed and in `PATH`
- `publish-nix.sh` will _only_ build and package all variants of MPF.Check
- The script requires `zip` and `git` to be installed and in `PATH`
## Information
@@ -81,8 +79,7 @@ A list of all changes in each stable release and current WIP builds can now be f
MPF uses some external libraries to assist with additional information gathering after the dumping process.
- **BurnOutSharp** - Protection scanning - [GitHub](https://github.com/mnadareski/BurnOutSharp)
- **UnshieldSharp** - Protection scanning - [GitHub](https://github.com/mnadareski/UnshieldSharp)
- **BinaryObjectScanner** - Protection scanning - [GitHub](https://github.com/SabreTools/BinaryObjectScanner)
- **WPFCustomMessageBox.thabse** - Custom message boxes in UI - [GitHub](https://github.com/thabse/WPFCustomMessageBox)
## Contributors

View File

@@ -1,5 +1,5 @@
# version format
version: 2.7.3-{build}
version: 3.1.1-{build}
# pull request template
pull_requests:
@@ -10,83 +10,15 @@ image: Visual Studio 2022
# build step
build_script:
- dotnet restore
# .NET Framework 4.8 Debug
- dotnet publish MPF\MPF.csproj -f net48 -r win7-x64 -c Debug --self-contained true --version-suffix %APPVEYOR_REPO_COMMIT%
- dotnet publish MPF.Check\MPF.Check.csproj -f net48 -r win7-x64 -c Debug --self-contained true --version-suffix %APPVEYOR_REPO_COMMIT%
# .NET Framework 4.8 Release
- dotnet publish MPF\MPF.csproj -f net48 -r win7-x64 -c Release --self-contained true --version-suffix %APPVEYOR_REPO_COMMIT% -p:DebugType=None -p:DebugSymbols=false
- dotnet publish MPF.Check\MPF.Check.csproj -f net48 -r win7-x64 -c Release --self-contained true --version-suffix %APPVEYOR_REPO_COMMIT% -p:DebugType=None -p:DebugSymbols=false
# .NET 6.0 Debug
- dotnet publish MPF\MPF.csproj -f net6.0-windows -r win-x64 -c Debug --self-contained true --version-suffix %APPVEYOR_REPO_COMMIT% -p:PublishSingleFile=true
- dotnet publish MPF.Check\MPF.Check.csproj -f net6.0 -r win-x64 -c Debug --self-contained true --version-suffix %APPVEYOR_REPO_COMMIT% -p:PublishSingleFile=true
- dotnet publish MPF.Check\MPF.Check.csproj -f net6.0 -r linux-x64 -c Debug --self-contained true --version-suffix %APPVEYOR_REPO_COMMIT% -p:PublishSingleFile=true
- dotnet publish MPF.Check\MPF.Check.csproj -f net6.0 -r osx-x64 -c Debug --self-contained true --version-suffix %APPVEYOR_REPO_COMMIT% -p:PublishSingleFile=true
# .NET 6.0 Release
- dotnet publish MPF\MPF.csproj -f net6.0-windows -r win-x64 -c Release --self-contained true --version-suffix %APPVEYOR_REPO_COMMIT% -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
- dotnet publish MPF.Check\MPF.Check.csproj -f net6.0 -r win-x64 -c Release --self-contained true --version-suffix %APPVEYOR_REPO_COMMIT% -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
- dotnet publish MPF.Check\MPF.Check.csproj -f net6.0 -r linux-x64 -c Release --self-contained true --version-suffix %APPVEYOR_REPO_COMMIT% -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
- dotnet publish MPF.Check\MPF.Check.csproj -f net6.0 -r osx-x64 -c Release --self-contained true --version-suffix %APPVEYOR_REPO_COMMIT% -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
# .NET 7.0 Debug
- dotnet publish MPF\MPF.csproj -f net7.0-windows -r win-x64 -c Debug --self-contained true --version-suffix %APPVEYOR_REPO_COMMIT% -p:PublishSingleFile=true
- dotnet publish MPF.Check\MPF.Check.csproj -f net7.0 -r win-x64 -c Debug --self-contained true --version-suffix %APPVEYOR_REPO_COMMIT% -p:PublishSingleFile=true
- dotnet publish MPF.Check\MPF.Check.csproj -f net7.0 -r linux-x64 -c Debug --self-contained true --version-suffix %APPVEYOR_REPO_COMMIT% -p:PublishSingleFile=true
- dotnet publish MPF.Check\MPF.Check.csproj -f net7.0 -r osx-x64 -c Debug --self-contained true --version-suffix %APPVEYOR_REPO_COMMIT% -p:PublishSingleFile=true
# .NET 7.0 Release
- dotnet publish MPF\MPF.csproj -f net7.0-windows -r win-x64 -c Release --self-contained true --version-suffix %APPVEYOR_REPO_COMMIT% -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
- dotnet publish MPF.Check\MPF.Check.csproj -f net7.0 -r win-x64 -c Release --self-contained true --version-suffix %APPVEYOR_REPO_COMMIT% -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
- dotnet publish MPF.Check\MPF.Check.csproj -f net7.0 -r linux-x64 -c Release --self-contained true --version-suffix %APPVEYOR_REPO_COMMIT% -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
- dotnet publish MPF.Check\MPF.Check.csproj -f net7.0 -r osx-x64 -c Release --self-contained true --version-suffix %APPVEYOR_REPO_COMMIT% -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
- dotnet build
# post-build step
after_build:
# Aaru
- ps: appveyor DownloadFile https://github.com/aaru-dps/Aaru/releases/download/v5.3.2/aaru-5.3.2_windows_x64.zip
- 7z x aaru-5.3.2_windows_x64.zip -oMPF\bin\Debug\net6.0-windows\win-x64\publish\Programs\Aaru *
# DiscImageCreator
- ps: appveyor DownloadFile https://github.com/saramibreak/DiscImageCreator/files/11660558/DiscImageCreator_20230606.zip
- 7z e DiscImageCreator_20230606.zip -oMPF\bin\Debug\net6.0-windows\win-x64\publish\Programs\Creator Release_ANSI\*
# Redumper
- ps: appveyor DownloadFile https://github.com/superg/redumper/releases/download/build_230/redumper-2023.10.14_build230-win64.zip
- 7z e redumper-2023.10.14_build230-win64.zip -oMPF\bin\Debug\net6.0-windows\win-x64\publish\Programs\Redumper redumper-2023.10.14_build230-win64\bin\*
# Create MPF Debug archives
- cd %APPVEYOR_BUILD_FOLDER%\MPF\bin\Debug\net6.0-windows\win-x64\publish\
- 7z a -tzip %APPVEYOR_BUILD_FOLDER%\MPF_%APPVEYOR_BUILD_NUMBER%_net6.0_win-x64_debug.zip *
# Create MPF.Check Debug archives
- cd %APPVEYOR_BUILD_FOLDER%\MPF.Check\bin\Debug\net6.0\win-x64\publish\
- 7z a -tzip %APPVEYOR_BUILD_FOLDER%\MPF.Check_%APPVEYOR_BUILD_NUMBER%_net6.0_win-x64_debug.zip *
- cd %APPVEYOR_BUILD_FOLDER%\MPF.Check\bin\Debug\net6.0\linux-x64\publish\
- 7z a -tzip %APPVEYOR_BUILD_FOLDER%\MPF.Check_%APPVEYOR_BUILD_NUMBER%_net6.0_linux-x64_debug.zip *
- cd %APPVEYOR_BUILD_FOLDER%\MPF.Check\bin\Debug\net6.0\osx-x64\publish\
- 7z a -tzip %APPVEYOR_BUILD_FOLDER%\MPF.Check_%APPVEYOR_BUILD_NUMBER%_net6.0_osx-x64_debug.zip *
# success/failure tracking
on_success:
- ps: Invoke-RestMethod https://raw.githubusercontent.com/DiscordHooks/appveyor-discord-webhook/master/send.ps1 -o send.ps1
- ps: ./send.ps1 success $env:WEBHOOK_URL
on_failure:
- ps: Invoke-RestMethod https://raw.githubusercontent.com/DiscordHooks/appveyor-discord-webhook/master/send.ps1 -o send.ps1
- ps: ./send.ps1 failure $env:WEBHOOK_URL
# artifact linking
artifacts:
- path: MPF_%APPVEYOR_BUILD_NUMBER%_net6.0_win-x64_debug.zip
name: MPF (.NET 6.0, Debug, Windows x64)
- path: MPF.Check_%APPVEYOR_BUILD_NUMBER%_net6.0_win-x64_debug.zip
name: MPF.Check (.NET 6.0, Debug, Windows x64)
- path: MPF.Check_%APPVEYOR_BUILD_NUMBER%_net6.0_linux-x64_debug.zip
name: MPF.Check (.NET 6.0, Debug, Linux x64)
- path: MPF.Check_%APPVEYOR_BUILD_NUMBER%_net6.0_osx-x64_debug.zip
name: MPF.Check (.NET 6.0, Debug, OSX x64)
- ps: ./send.ps1 failure $env:WEBHOOK_URL

View File

@@ -1,7 +1,7 @@
#! /bin/bash
# This batch file assumes the following:
# - .NET 7.0 (or newer) SDK is installed and in PATH
# - .NET 8.0 (or newer) SDK is installed and in PATH
# - zip is installed and in PATH
# - Git is installed and in PATH
# - The relevant commandline programs are already downloaded
@@ -10,8 +10,32 @@
# If any of these are not satisfied, the operation may fail
# in an unpredictable way and result in an incomplete output.
# TODO: Re-enable MPF building after figuring out how to build Windows desktop applications on Linux
# This may require an additional package to be installed?
# Optional parameters
USE_ALL=false
INCLUDE_PROGRAMS=false
NO_BUILD=false
NO_ARCHIVE=false
while getopts "upba" OPTION
do
case $OPTION in
u)
USE_ALL=true
;;
p)
INCLUDE_PROGRAMS=true
;;
b)
NO_BUILD=true
;;
a)
NO_ARCHIVE=true
;;
*)
echo "Invalid option provided"
exit 1
;;
esac
done
# Set the current directory as a variable
BUILD_FOLDER=$PWD
@@ -19,74 +43,132 @@ BUILD_FOLDER=$PWD
# Set the current commit hash
COMMIT=`git log --pretty=%H -1`
# Restore Nuget packages for all builds
echo "Restoring Nuget packages"
dotnet restore
# Create the build matrix arrays
UI_FRAMEWORKS=("net8.0-windows")
UI_RUNTIMES=("win-x86" "win-x64")
CHECK_FRAMEWORKS=("net8.0")
CHECK_RUNTIMES=("win-x86" "win-x64" "linux-x64" "osx-x64")
# .NET 6.0 Debug
echo "Building .NET 6.0 debug"
#dotnet publish MPF/MPF.csproj -f net6.0-windows -r win-x64 -c Debug --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true
dotnet publish MPF.Check/MPF.Check.csproj -f net6.0 -r win-x64 -c Debug --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true
dotnet publish MPF.Check/MPF.Check.csproj -f net6.0 -r linux-x64 -c Debug --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true
dotnet publish MPF.Check/MPF.Check.csproj -f net6.0 -r osx-x64 -c Debug --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true
# Use expanded lists, if requested
if [ $USE_ALL = true ]
then
UI_FRAMEWORKS=("net40" "net452" "net462" "net472" "net48" "netcoreapp3.1" "net5.0-windows" "net6.0-windows" "net7.0-windows" "net8.0-windows")
UI_RUNTIMES=("win-x86" "win-x64")
CHECK_FRAMEWORKS=("net20" "net35" "net40" "net452" "net462" "net472" "net48" "netcoreapp3.1" "net5.0" "net6.0" "net7.0" "net8.0")
CHECK_RUNTIMES=("win-x86" "win-x64" "win-arm64" "linux-x64" "linux-arm64" "osx-x64")
fi
# .NET 6.0 Release
echo "Building .NET 6.0 release"
#dotnet publish MPF/MPF.csproj -f net6.0-windows -r win-x64 -c Release --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true -p:PublishTrimmed=true -p:DebugType=None -p:DebugSymbols=false
dotnet publish MPF.Check/MPF.Check.csproj -f net6.0 -r win-x64 -c Release --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true -p:PublishTrimmed=true -p:DebugType=None -p:DebugSymbols=false
dotnet publish MPF.Check/MPF.Check.csproj -f net6.0 -r linux-x64 -c Release --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true -p:PublishTrimmed=true -p:DebugType=None -p:DebugSymbols=false
dotnet publish MPF.Check/MPF.Check.csproj -f net6.0 -r osx-x64 -c Release --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true -p:PublishTrimmed=true -p:DebugType=None -p:DebugSymbols=false
# Create the filter arrays
SINGLE_FILE_CAPABLE=("net5.0" "net5.0-windows" "net6.0" "net6.0-windows" "net7.0" "net7.0-windows" "net8.0" "net8.0-windows")
VALID_CROSS_PLATFORM_FRAMEWORKS=("netcoreapp3.1" "net5.0" "net6.0" "net7.0" "net8.0")
VALID_CROSS_PLATFORM_RUNTIMES=("win-arm64" "linux-x64" "linux-arm64" "osx-x64")
# .NET 7.0 Debug
echo "Building .NET 7.0 debug"
#dotnet publish MPF/MPF.csproj -f net7.0-windows -r win-x64 -c Debug --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true
dotnet publish MPF.Check/MPF.Check.csproj -f net7.0 -r win-x64 -c Debug --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true
dotnet publish MPF.Check/MPF.Check.csproj -f net7.0 -r linux-x64 -c Debug --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true
dotnet publish MPF.Check/MPF.Check.csproj -f net7.0 -r osx-x64 -c Debug --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true
# Only build if requested
if [ $NO_BUILD = false ]
then
# Restore Nuget packages for all builds
echo "Restoring Nuget packages"
dotnet restore
# .NET 7.0 Release
echo "Building .NET 7.0 release"
#dotnet publish MPF/MPF.csproj -f net7.0-windows -r win-x64 -c Release --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true -p:PublishTrimmed=true -p:DebugType=None -p:DebugSymbols=false
dotnet publish MPF.Check/MPF.Check.csproj -f net7.0 -r win-x64 -c Release --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true -p:PublishTrimmed=true -p:DebugType=None -p:DebugSymbols=false
dotnet publish MPF.Check/MPF.Check.csproj -f net7.0 -r linux-x64 -c Release --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true -p:PublishTrimmed=true -p:DebugType=None -p:DebugSymbols=false
dotnet publish MPF.Check/MPF.Check.csproj -f net7.0 -r osx-x64 -c Release --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true -p:PublishTrimmed=true -p:DebugType=None -p:DebugSymbols=false
# Build UI
for FRAMEWORK in "${UI_FRAMEWORKS[@]}"
do
for RUNTIME in "${UI_RUNTIMES[@]}"
do
# If we have an invalid combination of framework and runtime
if [[ ! $(echo ${VALID_CROSS_PLATFORM_FRAMEWORKS[@]} | fgrep -w $FRAMEWORK) ]]
then
if [[ $(echo ${VALID_CROSS_PLATFORM_RUNTIMES[@]} | fgrep -w $RUNTIME) ]]
then
continue
fi
fi
# Create MPF Debug archives
#cd $BUILD_FOLDER/MPF/bin/Debug/net6.0-windows/win-x64/publish/
#zip -r $BUILD_FOLDER/MPF_net6.0_win-x64_debug.zip .
#cd $BUILD_FOLDER/MPF/bin/Debug/net7.0-windows/win-x64/publish/
#zip -r $BUILD_FOLDER/MPF_net7.0_win-x64_debug.zip .
# Only .NET 5 and above can publish to a single file
if [[ $(echo ${SINGLE_FILE_CAPABLE[@]} | fgrep -w $FRAMEWORK) ]]
then
dotnet publish MPF/MPF.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true
dotnet publish MPF/MPF.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
else
dotnet publish MPF/MPF.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT
dotnet publish MPF/MPF.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:DebugType=None -p:DebugSymbols=false
fi
done
done
# Create MPF Release archives
#cd $BUILD_FOLDER/MPF/bin/Release/net6.0-windows/win-x64/publish/
#zip -r $BUILD_FOLDER/MPF_net6.0_win-x64_release.zip .
#cd $BUILD_FOLDER/MPF/bin/Release/net7.0-windows/win-x64/publish/
#zip -r $BUILD_FOLDER/MPF_net7.0_win-x64_release.zip .
# Build Check
for FRAMEWORK in "${CHECK_FRAMEWORKS[@]}"
do
for RUNTIME in "${CHECK_RUNTIMES[@]}"
do
# If we have an invalid combination of framework and runtime
if [[ ! $(echo ${VALID_CROSS_PLATFORM_FRAMEWORKS[@]} | fgrep -w $FRAMEWORK) ]]
then
if [[ $(echo ${VALID_CROSS_PLATFORM_RUNTIMES[@]} | fgrep -w $RUNTIME) ]]
then
continue
fi
fi
# Create MPF.Check Debug archives
cd $BUILD_FOLDER/MPF.Check/bin/Debug/net6.0/win-x64/publish/
zip -r $BUILD_FOLDER/MPF.Check_net6.0_win-x64_debug.zip .
cd $BUILD_FOLDER/MPF.Check/bin/Debug/net6.0/linux-x64/publish/
zip -r $BUILD_FOLDER/MPF.Check_net6.0_linux-x64_debug.zip .
cd $BUILD_FOLDER/MPF.Check/bin/Debug/net6.0/osx-x64/publish/
zip -r $BUILD_FOLDER/MPF.Check_net6.0_osx-x64_debug.zip .
cd $BUILD_FOLDER/MPF.Check/bin/Debug/net7.0/win-x64/publish/
zip -r $BUILD_FOLDER/MPF.Check_net7.0_win-x64_debug.zip .
cd $BUILD_FOLDER/MPF.Check/bin/Debug/net7.0/linux-x64/publish/
zip -r $BUILD_FOLDER/MPF.Check_net7.0_linux-x64_debug.zip .
cd $BUILD_FOLDER/MPF.Check/bin/Debug/net7.0/osx-x64/publish/
zip -r $BUILD_FOLDER/MPF.Check_net7.0_osx-x64_debug.zip .
# Only .NET 5 and above can publish to a single file
if [[ $(echo ${SINGLE_FILE_CAPABLE[@]} | fgrep -w $FRAMEWORK) ]]
then
dotnet publish MPF.Check/MPF.Check.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true
dotnet publish MPF.Check/MPF.Check.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
else
dotnet publish MPF.Check/MPF.Check.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT
dotnet publish MPF.Check/MPF.Check.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:DebugType=None -p:DebugSymbols=false
fi
done
done
fi
# Create MPF.Check Release archives
cd $BUILD_FOLDER/MPF.Check/bin/Release/net6.0/win-x64/publish/
zip -r $BUILD_FOLDER/MPF.Check_net6.0_win-x64_release.zip .
cd $BUILD_FOLDER/MPF.Check/bin/Release/net6.0/linux-x64/publish/
zip -r $BUILD_FOLDER/MPF.Check_net6.0_linux-x64_release.zip .
cd $BUILD_FOLDER/MPF.Check/bin/Release/net6.0/osx-x64/publish/
zip -r $BUILD_FOLDER/MPF.Check_net6.0_osx-x64_release.zip .
cd $BUILD_FOLDER/MPF.Check/bin/Release/net7.0/win-x64/publish/
zip -r $BUILD_FOLDER/MPF.Check_net7.0_win-x64_release.zip .
cd $BUILD_FOLDER/MPF.Check/bin/Release/net7.0/linux-x64/publish/
zip -r $BUILD_FOLDER/MPF.Check_net7.0_linux-x64_release.zip .
cd $BUILD_FOLDER/MPF.Check/bin/Release/net7.0/osx-x64/publish/
zip -r $BUILD_FOLDER/MPF.Check_net7.0_osx-x64_release.zip .
# Only create archives if requested
if [ $NO_ARCHIVE = false ]
then
# Create UI archives
for FRAMEWORK in "${UI_FRAMEWORKS[@]}"
do
for RUNTIME in "${UI_RUNTIMES[@]}"
do
cd $BUILD_FOLDER/MPF/bin/Debug/${FRAMEWORK}/${RUNTIME}/publish/
if [ $INCLUDE_PROGRAMS = true ]
then
zip -r $BUILD_FOLDER/MPF_${FRAMEWORK}_${RUNTIME}_debug.zip .
else
zip -r $BUILD_FOLDER/MPF_${FRAMEWORK}_${RUNTIME}_debug.zip . -x 'Programs/\*'
fi
cd $BUILD_FOLDER/MPF/bin/Release/${FRAMEWORK}/${RUNTIME}/publish/
if [ $INCLUDE_PROGRAMS = true ]
then
zip -r $BUILD_FOLDER/MPF_${FRAMEWORK}_${RUNTIME}_release.zip .
else
zip -r $BUILD_FOLDER/MPF_${FRAMEWORK}_${RUNTIME}_release.zip . -x 'Programs/\*'
fi
done
done
# Create Check archives
for FRAMEWORK in "${CHECK_FRAMEWORKS[@]}"
do
for RUNTIME in "${CHECK_RUNTIMES[@]}"
do
# If we have an invalid combination of framework and runtime
if [[ ! $(echo ${VALID_CROSS_PLATFORM_FRAMEWORKS[@]} | fgrep -w $FRAMEWORK) ]]
then
if [[ $(echo ${VALID_CROSS_PLATFORM_RUNTIMES[@]} | fgrep -w $RUNTIME) ]]
then
continue
fi
fi
cd $BUILD_FOLDER/MPF.Check/bin/Debug/${FRAMEWORK}/${RUNTIME}/publish/
zip -r $BUILD_FOLDER/MPF.Check_${FRAMEWORK}_${RUNTIME}_debug.zip .
cd $BUILD_FOLDER/MPF.Check/bin/Release/${FRAMEWORK}/${RUNTIME}/publish/
zip -r $BUILD_FOLDER/MPF.Check_${FRAMEWORK}_${RUNTIME}_release.zip .
done
done
# Reset the directory
cd $BUILD_FOLDER
fi

View File

@@ -1,108 +0,0 @@
@echo OFF
REM This batch file assumes the following:
REM - .NET Framework 4.8 SDK is installed and in PATH
REM - .NET 7.0 (or newer) SDK is installed and in PATH
REM - 7-zip commandline (7z.exe) is installed and in PATH
REM - Git for Windows is installed and in PATH
REM - The relevant commandline programs are already downloaded
REM and put into their respective folders
REM
REM If any of these are not satisfied, the operation may fail
REM in an unpredictable way and result in an incomplete output.
REM Set the current directory as a variable
set BUILD_FOLDER=%~dp0
REM Set the current commit hash
for /f %%i in ('git log --pretty^=%%H -1') do set COMMIT=%%i
REM Restore Nuget packages for all builds
echo Restoring Nuget packages
dotnet restore
REM .NET Framework 4.8 Debug
echo Building .NET Framework 4.8 debug
dotnet publish MPF\MPF.csproj -f net48 -r win7-x64 -c Debug --self-contained true --version-suffix %COMMIT%
dotnet publish MPF.Check\MPF.Check.csproj -f net48 -r win7-x64 -c Debug --self-contained true --version-suffix %COMMIT%
REM .NET Framework 4.8 Release
echo Building .NET Framework 4.8 release
dotnet publish MPF\MPF.csproj -f net48 -r win7-x64 -c Release --self-contained true --version-suffix %COMMIT% -p:DebugType=None -p:DebugSymbols=false
dotnet publish MPF.Check\MPF.Check.csproj -f net48 -r win7-x64 -c Release --self-contained true --version-suffix %COMMIT% -p:DebugType=None -p:DebugSymbols=false
REM .NET 6.0 Debug
echo Building .NET 6.0 debug
dotnet publish MPF\MPF.csproj -f net6.0-windows -r win-x64 -c Debug --self-contained true --version-suffix %COMMIT% -p:PublishSingleFile=true
dotnet publish MPF.Check\MPF.Check.csproj -f net6.0 -r win-x64 -c Debug --self-contained true --version-suffix %COMMIT% -p:PublishSingleFile=true
dotnet publish MPF.Check\MPF.Check.csproj -f net6.0 -r linux-x64 -c Debug --self-contained true --version-suffix %COMMIT% -p:PublishSingleFile=true
dotnet publish MPF.Check\MPF.Check.csproj -f net6.0 -r osx-x64 -c Debug --self-contained true --version-suffix %COMMIT% -p:PublishSingleFile=true
REM .NET 6.0 Release
echo Building .NET 6.0 release
dotnet publish MPF\MPF.csproj -f net6.0-windows -r win-x64 -c Release --self-contained true --version-suffix %COMMIT% -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
dotnet publish MPF.Check\MPF.Check.csproj -f net6.0 -r win-x64 -c Release --self-contained true --version-suffix %COMMIT% -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
dotnet publish MPF.Check\MPF.Check.csproj -f net6.0 -r linux-x64 -c Release --self-contained true --version-suffix %COMMIT% -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
dotnet publish MPF.Check\MPF.Check.csproj -f net6.0 -r osx-x64 -c Release --self-contained true --version-suffix %COMMIT% -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
REM .NET 7.0 Debug
echo Building .NET 7.0 debug
dotnet publish MPF\MPF.csproj -f net7.0-windows -r win-x64 -c Debug --self-contained true --version-suffix %COMMIT% -p:PublishSingleFile=true
dotnet publish MPF.Check\MPF.Check.csproj -f net7.0 -r win-x64 -c Debug --self-contained true --version-suffix %COMMIT% -p:PublishSingleFile=true
dotnet publish MPF.Check\MPF.Check.csproj -f net7.0 -r linux-x64 -c Debug --self-contained true --version-suffix %COMMIT% -p:PublishSingleFile=true
dotnet publish MPF.Check\MPF.Check.csproj -f net7.0 -r osx-x64 -c Debug --self-contained true --version-suffix %COMMIT% -p:PublishSingleFile=true
REM .NET 7.0 Release
echo Building .NET 7.0 release
dotnet publish MPF\MPF.csproj -f net7.0-windows -r win-x64 -c Release --self-contained true --version-suffix %COMMIT% -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
dotnet publish MPF.Check\MPF.Check.csproj -f net7.0 -r win-x64 -c Release --self-contained true --version-suffix %COMMIT% -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
dotnet publish MPF.Check\MPF.Check.csproj -f net7.0 -r linux-x64 -c Release --self-contained true --version-suffix %COMMIT% -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
dotnet publish MPF.Check\MPF.Check.csproj -f net7.0 -r osx-x64 -c Release --self-contained true --version-suffix %COMMIT% -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
REM Create MPF Debug archives
cd %BUILD_FOLDER%\MPF\bin\Debug\net48\win7-x64\publish\
7z a -tzip %BUILD_FOLDER%\MPF_net48_debug.zip *
cd %BUILD_FOLDER%\MPF\bin\Debug\net6.0-windows\win-x64\publish\
7z a -tzip %BUILD_FOLDER%\MPF_net6.0_win-x64_debug.zip *
cd %BUILD_FOLDER%\MPF\bin\Debug\net7.0-windows\win-x64\publish\
7z a -tzip %BUILD_FOLDER%\MPF_net7.0_win-x64_debug.zip *
REM Create MPF Release archives
cd %BUILD_FOLDER%\MPF\bin\Release\net48\win7-x64\publish\
7z a -tzip %BUILD_FOLDER%\MPF_net48_release.zip *
cd %BUILD_FOLDER%\MPF\bin\Release\net6.0-windows\win-x64\publish\
7z a -tzip %BUILD_FOLDER%\MPF_net6.0_win-x64_release.zip *
cd %BUILD_FOLDER%\MPF\bin\Release\net7.0-windows\win-x64\publish\
7z a -tzip %BUILD_FOLDER%\MPF_net7.0_win-x64_release.zip *
REM Create MPF.Check Debug archives
cd %BUILD_FOLDER%\MPF.Check\bin\Debug\net48\win7-x64\publish\
7z a -tzip %BUILD_FOLDER%\MPF.Check_net48_debug.zip *
cd %BUILD_FOLDER%\MPF.Check\bin\Debug\net6.0\win-x64\publish\
7z a -tzip %BUILD_FOLDER%\MPF.Check_net6.0_win-x64_debug.zip *
cd %BUILD_FOLDER%\MPF.Check\bin\Debug\net6.0\linux-x64\publish\
7z a -tzip %BUILD_FOLDER%\MPF.Check_net6.0_linux-x64_debug.zip *
cd %BUILD_FOLDER%\MPF.Check\bin\Debug\net6.0\osx-x64\publish\
7z a -tzip %BUILD_FOLDER%\MPF.Check_net6.0_osx-x64_debug.zip *
cd %BUILD_FOLDER%\MPF.Check\bin\Debug\net7.0\win-x64\publish\
7z a -tzip %BUILD_FOLDER%\MPF.Check_net7.0_win-x64_debug.zip *
cd %BUILD_FOLDER%\MPF.Check\bin\Debug\net7.0\linux-x64\publish\
7z a -tzip %BUILD_FOLDER%\MPF.Check_net7.0_linux-x64_debug.zip *
cd %BUILD_FOLDER%\MPF.Check\bin\Debug\net7.0\osx-x64\publish\
7z a -tzip %BUILD_FOLDER%\MPF.Check_net7.0_osx-x64_debug.zip *
REM Create MPF.Check Release archives
cd %BUILD_FOLDER%\MPF.Check\bin\Release\net48\win7-x64\publish\
7z a -tzip %BUILD_FOLDER%\MPF.Check_net48_release.zip *
cd %BUILD_FOLDER%\MPF.Check\bin\Release\net6.0\win-x64\publish\
7z a -tzip %BUILD_FOLDER%\MPF.Check_net6.0_win-x64_release.zip *
cd %BUILD_FOLDER%\MPF.Check\bin\Release\net6.0\linux-x64\publish\
7z a -tzip %BUILD_FOLDER%\MPF.Check_net6.0_linux-x64_release.zip *
cd %BUILD_FOLDER%\MPF.Check\bin\Release\net6.0\osx-x64\publish\
7z a -tzip %BUILD_FOLDER%\MPF.Check_net6.0_osx-x64_release.zip *
cd %BUILD_FOLDER%\MPF.Check\bin\Release\net7.0\win-x64\publish\
7z a -tzip %BUILD_FOLDER%\MPF.Check_net7.0_win-x64_release.zip *
cd %BUILD_FOLDER%\MPF.Check\bin\Release\net7.0\linux-x64\publish\
7z a -tzip %BUILD_FOLDER%\MPF.Check_net7.0_linux-x64_release.zip *
cd %BUILD_FOLDER%\MPF.Check\bin\Release\net7.0\osx-x64\publish\
7z a -tzip %BUILD_FOLDER%\MPF.Check_net7.0_osx-x64_release.zip *

163
publish-win.ps1 Normal file
View File

@@ -0,0 +1,163 @@
# This batch file assumes the following:
# - .NET 8.0 (or newer) SDK is installed and in PATH
# - 7-zip commandline (7z.exe) is installed and in PATH
# - Git for Windows is installed and in PATH
# - The relevant commandline programs are already downloaded
# and put into their respective folders
#
# If any of these are not satisfied, the operation may fail
# in an unpredictable way and result in an incomplete output.
# Optional parameters
param(
[Parameter(Mandatory = $false)]
[Alias("UseAll")]
[switch]$USE_ALL,
[Parameter(Mandatory = $false)]
[Alias("IncludePrograms")]
[switch]$INCLUDE_PROGRAMS,
[Parameter(Mandatory = $false)]
[Alias("NoBuild")]
[switch]$NO_BUILD,
[Parameter(Mandatory = $false)]
[Alias("NoArchive")]
[switch]$NO_ARCHIVE
)
# Set the current directory as a variable
$BUILD_FOLDER = $PSScriptRoot
# Set the current commit hash
$COMMIT = git log --pretty=format:"%H" -1
# Create the build matrix arrays
$UI_FRAMEWORKS = @('net8.0-windows')
$UI_RUNTIMES = @('win-x86', 'win-x64')
$CHECK_FRAMEWORKS = @('net8.0')
$CHECK_RUNTIMES = @('win-x86', 'win-x64', 'linux-x64', 'osx-x64')
# Use expanded lists, if requested
if ($USE_ALL.IsPresent)
{
$UI_FRAMEWORKS = @('net40', 'net452', 'net462', 'net472', 'net48', 'netcoreapp3.1', 'net5.0-windows', 'net6.0-windows', 'net7.0-windows', 'net8.0-windows')
$UI_RUNTIMES = @('win-x86', 'win-x64')
$CHECK_FRAMEWORKS = @('net20', 'net35', 'net40', 'net452', 'net462', 'net472', 'net48', 'netcoreapp3.1', 'net5.0', 'net6.0', 'net7.0', 'net8.0')
$CHECK_RUNTIMES = @('win-x86', 'win-x64', 'win-arm64', 'linux-x64', 'linux-arm64', 'osx-x64')
}
# Create the filter arrays
$SINGLE_FILE_CAPABLE = @('net5.0', 'net5.0-windows', 'net6.0', 'net6.0-windows', 'net7.0', 'net7.0-windows', 'net8.0', 'net8.0-windows')
$VALID_CROSS_PLATFORM_FRAMEWORKS = @('netcoreapp3.1', 'net5.0', 'net6.0', 'net7.0', 'net8.0')
$VALID_CROSS_PLATFORM_RUNTIMES = @('win-arm64', 'linux-x64', 'linux-arm64', 'osx-x64')
# Only build if requested
if (!$NO_BUILD.IsPresent)
{
# Restore Nuget packages for all builds
Write-Host "Restoring Nuget packages"
dotnet restore
# Build UI
foreach ($FRAMEWORK in $UI_FRAMEWORKS)
{
foreach ($RUNTIME in $UI_RUNTIMES)
{
# If we have an invalid combination of framework and runtime
if ($VALID_CROSS_PLATFORM_FRAMEWORKS -notcontains $FRAMEWORK -and $VALID_CROSS_PLATFORM_RUNTIMES -contains $RUNTIME)
{
continue
}
# Only .NET 5 and above can publish to a single file
if ($SINGLE_FILE_CAPABLE -contains $FRAMEWORK)
{
dotnet publish MPF\MPF.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true
dotnet publish MPF\MPF.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
}
else
{
dotnet publish MPF\MPF.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT
dotnet publish MPF\MPF.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:DebugType=None -p:DebugSymbols=false
}
}
}
# Build Check
foreach ($FRAMEWORK in $CHECK_FRAMEWORKS)
{
foreach ($RUNTIME in $CHECK_RUNTIMES)
{
# If we have an invalid combination of framework and runtime
if ($VALID_CROSS_PLATFORM_FRAMEWORKS -notcontains $FRAMEWORK -and $VALID_CROSS_PLATFORM_RUNTIMES -contains $RUNTIME)
{
continue
}
# Only .NET 5 and above can publish to a single file
if ($SINGLE_FILE_CAPABLE -contains $FRAMEWORK)
{
dotnet publish MPF.Check\MPF.Check.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true
dotnet publish MPF.Check\MPF.Check.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
}
else
{
dotnet publish MPF.Check\MPF.Check.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT
dotnet publish MPF.Check\MPF.Check.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:DebugType=None -p:DebugSymbols=false
}
}
}
}
# Only create archives if requested
if (!$NO_ARCHIVE.IsPresent)
{
# Create UI archives
foreach ($FRAMEWORK in $UI_FRAMEWORKS)
{
foreach ($RUNTIME in $UI_RUNTIMES)
{
Set-Location -Path $BUILD_FOLDER\MPF\bin\Debug\${FRAMEWORK}\${RUNTIME}\publish\
if ($INCLUDE_PROGRAMS.IsPresent)
{
7z a -tzip $BUILD_FOLDER\MPF_${FRAMEWORK}_${RUNTIME}_debug.zip *
}
else
{
7z a -tzip -x!Programs\* $BUILD_FOLDER\MPF_${FRAMEWORK}_${RUNTIME}_debug.zip *
}
Set-Location -Path $BUILD_FOLDER\MPF\bin\Release\${FRAMEWORK}\${RUNTIME}\publish\
if ($INCLUDE_PROGRAMS.IsPresent)
{
7z a -tzip $BUILD_FOLDER\MPF_${FRAMEWORK}_${RUNTIME}_release.zip *
}
else
{
7z a -tzip -x!Programs\* $BUILD_FOLDER\MPF_${FRAMEWORK}_${RUNTIME}_release.zip *
}
}
}
# Create Check archives
foreach ($FRAMEWORK in $CHECK_FRAMEWORKS)
{
foreach ($RUNTIME in $CHECK_RUNTIMES)
{
# If we have an invalid combination of framework and runtime
if ($VALID_CROSS_PLATFORM_FRAMEWORKS -notcontains $FRAMEWORK -and $VALID_CROSS_PLATFORM_RUNTIMES -contains $RUNTIME)
{
continue
}
Set-Location -Path $BUILD_FOLDER\MPF.Check\bin\Debug\${FRAMEWORK}\${RUNTIME}\publish\
7z a -tzip $BUILD_FOLDER\MPF.Check_${FRAMEWORK}_${RUNTIME}_debug.zip *
Set-Location -Path $BUILD_FOLDER\MPF.Check\bin\Release\${FRAMEWORK}\${RUNTIME}\publish\
7z a -tzip $BUILD_FOLDER\MPF.Check_${FRAMEWORK}_${RUNTIME}_release.zip *
}
}
# Reset the directory
Set-Location -Path $PSScriptRoot
}