Compare commits

...

760 Commits
2.2 ... 2.7.5

Author SHA1 Message Date
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
Matt Nadareski
33b4be8b24 Bump version 2023-10-26 09:35:25 -04:00
Matt Nadareski
71a4edc8ba Update changelog 2023-10-26 09:18:13 -04:00
Deterous
ceb305eb54 Use relative path internally (#589) 2023-10-26 06:17:17 -07:00
Matt Nadareski
0b0d13dcf3 Use Array.Empty in hasher 2023-10-26 01:25:34 -04:00
Matt Nadareski
9f02368622 Fix two small nullability issues 2023-10-26 01:00:09 -04:00
Matt Nadareski
152010ee14 Update to MMI 3.0.0-preview.4 2023-10-26 00:58:44 -04:00
Matt Nadareski
c6d5f0aea5 Update to BurnOutSharp 2.9.0 2023-10-26 00:57:23 -04:00
Matt Nadareski
8c2ad64bb8 Update XUnit packages 2023-10-25 23:19:44 -04:00
Matt Nadareski
fa54d694b6 Fix CRC32 hashing (fixes #587) 2023-10-25 22:38:30 -04:00
Matt Nadareski
dc35b08624 Attempt to parse out PS5 params.json 2023-10-25 16:56:13 -04:00
Matt Nadareski
4429515ba2 Update package versions (fixes #586) 2023-10-25 16:22:32 -04:00
Matt Nadareski
fdbc7b34e5 Update changelog 2023-10-19 20:58:15 -04:00
Deterous
a1ab442cf0 Set UDF CD threshold at 800MB (#585)
* Set UDF CD threshold at 800MB

* Condense DriveFormat checks
2023-10-19 17:56:58 -07:00
Matt Nadareski
9ed5c297f6 Expose suffix setting (fixes #428) 2023-10-18 22:23:40 -04:00
Matt Nadareski
4ce9b214b0 Wire through filename suffix 2023-10-18 22:19:25 -04:00
Matt Nadareski
7dcdadda75 Add filename suffix setting (nw) 2023-10-18 22:04:15 -04:00
Matt Nadareski
f87a4d9fe2 Fix up DumpEnvironment a bit 2023-10-18 21:54:04 -04:00
Matt Nadareski
e4e5d173f0 Rearrange OptionsWindow to be easier to navigate 2023-10-18 16:35:34 -04:00
Matt Nadareski
115b242d59 Add optional file deletion (fixes #126) 2023-10-18 15:49:57 -04:00
Matt Nadareski
706bf8a431 Add deleteable file lists for Redumper and DIC 2023-10-18 15:41:51 -04:00
Matt Nadareski
87ecf1aa99 Add framework for deleteable files 2023-10-18 15:33:32 -04:00
Matt Nadareski
b5cf274333 Simply theme application 2023-10-18 10:31:58 -04:00
Matt Nadareski
4f4b6879b6 Fix drive letter check (fixes #584) 2023-10-18 10:22:05 -04:00
Matt Nadareski
3b19463913 Create method for applying theme 2023-10-18 10:21:40 -04:00
Matt Nadareski
37386cd182 Fix multiple handler invocation 2023-10-18 03:28:41 -04:00
Matt Nadareski
e04bdad16c Add first-run Options title, fix saving bug 2023-10-18 02:59:41 -04:00
Matt Nadareski
e37f12705d Fix drive letter issue in UI 2023-10-18 02:30:38 -04:00
Matt Nadareski
5c8dc2c23a Show options window on first run (fixes #467) 2023-10-18 02:10:29 -04:00
Matt Nadareski
e9121f3b03 Create skeleton for first-run 2023-10-18 01:59:55 -04:00
Matt Nadareski
d68175db4e Convert Drive to use paths internally 2023-10-18 01:47:42 -04:00
Matt Nadareski
9d8181b0e2 Add path variants for PlayStation info 2023-10-18 01:29:26 -04:00
Matt Nadareski
6d657f268a Split InfoTool into 2 classes 2023-10-18 01:17:12 -04:00
Matt Nadareski
3b3c5f823d Bump version 2023-10-17 20:53:07 -04:00
Matt Nadareski
09fc313492 Support Redumper 231 outputs 2023-10-17 20:51:48 -04:00
Matt Nadareski
316d0f6e54 Clean up issue templates 2023-10-16 00:44:57 -04:00
Matt Nadareski
a0033238bd Get SecuROM data from Redumper (fixes #583) 2023-10-16 00:38:56 -04:00
Matt Nadareski
5b1c6a7f46 Update changlog 2023-10-15 21:51:51 -04:00
fuzzball
8c0dff6552 Correct the condition (#582) 2023-10-15 18:51:23 -07:00
Matt Nadareski
43b230c84a Update Redumper to build 230 2023-10-15 21:42:45 -04:00
Matt Nadareski
f1b657011d Enable parameters checkbox by default (fixes #580) 2023-10-15 15:33:18 -04:00
Matt Nadareski
e4d8ac8e1c Fix Redumper retry count not showing 2023-10-14 23:04:05 -04:00
Matt Nadareski
08f44173dd Update changelog 2023-10-13 10:56:10 -04:00
Deterous
54765c71fd Remove code for getting UH from DIC logs (#577) 2023-10-13 07:55:52 -07:00
Matt Nadareski
01f8b49214 Update changelog 2023-10-13 10:54:57 -04:00
Deterous
e8b0b3efaa Improve check for which program supports which media (#578)
* Move ProgramSupportsMedia to MPF.Core.Utilities.Tools

* Implement ProgramSupportsMedia slightly less naively

* ProgramSupportsMedia only considers MPF-supported types
2023-10-13 07:54:10 -07:00
Matt Nadareski
f637938858 Update changelog 2023-10-12 15:47:13 -04:00
Deterous
ae326c1d2f Disable dumping button when Redumper selected with unsupported media type (#576)
* Disable dumping button when HDDVD + Redumper selected

* Exclude Bluray+Redumper as well

* Move program-supports-mediatype logic to helper function

* Fix for net48

* Fix for net48

* Undo fixes for net48
2023-10-12 12:46:44 -07:00
Matt Nadareski
a4a1e6bf0a Suppress some unnecessary messages 2023-10-12 01:27:54 -04:00
Matt Nadareski
ecee44966e Gate some switch expressions 2023-10-12 01:19:47 -04:00
Matt Nadareski
83437977ba Update changelog 2023-10-11 23:56:00 -04:00
Matt Nadareski
8fcac1d425 Cleanup and gated code 2023-10-11 23:55:50 -04:00
Matt Nadareski
705b5f1049 Fix options loading for Check 2023-10-11 16:37:10 -04:00
Matt Nadareski
367d0c104b Bump version 2023-10-11 13:48:32 -04:00
Matt Nadareski
5d4bed2d9e Remove unnecessary namespacing 2023-10-11 13:41:42 -04:00
Matt Nadareski
63756192d8 Enable path browse button by default 2023-10-11 13:13:36 -04:00
Matt Nadareski
b68ec78184 Version-gate some more newer syntax 2023-10-11 13:07:45 -04:00
Matt Nadareski
c55d5183fb Fix Check always showing Help text 2023-10-11 12:54:40 -04:00
Matt Nadareski
f82e925944 Version-gate some newer syntax 2023-10-11 12:37:31 -04:00
Matt Nadareski
c19f9ea173 Var-ify many instances 2023-10-11 12:29:06 -04:00
Matt Nadareski
76b2dd79ab Handle some suggested changes 2023-10-11 12:26:24 -04:00
Matt Nadareski
c96ff23ad1 Update changelog 2023-10-11 12:14:56 -04:00
Matt Nadareski
cd68b55b93 Enable nullability in MPF.Test 2023-10-11 12:14:42 -04:00
Matt Nadareski
cad14d96f7 Enable nullability in MPF.UI.Core 2023-10-11 11:49:56 -04:00
Matt Nadareski
daaf9f1932 Enable nullability in MPF 2023-10-11 11:16:34 -04:00
Matt Nadareski
cb7502b450 Update changelog 2023-10-11 10:34:00 -04:00
Deterous
ece142bbf1 Place message boxes at center of main window (#574) 2023-10-11 07:33:26 -07:00
Matt Nadareski
611fee4605 Remove all but .NET 6 for AppVeyor packaging (fixes #573) 2023-10-10 23:44:30 -04:00
Matt Nadareski
791e2d0272 Enable nullability in Check 2023-10-10 23:22:21 -04:00
Matt Nadareski
81742a4676 Unify handling of enable/disable events 2023-10-10 22:05:39 -04:00
Matt Nadareski
1ff3f2210c Handle numeric disc titles as issue numbers (fixes #543) 2023-10-10 21:55:51 -04:00
Matt Nadareski
be9e4b91d5 Add generic drive flag for Redumper (fixes #546) 2023-10-10 21:45:17 -04:00
Matt Nadareski
854dcc5f95 Add BE read flag for Redumper (fixes #545) 2023-10-10 21:42:16 -04:00
Matt Nadareski
29b71db33a Allow user-supplied information in Check (fixes #572) 2023-10-10 21:27:36 -04:00
Matt Nadareski
2ee64b222a Fix errant version detection issue 2023-10-10 20:56:53 -04:00
Matt Nadareski
afda54f97b Add pull-all flag for Check (fixes #571) 2023-10-10 20:51:39 -04:00
Matt Nadareski
ad37c573b6 Bump version 2023-10-10 20:25:11 -04:00
Matt Nadareski
b6cb3104ae Update changelog 2023-10-10 16:16:06 -04:00
Deterous
af9b0dc214 Fix media combobox not updating (#568)
* Fix media combobox not updating

* Don't call SetCurrentDiscType rather than clear the variable
2023-10-10 13:15:47 -07:00
Matt Nadareski
b5440032de Recentralize some Check functionality 2023-10-10 15:15:42 -04:00
Matt Nadareski
a8e783235c Fix dumping button not enabling 2023-10-10 02:03:18 -04:00
Matt Nadareski
fc97fe99e3 Fix media type ordering 2023-10-10 01:52:47 -04:00
Matt Nadareski
1c73b1133f Consolidate some constants 2023-10-09 11:49:26 -04:00
Matt Nadareski
908eccaafa Remove LogOutputViewModel 2023-10-09 11:44:06 -04:00
Matt Nadareski
e114d126c5 Move MainViewModel to Core 2023-10-09 11:35:49 -04:00
Matt Nadareski
c07b4e4a28 Remove message boxes from MainViewModel 2023-10-09 11:01:43 -04:00
Matt Nadareski
06491a6611 Detected type and selected type are different 2023-10-09 10:35:44 -04:00
Matt Nadareski
b9a35850ad Fix null reference exception in disc type 2023-10-09 00:42:33 -04:00
Matt Nadareski
17a0c5d083 Remove more Windows from MainViewModel 2023-10-09 00:13:40 -04:00
Matt Nadareski
1b9523c799 Remove some message boxes from MainViewModel 2023-10-09 00:03:02 -04:00
Matt Nadareski
ae80ecefc8 Remove WinForms from MainViewModel 2023-10-08 23:46:24 -04:00
Matt Nadareski
50ae32e3db Use callback for logging, fix Options window 2023-10-08 23:40:56 -04:00
Matt Nadareski
2d43873398 Remove MainWindow from MainViewModel 2023-10-08 23:15:24 -04:00
Matt Nadareski
a3df433fef Small updtes to MainViewModel 2023-10-08 23:06:17 -04:00
Matt Nadareski
35e71d8527 Perform most of MainViewModel changes 2023-10-08 22:49:46 -04:00
Matt Nadareski
c5d07e4be1 Start migrating MainViewModel 2023-10-08 20:52:29 -04:00
Matt Nadareski
0df5093b45 Fix log output 2023-10-08 00:39:31 -04:00
Matt Nadareski
05d920d095 Move decoupled view models 2023-10-07 23:37:01 -04:00
Matt Nadareski
d2f7ac9843 Refine info window bindings further 2023-10-07 23:12:40 -04:00
Matt Nadareski
857a302aad Refine options window bindings further 2023-10-07 22:48:43 -04:00
Matt Nadareski
46de589791 Refine options window bindings 2023-10-07 22:40:51 -04:00
Matt Nadareski
f14961061c Handle Redump password changing better 2023-10-07 21:31:39 -04:00
Matt Nadareski
453fcf5cb1 Use binding for more disc info textboxes 2023-10-07 21:23:14 -04:00
Matt Nadareski
24cdb27cdb Split options window code a bit more 2023-10-07 11:47:24 -04:00
Matt Nadareski
24235c5896 Update changelog 2023-10-07 11:32:19 -04:00
Deterous
c226f8cf58 Clarify build instructions in README (#566)
* Update README.md

* publish-win.bat requirements

* Mention publish-nix.sh and its requirements
2023-10-07 08:31:59 -07:00
Matt Nadareski
b8f6c9f65f Split info window code a bit more 2023-10-07 02:19:04 -04:00
Matt Nadareski
bb42e2db10 Split logging code a bit more 2023-10-07 01:59:28 -04:00
Matt Nadareski
db544fd4b7 Move LogLevel enumeration 2023-10-07 01:34:59 -04:00
Matt Nadareski
370c6bde5a Move element items to Core 2023-10-07 01:30:11 -04:00
Matt Nadareski
d7965ee37f Remove unnecessary include 2023-10-07 01:22:50 -04:00
Matt Nadareski
b2c6e07ed1 Fix failing tests 2023-10-07 01:11:56 -04:00
Matt Nadareski
19cef20ceb Allow nullability for modern .NET 2023-10-07 01:02:21 -04:00
Deterous
058a1aeeaa Remove debug symbols in release builds (#565)
* Remove debugging symbols from appveyor builds

* Remove debugging symbols from release build with nix build script

* Remove debug symbols from release build with win build script

* Explicitly define Debug profile

* Add missing Debug profile flag
2023-10-06 18:35:20 -07:00
Matt Nadareski
d185077925 Fix failing tests 2023-10-06 20:42:34 -04:00
Matt Nadareski
e3948ba91b Consolidate into MPF.Core 2023-10-06 20:39:59 -04:00
Matt Nadareski
4a30f94007 Remove IMAPI2 as a dependency (#563)
* Remove IMAPI2 with no substitution

* Remove framework exceptions

* Slight reorganization of code

* Update README with support information

* Remove msbuild-specific AppVeyor items

* Be trickier when it comes to type from size

* Fix formatting of changelist

* Fix "detected" message

* Be smarter about media type based on system
2023-10-06 17:22:09 -07:00
Matt Nadareski
1ae27bf9e3 Fix missing comma 2023-10-04 20:53:33 -04:00
Matt Nadareski
bc88103471 Add submission info preamble 2023-10-04 20:51:07 -04:00
Matt Nadareski
9ec6fbfe52 Don't reverse the CRC32 output 2023-10-04 16:40:46 -04:00
Matt Nadareski
ebdb8de6b3 Use System.IO.Hashing for CRC32 2023-10-04 16:37:33 -04:00
Matt Nadareski
b8b56f6308 Build number not job number 2023-10-04 16:03:11 -04:00
Matt Nadareski
0726f5a402 Undo accidential temp commit 2023-10-04 15:51:10 -04:00
Matt Nadareski
90da5d1a7e Fix errant space in variable name 2023-10-04 15:50:56 -04:00
Matt Nadareski
bd3c518fa0 Job number not build ID 2023-10-04 15:39:11 -04:00
Matt Nadareski
44272f60bf Try out using version number in AppVeyor 2023-10-04 15:31:43 -04:00
Matt Nadareski
640ce8d854 Add note about git being required 2023-10-04 14:53:55 -04:00
Matt Nadareski
74732058f2 Publish, but don't package, release builds 2023-10-04 14:48:56 -04:00
Matt Nadareski
22e480ccd1 Try alternate archive naming 2023-10-04 14:35:46 -04:00
Matt Nadareski
32a0cb0394 Attempt to replace NRT 2023-10-04 14:26:13 -04:00
Matt Nadareski
88551bc2ed Bump version 2023-10-04 13:14:05 -04:00
Matt Nadareski
039af56f6a Be clearer with protection outputs (fixes #561) 2023-10-04 12:51:29 -04:00
Matt Nadareski
7a428e2add Ensure multisession info is populated (fixes #560) 2023-10-04 12:01:01 -04:00
Matt Nadareski
c8dd85eb72 Update redumper to build 221 (fixes #559) 2023-10-03 17:41:23 -04:00
Matt Nadareski
7c7a19c5a0 Clean up csproj files 2023-10-02 18:18:14 -04:00
Matt Nadareski
9bedd26b24 Fix path tests 2023-10-02 17:50:18 -04:00
Matt Nadareski
09afdf52fb Skip system detection on inactive drives (fixes #558) 2023-10-02 01:17:14 -04:00
Matt Nadareski
c69afe69dd Try out more UI functionality 2023-10-02 01:00:31 -04:00
Matt Nadareski
ab18c7920a Ensure popups are topmost 2023-10-01 23:28:58 -04:00
Matt Nadareski
5af1841d13 Fix XGD4 PIC reading (fixes #557) 2023-09-29 11:12:35 -04:00
Matt Nadareski
8c324e3b8b Fix redumper EDC detection output (fixes #556) 2023-09-28 22:30:51 -04:00
Matt Nadareski
d99099d587 Tweak README again 2023-09-27 15:50:30 -04:00
Matt Nadareski
fa7b46a516 Omit track 0.2 and 00.2 from hash search (fixes #554) 2023-09-27 13:28:40 -04:00
Matt Nadareski
f7c746b536 Move to config.json 2023-09-27 11:29:44 -04:00
Matt Nadareski
b6e109133f Add setting for pulling comment/contents (fixes #552) 2023-09-27 10:45:53 -04:00
Matt Nadareski
0d694c1bde Address some warnings and infos 2023-09-27 00:38:00 -04:00
Matt Nadareski
47f45fa46f Stop compiling Chime finally 2023-09-27 00:05:36 -04:00
Matt Nadareski
481f4b41d1 Fully sync AppVeyor build with script 2023-09-26 23:44:06 -04:00
Matt Nadareski
359ad87faa Add placeholders for release builds 2023-09-26 23:40:17 -04:00
Matt Nadareski
ba47cb7da2 Remove errant character from script 2023-09-26 23:30:53 -04:00
Matt Nadareski
8927c49963 Update to MMI 3.0.0-preview.2 2023-09-26 23:26:48 -04:00
Matt Nadareski
21f9668093 Update Nuget packages 2023-09-26 23:01:05 -04:00
Matt Nadareski
371571d13f Bump version 2023-09-26 22:15:05 -04:00
Matt Nadareski
b231f82c4c Reset debug option to false 2023-09-26 21:41:19 -04:00
Matt Nadareski
1741326253 Force information window to top 2023-09-26 21:39:41 -04:00
Matt Nadareski
1878ef5ad6 Combine build scripts 2023-09-26 21:35:43 -04:00
Matt Nadareski
ae42f5edd7 Fix options not saving on update 2023-09-26 21:27:36 -04:00
Matt Nadareski
4362ed71e0 Handle invalid characters when changing program 2023-09-26 21:09:53 -04:00
Matt Nadareski
f02904ea49 Add release publish scripts 2023-09-26 17:13:15 -04:00
Matt Nadareski
abf4eb9b7c Update AppVeyor to match scripts 2023-09-26 16:43:57 -04:00
Matt Nadareski
1f942977cc Normalize publish scripts 2023-09-26 16:15:43 -04:00
Matt Nadareski
7edadd4739 Disable subdump download in AppVeyor 2023-09-26 14:12:32 -04:00
Matt Nadareski
cb09816c63 Add .NET 7 to build scripts 2023-09-26 14:11:33 -04:00
Matt Nadareski
48f0a826ca Update redumper to build 219 2023-09-26 13:59:09 -04:00
Matt Nadareski
9c10842924 Normalize Redumper CSS output (fixes #553) 2023-09-26 11:26:56 -04:00
Matt Nadareski
def499769f Bump version 2023-09-25 22:01:33 -04:00
Matt Nadareski
5e1e61a441 Fix tests that have been broken for a while 2023-09-25 21:53:34 -04:00
Matt Nadareski
4896a12ec5 Add .NET 7 as a build target (not AppVeyor) 2023-09-25 21:49:49 -04:00
Matt Nadareski
b28ad5d3cd Move MainWindow 2023-09-25 21:39:28 -04:00
Matt Nadareski
4cdbbcedf0 Move options into MainViewModel 2023-09-25 21:29:55 -04:00
Matt Nadareski
6ab63ba651 Make logger a local reference 2023-09-25 21:21:51 -04:00
Matt Nadareski
d36c5099f3 Set parent on MainViewModel init 2023-09-25 21:16:43 -04:00
Matt Nadareski
8ba8347a0c Make Options non-cloneable 2023-09-25 21:11:14 -04:00
Matt Nadareski
4c5184eac5 More decoupling App 2023-09-25 21:03:29 -04:00
Matt Nadareski
9914b94716 Fix build 2023-09-25 20:55:52 -04:00
Matt Nadareski
150e69eca5 Begin decoupling App 2023-09-25 16:58:14 -04:00
Matt Nadareski
f09923974b Move OptionsWindow 2023-09-25 15:54:18 -04:00
Matt Nadareski
d98bc28930 Move to csproj tag for internals 2023-09-25 15:47:27 -04:00
Matt Nadareski
ee4a7ab653 Move LogOutput 2023-09-25 15:43:01 -04:00
Matt Nadareski
6b20aee320 Remove log formatting code 2023-09-25 15:38:33 -04:00
Matt Nadareski
a5849325f3 Remove App references from LogViewModel 2023-09-25 15:36:50 -04:00
Matt Nadareski
86d2d83fbe Remove EnableLogFormatting 2023-09-25 15:27:25 -04:00
Matt Nadareski
308e0d6937 Remove EnableProgressProcessing 2023-09-25 15:25:45 -04:00
Matt Nadareski
3541bca2d0 Move WPFCustomMessageBox 2023-09-25 15:18:46 -04:00
Matt Nadareski
2496099532 Move Constants 2023-09-25 15:17:45 -04:00
Matt Nadareski
6001395181 Move RedumpSystemComboBoxItem 2023-09-25 15:12:05 -04:00
Matt Nadareski
fa2192a284 Not building against .NET Standard 2023-09-25 15:09:21 -04:00
Matt Nadareski
f09155936e Update changelog 2023-09-18 15:40:01 -04:00
Markus Persson
a07fca6a7b Fix timestamp (#551)
Timestamp used 12-hour clock. Fixed to 24-hour.
2023-09-18 12:36:29 -07:00
Matt Nadareski
5fe7a1dac8 Migrate to Nuget package for PIC 2023-09-13 16:50:13 -04:00
Matt Nadareski
16ed2f9595 Migrate to Nuget package for cuesheets 2023-09-13 16:36:51 -04:00
Matt Nadareski
9004d2bc7b Migrate to Nuget package for XMID 2023-09-13 16:16:47 -04:00
Matt Nadareski
cc98b38290 Remove dd for Windows (fixes #544) 2023-09-07 10:36:33 -04:00
Matt Nadareski
18ef0cddff Merge branch 'master' of https://github.com/SabreTools/MPF 2023-09-05 09:26:53 -04:00
Deterous
d9ca55d96c Fix serial parsing from #542 (#549)
Serial numbers begin at serial[5]
2023-09-05 06:26:31 -07:00
Matt Nadareski
816c94de58 Migrate to Nuget package for Redump 2023-09-05 00:08:09 -04:00
Matt Nadareski
c2ba58148a Retrofit README 2023-09-02 15:24:03 -04:00
Matt Nadareski
d9533f2448 Update changelog 2023-08-31 11:14:41 -04:00
Deterous
5126d1854c Fix PlayStation serial code region parsing (#542)
* Fix PlayStation serial code region parsing

- Do not assume S_J_ and S_P_ serials are Japanese
- Fix typo: PABX -> PBPX
- PCXC are Japanese
- PUBX are USA

* Remove S_J_ serial case

* Make S_P_ serial case comment clearer

* Retain PABX serial just in case

* Parse S_P_ serials to detect Asia/Korea

* C# style substring

* String literals
2023-08-31 08:10:42 -07:00
Matt Nadareski
6baaf132a7 Fix dumping path in DD 2023-08-29 21:07:01 -04:00
Matt Nadareski
98a30e6558 Swap order of operations for changing program 2023-08-28 12:16:53 -04:00
Matt Nadareski
0912b78568 Handle extension changing only 2023-08-28 11:24:58 -04:00
Matt Nadareski
d92a1d566d Add helper for changing dumping program from UI 2023-08-26 23:17:49 -04:00
Matt Nadareski
ff40d18ed3 Fix speed setting in Aaru (fixes #539) 2023-08-26 23:10:17 -04:00
Matt Nadareski
4d9cd85ba6 Add CD Projekt ID field (fixes #530) 2023-08-26 22:57:44 -04:00
Matt Nadareski
b6ae390cee Bump version to 2.6.3 2023-08-15 13:07:33 -04:00
Matt Nadareski
4692028cfb Update changelog 2023-08-15 12:18:52 -04:00
TheRogueArchivist
3ada7db916 Update SafeDisc Sanitization (#531)
* Update SafeDisc Sanitization

* Fix "Macrovision Protected Application" parsing
2023-08-15 09:18:01 -07:00
Matt Nadareski
aa4b2f415d Non-zero data start only for audio discs (fixes #536) 2023-08-15 12:07:29 -04:00
Matt Nadareski
56e91bf177 Add dumping date to log (fixes #533) 2023-08-15 00:47:32 -04:00
Matt Nadareski
228c752585 Make use flag required for MPF.Check (fixes #534) 2023-08-15 00:30:30 -04:00
Matt Nadareski
6ce7ccfa91 Remove _drive.txt from required UIC outputs (fixes #532) 2023-08-12 19:51:06 -04:00
Matt Nadareski
c8c98278b6 Add known .NET 6 limitations to README 2023-07-24 13:45:31 -04:00
Matt Nadareski
0fc57c58cf Update redumper to build 195 2023-07-24 13:23:15 -04:00
Matt Nadareski
3dcac28488 Bump version to 2.6.2 2023-07-24 09:40:20 -04:00
Matt Nadareski
4cd7073bf6 Clarify build instructions 2023-07-23 23:51:05 -04:00
Matt Nadareski
1af21e7aba Add build instructions to README 2023-07-23 23:48:04 -04:00
Matt Nadareski
52adcd0b46 Add *nix publish script 2023-07-23 23:44:03 -04:00
Matt Nadareski
8a91593e58 Add Windows publish script 2023-07-23 21:42:10 -04:00
Matt Nadareski
729f8273fc Fix universal hash URL generation 2023-07-23 19:16:57 -04:00
Matt Nadareski
78df6f6583 Fix .NET Framework 4.8 build 2023-07-23 16:48:44 -04:00
Matt Nadareski
dc8dae4df7 Attempt to match universal hash 2023-07-23 16:41:40 -04:00
Matt Nadareski
53db9dbf81 Modify the track count on checking 2023-07-23 15:09:46 -04:00
Matt Nadareski
22755a4af9 Skip extra tracks during checking (fixes #529) 2023-07-22 21:31:20 -04:00
Matt Nadareski
ba28b414ba Normalize old universal hash text (fixes #528) 2023-07-20 20:26:57 -04:00
Matt Nadareski
95d10ecb1e Always show extension for Redumper (fixes #525) 2023-07-20 11:35:15 -04:00
Matt Nadareski
c5ab2c747a Support LibCrypt data from Redumper 2023-07-19 17:07:59 -04:00
Matt Nadareski
476c494f4e Universal hash only for audio discs (fixes #524) 2023-07-19 13:33:14 -04:00
Matt Nadareski
758c49c1cc Ensure custom parameters properly set (fixes #523) 2023-07-19 12:51:22 -04:00
Matt Nadareski
d80ad3b3cf Bump version to 2.6.1 2023-07-19 09:36:34 -04:00
Matt Nadareski
3d06f80703 Fix comment field pulling again 2023-07-19 00:05:33 -04:00
Matt Nadareski
7344460409 Fix comment field pulling again (fixes #522) 2023-07-18 22:40:08 -04:00
Matt Nadareski
19f58d9dde Set extensions for Redumper in UI (fixes #521) 2023-07-18 20:56:08 -04:00
Matt Nadareski
ad9f39f832 Be more explicit about .NET 6 limitation 2023-07-17 13:23:00 -04:00
Matt Nadareski
d51117b058 Be more explicit about .NET 6 limitation (fixes #519) 2023-07-17 13:17:42 -04:00
Matt Nadareski
0d65d5114a Set best compression levels for log files (fixes #518) 2023-07-17 13:13:16 -04:00
Matt Nadareski
30fec3c3d0 Don't pull comment fields that auto-populate (fixes #517) 2023-07-17 10:28:37 -04:00
Matt Nadareski
8eb86fde90 Simplify Redumper error value extraction (fixes #515) 2023-07-14 16:42:47 -04:00
Matt Nadareski
7616c6b2ba Bump version to 2.6 2023-07-14 13:47:00 -04:00
Matt Nadareski
e0742cdfc7 Update Nuget packages to newest stable 2023-07-14 12:07:44 -04:00
Matt Nadareski
a19937d630 Omit pulling universal hash (fixes #513) 2023-07-12 17:29:35 -04:00
Matt Nadareski
fa92402bc5 Ensure we found the tags we're skipping (fixes #512) 2023-07-11 10:57:50 -04:00
Matt Nadareski
cdc3da5839 Reduce pulled information for Xbox and X360 (fixes #512) 2023-07-10 11:15:32 -04:00
Matt Nadareski
3430f8c1db New layerbreak takes precedence 2023-07-10 09:38:52 -04:00
Matt Nadareski
12fd55e76c Parse and format Redumper CD multisession data 2023-07-09 20:55:33 -04:00
Matt Nadareski
0013606d61 Update redumper to build 183 2023-07-09 14:43:47 -04:00
Matt Nadareski
4d520d7d63 Add ordering to disc or book type 2023-07-06 22:50:21 -04:00
Matt Nadareski
7bfe174680 Use HashSet for disc or book type 2023-07-06 15:21:37 -04:00
Matt Nadareski
4951e7bf42 Strip colons from Redumper disc key (fixes #508) 2023-06-27 15:44:55 -04:00
Matt Nadareski
d7d9c468ae UMDs are Sony discs (fixes #507) 2023-06-26 11:46:57 -04:00
Matt Nadareski
32faa33ad3 Update redumper to build 176 2023-06-26 01:06:46 -04:00
Matt Nadareski
8a5475380a Check Redumper dat section for completeness 2023-06-23 11:20:17 -04:00
Matt Nadareski
1dd5542390 Add missing Aaru error log to zip 2023-06-21 16:27:23 -04:00
Matt Nadareski
5156b89eca Normalize multi-instance site tags (fixes #505) 2023-06-19 21:21:20 -04:00
Matt Nadareski
83ea04c880 Normalize Redumper CSS outputs 2023-06-19 21:13:18 -04:00
Matt Nadareski
8695d2981e Fix non-reading loop 2023-06-18 23:18:50 -04:00
Matt Nadareski
e0c299e6f0 Adjust CSS title key parsing 2023-06-18 23:01:17 -04:00
Matt Nadareski
16ec54f389 Add support for redumper CD synonyms 2023-06-18 21:56:49 -04:00
Matt Nadareski
998bf5d5fa Update redumper to build 174 2023-06-18 21:55:57 -04:00
Matt Nadareski
07ec821e9a Hook up CSS output for testing 2023-06-18 21:15:50 -04:00
Matt Nadareski
56cf8f3574 Initial support for Redumper CSS outputs 2023-06-18 21:13:25 -04:00
Matt Nadareski
c6ebfcd6d9 Update redumper to build 173 2023-06-18 21:01:52 -04:00
Matt Nadareski
6ceffce63d Fix previous commit, clean up helpers 2023-06-12 13:42:13 -04:00
Matt Nadareski
f43aafc00d Update Redumper PS1 output parsing 2023-06-12 13:31:49 -04:00
Matt Nadareski
ab598d8377 Fix Redumper DAT/layer parsing 2023-06-11 00:57:42 -04:00
Matt Nadareski
dbd876a1c1 Fix VSCode build 2023-06-11 00:56:54 -04:00
Matt Nadareski
fd102cb56b Fix 2-layer DVD support in Redumper 2023-06-10 20:46:54 -04:00
Matt Nadareski
0afb49b657 Add TODO with notes to Redumper 2023-06-10 18:01:52 -04:00
Matt Nadareski
be980fe0c4 Add placeholder and TODOs for Redumper 2023-06-10 17:49:31 -04:00
Matt Nadareski
08b4e8d602 Support Redumper DVD layerbreak 2023-06-10 17:41:48 -04:00
Matt Nadareski
e483e1cdc6 Unblock Redumper DVD support 2023-06-10 12:03:14 -04:00
Matt Nadareski
5c2dce78e2 Update redumper to build 166 2023-06-09 23:00:20 -04:00
Matt Nadareski
26ea383775 Update to DIC 20230606 2023-06-06 11:13:29 -04:00
Matt Nadareski
64938fd7f1 Fix MCD region(s) parsing 2023-05-30 13:04:33 -04:00
Matt Nadareski
22318ee3c1 Add MCD/SS header support for Redumper (fixes #495)
This also does a replacement for all instances of `?? "";` with `?? string.Empty;`, which is more correct.
2023-05-30 12:58:37 -04:00
Matt Nadareski
b2b54a2706 Update changelog 2023-05-28 16:13:57 -04:00
fuzzball
40f04e0321 Change the fast-forward keyword (#493) 2023-05-28 13:06:49 -07:00
fuzzball
a7638b8063 Get write offset from redumper 119 (#484) 2023-05-28 13:06:20 -07:00
Matt Nadareski
2abcad2a0f Add more safety to DAT generation (fixes #492) 2023-05-28 16:05:00 -04:00
Matt Nadareski
e088b05de4 Update redumper to build 151 2023-05-19 13:10:39 -04:00
Matt Nadareski
a31d894b79 Add executable listing for XSX (fixes #491) 2023-05-10 09:26:50 -04:00
Matt Nadareski
34fae4572d Fix non-zero offset text (fixes #490) 2023-05-09 13:38:22 -04:00
Matt Nadareski
1ecf0ad1fa Fix other media type method 2023-04-26 16:00:20 -04:00
Matt Nadareski
7c7509020f Add PIC identifier to SubmissionInfo (fixes #488) 2023-04-26 10:24:30 -04:00
Matt Nadareski
7c6b118282 Add internal theme support with class (fixes #487) 2023-04-26 09:51:29 -04:00
Matt Nadareski
c154a844e3 Truncate PIC data for PS4/PS5 (fixes #486) 2023-04-26 08:35:36 -04:00
Matt Nadareski
088f1b8545 Add missing BD disc type identifier string 2023-04-26 08:29:56 -04:00
Matt Nadareski
fc3c636bdd Single file packing for .NET 6 again 2023-04-25 09:07:42 -04:00
Matt Nadareski
14c807c882 Fix subdump mkdir path in AV config 2023-04-24 21:52:52 -04:00
Matt Nadareski
d0e9c51786 Fix subdump output path in AV config 2023-04-24 21:42:20 -04:00
Matt Nadareski
b428bc0ba0 Be more specific with runtime identifiers 2023-04-24 21:31:39 -04:00
Matt Nadareski
6dbbb91438 De-indent ringcode data 2023-04-24 14:42:13 -04:00
Matt Nadareski
2066d36424 Ensure blank lines don't interfere 2023-04-24 00:01:07 -04:00
Matt Nadareski
29552cd39d Fix upcoming suppl DAT parsing 2023-04-23 23:59:44 -04:00
Matt Nadareski
640e7091cc Attempt to more accurately parse layerbreaks 2023-04-23 23:38:57 -04:00
Matt Nadareski
af12c18d2e Fix missing size for ISO data 2023-04-23 22:26:24 -04:00
Matt Nadareski
f28cf614c3 Fix info tool hash finding 2023-04-23 10:00:13 -04:00
Matt Nadareski
888cb8ec9f Support single digit subs 2023-04-23 09:45:56 -04:00
Matt Nadareski
04035ac524 Add suppl support to Xbox 2023-04-22 21:51:18 -04:00
Matt Nadareski
600374eb2d Prepare for future DIC changes
This also takes care of additional `sub` files generated for nonstandard tracks, such as Track 00 and Track AA (fixes #483)
2023-04-22 21:19:02 -04:00
Matt Nadareski
6ecb932a82 Start migrating to datafile serialization 2023-04-22 16:48:40 -04:00
Matt Nadareski
4cbc9ac109 Add datafile helper method 2023-04-22 16:25:18 -04:00
Matt Nadareski
5ee0b7345b Add datafile models 2023-04-22 15:11:14 -04:00
Matt Nadareski
cc55330fad Make TOC file optional for CD/GD 2023-04-21 13:19:27 -04:00
Matt Nadareski
6589380fdf Remove path from PS1/PS2 serial (fixes #481) 2023-04-19 11:36:13 -04:00
Matt Nadareski
bd45482bf7 Disable special SmartE handling for DIC 2023-04-17 14:06:27 -04:00
Matt Nadareski
e14c8a8f03 Ensure dumping program box can enable/disable 2023-04-13 12:34:51 -04:00
Matt Nadareski
4ac00e9a1a Comment out . handling for DIC 2023-04-13 12:33:14 -04:00
Matt Nadareski
edf983e304 Update to DIC 20230413 2023-04-13 12:27:46 -04:00
Matt Nadareski
01a69ef9b3 Add dumping program selection to main UI (#479)
* Add dumping program selection to main UI

* Fix program dropdown

* Fix lingering location

* Final changes
2023-04-11 08:15:53 -07:00
Matt Nadareski
a368afc14a UMDs always have "2 layers" (fixes #473) 2023-04-10 10:21:47 -04:00
Matt Nadareski
d83fed16f5 Handle PIC based on disc type 2023-04-10 10:14:44 -04:00
Matt Nadareski
949df08690 Add PIC models for BD (unused) 2023-04-10 10:13:58 -04:00
Matt Nadareski
ab3abb5b3e Re-enable BD33 and BD66 2023-04-10 08:57:51 -04:00
Matt Nadareski
d16e73a530 Add UltraCade 2023-04-09 21:24:47 -04:00
Matt Nadareski
3f8c55ca47 Clarify non-Redump systems 2023-04-09 21:10:08 -04:00
Matt Nadareski
2e9aaa50f9 Update redumper to build 118
This change also does the following:
- Performs some minor cleanup on `OptionsViewModel`
- Add options for enabling verbose and debug by default for Redumper (similar to Aaru)
2023-04-09 20:54:52 -04:00
Matt Nadareski
36951dc5da Resync with Redump 2023-04-08 21:32:20 -04:00
Matt Nadareski
b39c8dd738 Add DIC . notice to README 2023-04-01 21:51:02 -04:00
Matt Nadareski
a1155cf9b7 Update to DIC 20230401 2023-04-01 20:32:42 -04:00
Matt Nadareski
1d151d213e Update redumper to build 115 2023-03-31 10:30:19 -04:00
Matt Nadareski
6dc0c1438a Update changelog 2023-03-31 10:25:09 -04:00
fuzzball
8739569db6 Change the mark for fast-forwarding (#475) 2023-03-31 07:21:36 -07:00
Matt Nadareski
0dcba9ce71 Add Hasbro iON (fixes #471) 2023-03-20 15:59:16 -04:00
Matt Nadareski
8d37b85e12 Update README 2023-03-20 15:47:01 -04:00
Matt Nadareski
43cf8e1a45 Split MMI invocation in drive listing 2023-03-20 15:16:35 -04:00
Matt Nadareski
7317553483 Update redumper to build 113 2023-03-20 10:19:27 -04:00
Matt Nadareski
0b342e265c Add Windows 7 note to README 2023-03-20 10:11:39 -04:00
Matt Nadareski
08359dd45f Update changelog 2023-03-19 14:13:32 -04:00
fuzzball
a24415cae6 Increase the version of AppVeyor (#470) 2023-03-19 11:09:33 -07:00
fuzzball
59f8161308 Pull hardware info from redumper log (#469) 2023-03-19 11:09:12 -07:00
Matt Nadareski
51f955a14c Add warning to login tab 2023-03-13 14:23:54 -04:00
Matt Nadareski
ddebdef00c Update README 2023-03-13 13:25:32 -04:00
Matt Nadareski
6a4b6d613a Bump version to 2.5, update copyright year 2023-03-12 15:48:24 -04:00
Matt Nadareski
97aef5e29c Update changelog 2023-03-12 15:43:15 -04:00
fuzzball
9549178c3a Detect EOF during the search for error counts (#465) 2023-03-12 13:22:49 -04:00
Matt Nadareski
c531539c87 Readd accidentally deleted line (fixes #452) 2023-03-11 22:26:45 -05:00
Matt Nadareski
fa04461631 Return list of missing files for Redumper 2023-03-10 20:56:31 -05:00
Matt Nadareski
83d230dfe1 Handle quotes embedded 2023-03-10 17:05:18 -05:00
Matt Nadareski
ca18bbb72c Ensure drive and speed are set 2023-03-10 16:52:46 -05:00
Matt Nadareski
de0f2c1ad9 Update parameters with = handling 2023-03-10 16:43:17 -05:00
Matt Nadareski
2f0019282e Ensure min values are taken care of 2023-03-10 16:13:19 -05:00
Matt Nadareski
72ca479c9f Ensure Redumper parameters are set 2023-03-10 15:33:27 -05:00
Matt Nadareski
de3c4362e7 Fix incorrect image name setting 2023-03-10 15:10:24 -05:00
Matt Nadareski
987348fee8 Use and trim quotes for Redumper 2023-03-10 14:13:36 -05:00
Matt Nadareski
100e012fe6 Fix Redumper path generation 2023-03-10 10:27:09 -05:00
Matt Nadareski
af59ebe1ff Add TOC back as optional file 2023-03-09 13:43:10 -05:00
Matt Nadareski
0762c88655 Fix errant forward slashes 2023-03-09 10:18:23 -05:00
Matt Nadareski
bbe9c94545 Update to DIC 20230309 2023-03-09 10:07:06 -05:00
Matt Nadareski
c7efda7da8 Move drive finding inside of the try/catch 2023-03-09 09:10:29 -05:00
Matt Nadareski
5c78f9bc29 Add identifiers in more places 2023-03-08 22:49:01 -05:00
Matt Nadareski
e7c17c7b4b Remove unsupported identifiers 2023-03-08 22:30:05 -05:00
Matt Nadareski
7a2497f168 Add win7-x64 to identifier list 2023-03-08 22:25:21 -05:00
Matt Nadareski
5172f6f253 Attempt to support Windows 7 2023-03-08 22:11:32 -05:00
Matt Nadareski
9017472fa4 Set saner defaults for dumping speeds
I have to assume that new users will never look a the settings, so max is no longer a default that makes sense.
2023-03-08 11:43:05 -05:00
Matt Nadareski
6659c410c6 Minor cosmetic changes 2023-03-07 10:47:14 -05:00
fuzzball
8c7b66a2f5 Get error count from recent redumper log (#462)
* Get error count from recent redumper log

* Revert "Get error count from recent redumper log"

This reverts commit 50f5f71686.

* Fix to keep existing code format
2023-03-07 07:21:33 -08:00
Matt Nadareski
8f57d78200 Update to BurnOutSharp 2.7.0 2023-03-06 09:52:59 -05:00
Matt Nadareski
ad2ee9efa8 Update changelog 2023-03-04 22:43:44 -05:00
Wilson
192964f65a Originally intended behaviour of the Update Label button. (#459)
* Originally intended behaviour of the Update Label button. Sets up the environment and simply updates the output path and filename. No System, Media, Drive, or Speed changes. It's a very fast update meant for large lots of similar discs.

* Functionized the FastUpdateLabel code path.
Added new UI option to enable Fast Update Label feature.
Added missing function calls. Prevents UI misbehaviour.

* Fixed several glaring holes in the process. ^_^

* Removed some unecessary code.
2023-03-04 19:40:10 -08:00
Matt Nadareski
672f30af35 Handle undetected discs on refresh (fixes #461) 2023-03-03 08:40:33 -05:00
Matt Nadareski
a7a17298f2 Fix incorrect option slider display 2023-03-01 12:34:53 -05:00
Matt Nadareski
e17ad8e4a1 Handle missing extension gracefully (fixes #460) 2023-03-01 10:51:41 -05:00
Matt Nadareski
2a544676e6 Can't publish single file for UI 2023-02-25 10:52:12 -05:00
Matt Nadareski
6abfa9581d Update VSCode config files 2023-02-25 10:38:30 -05:00
Matt Nadareski
a6de548e5d Update changelog 2023-02-24 22:18:22 -05:00
fuzzball
9dc3e579b5 Get the version of redumper (#446)
* Get the version

* Correct the name of redumper (lowercase)
2023-02-24 19:15:58 -08:00
Matt Nadareski
c5be7d7f73 Trim PIC for PS3 (fixes #443) 2023-02-24 16:09:05 -05:00
Matt Nadareski
70c0da703b Use media size for type detection on .NET 6 2023-02-24 15:14:43 -05:00
Matt Nadareski
1cbe81fba6 Add Redumper non-zero data start 2023-02-24 13:40:14 -05:00
Matt Nadareski
420e356f34 Fix Redumper write offset support 2023-02-24 13:32:22 -05:00
Matt Nadareski
0e0ff0cb80 Add Redumper Universal Hash support 2023-02-24 13:00:38 -05:00
Matt Nadareski
9003d05ae2 Make .NET 6 slightly better 2023-02-24 00:17:53 -05:00
Matt Nadareski
c247225cac Remove System.Management 2023-02-23 23:36:19 -05:00
Matt Nadareski
4bccaa8ecf Introduce cross-platform MMI 2023-02-23 22:29:26 -05:00
Matt Nadareski
b579fec7ab Semi-unify drive finding 2023-02-23 21:37:36 -05:00
Matt Nadareski
37e4525c98 Fix typo in DICMultiSectorReadValue (fixes #447) 2023-02-23 21:04:56 -05:00
Matt Nadareski
2269537848 Fix other -windows places for .NET 6 2023-02-23 17:11:46 -05:00
Matt Nadareski
441fb91222 Packaging requires -windows for framework 2023-02-23 17:04:39 -05:00
Matt Nadareski
89e0473019 Handle no drives betterer 2023-02-23 16:59:37 -05:00
Matt Nadareski
3e13b35c84 Handle no drives better 2023-02-23 16:55:27 -05:00
Matt Nadareski
fd4910fc36 Attempt to handle no drives (fixes #444) 2023-02-23 16:50:00 -05:00
Matt Nadareski
964685770f Reformat CICM for old .NET versions 2023-02-23 16:43:36 -05:00
Matt Nadareski
634d64b5a1 Update Redumper to build_106 2023-02-23 16:36:11 -05:00
Matt Nadareski
19ec1c448f Enable .NET 6 Windows builds 2023-02-23 16:33:39 -05:00
Matt Nadareski
e3532d6f02 Fix Aaru removal 2023-02-23 16:26:16 -05:00
Matt Nadareski
aaf0aabb55 Remove Aaru as submodule 2023-02-23 16:11:29 -05:00
Matt Nadareski
575a5936ca Remove usage of Aaru 2023-02-23 15:47:01 -05:00
Matt Nadareski
391d265353 Use relative path output for DIC 2023-02-23 15:22:21 -05:00
Matt Nadareski
f90d19821c Update to DIC 20230201 (fixes #450) 2023-02-23 15:00:07 -05:00
Matt Nadareski
b79babf57e Add nicer failure message (fixes #453) 2023-02-23 14:32:46 -05:00
Matt Nadareski
a929bb0022 Fix relative paths for DIC (fixes #452) 2023-02-23 13:01:17 -05:00
Matt Nadareski
975eb97e27 Be smarter about old paths (fixes #455) 2023-02-23 12:31:27 -05:00
Matt Nadareski
49800cf0ed Go back to pre .NET 7 Aaru 2023-01-20 23:10:31 -08:00
Matt Nadareski
de18609e00 Add drive format (fs) to log 2023-01-20 22:55:13 -08:00
Matt Nadareski
9911446bf9 Revert submodule update 2023-01-20 22:46:02 -08:00
Matt Nadareski
a9223211ab Revert "Update internal Aaru to latest source"
This reverts commit a30ee3e6ff.
2023-01-20 22:45:28 -08:00
Matt Nadareski
54c4eeba03 Revert "Update internal Aaru to latest source"
This reverts commit a30ee3e6ff.
2023-01-20 22:39:21 -08:00
Matt Nadareski
a30ee3e6ff Update internal Aaru to latest source 2023-01-20 22:30:54 -08:00
Matt Nadareski
1fa19489a3 ReadAllText not ReadAllLines 2023-01-20 21:41:05 -08:00
John Veness
f6298dfe89 Make output file options consistent (#449)
Made "Output Protection File" to be consistent with "Output Submission JSON".
2023-01-16 10:32:52 -08:00
Matt Nadareski
79802a53f6 Update nuget packages 2023-01-06 13:54:24 -08:00
Matt Nadareski
54bf43fd6b Use msbuild for .NET Framework 4.8 2022-12-30 11:41:56 -08:00
Matt Nadareski
bb2b2f668b Use msbuild for .NET Framework 4.8 2022-12-30 11:37:14 -08:00
Matt Nadareski
ecca27e012 More strict when custom parameters editing 2022-12-30 11:24:51 -08:00
Matt Nadareski
fe0699ca48 Tweak AppVeyor, show Check 6.0 builds 2022-12-30 11:20:34 -08:00
Matt Nadareski
73b2f0921f Address some UI concerns 2022-12-30 10:59:32 -08:00
fuzzball
c56230c3af Skip during detection (#445) 2022-12-30 09:55:23 -08:00
Matt Nadareski
919b62822d Fix AppVeyor pathing 2022-12-29 22:15:27 -08:00
Matt Nadareski
21b0c9a08d Update options loader with sane defaults (fixes #442) 2022-12-24 12:59:58 -08:00
Matt Nadareski
63fafd05b3 Update to Aaru v5.3.2 LTS 2022-12-23 09:19:01 -08:00
Matt Nadareski
0a2493a953 Fix redumper error count parsing 2022-12-20 23:21:29 -08:00
Matt Nadareski
87ab750714 Fix typo in ToInternalProgram 2022-12-19 22:16:47 -08:00
Matt Nadareski
5cf3eca9eb Fix XGD media type outputs (fixes #441) 2022-12-19 12:38:18 -08:00
Matt Nadareski
b4a079b65f Remove x86 requirement for build 2022-12-19 09:41:59 -08:00
Matt Nadareski
7f2d501edf Output security sectors to info (fixes #440) 2022-12-15 22:14:31 -08:00
Matt Nadareski
c981f94092 Update AppVeyor version 2022-12-15 22:13:01 -08:00
Matt Nadareski
afba46b8b0 Fix a couple redumper things 2022-12-13 20:12:18 -08:00
Matt Nadareski
4e416df3c8 Fix incorrect SetParameters 2022-12-13 20:04:20 -08:00
Matt Nadareski
1b54e52351 Force a filename for redumper 2022-12-13 17:20:45 -08:00
Matt Nadareski
5e1568a148 Add missing redumper output file 2022-12-13 16:39:31 -08:00
Matt Nadareski
8f0ac56cf8 Reenable write offset for all CDs (fixes #439) 2022-12-13 16:16:42 -08:00
Matt Nadareski
37f0f9d4a4 Update README 2022-12-13 15:46:07 -08:00
Matt Nadareski
a48f75c704 Initial attempt at parsing redumper outputs 2022-12-13 15:35:12 -08:00
Matt Nadareski
58a683e3c9 Fix incorrect naming in Options window 2022-12-13 14:15:38 -08:00
Matt Nadareski
907637b128 Update redumper to build 81 2022-12-13 13:52:57 -08:00
Matt Nadareski
85f3e97a44 Add redumper to the UI 2022-12-13 13:47:15 -08:00
Matt Nadareski
beae9691fd Add new parameter and mode validation 2022-12-13 13:25:19 -08:00
Matt Nadareski
b7876d54cc Update redumper strings 2022-12-13 12:09:47 -08:00
Matt Nadareski
5b24223cb5 Merge branch 'master' of https://github.com/SabreTools/MPF 2022-12-13 11:57:06 -08:00
Matt Nadareski
88cadff9ef General UI Cleanup (#438)
* Update Nuget packages to newest stable

* Simplify path selection in UI

* Update changelog

* Fix broken normalization test

* Update drive info before dumping
2022-12-13 11:48:26 -08:00
Matt Nadareski
b92b39e7eb Update Nuget packages to newest stable 2022-12-12 14:45:07 -08:00
Matt Nadareski
8d29a29591 Update changelog 2022-12-04 00:08:53 -08:00
Terry Janas
daf516bf9c Populate internal serial and version from PS3 disc (#432) 2022-12-04 00:03:29 -08:00
Matt Nadareski
115d9857af Add HD-DVD to speed definitions (fixes #429) 2022-12-02 14:39:17 -08:00
Matt Nadareski
b322146e9e Add .NET 6.0 to tests, remove msbuild args 2022-11-19 23:03:26 -08:00
Matt Nadareski
b6e3c9da82 Remove .NET 6.0 from tests, add TODO 2022-11-19 22:39:42 -08:00
Matt Nadareski
6abdc632dc Remove windows from test target 2022-11-19 20:49:59 -08:00
Matt Nadareski
335ca6d5ac Add Xbox Series X short name to list 2022-11-17 18:45:16 -08:00
Matt Nadareski
8752426694 Update changelog 2022-11-17 16:24:45 -08:00
Matt Nadareski
5357ba5900 Add Xbox Series X and PS5 to list, fix Acorn 2022-11-17 16:16:05 -08:00
Matt Nadareski
f2686096bd Add _drive file to zip for UIC (fixes #425) 2022-11-07 09:03:45 -08:00
Matt Nadareski
c4ef14ea3c Bump version to 2.4 2022-10-26 13:08:40 -07:00
Matt Nadareski
03668bd6af Update changelog 2022-10-25 21:11:16 -07:00
fuzzball
2d90a63ca7 Add + to positive offsets in a better way (#422) 2022-10-25 21:07:51 -07:00
Matt Nadareski
11e6e37331 Update changelog 2022-10-24 22:49:09 -07:00
Terry Janas
b229a2d59e Populate internal serial from PS5 disc (#423) 2022-10-24 21:06:57 -07:00
Matt Nadareski
c61af9316f Move to unused Chime class 2022-10-21 23:06:07 -07:00
Matt Nadareski
02e3040e1b Add unused notification method 2022-10-21 22:41:23 -07:00
Matt Nadareski
60bbe29435 Update changelog 2022-10-21 11:42:15 -07:00
Matt Nadareski
3274ea08aa Add PS4 version finding (tjanas) 2022-10-21 11:34:14 -07:00
Terry Janas
97a61dea32 Populate internal serial from PS4 disc (#421)
* Populate internal serial from PS4 disc

* Move GetPlayStation4Serial to that it precedes GetPlayStation4Version
2022-10-21 11:32:35 -07:00
Matt Nadareski
6cccf20b03 Fix layerbreak-based checks (fixes #399) 2022-10-21 11:06:27 -07:00
Matt Nadareski
0a7e17ed00 Fix readonly Filename info display 2022-10-20 14:21:09 -07:00
Matt Nadareski
706f75c0eb Fix readonly Filename info display 2022-10-20 14:21:03 -07:00
Matt Nadareski
022e87c4bb Fix NRE with offsets 2022-10-20 13:03:07 -07:00
Matt Nadareski
8b29ac7e47 Update issue templates to be more accurate 2022-10-20 13:00:07 -07:00
Matt Nadareski
4d5b8baf6f Add logging to !submissionInfo formatting failure 2022-10-20 12:45:01 -07:00
Matt Nadareski
e199e5a08c Add logging to !submissionInfo writing failure 2022-10-20 12:23:35 -07:00
Matt Nadareski
6cc2351bf7 Fix multiple DiscType* for DIC 2022-10-20 12:17:05 -07:00
Matt Nadareski
c391dbd3c8 Fix multiple DiscType for DIC 2022-10-19 23:23:04 -07:00
Matt Nadareski
709d980b67 Add disc type parsing for Aaru and DIC 2022-10-19 12:57:46 -07:00
Matt Nadareski
0903855d5c Add framework for reported disc type 2022-10-19 12:34:40 -07:00
Matt Nadareski
6ad2505bf8 Put Redump limitations behind existing flag 2022-10-18 17:03:21 -07:00
Matt Nadareski
5db8756639 Disable XGD1 PVD reporting 2022-10-18 16:18:49 -07:00
Matt Nadareski
5d176408a2 Disable XGD layerbreak reporting 2022-10-18 16:16:40 -07:00
Matt Nadareski
ab0b569798 Disable XGD version reporting 2022-10-18 16:12:40 -07:00
Matt Nadareski
ee76d49e56 Disable layerbreak generation for BD 2022-10-18 16:08:38 -07:00
Matt Nadareski
c75d2dcae2 Add + to positive offsets 2022-10-18 11:49:57 -07:00
Matt Nadareski
7c411d36db Add MS ZipFile package to MPF.Library 2022-10-17 21:04:58 -07:00
Matt Nadareski
ca767cf576 Change location of dumping info (fixes #415) 2022-10-17 20:56:28 -07:00
Matt Nadareski
b57e0bb97e Remove extraneous packages 2022-10-17 16:58:27 -07:00
Matt Nadareski
5f059253a4 Add CodePages package to MPF.Library 2022-10-17 16:42:04 -07:00
Matt Nadareski
e0f8443653 Add System.Memory package to MPF.Library 2022-10-17 16:20:41 -07:00
Matt Nadareski
4c23a4bbf3 Add compression result reason to log 2022-10-17 15:41:00 -07:00
Matt Nadareski
0c1486bbce Update BurnOutSharp to 2.3.4 2022-10-17 13:41:21 -07:00
Matt Nadareski
6f41c9a331 Remove deprecated protection setting 2022-10-17 11:14:41 -07:00
Matt Nadareski
2879dd29d6 Update Nuget packages 2022-10-17 11:00:51 -07:00
Matt Nadareski
42e1ef45b4 Add multi-language helper for filter 2022-10-16 23:15:09 -07:00
Matt Nadareski
19493fdf0c Add language filtering to formatter 2022-10-16 22:59:54 -07:00
Matt Nadareski
5870ad0673 Add unused article formatter 2022-10-16 21:21:56 -07:00
Matt Nadareski
cbd2850d1b Add specialized CDS/SafeDisc filter 2022-10-16 14:27:26 -07:00
Matt Nadareski
e7c36c104a Fix hardware info 2022-10-13 00:04:28 -07:00
Matt Nadareski
960840d9ba Add hardware info to DIC and Aaru 2022-10-12 21:40:38 -07:00
Matt Nadareski
b8ac1bc9d4 Fix missing info in Aaru 2022-10-12 21:27:58 -07:00
Matt Nadareski
89edf9a8f6 Create MPF log helper and filter for deletion 2022-10-11 13:19:06 -07:00
Matt Nadareski
eb8db0b311 Try adding MPF logs to zip 2022-10-11 12:54:11 -07:00
Matt Nadareski
b10cf8b78a Enable separated protection info output by default 2022-10-11 10:11:48 -07:00
Matt Nadareski
1dd5c0d6d0 Fix missing info reference change 2022-10-06 14:24:47 -07:00
Matt Nadareski
a1e00d23a4 Update Aaru submodules 2022-10-06 11:57:05 -07:00
Matt Nadareski
1a9d38dd0e Add dumping info section skeleton 2022-10-06 11:11:23 -07:00
Matt Nadareski
2f3e7d105d Add dumping info section skeleton 2022-10-06 10:25:27 -07:00
Matt Nadareski
a72b3c32b1 Report specific DIC version, if possible 2022-10-06 10:04:56 -07:00
Matt Nadareski
a479b16ae2 Add initial framework for reporting dumping program 2022-10-05 15:43:02 -07:00
Matt Nadareski
a9e7b6f5b3 Add unused command filename parser 2022-10-05 12:08:11 -07:00
Matt Nadareski
60605d7d00 Merge branch 'master' of https://github.com/SabreTools/MPF 2022-10-04 20:50:38 -07:00
Matt Nadareski
9dc7f726e9 Use default directory for folder browsing, if possible (fixes #412) 2022-10-04 20:45:28 -07:00
Whovian9369
46134032d6 Add a check for the PS4 Update file, and add some file info to the comments. (#411) 2022-10-04 20:14:50 -07:00
Markus Persson
9847c8f351 Update Drive.cs (#410)
Fixed console return typo.
2022-10-04 13:50:37 -07:00
Matt Nadareski
7b506586cd Add PS3 folder/file checks for detection (fixes #409) 2022-10-04 13:36:50 -07:00
Matt Nadareski
40bbd422b7 Possibly fix PIC parsing 2022-09-28 13:38:50 -07:00
Matt Nadareski
285fd69ab4 Update to DIC 20220909 2022-09-28 13:15:05 -07:00
Matt Nadareski
b436e19bbb Add important Redumper note 2022-09-28 13:04:17 -07:00
Matt Nadareski
bf6e079289 Minor Redumper cleanup 2022-09-28 12:49:17 -07:00
Matt Nadareski
b623866b6e Create Redumper extensions class 2022-09-28 12:43:40 -07:00
Matt Nadareski
df7046723c Add Redumper command generation 2022-09-28 12:38:23 -07:00
Matt Nadareski
3cd3047790 Add Redumper command validation 2022-09-28 12:22:14 -07:00
Matt Nadareski
abd60612c5 Add Redumper dumping command 2022-09-28 11:52:56 -07:00
Matt Nadareski
02292acee1 Add Redumper command support and reset 2022-09-28 11:48:25 -07:00
Matt Nadareski
c923899898 Add Redumper parameter values 2022-09-28 11:33:59 -07:00
Matt Nadareski
fcdd2ad036 Add Redumper constants 2022-09-28 11:23:04 -07:00
Matt Nadareski
02d3af0ac1 Download Redumper with AppVeyor 2022-09-28 11:12:03 -07:00
Matt Nadareski
0516bccece Add skeleton for Redumper 2022-09-27 23:08:12 -07:00
Matt Nadareski
2d6389d54d Fix ringcode guide images (fixes #407) 2022-09-27 22:54:23 -07:00
Matt Nadareski
60d5a117b5 Electronic not Electric (fixes #402) 2022-08-30 10:42:04 -07:00
Matt Nadareski
9b7e2e35f5 Verify GD-ROM outputs, finally (fixes #401) 2022-08-29 19:49:31 -07:00
Matt Nadareski
ddfb383f23 Add Sony Electronic Book system (fixes #400) 2022-08-29 10:42:20 -07:00
Matt Nadareski
a1707486f4 Update Aaru submodule to latest devel 2022-08-29 10:36:27 -07:00
Matt Nadareski
451101ec67 Update PSX content check 2022-08-25 14:38:28 -07:00
Matt Nadareski
83c583b7e6 Update to new constructor 2022-08-25 11:54:55 -07:00
Matt Nadareski
33cb3d3c97 Fix missing assignment 2022-08-25 11:36:42 -07:00
Matt Nadareski
3d98f345c1 Fix psxt001z namespace 2022-08-25 10:45:22 -07:00
Matt Nadareski
de8ce5c110 Add internal name for Cleanrip outputs (fixes #398) 2022-08-25 10:38:20 -07:00
Matt Nadareski
fd9ed77316 Fix missing parenthesis 2022-08-25 10:23:28 -07:00
Matt Nadareski
d974b73cce Fix serial parsing for Dreamcast (fixes #397) 2022-08-25 10:18:35 -07:00
Matt Nadareski
da8a67fcad Update to BurnOutSharp 2.3.1 (nw) 2022-08-25 10:11:28 -07:00
Matt Nadareski
471bc60ed7 Add Mattel Fisher-Price iXL detection 2022-08-25 10:07:15 -07:00
Matt Nadareski
fef46be34c Update to BurnOutSharp 2.3.0 (nw) 2022-08-25 09:48:37 -07:00
Matt Nadareski
9a92dbdedb Give .NET 6 priority for web calls 2022-07-26 13:47:19 -07:00
Matt Nadareski
d6be0a4154 Trim leading file paths for XBONE (fixes #394) 2022-07-25 09:38:02 -07:00
Matt Nadareski
d3ae372903 Disable nonstandard BD-ROM sizes 2022-07-08 22:05:24 -07:00
Matt Nadareski
7621ef1a13 Fix .bin file paths; update internal filename generation 2022-07-08 14:03:44 -07:00
Matt Nadareski
148fdd0590 Update to DIC 20220707 (possibly nw) 2022-07-08 13:37:51 -07:00
Matt Nadareski
232256310a Update changelog 2022-07-05 22:51:21 -07:00
Matt Nadareski
5e938791ef Add files for XBONE (fixes #390) 2022-07-05 22:43:28 -07:00
Matt Nadareski
08405dd9b4 Framework for XBONE filenames 2022-07-05 22:11:21 -07:00
Matt Nadareski
512c637ea3 Add Bandai Playdia QIS detection (fixes #393) 2022-07-05 22:06:04 -07:00
Matt Nadareski
909ebd72a1 Add Sharp X68k detection (fixes #387) 2022-07-05 22:02:31 -07:00
Matt Nadareski
123136e90e Fix incomplete system name detection 2022-05-29 14:07:38 -07:00
Matt Nadareski
299d25af27 Implement Drive.Create for safety 2022-04-20 13:47:55 -07:00
Matt Nadareski
9eee2f6444 Update changelog 2022-04-20 12:36:11 -07:00
Matt Nadareski
2afb10b73b Organize projects in solution 2022-04-20 12:33:47 -07:00
Matt Nadareski
1b016e5915 Add PIC.bin to log zip 2022-04-19 22:50:43 -07:00
Matt Nadareski
1c403e1748 Possibly fix tab regex replacement 2022-04-19 22:40:08 -07:00
Matt Nadareski
a372a133ca Reorganize GetMediaType 2022-04-19 21:07:09 -07:00
Matt Nadareski
08a7df504b Simplify IsOptical (.NET 6) 2022-04-19 20:39:42 -07:00
Matt Nadareski
39f9d9a86d Add optical media support method (.NET 6) 2022-04-19 20:25:36 -07:00
Matt Nadareski
bdd5af65ce Better get drive list (.NET 6) 2022-04-19 20:19:22 -07:00
Matt Nadareski
92d7d2ab91 Fix CD-R multisession info (fixes #384) 2022-04-19 17:12:37 -07:00
Matt Nadareski
8d4d7ce449 Update changelog 2022-04-19 16:53:58 -07:00
Matt Nadareski
d2d650cace Use Aaru for media type retrival (.NET 6) 2022-04-19 16:53:13 -07:00
Matt Nadareski
772cefd700 Multisession is multi-line 2022-04-19 12:49:18 -07:00
Matt Nadareski
e20350160b Add Aaru as a submodule for .NET 6 2022-04-18 22:59:07 -07:00
Matt Nadareski
08f4e91b27 Update solution file for VS2022 2022-04-18 21:53:20 -07:00
Matt Nadareski
021237bc38 Sync to newest CICM 2022-04-18 21:34:48 -07:00
Matt Nadareski
78c36db2f9 Avoid whitespace changes for PVD, Header, and Cuesheet 2022-04-18 21:20:12 -07:00
Matt Nadareski
7fd2562cb5 Add filesystem logging for .NET 6 2022-04-18 12:42:18 -07:00
Matt Nadareski
15a3be2e66 Update changelist 2022-04-18 11:03:34 -07:00
Matt Nadareski
9b6a540ec6 Remove .NET Core 3.1 entirely 2022-04-18 11:03:21 -07:00
Matt Nadareski
2b51085bc2 Remove .NET Core 3.1 from test project for now 2022-04-17 22:53:07 -07:00
Matt Nadareski
822134070b .NET 6.0 and Cleanup 2022-04-17 22:39:39 -07:00
Matt Nadareski
26cd874779 Update to BurnOutSharp 2.1.0 2022-04-17 22:14:13 -07:00
Matt Nadareski
9f21b68541 Use built-in NETFRAMEWORK directive 2022-04-17 21:54:04 -07:00
Matt Nadareski
e49d95663b Gate ManagmentObject use further 2022-04-17 21:30:04 -07:00
Matt Nadareski
2c70392ada Add size-based media type detection for non-Framework 2022-04-17 21:00:02 -07:00
Matt Nadareski
1b2248b1e7 Revert AppVeyor to VS2019 for now 2022-04-17 16:34:33 -07:00
Matt Nadareski
a322dc6353 Update copyright date to 2022 2022-04-17 16:29:28 -07:00
Matt Nadareski
7c78ae47c6 Remove needless csproj constants 2022-04-17 16:28:09 -07:00
Matt Nadareski
a14dec1b7e Update Nuget packages 2022-04-17 16:22:11 -07:00
Matt Nadareski
53b5862697 Update AppVeyor to VS2022 2022-04-17 16:19:33 -07:00
Matt Nadareski
83d379c7b5 Convert internal libraries to .NET Standard 2.0 2022-04-17 16:18:40 -07:00
Matt Nadareski
be33db8339 Sanitize whitespace around tabs 2022-04-16 12:15:23 -07:00
Matt Nadareski
97e9924a0b Be more picky about multisession 2022-04-12 22:04:55 -07:00
Matt Nadareski
7daab55639 Fix clone issue with copy protection 2022-04-12 21:22:49 -07:00
Matt Nadareski
9d70b7469a Convert triple space to tab 2022-04-12 17:03:32 -07:00
Matt Nadareski
b5504902c4 Parse and format CD multisession data (fixes #370) 2022-04-12 16:09:18 -07:00
Matt Nadareski
f5e82ccd75 Separate common arguments to new helper 2022-04-12 12:06:45 -07:00
Matt Nadareski
f4c4c21a10 Consolidate Redump login testing 2022-04-12 12:01:26 -07:00
Matt Nadareski
661d2440f2 Move helper methods around 2022-04-12 11:51:05 -07:00
Matt Nadareski
f4af8097f6 Move and update options loader; clean up Check 2022-04-12 11:27:10 -07:00
Matt Nadareski
e1822905e7 Add multisession helper method skeleton 2022-04-12 10:18:52 -07:00
Matt Nadareski
e5154dad5b Add multisession pseudo-tag 2022-04-12 10:10:07 -07:00
Matt Nadareski
30f8932039 Rename MPF.GUI to MPF.UI 2022-04-11 12:01:16 -07:00
Matt Nadareski
c4f0792c77 Create core UI library 2022-04-11 10:32:03 -07:00
Matt Nadareski
79c7f13ff9 Add warning to tooltip 2022-04-10 22:27:05 -07:00
Matt Nadareski
dbeeb0c69b Make protection field user-editable (fixes #340) 2022-04-10 22:23:15 -07:00
Matt Nadareski
58c53ff5e2 Add option for copy protection file output (fixes #372) 2022-04-10 22:12:20 -07:00
Matt Nadareski
87c441887a Convert status label to TextBlock (fixes #382) 2022-04-10 21:11:50 -07:00
Matt Nadareski
8ee4dab239 Combine cases in protection scan 2022-04-10 16:14:13 -07:00
Matt Nadareski
e01ebf8d8e Explicitly sanitize '?' from path 2022-04-09 13:21:31 -07:00
Matt Nadareski
e92bcd378c Disable PVD creation for Aaru 2022-04-08 20:55:33 -07:00
Matt Nadareski
80156e73d1 Even even stricter copy protection output 2022-04-07 20:56:28 -07:00
Matt Nadareski
97803cd860 Report dictionary to InfoTool 2022-04-04 22:44:15 -07:00
Matt Nadareski
9a5feee095 Even stricter output for copy protection section 2022-04-04 22:25:07 -07:00
Matt Nadareski
84410056bd Compress JSON for artifacts alone (fixes #379) 2022-04-04 22:15:50 -07:00
Matt Nadareski
acd0e41703 Further separate out protection scan outputs 2022-04-04 22:06:56 -07:00
Matt Nadareski
c7dfb9dca7 Increase JSON accuracy for disc types (fixes #381) 2022-04-04 21:46:13 -07:00
Matt Nadareski
0efd82bd59 Normalize newlines in comments and contents (fixes #380) 2022-04-04 21:12:50 -07:00
John Veness
426ceff451 Fix RingCodeGuideWindow.xaml so text matches images (#378)
* Fix RingCodeGuideWindow.xaml so text matches image

* Fix RingCodeGuideWindow.xaml so text matches img 2
2022-04-01 09:57:07 -07:00
Matt Nadareski
f2be7ed34c Update Nuget packages 2022-03-27 20:57:42 -07:00
Matt Nadareski
5dcc783b95 Fix a couple Redump URL-related things 2022-03-25 09:28:11 -07:00
Matt Nadareski
d1c641e934 Fix ring serialization (fixes #376) 2022-03-25 09:00:16 -07:00
Wilson
59be63785d Windows 7 UI Fix (#375)
* Updated the Main Menu grid width setting so that the Help menu now appears proprely on Windows 7.

* Changed the Secret log level color from Blue to Teal so that the text is much more readable.

* Undid the Secret color change.
2022-03-15 16:25:43 -07:00
Matt Nadareski
0bf85ec729 Refine copy protection section showing 2022-03-13 13:14:37 -07:00
Matt Nadareski
b54c2dc254 Clear out fully matched IDs from the partial list 2022-03-12 22:19:03 -08:00
Matt Nadareski
021fcd0641 Fix submission info clone 2022-03-12 22:06:04 -08:00
Matt Nadareski
f2dadae7a3 Add both fully and partially matching to info file 2022-03-12 21:27:23 -08:00
Matt Nadareski
7ce7df2625 Explicitly clear list, just in case 2022-03-12 21:14:26 -08:00
Matt Nadareski
09fcd384ab Make fully and partially matching IDs more apparent
Add write offset as read-only field
2022-03-12 21:09:51 -08:00
Matt Nadareski
5f8625a384 Fix tabs in Games and Videos boxes (fixes #373) 2022-03-09 15:34:44 -08:00
Matt Nadareski
621011af7a Remove redundant check around volume label 2022-03-09 14:25:01 -08:00
Matt Nadareski
a0b81941d1 Return faster on empty protection sets 2022-03-08 22:37:12 -08:00
Matt Nadareski
e735335773 Update Aaru Nuget package 2022-03-08 09:13:37 -08:00
Matt Nadareski
7b62572a56 Handle sanitized protections edge case 2022-03-06 22:19:04 -08:00
Matt Nadareski
0f921c926b Reorder event handers 2022-03-06 00:04:23 -08:00
Matt Nadareski
4d8a4d23c0 Add upper case <TAB> processing 2022-03-05 23:20:13 -08:00
Matt Nadareski
9d7eaa46fd Update to Aaru v5.3.1 LTS 2022-03-05 21:53:52 -08:00
Matt Nadareski
cc9664f7d6 Track last used drive on refresh; Clean up pre-dump validation 2022-03-05 21:50:18 -08:00
Matt Nadareski
573b3e9d1c Fix "missing" option in window 2022-03-05 21:05:34 -08:00
Matt Nadareski
9808694d89 Update changelog 2022-03-01 23:04:43 -08:00
Wilson
d70d8f5b6e Added a button to quickly update the volume label and dump path. (#369)
* Added a button to quickly update the volume label and dump path.
Behaves identically to switching the selected drive combo box. (Calls the InitializeUIValues method with rescanDrives set to false.)

* Typo fixed the odd w.
2022-03-01 21:43:05 -08:00
Matt Nadareski
b75c2d80bf Force internal drive refresh 2022-03-01 16:41:54 -08:00
Matt Nadareski
aa747ff651 Update to DIC 20220301 2022-03-01 13:27:31 -08:00
Matt Nadareski
bcbf5daf0d Add Xbox One system detection (fixes #368) 2022-02-24 11:22:04 -08:00
Matt Nadareski
aee1c05a45 Update Nuget packages to newest stable 2022-02-24 10:58:36 -08:00
Matt Nadareski
0bb96a8dd3 Specifically include Unsafe Nuget package 2022-02-22 09:08:29 -08:00
Matt Nadareski
1e1d2c7b63 Move path normalization to better place 2022-02-13 22:49:33 -08:00
Matt Nadareski
f12375cddc Assign normalized path to parameters 2022-02-12 23:25:34 -08:00
Matt Nadareski
ed26e6611a Cap X360 directory check to 500MB (fixes #362) 2022-02-11 12:59:37 -08:00
Matt Nadareski
98f77eca07 Add option to limit region and language selections (fixes #361) 2022-02-11 09:55:00 -08:00
Matt Nadareski
1b2b560f8f Fix failing module tests 2022-02-10 13:40:07 -08:00
Matt Nadareski
b49cc0c9bd Fix small DVD layer finding corner case (fixes #363) 2022-02-10 12:02:54 -08:00
Matt Nadareski
4ba58ea861 Make FillFromRedump private again 2022-02-10 11:54:25 -08:00
Matt Nadareski
3f52a20c90 Add /mr default flag options (fixes #360) 2022-02-10 11:52:13 -08:00
Matt Nadareski
580089d06e Fix Redump disc title pulling (fixes #359) 2022-02-10 11:37:36 -08:00
Matt Nadareski
1397ab0fa6 Update to DIC 20211001
Note that there are some code changes that could affect how things like offsets are parsed. Testing is needed.
2022-02-10 11:21:38 -08:00
Matt Nadareski
87400793eb Bump version to 2.3 2022-02-05 13:45:06 -08:00
Matt Nadareski
45f79d95b1 Adjust paths for DIC just before dumping (fixes #358) 2022-02-04 20:46:56 -08:00
Matt Nadareski
c8a4a61028 Normalize PS1/PS2 executable names 2022-02-03 22:28:14 -08:00
Matt Nadareski
44091981b2 Check explicitly for no matches on Redump pull (fixes #357) 2022-02-03 15:43:17 -08:00
Matt Nadareski
d3352643fc Ensure drive is not null for volume labels (fixes #356) 2022-02-02 20:05:15 -08:00
Matt Nadareski
114c7fb38a Make error clearer if something is unsupported in Check 2022-02-02 19:59:53 -08:00
Matt Nadareski
dc7da708dc Add alternate pseudo-tag for Playable Demos 2022-02-02 12:54:08 -08:00
Matt Nadareski
72e56aa1c7 Ensure Games pseudo-tag is multi-line 2022-02-02 12:50:32 -08:00
Matt Nadareski
99ceab07ad Ensure version only pulled if one doesn't exist (fixes #355) 2022-02-01 20:54:26 -08:00
Matt Nadareski
c0f6c072ce Read longer string for Saturn internal serial 2022-02-01 20:51:33 -08:00
Matt Nadareski
e039124f6c Add verification reminders for pulled tags 2022-02-01 13:04:28 -08:00
Matt Nadareski
c96e4a4c7a Add another hand-formatted version of SS tag 2022-01-31 10:50:53 -08:00
Matt Nadareski
622a08acf3 Update changelog 2022-01-31 09:49:07 -08:00
Matt Nadareski
f44b6bf0d0 Slightly rename UK and USA regions for UI 2022-01-31 09:47:42 -08:00
Matt Nadareski
a6d75e15ea Check for $SystemUpdate folder for X360 discs 2022-01-30 20:51:47 -08:00
Matt Nadareski
a02f03c4cb Move internal serial before volume label 2022-01-30 15:57:12 -08:00
Matt Nadareski
d48f5132fb Add another hand-formatted version of SS tag 2022-01-28 23:12:25 -08:00
Matt Nadareski
ed4ac24efa Add Sierra ID to list of pseudo-tags 2022-01-28 09:24:37 -08:00
Matt Nadareski
9f3b8a7c2c Adjust long names for some languages 2022-01-27 21:40:23 -08:00
Matt Nadareski
612d4bb1f5 Fix incorrect region two-letter code 2022-01-27 21:25:58 -08:00
Matt Nadareski
b58a50d246 Disable unnecessary cuesheet parsing 2022-01-27 20:49:32 -08:00
Matt Nadareski
af83811d57 Fix parsing of non-tag tags again 2022-01-27 17:17:42 -08:00
Matt Nadareski
66835fe6ab Fix non-tag tag shortnames 2022-01-27 16:27:40 -08:00
Matt Nadareski
34cc1d33c6 Hook up additional Xbox field to disc info window 2022-01-27 16:04:14 -08:00
Matt Nadareski
a42d14e3b8 Fix incorrect language three-letter code 2022-01-27 15:39:09 -08:00
Matt Nadareski
87aa165edf Add more non-tag support; rearrange info window 2022-01-27 13:35:47 -08:00
Matt Nadareski
d217d62007 Start supporting ordered tags and non-tags 2022-01-27 12:13:17 -08:00
Matt Nadareski
27bcc0d40a Better helper method organization 2022-01-27 10:58:40 -08:00
Matt Nadareski
e1df075cde Make site code formatting helper method 2022-01-27 10:51:27 -08:00
Matt Nadareski
8358692e8d Ensure ordering in output site tags 2022-01-27 10:45:48 -08:00
Matt Nadareski
e1fae01dab Add support for all ISO region codes 2022-01-26 23:29:06 -08:00
Matt Nadareski
d206ab140a Add support for all ISO language codes 2022-01-26 21:38:11 -08:00
Matt Nadareski
9d8722ab17 Try to delete old log archive before writing (fixes #348) 2022-01-26 09:51:37 -08:00
Matt Nadareski
c4fa40c403 Sync with Redump region and language selection (fixes #349) 2022-01-26 09:41:34 -08:00
Matt Nadareski
1d0b06bfbe Use volume label in checks, not formatted version 2022-01-20 13:20:15 -08:00
Matt Nadareski
2cdf473dcb Be smarter about volume labels 2022-01-20 13:15:53 -08:00
Matt Nadareski
1af9e2c2da Conditionally pull region from Redump 2022-01-14 16:30:20 -08:00
Matt Nadareski
9a1815fa1e Differentiate XMID and XeMID 2022-01-14 13:06:11 -08:00
Matt Nadareski
f601961c49 Fix crash on invalid parameters 2022-01-13 10:25:57 -08:00
Matt Nadareski
406acd34c5 Fix Sega CD internal serial reading 2022-01-09 22:38:22 -08:00
Matt Nadareski
31cdcbbc25 Reformat Saturn internal date (fixes #346) 2022-01-09 14:44:55 -08:00
Matt Nadareski
2215ce71c9 Make protection read-only field multiline 2022-01-07 21:41:49 -08:00
Matt Nadareski
1872fbb1c8 Fix incorrect header check 2022-01-07 20:57:47 -08:00
Matt Nadareski
d99f912ac2 Add hidden debug option for "ShowDebugViewMenuItem" (fixes #334) 2022-01-07 13:25:46 -08:00
Matt Nadareski
00a76fb648 Only include booleans if the value is true 2022-01-07 12:49:10 -08:00
Matt Nadareski
187e951a47 Fix IsReadOnly 2022-01-07 09:04:12 -08:00
Matt Nadareski
c0b9b27aae Adjust width ratios for disc info window 2022-01-06 22:19:06 -08:00
Matt Nadareski
b76bb17396 Convert postgap and VCD fields to checkboxes 2022-01-06 21:51:20 -08:00
Matt Nadareski
2efa6d3623 Fix scrolling issues in disc info window 2022-01-06 21:31:46 -08:00
Matt Nadareski
3972ce633d Changed IsEnabled to IsReadOnly 2022-01-06 21:30:47 -08:00
Matt Nadareski
0dc7901393 Further disc info window tweaks 2022-01-06 17:08:45 -08:00
Matt Nadareski
a25ba6eaa5 Add tab setting (fixes #303) 2022-01-06 15:25:25 -08:00
Matt Nadareski
4ea48dfe57 Tweak minimalized layout a bit more 2022-01-06 14:57:38 -08:00
Matt Nadareski
8f7ad8b2ee Unban newly opened consoles 2022-01-06 10:23:07 -08:00
Matt Nadareski
3b9800df07 Add <tab> processing 2022-01-04 21:28:51 -08:00
Matt Nadareski
4c80d3234e Logically group more things in disc info window 2022-01-04 21:19:46 -08:00
Matt Nadareski
9e4af1d66b Remove Enter/Escape registration on disc info window 2022-01-04 11:13:17 -08:00
Matt Nadareski
73555df2ea Omit volume label for "Audio CD" (fixes #343) 2022-01-03 21:26:54 -08:00
Matt Nadareski
3ca78604fd Fix InfoTool tests 2022-01-02 22:01:21 -08:00
Matt Nadareski
0138046923 Add newlines for mutliline special fields 2022-01-02 21:54:49 -08:00
Matt Nadareski
2129184209 Add missing continue statement 2022-01-02 13:58:58 -08:00
Matt Nadareski
dd2116f8a6 Fix newline skipping 2022-01-01 22:24:03 -08:00
Matt Nadareski
814c2d9149 Force scroll visibility, tweak text sizes again 2022-01-01 21:42:05 -08:00
Matt Nadareski
b3f7276044 Skip unnecessary newlines in parsing 2022-01-01 21:36:39 -08:00
Matt Nadareski
ad88aa980b Tweak more Disc Info window formatting 2022-01-01 21:13:35 -08:00
Matt Nadareski
aca55e9203 Handle pulled linebreaks better, again 2022-01-01 20:46:44 -08:00
Matt Nadareski
cc3330bb27 Skip anti-modchip string in some cases 2022-01-01 20:42:52 -08:00
Matt Nadareski
1370909db7 Handle pulled linebreaks better (fixes #342) 2022-01-01 14:18:14 -08:00
Matt Nadareski
08cc0c394b Sanitize filename after check (fixes #341) 2022-01-01 14:16:58 -08:00
Matt Nadareski
cb6692aea3 Add even more safety to clone 2022-01-01 13:58:33 -08:00
Matt Nadareski
b3badb3a55 Add first attempt 2-layer ringcode guide 2021-12-31 14:21:14 -08:00
Matt Nadareski
cbf73901d3 Add model for 2-layer ringcode guide 2021-12-31 13:54:31 -08:00
Matt Nadareski
4822e45d58 Ensure all fields are read-only on read-only tab 2021-12-30 22:21:33 -08:00
Matt Nadareski
1d930d36bf Be smarter about showing update checks 2021-12-30 21:00:01 -08:00
Matt Nadareski
9effcc403d Allow internal serial and volume label to be hidden 2021-12-30 20:56:55 -08:00
Matt Nadareski
05dcc039bd Tweak new disc information fields and tabs 2021-12-30 20:48:32 -08:00
Matt Nadareski
cb08656abc Add horizontal scroll to user input 2021-12-30 20:45:15 -08:00
Matt Nadareski
69b22fc736 Show most read-only fields in new tab (fixes #301) 2021-12-30 17:27:08 -08:00
Matt Nadareski
bf857f6ce7 Fix CSSKey log handling (fixes #333) 2021-12-30 15:47:44 -08:00
Matt Nadareski
7ebf2378b3 Try to handle multi-line fields during parsing 2021-12-30 15:36:58 -08:00
Matt Nadareski
aec25dab37 Clean up default handling of fields 2021-12-30 15:23:17 -08:00
Matt Nadareski
e11969780d Add new tabs for special site information 2021-12-30 14:52:49 -08:00
Matt Nadareski
02c98b1547 Add internal structure for special site codes 2021-12-30 13:00:55 -08:00
Matt Nadareski
c864589478 Start overhauling Redump information pulling, again 2021-12-30 11:09:37 -08:00
142 changed files with 28152 additions and 15616 deletions

View File

@@ -10,7 +10,6 @@ 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.
- .NET Core 3.1 and .NET 5.0 have known limitations, so make sure that what you're asking for isn't already in another build.
- 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

@@ -10,7 +10,6 @@ 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.
- .NET Core 3.1 and .NET 5.0 have known limitations, so make sure that what you're giving information on isn't already in another build.
- 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

@@ -10,7 +10,6 @@ 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.
- .NET Core 3.1 and .NET 5.0 have known issues, please try using another build to reproduce the error
- Check multiple discs to help narrow down the issue
- Check the Options to see if changing any of those affects your issue.
@@ -25,9 +24,9 @@ What version are you using?
**Build**
What runtime version are you using?
- [ ] .NET Framework 4.7.2 running on (Operating System)
- [ ] .NET Framework 4.8 running on (Operating System)
- [ ] .NET Core 3.1 running on (Operating System)
- [ ] .NET 6.0 running on (Operating System)
- [ ] .NET 7.0 running on (Operating System)
**Describe the issue**
A clear and concise description of what the bug is.

3
.gitmodules vendored
View File

@@ -1,3 +0,0 @@
[submodule "MPF.Library/Aaru/CICMMetadata"]
path = CICMMetadata
url = https://github.com/claunia/CICMMetadata

4
.vscode/launch.json vendored
View File

@@ -10,9 +10,9 @@
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/MPF/bin/Debug/netcoreapp3.1/MPF.dll",
"program": "${workspaceFolder}/MPF.Check/bin/Debug/net6.0/MPF.Check.dll",
"args": [],
"cwd": "${workspaceFolder}/MPF",
"cwd": "${workspaceFolder}/MPF.Check",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
"console": "internalConsole",
"stopAtEntry": false

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"omnisharp.projectLoadTimeout": 480
}

16
.vscode/tasks.json vendored
View File

@@ -7,21 +7,7 @@
"type": "process",
"args": [
"build",
"${workspaceFolder}/MPF/MPF.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/MPF/MPF.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
"${workspaceFolder}/MPF.sln",
],
"problemMatcher": "$msCompile"
},

View File

@@ -1,4 +1,748 @@
### 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
- Add path variants for PlayStation info
- Convert Drive to use paths internally
- Create skeleton for first-run
- Show options window on first run
- Fix drive letter issue in UI
- Add first-run Options title, fix saving bug
- Fix multiple handler invocation
- Create method for applying theme
- Fix drive letter check
- Simply theme application
- Add framework for deleteable files
- Add deleteable file lists for Redumper and DIC
- Add optional file deletion
- Rearrange OptionsWindow to be easier to navigate
- Fix up DumpEnvironment a bit
- Add filename suffix setting (nw)
- Wire through filename suffix
- Expose suffix setting
- Set UDF CD threshold at 800MB (Deterous)
- Update package versions
- Attempt to parse out PS5 params.json
- Fix CRC32 hashing
- Update XUnit packages
- Update to BurnOutSharp 2.9.0
- Update to MMI 3.0.0-preview.4
- Fix two small nullability issues
- Use Array.Empty in hasher
- Always use relative path internally (Deterous)
### 2.7.2 (2023-10-17)
- Fix options loading for Check
- Cleanup and gated code
- Gate some switch expressions
- Suppress some unnecessary messages
- Disable dumping button when Redumper selected with unsupported media type (Deterous)
- Improve check for which program supports which media (Deterous)
- Remove code for getting Universal Hash from DIC logs (Deterous)
- Fix Redumper retry count not showing
- Enable parameters checkbox by default
- Update Redumper to build 230
- Fix GetPVD in dic (fuzz6001)
- Get SecuROM data from Redumper
- Clean up issue templates
- Support Redumper 231 outputs
### 2.7.1 (2023-10-11)
- Add pull-all flag for Check
- Fix errant version detection issue
- Allow user-supplied information in Check
- Add BE read flag for Redumper
- Add generic drive flag for Redumper
- Handle numeric disc titles as issue numbers
- Unify handling of enable/disable events
- Enable nullability in Check
- Remove all but .NET 6 for AppVeyor packaging
- Place message boxes at center of main window (Deterous)
- Enable nullability in MPF
- Enable nullability in MPF.UI.Core
- Enable nullability in MPF.Test
- Handle some suggested changes
- Var-ify many instances
- Version-gate some newer syntax
- Fix Check always showing Help text
- Version-gate some more newer syntax
- Enable path browse button by default
- Remove unnecessary namespacing
### 2.7.0 (2023-10-11)
- Attempt to replace NRT
- Try alternate archive naming
- Publish, but don't package, release builds
- Add note about git being required
- Try out using version number in AppVeyor
- Job number not build ID
- Fix errant space in variable name
- Build number not job number
- Use System.IO.Hashing for CRC32
- Don't reverse the CRC32 output
- Add submission info preamble
- Fix missing comma
- Remove IMAPI2 with no substitution
- Remove framework exceptions
- Slight reorganization of code
- Update README with support information
- Remove msbuild-specific AppVeyor items
- Be trickier when it comes to type from size
- Fix "detected" message
- Be smarter about media type based on system
- Consolidate into MPF.Core
- Fix failing tests
- Remove debug symbols in release builds (Deterous)
- Allow nullability for modern .NET
- Fix failing tests
- Remove unnecessary include
- Move element items to Core
- Move LogLevel enumeration
- Split logging code a bit more
- Split info window code a bit more
- Clarify build instructions in README (Deterous)
- Split options window code a bit more
- Use binding for more disc info textboxes
- Handle Redump password changing better
- Refine options window bindings
- Refine options window bindings further
- Refine info window bindings further
- Move decoupled view models
- Fix log output
- Start migrating MainViewModel
- Perform most of MainViewModel changes
- Small updtes to MainViewModel
- Remove MainWindow from MainViewModel
- Use callback for logging, fix Options window
- Remove WinForms from MainViewModel
- Remove some message boxes from MainViewModel
- Remove more Windows from MainViewModel
- Fix null reference exception in disc type
- Detected type and selected type are different
- Remove message boxes from MainViewModel
- Move MainViewModel to Core
- Remove LogOutputViewModel
- Consolidate some constants
- Fix media type ordering
- Fix dumping button not enabling
- Recentralize some Check functionality
- Fix media combobox not updating (Deterous)
### 2.6.6 (2023-10-04)
- Update Nuget packages
- Update to MMI 3.0.0-preview.2
- Remove errant character from script
- Add placeholders for release builds
- Fully sync AppVeyor build with script
- Stop compiling Chime finally
- Address some warnings and infos
- Add setting for pulling comment/contents
- Move to config.json
- Omit track 0.2 and 00.2 from hash search
- Tweak README again
- Fix redumper EDC detection output
- Fix XGD4 PIC reading
- Ensure popups are topmost
- Try out more UI functionality
- Skip system detection on inactive drives
- Fix path tests
- Clean up csproj files
- Update redumper to build 221
- Ensure multisession info is populated
- Be clearer with protection outputs
### 2.6.5 (2023-09-27)
- Normalize Redumper CSS output
- Update redumper to build 219
- Add .NET 7 to build scripts
- Disable subdump download in AppVeyor
- Normalize publish scripts
- Update AppVeyor to match scripts
- Add release publish scripts
- Handle invalid characters when changing program
- Fix options not saving on update
- Combine build scripts
- Force information window to top
- Reset debug option to false
### 2.6.4 (2023-09-25)
- Add CD Projekt ID field
- Fix speed setting in Aaru
- Add helper for changing dumping program from UI
- Handle extension changing only
- Swap order of operations for changing program
- Fix dumping path in DD
- Fix PlayStation serial code region parsing (Deterous)
- Retrofit README
- Migrate to Nuget package for Redump
- Remove dd for Windows
- Migrate to Nuget package for XMID
- Migrate to Nuget package for cuesheets
- Migrate to Nuget package for PIC
- Fix timestamp (Ragowit)
- Not building against .NET Standard
- Move RedumpSystemComboBoxItem
- Move Constants
- Move WPFCustomMessageBox
- Remove EnableProgressProcessing
- Remove EnableLogFormatting
- Remove App references from LogViewModel
- Remove log formatting code
- Move LogOutput
- Move to csproj tag for internals
- Move OptionsWindow
- Begin decoupling App
- Fix build
- More decoupling App
- Make Options non-cloneable
- Set parent on MainViewModel init
- Make logger a local reference
- Move options into MainViewModel
- Move MainWindow
- Add .NET 7 as a build target (not AppVeyor)
- Fix tests that have been broken for a while
### 2.6.3 (2023-08-15)
- Update redumper to build 195
- Add known .NET 6 limitations to README
- Remove `_drive.txt` from required UIC outputs
- Make `use` flag required for MPF.Check
- Add dumping date to log
- Non-zero data start only for audio discs
- Update SafeDisc Sanitization (TheRogueArchivist)
### 2.6.2 (2023-07-25)
- Ensure custom parameters properly set
- Universal hash only for audio discs
- Support LibCrypt data from Redumper
- Always show extension for Redumper
- Normalize old universal hash text
- Skip extra tracks during checking
- Modify the track count on checking
- Attempt to match universal hash
- Fix .NET Framework 4.8 build
- Fix universal hash URL generation
- Add Windows publish script
- Add *nix publish script
- Add build instructions to README
- Clarify build instructions
### 2.6.1 (2023-07-19)
- Simplify Redumper error value extraction
- Don't pull comment fields that auto-populate
- Set best compression levels for log files
- Be more explicit about .NET 6 limitation
- Set extensions for Redumper in UI
- Fix comment field pulling again
### 2.6 (2023-07-14)
- Update README
- Add warning to login tab
- Pull hardware info from redumper log (fuzz6001)
- Increase the version of AppVeyor (fuzz6001)
- Add Windows 7 note to README
- Update redumper to build 113
- Split MMI invocation in drive listing
- Update README
- Add Hasbro iON
- Get the write offset from the latest redumper (fuzz6001)
- Update redumper to build 115
- Update to DIC 20230401
- Add DIC `.` notice to README
- Resync with Redump
- Update redumper to build 118
- Clarify non-Redump systems
- Add UltraCade
- Re-enable BD33 and BD66
- Add PIC models for BD (unused)
- Handle PIC based on disc type
- UMDs always have "2 layers"
- Add dumping program selection to main UI
- Update to DIC 20230413
- Comment out `.` handling for DIC
- Ensure dumping program box can enable/disable
- Disable special SmartE handling for DIC
- Remove path from PS1/PS2 serial
- Make TOC file optional for CD/GD
- Add datafile models
- Add datafile helper method
- Start migrating to datafile serialization
- Prepare for future DIC changes
- Add suppl support to Xbox
- Support single digit subs
- Fix info tool hash finding
- Fix missing size for ISO data
- Attempt to more accurately parse layerbreaks
- Fix upcoming suppl DAT parsing
- Ensure blank lines don't interfere
- De-indent ringcode data
- Be more specific with runtime identifiers
- Fix subdump output path in AV config
- Fix subdump mkdir path in AV config
- Single file packing for .NET 6 again
- Add missing BD disc type identifier string
- Truncate PIC data for PS4/PS5
- Add internal theme support with class
- Add PIC identifier to SubmissionInfo
- Fix other media type method
- Fix non-zero offset text
- Add executable listing for XSX
- Update redumper to build 151
- Add more safety to DAT generation
- Get write offset from redumper 119 (fuzz6001)
- Update hardware information pull from redumper (fuzz6001)
- Add MCD/SS header support for Redumper
- Fix MCD region(s) parsing
- Update to DIC 20230606
- Update redumper to build 166
- Unblock Redumper DVD support
- Support Redumper DVD layerbreak
- Add placeholder and TODOs for Redumper
- Add TODO with notes to Redumper
- Fix 2-layer DVD support in Redumper
- Fix VSCode build
- Fix Redumper DAT/layer parsing
- Update Redumper PS1 output parsing
- Fix previous commit, clean up helpers
- Update redumper to build 173
- Initial support for Redumper CSS outputs
- Hook up CSS output for testing
- Update redumper to build 174
- Add support for redumper CD synonyms
- Adjust CSS title key parsing
- Fix non-reading loop
- Normalize Redumper CSS outputs
- Normalize multi-instance site tags
- Add missing Aaru error log to zip
- Check Redumper dat section for completeness
- Update redumper to build 176
- UMDs are Sony discs
- Strip colons from Redumper disc key
- Use `HashSet` for disc or book type
- Add ordering to disc or book type
- Update redumper to build 183
- Parse and format Redumper CD multisession data
- New layerbreak takes precedence
- Reduce pulled information for Xbox and X360
- Ensure we found the tags we're skipping
- Omit pulling universal hash
- Update Nuget packages to newest stable
### 2.5 (2023-03-12)
- Add _drive file to zip for UIC
- Add Xbox Series X and PS5 to list, fix Acorn
- Add Xbox Series X short name to list
- Remove windows from test target
- Remove .NET 6.0 from tests, add TODO
- Add .NET 6.0 to tests, remove msbuild args
- Add HD-DVD to speed definitions
- Add PS3 internal serial and version parsing (tjanas)
- Update Nuget packages to newest stable
- Simplify path selection in UI
- Fix broken normalization test
- Update drive info before dumping
- Update redumper strings
- Add new parameter and mode validation
- Add redumper to the UI
- Update redumper to build 81
- Fix incorrect naming in Options window
- Initial attempt at parsing redumper outputs
- Update README
- Reenable write offset for all CDs
- Add missing redumper output file
- Force a filename for redumper
- Fix incorrect SetParameters
- Fix a couple redumper things
- Update AppVeyor version
- Output security sectors to info
- Remove x86 requirement for build
- Fix XGD media type outputs
- Fix typo in ToInternalProgram
- Fix redumper error count parsing
- Update to Aaru v5.3.2 LTS
- Update options loader with sane defaults
- Fix AppVeyor pathing
- Skip during detection
- Address some UI concerns
- Tweak AppVeyor, show Check 6.0 builds
- More strict when custom parameters editing
- Use msbuild for .NET Framework 4.8
- Update nuget packages
- ReadAllText not ReadAllLines
- Add drive format (fs) to log
- Go back to pre .NET 7 Aaru
- Be smarter about old paths
- Fix relative paths for DIC
- Add nicer failure message
- Update to DIC 20230201
- Use relative path output for DIC
- Remove usage of Aaru
- Remove Aaru as submodule
- Fix Aaru removal
- Enable .NET 6 Windows builds
- Update Redumper to build_106
- Reformat CICM for old .NET versions
- Attempt to handle no drives
- Handle no drives better
- Handle no drives betterer
- Packaging requires `-windows` for framework
- Fix other `-windows` places for .NET 6
- Fix typo in DICMultiSectorReadValue
- Semi-unify drive finding
- Introduce cross-platform MMI
- Remove System.Management
- Add Redumper Universal Hash support
- Fix Redumper write offset support
- Add Redumper non-zero data start
- Use media size for type detection on .NET 6
- Trim PIC for PS3
- Get the version of redumper (fuzz6001)
- Update VSCode config files
- Can't publish single file for UI
- Handle missing extension gracefully
- Fix incorrect option slider display
- Handle undetected discs on refresh
- Originally intended behaviour of the Update Label button (IcySon55)
- Update to BurnOutSharp 2.7.0
- Get error count from recent redumper log (fuzz6001)
- Minor cosmetic changes
- Set saner defaults for dumping speeds
- Attempt to support Windows 7
- Add `win7-x64` to identifier list
- Remove unsupported identifiers
- Add identifiers in more places
- Move drive finding inside of the try/catch
- Update to DIC 20230309
- Fix errant forward slashes
- Add TOC back as optional file
- Fix Redumper path generation
- Use and trim quotes for Redumper
- Fix incorrect image name setting
- Ensure Redumper parameters are set
- Ensure min values are taken care of
- Update parameters with `=` handling
- Ensure drive and speed are set
- Handle quotes embedded
- Return list of missing files for Redumper
- Readd accidentally deleted line
- Detect EOF during the search for error counts (fuzz6001)
### 2.4 (2022-10-26)
- Update to DIC 20211001
- Fix Redump disc title pulling
- Add /mr default flag options
- Make FillFromRedump private again
- Fix DVD layer finding corner case
- Fix failing module tests
- Add option to limit region and language selections
- Cap X360 directory check to 500MB
- Assign normalized path to parameters
- Move path normalization to better place
- Specifically include Unsafe Nuget package
- Update Nuget packages to newest stable
- Add Xbox One system detection
- Update to DIC 20220301
- Force internal drive refresh
- Add single drive refresh button (IcySon55)
- Fix "missing" option in window
- Track last used drive on refresh
- Clean up pre-dump validation
- Update to Aaru v5.3.1 LTS
- Add upper case `<TAB>` processing
- Reorder event handers
- Handle sanitized protections edge case
- Update Aaru Nuget package
- Return faster on empty protection sets
- Remove redundant check around volume label
- Fix tabs in Games and Videos boxes
- Make fully and partially matching IDs more apparent
- Add write offset as read-only field
- Explicitly clear list, just in case
- Add both fully and partially matching to info file
- Fix submission info clone
- Clear out fully matched IDs from the partial list
- Refine copy protection section showing
- Update Nuget packages
- Normalize newlines in comments and contents
- Increase JSON accuracy for disc types
- Further separate out protection scan outputs
- Compress JSON for artifacts alone
- Even stricter output for copy protection section
- Report dictionary to InfoTool
- Even even stricter copy protection output
- Disable PVD creation for Aaru
- Explicitly sanitize '?' from path
- Combine cases in protection scan
- Convert status label to TextBlock
- Add option for copy protection file output
- Make protection field user-editable
- Add warning to tooltip
- Create core UI library
- Rename MPF.GUI to MPF.UI
- Add multisession pseudo-tag
- Add multisession helper method skeleton
- Move and update options loader; clean up Check
- Move helper methods around
- Consolidate Redump login testing
- Separate common arguments to new helper
- Parse and format CD multisession data
- Convert triple space to tab
- Fix clone issue with copy protection
- Be more picky about multisession
- Sanitize whitespace around tabs
- Convert internal libraries to .NET Standard 2.0
- Update AppVeyor to VS2022
- Update Nuget packages
- Remove needless csproj constants
- Update copyright date to 2022
- Revert AppVeyor to VS2019 for now
- Add size-based media type detection for non-Framework
- Gate ManagmentObject use further
- Use built-in NETFRAMEWORK directive
- Update to BurnOutSharp 2.1.0
- .NET 6.0 and Cleanup
- Remove .NET Core 3.1 from test project for now
- Remove .NET Core 3.1 entirely
- Add filesystem logging for .NET 6
- Avoid whitespace changes for PVD, Header, and Cuesheet
- Sync to newest CICM
- Update solution file for VS2022
- Add Aaru as a submodule for .NET 6
- Multisession is multi-line
- Use Aaru for media type retrival (.NET 6)
- Fix CD-R multisession info
- Better get drive list (.NET 6)
- Add optical media support method (.NET 6)
- Simplify IsOptical (.NET 6)
- Reorganize GetMediaType
- Possibly fix tab regex replacement
- Add PIC.bin to log zip
- Organize projects in solution
- Implement Drive.Create for safety
- Fix incomplete system name detection
- Add Sharp X68k detection
- Add Bandai Playdia QIS detection
- Framework for XBONE filenames
- Add files for XBONE
- Update to DIC 20220707
- Fix .bin file paths; update internal filename generation
- Disable nonstandard BD-ROM sizes
- Trim leading file paths for XBONE
- Give .NET 6 priority for web calls
- Update to BurnOutSharp 2.3.0 (nw)
- Add Mattel Fisher-Price iXL detection
- Update to BurnOutSharp 2.3.1 (nw)
- Fix serial parsing for Dreamcast
- Fix missing parenthesis
- Add internal name for Cleanrip outputs
- Fix psxt001z namespace
- Fix missing assignment
- Update to new constructor
- Update PSX content check
- Update Aaru submodule to latest devel
- Add Sony Electronic Book system
- Verify GD-ROM outputs, finally
- Electronic not Electric
- Fix ringcode guide images
- Add skeleton for Redumper
- Download Redumper with AppVeyor
- Add Redumper constants
- Add Redumper parameter values
- Add Redumper command support and reset
- Add Redumper dumping command
- Add Redumper command validation
- Add Redumper command generation
- Create Redumper extensions class
- Minor Redumper cleanup
- Add important Redumper note
- Update to DIC 20220909
- Possibly fix PIC parsing
- Add PS3 folder/file checks for detection
- Use default directory for folder browsing, if possible
- Add unused command filename parser
- Add initial framework for reporting dumping program
- Report specific DIC version, if possible
- Add dumping info section skeleton
- Update Aaru submodules
- Fix missing info reference change
- Enable separated protection info output by default
- Try adding MPF logs to zip
- Create MPF log helper and filter for deletion
- Fix missing info in Aaru
- Add hardware info to DIC and Aaru
- Fix hardware info
- Add specialized CDS/SafeDisc filter
- Add unused article formatter
- Add language filtering to formatter
- Add multi-language helper for filter
- Update Nuget packages
- Remove deprecated protection setting
- Update BurnOutSharp to 2.3.4
- Add compression result reason to log
- Add System.Memory package to MPF.Library
- Add CodePages package to MPF.Library
- Remove extraneous packages
- Change location of dumping info
- Add MS ZipFile package to MPF.Library
- Add + to positive offsets
- Disable layerbreak generation for BD
- Disable XGD version reporting
- Disable XGD layerbreak reporting
- Disable XGD1 PVD reporting
- Put Redump limitations behind existing flag
- Add framework for reported disc type
- Add disc type parsing for Aaru and DIC
- Fix multiple DiscType for DIC
- Fix multiple DiscType* for DIC
- Add logging to !submissionInfo writing failure
- Add logging to !submissionInfo formatting failure
- Update issue templates to be more accurate
- Fix NRE with offsets
- Fix readonly Filename info display
- Fix layerbreak-based checks
- Add PS4 serial finding (tjanas)
- Add unused notification method
- Move to unused Chime class
- Add PS5 serial finding (tjanas)
- Fix offset formatting (fuzz6001)
### 2.3 (2022-02-05)
- Start overhauling Redump information pulling, again
- Add internal structure for special site codes
- Add new tabs for special site information
- Clean up default handling of fields
- Try to handle multi-line fields during parsing
- Fix CSSKey log handling
- Show most read-only fields in new tab
- Add horizontal scroll to user input
- Tweak new disc information fields and tabs
- Allow internal serial and volume label to be hidden
- Be smarter about showing update checks
- Ensure all fields are read-only on read-only tab
- Add model for 2-layer ringcode guide
- Add first attempt 2-layer ringcode guide
- Add even more safety to clone
- Sanitize filename after check
- Handle pulled linebreaks better
- Skip anti-modchip string in some cases
- Handle pulled linebreaks better, again
- Tweak more Disc Info window formatting
- Skip unnecessary newlines in parsing
- Force scroll visibility, tweak text sizes again
- Fix newline skipping
- Add missing continue statement
- Add newlines for mutliline special fields
- Omit volume label for "Audio CD"
- Remove Enter/Escape registration on disc info window
- Logically group more things in disc info window
- Remove tab key from disc info window
- Add `<tab>` processing
- Unban newly opened consoles
- Tweak minimalized layout a bit more
- Add tab setting
- Further disc info window tweaks
- Changed IsEnabled to IsReadOnly
- Fix scrolling issues in disc info window
- Convert postgap and VCD fields to checkboxes
- Adjust width ratios for disc info window
- Fix IsReadOnly
- Only include booleans if the value is true
- Add hidden debug option for "ShowDebugViewMenuItem"
- Fix incorrect header check
- Make protection read-only field multiline
- Reformat Saturn internal date
- Fix Sega CD internal serial reading
- Fix crash on invalid parameters
- Differentiate XMID and XeMID
- Conditionally pull region from Redump
- Be smarter about volume labels
- Use volume label in checks, not formatted version
- Sync with Redump region and language selection
- Try to delete old log archive before writing
- Add support for all ISO language codes
- Add support for all ISO region codes
- Ensure ordering in output site tags
- Make site code formatting helper method
- Better helper method organization
- Start supporting ordered tags and non-tags
- Add more non-tag support; rearrange info window
- Fix incorrect language three-letter code
- Hook up additional Xbox field to disc info window
- Fix non-tag tag shortnames
- Fix parsing of non-tag tags again
- Disable unnecessary cuesheet parsing
- Fix incorrect region two-letter code
- Adjust long names for some languages
- Add Sierra ID to list of pseudo-tags
- Add another hand-formatted version of SS tag
- Move internal serial before volume label
- Check for $SystemUpdate folder for X360 discs
- Slightly rename UK and USA regions for UI
- Add another hand-formatted version of SS tag
- Add verification reminders for pulled tags
- Read longer string for Saturn internal serial
- Ensure version only pulled if one doesn't exist
- Ensure Games pseudo-tag is multi-line
- Add alternate pseudo-tag for Playable Demos
- Make error clearer if something is unsupported in Check
- Ensure drive is not null for volume labels
- Check explicitly for no matches on Redump pull
- Normalize PS1/PS2 executable names
- Adjust paths for DIC just before dumping
### 2.2 (2021-12-30)
- Fix Saturn header finding
- Add Pocket PC support
- Add HD-DVD-Video support
@@ -50,6 +794,7 @@
- Add safety around volume labels
### 2.1 (2021-07-22)
- Enum, no more
- Sony works backward
- Add experimental dark mode
@@ -79,6 +824,7 @@
- Update to BurnOutSharp 1.7.0
### 2.0 (2021-04-23)
- Rename DICUI to Media Preservation Frontend (MPF)
- Add handling for BEh drive _mainInfo.txt changes
- Fix multiline regex fields during info pulling
@@ -133,6 +879,7 @@
- Update to BurnOutSharp 1.6.1
### 1.18 (2020-11-10)
- Add more information extraction and generation for Aaru
- Remove instances of CD Check from copy protection (again, sorry)
- Fix multiline submission info outputs
@@ -157,6 +904,7 @@
- Added HD-DVD-Video detection
### 1.17.1 (2020-09-14)
- Shuffled some shared, internal UI variables
- Synced WPF and Avalonia UI internals
- Made the disc information window less prone to bugs
@@ -164,6 +912,7 @@
- Added support for old(?) DIC flags: `/fix` and `/re`
### 1.17 (2020-09-12)
- Updated to Aaru version 5.1
- Updated to BurnOutSharp version 1.4.0
- Updated to DIC version 20200716
@@ -323,7 +1072,7 @@
- edccchk now run on all CD-Roms
- Discs unsupported by Windows are now regonized
- Extra \ when accepting default save has been removed.
### 1.02b (2018-05-18)
- Added missing DLL
@@ -336,4 +1085,4 @@
### 1.01d (2018-05-18)
-Combine IBM PC-CD options, misc fixes.
-Combine IBM PC-CD options, misc fixes.

Submodule CICMMetadata deleted from 7944bca8e6

View File

@@ -1,26 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net48;netcoreapp3.1;net5.0</TargetFrameworks>
<PlatformTarget>x86</PlatformTarget>
<TargetFrameworks>net48;net6.0;net7.0</TargetFrameworks>
<RuntimeIdentifiers>win-x64;linux-x64;osx-x64</RuntimeIdentifiers>
<OutputType>Exe</OutputType>
<Title>MPF Check</Title>
<AssemblyName>MPF.Check</AssemblyName>
<Description>Validator for various dumping programs</Description>
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
<Copyright>Copyright (c)2019-2021</Copyright>
<RepositoryUrl>https://github.com/SabreTools/MPF</RepositoryUrl>
<Version>2.2</Version>
<AssemblyVersion>$(Version)</AssemblyVersion>
<FileVersion>$(Version)</FileVersion>
<IncludeSource>true</IncludeSource>
<IncludeSymbols>true</IncludeSymbols>
<Copyright>Copyright (c)2019-2023</Copyright>
<VersionPrefix>2.7.5</VersionPrefix>
</PropertyGroup>
<PropertyGroup>
<NrtRevisionFormat>$(Version)-{chash:8}</NrtRevisionFormat>
<NrtResolveSimpleAttributes>true</NrtResolveSimpleAttributes>
<NrtShowRevision>false</NrtShowRevision>
<PropertyGroup Condition="'$(TargetFramework)'!='net48'">
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
@@ -28,25 +20,18 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="BurnOutSharp" PrivateAssets="build; analyzers" ExcludeAssets="contentFiles" Version="1.8.0" GeneratePathProperty="true">
<ProjectReference Include="..\MPF.Core\MPF.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BurnOutSharp" PrivateAssets="build; analyzers" ExcludeAssets="contentFiles" Version="2.9.0" GeneratePathProperty="true">
<IncludeAssets>runtime; compile; build; native; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Unclassified.NetRevisionTask" Version="0.4.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="SabreTools.RedumpLib" Version="1.1.1" />
</ItemGroup>
<ItemGroup>
<Content Include="$(PkgBurnOutSharp)\content\**" PackagePath="contentFiles\any\any;content" CopyToOutputDirectory="Always" PackageCopyToOutput="true" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MPF.Library\MPF.Library.csproj">
<Project>{51ab0928-13f9-44bf-a407-b6957a43a056}</Project>
<Name>MPF.Library</Name>
</ProjectReference>
<ProjectReference Include="..\RedumpLib\RedumpLib.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,13 +1,11 @@
using System;
using System.IO;
using System.Linq;
using BurnOutSharp;
using MPF.Core.Converters;
using BinaryObjectScanner;
using MPF.Core;
using MPF.Core.Data;
using MPF.Core.Utilities;
using MPF.Library;
using RedumpLib.Data;
using RedumpLib.Web;
using SabreTools.RedumpLib.Data;
using SabreTools.RedumpLib.Web;
namespace MPF.Check
{
@@ -15,147 +13,45 @@ namespace MPF.Check
{
public static void Main(string[] args)
{
// Help options
if (args.Length == 0 || args[0] == "-h" || args[0] == "-?")
// Try processing the standalone arguments
bool? standaloneProcessed = OptionsLoader.ProcessStandaloneArguments(args);
if (standaloneProcessed != false)
{
DisplayHelp();
if (standaloneProcessed == null)
DisplayHelp();
return;
}
// List options
if (args[0] == "-lm" || args[0] == "--listmedia")
// Try processing the common arguments
(bool success, MediaType mediaType, RedumpSystem? knownSystem, var error) = OptionsLoader.ProcessCommonArguments(args);
if (!success)
{
ListMediaTypes();
Console.ReadLine();
DisplayHelp(error);
return;
}
else if (args[0] == "-lp" || args[0] == "--listprograms")
{
ListPrograms();
Console.ReadLine();
return;
}
else if (args[0] == "-ls" || args[0] == "--listsystems")
{
ListSystems();
Console.ReadLine();
return;
}
// Normal operation check
if (args.Length < 3)
{
DisplayHelp("Invalid number of arguments");
return;
}
// Check the MediaType
var mediaType = EnumConverter.ToMediaType(args[0].Trim('"'));
if (mediaType == MediaType.NONE)
{
DisplayHelp($"{args[0]} is not a recognized media type");
return;
}
// Check the RedumpSystem
var knownSystem = Extensions.ToRedumpSystem(args[1].Trim('"'));
if (knownSystem == null)
{
DisplayHelp($"{args[1]} is not a recognized system");
return;
}
// Default values
string username = null, password = null;
string internalProgram = "DiscImageCreator";
string path = string.Empty;
bool scan = false, compress = false, json = false;
// Loop through and process options
int startIndex = 2;
for (; startIndex < args.Length; startIndex++)
(var options, var seedInfo, var path, int startIndex) = OptionsLoader.LoadFromArguments(args, startIndex: 2);
if (options.InternalProgram == InternalProgram.NONE)
{
// Redump login
if (args[startIndex].StartsWith("-c=") || args[startIndex].StartsWith("--credentials="))
{
string[] credentials = args[startIndex].Split('=')[1].Split(';');
username = credentials[0];
password = credentials[1];
}
else if (args[startIndex] == "-c" || args[startIndex] == "--credentials")
{
username = args[startIndex + 1];
password = args[startIndex + 2];
startIndex += 2;
}
// Use specific program
else if (args[startIndex].StartsWith("-u=") || args[startIndex].StartsWith("--use="))
{
internalProgram = args[startIndex].Split('=')[1];
}
else if (args[startIndex] == "-u" || args[startIndex] == "--use")
{
internalProgram = args[startIndex + 1];
startIndex++;
}
// Use a device path for physical checks
else if (args[startIndex].StartsWith("-p=") || args[startIndex].StartsWith("--path="))
{
path = args[startIndex].Split('=')[1];
}
else if (args[startIndex] == "-p" || args[startIndex] == "--path")
{
path = args[startIndex + 1];
startIndex++;
}
// Scan for protection (requires device path)
else if (args[startIndex].Equals("-s") || args[startIndex].Equals("--scan"))
{
scan = true;
}
// Output submission JSON
else if (args[startIndex].Equals("-j") || args[startIndex].Equals("--json"))
{
json = true;
}
// Compress log and extraneous files
else if (args[startIndex].Equals("-z") || args[startIndex].Equals("--zip"))
{
compress = true;
}
// Default, we fall out
else
{
break;
}
DisplayHelp("A program name needs to be provided");
return;
}
// Make new Progress objects
var resultProgress = new Progress<Result>();
resultProgress.ProgressChanged += ProgressUpdated;
resultProgress.ProgressChanged += ConsoleLogger.ProgressUpdated;
var protectionProgress = new Progress<ProtectionProgress>();
protectionProgress.ProgressChanged += ProgressUpdated;
protectionProgress.ProgressChanged += ConsoleLogger.ProgressUpdated;
// If credentials are invalid, alert the user
if (!string.IsNullOrWhiteSpace(username) && !string.IsNullOrWhiteSpace(password))
{
using (RedumpWebClient wc = new RedumpWebClient())
{
bool? loggedIn = wc.Login(username, password);
if (loggedIn == true)
Console.WriteLine("Redump username and password accepted!");
else if (loggedIn == false)
Console.WriteLine("Redump username and password denied!");
else
Console.WriteLine("An error occurred validating your crendentials!");
}
}
// Validate the supplied credentials
#if NET48
(bool? _, string message) = RedumpWebClient.ValidateCredentials(options?.RedumpUsername, options?.RedumpPassword);
#else
(bool? _, string? message) = RedumpHttpClient.ValidateCredentials(options.RedumpUsername ?? string.Empty, options.RedumpPassword ?? string.Empty).ConfigureAwait(false).GetAwaiter().GetResult();
#endif
if (!string.IsNullOrWhiteSpace(message))
Console.WriteLine(message);
// Loop through all the rest of the args
for (int i = startIndex; i < args.Length; i++)
@@ -171,24 +67,15 @@ namespace MPF.Check
string filepath = Path.GetFullPath(args[i].Trim('"'));
// Now populate an environment
var options = new Options
{
InternalProgram = EnumConverter.ToInternalProgram(internalProgram),
ScanForProtection = scan && !string.IsNullOrWhiteSpace(path),
PromptForDiscInformation = false,
ShowDiscEjectReminder = false,
OutputSubmissionJSON = json,
CompressLogFiles = compress,
RedumpUsername = username,
RedumpPassword = password,
};
#if NET48
Drive drive = null;
#else
Drive? drive = null;
#endif
if (!string.IsNullOrWhiteSpace(path))
drive = new Drive(null, new DriveInfo(path));
drive = Drive.Create(null, path);
var env = new DumpEnvironment(options, "", filepath, drive, knownSystem, mediaType, null);
var env = new DumpEnvironment(options, filepath, drive, knownSystem, mediaType, internalProgram: null, parameters: null);
// Finally, attempt to do the output dance
var result = env.VerifyAndSaveDumpOutput(resultProgress, protectionProgress).ConfigureAwait(false).GetAwaiter().GetResult();
@@ -200,7 +87,11 @@ 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);
@@ -214,77 +105,14 @@ namespace MPF.Check
Console.WriteLine("-ls, --listsystems List supported system types");
Console.WriteLine("-lp, --listprograms List supported dumping program outputs");
Console.WriteLine();
Console.WriteLine("Check Options:");
Console.WriteLine("-c, --credentials <user> <pw> Redump username and password");
Console.WriteLine("-u, --use <program> Dumping program output type");
Console.WriteLine("-p, --path <drivepath> Physical drive path for additional checks");
Console.WriteLine("-s, --scan Enable copy protection scan (requires --path)");
Console.WriteLine("-j, --json Enable submission JSON output");
Console.WriteLine("-z, --zip Enable log file compression");
var supportedArguments = OptionsLoader.PrintSupportedArguments();
foreach (string argument in supportedArguments)
{
Console.WriteLine(argument);
}
Console.WriteLine();
}
/// <summary>
/// List all media types with their short usable names
/// </summary>
private static void ListMediaTypes()
{
Console.WriteLine("Supported Media Types:");
foreach (var val in Enum.GetValues(typeof(MediaType)))
{
if (((MediaType)val) == MediaType.NONE)
continue;
Console.WriteLine($"{((MediaType?)val).ShortName()} - {((MediaType?)val).LongName()}");
}
}
/// <summary>
/// List all programs with their short usable names
/// </summary>
private static void ListPrograms()
{
Console.WriteLine("Supported Programs:");
foreach (var val in Enum.GetValues(typeof(InternalProgram)))
{
if (((InternalProgram)val) == InternalProgram.NONE)
continue;
Console.WriteLine($"{((InternalProgram?)val).LongName()}");
}
}
/// <summary>
/// List all systems with their short usable names
/// </summary>
private static void ListSystems()
{
Console.WriteLine("Supported Known Systems:");
var knownSystems = Enum.GetValues(typeof(RedumpSystem))
.OfType<RedumpSystem?>()
.Where(s => s != null && !s.IsMarker() && s.GetCategory() != SystemCategory.NONE)
.OrderBy(s => s.LongName() ?? string.Empty);
foreach (var val in knownSystems)
{
Console.WriteLine($"{val.ShortName()} - {val.LongName()}");
}
}
/// <summary>
/// Simple process counter to write to console
/// </summary>
private static void ProgressUpdated(object sender, Result value)
{
Console.WriteLine(value.Message);
}
/// <summary>
/// Simple process counter to write to console
/// </summary>
private static void ProgressUpdated(object sender, ProtectionProgress value)
{
Console.WriteLine($"{value.Percentage * 100:N2}%: {value.Filename} - {value.Protection}");
}
}
}

View File

@@ -1,4 +0,0 @@
using System.Runtime.CompilerServices;
// Anything marked as internal can be used by the test methods
[assembly: InternalsVisibleTo("MPF.Test")]

33
MPF.Core/ConsoleLogger.cs Normal file
View File

@@ -0,0 +1,33 @@
using System;
using BinaryObjectScanner;
using MPF.Core.Data;
namespace MPF.Core
{
public static class ConsoleLogger
{
/// <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);
}
/// <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

@@ -2,11 +2,8 @@
using System.Collections.Concurrent;
using System.IO;
using System.Reflection;
#if NET_FRAMEWORK
using IMAPI2;
#endif
using MPF.Core.Data;
using RedumpLib.Data;
using SabreTools.RedumpLib.Data;
namespace MPF.Core.Converters
{
@@ -34,47 +31,6 @@ namespace MPF.Core.Converters
}
}
#if NET_FRAMEWORK
/// <summary>
/// Convert IMAPI physical media type to a MediaType
/// </summary>
/// <param name="type">IMAPI_MEDIA_PHYSICAL_TYPE value to check</param>
/// <returns>MediaType if possible, null on error</returns>
public static MediaType? IMAPIToMediaType(this IMAPI_MEDIA_PHYSICAL_TYPE type)
{
switch (type)
{
case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_UNKNOWN:
return MediaType.NONE;
case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_CDROM:
case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_CDR:
case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_CDRW:
return MediaType.CDROM;
case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_DVDROM:
case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_DVDRAM:
case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_DVDPLUSR:
case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_DVDPLUSRW:
case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_DVDPLUSR_DUALLAYER:
case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_DVDDASHR:
case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_DVDDASHRW:
case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_DVDDASHR_DUALLAYER:
case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_DISK:
case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_DVDPLUSRW_DUALLAYER:
return MediaType.DVD;
case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_HDDVDROM:
case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_HDDVDR:
case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_HDDVDRAM:
return MediaType.HDDVD;
case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_BDROM:
case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_BDR:
case IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_BDRE:
return MediaType.BluRay;
default:
return null;
}
}
#endif
#endregion
#region Convert to Long Name
@@ -82,7 +38,11 @@ namespace MPF.Core.Converters
/// <summary>
/// Long name method cache
/// </summary>
#if NET48
private static readonly ConcurrentDictionary<Type, MethodInfo> LongNameMethods = new ConcurrentDictionary<Type, MethodInfo>();
#else
private static readonly ConcurrentDictionary<Type, MethodInfo?> LongNameMethods = new ConcurrentDictionary<Type, MethodInfo?>();
#endif
/// <summary>
/// Get the string representation of a generic enumerable value
@@ -93,13 +53,12 @@ namespace MPF.Core.Converters
{
try
{
var sourceType = value?.GetType();
var sourceType = value.GetType();
sourceType = Nullable.GetUnderlyingType(sourceType) ?? sourceType;
if (!LongNameMethods.TryGetValue(sourceType, out MethodInfo method))
if (!LongNameMethods.TryGetValue(sourceType, out var method))
{
method = typeof(RedumpLib.Data.Extensions).GetMethod("LongName", new[] { typeof(Nullable<>).MakeGenericType(sourceType) });
method = typeof(Extensions).GetMethod("LongName", new[] { typeof(Nullable<>).MakeGenericType(sourceType) });
if (method == null)
method = typeof(EnumConverter).GetMethod("LongName", new[] { typeof(Nullable<>).MakeGenericType(sourceType) });
@@ -107,7 +66,7 @@ namespace MPF.Core.Converters
}
if (method != null)
return method.Invoke(null, new[] { value }) as string;
return method.Invoke(null, new[] { value }) as string ?? string.Empty;
else
return string.Empty;
}
@@ -131,10 +90,10 @@ namespace MPF.Core.Converters
case InternalProgram.Aaru:
return "Aaru";
case InternalProgram.DD:
return "dd";
case InternalProgram.DiscImageCreator:
return "DiscImageCreator";
case InternalProgram.Redumper:
return "Redumper";
#endregion
@@ -157,7 +116,7 @@ namespace MPF.Core.Converters
}
}
#endregion
#endregion
#region Convert From String
@@ -166,9 +125,13 @@ 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())
switch (internalProgram?.ToLowerInvariant())
{
// Dumping support
case "aaru":
@@ -181,8 +144,9 @@ namespace MPF.Core.Converters
case "dicreator":
case "discimagecreator":
return InternalProgram.DiscImageCreator;
case "dd":
return InternalProgram.DD;
case "rd":
case "redumper":
return InternalProgram.Redumper;
// Verification support only
case "cleanrip":

View File

@@ -1,6 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using RedumpLib.Data;
using SabreTools.RedumpLib.Data;
namespace MPF.Core.Data
{
@@ -16,11 +16,12 @@ namespace MPF.Core.Data
// 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 };
// Private lists of known drive speed ranges
private 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 };
private static IReadOnlyList<int> DVD { get; } = CD.Where(s => s <= 24).ToList();
private static IReadOnlyList<int> BD { get; } = CD.Where(s => s <= 16).ToList();
private static IReadOnlyList<int> Unknown { get; } = new List<int> { 1 };
// Lists of known drive speed ranges
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 };
/// <summary>
/// Get list of all drive speeds for a given MediaType
@@ -35,10 +36,11 @@ namespace MPF.Core.Data
case MediaType.GDROM:
return CD;
case MediaType.DVD:
case MediaType.HDDVD:
case MediaType.NintendoGameCubeGameDisc:
case MediaType.NintendoWiiOpticalDisc:
return DVD;
case MediaType.HDDVD:
return HDDVD;
case MediaType.BluRay:
return BD;
default:
@@ -82,13 +84,20 @@ namespace MPF.Core.Data
// 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 MatchingIDsField = "Matching IDs";
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)";
@@ -100,12 +109,7 @@ namespace MPF.Core.Data
public const string PlayStationEDCField = "EDC";
public const string PlayStationAntiModchipField = "Anti-modchip";
public const string PlayStationLibCryptField = "LibCrypt";
public const string XBOXDMIHash = "DMI";
public const string XBOXPFIHash = "PFI";
public const string XBOXSSHash = "SS";
public const string XBOXSSRanges = "Security Sector Ranges";
public const string XBOXSSVersion = "Security Sector Version";
public const string XBOXXeMID = "XeMID";
// Default values

View File

@@ -2,24 +2,23 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Management;
using Microsoft.Management.Infrastructure;
using Microsoft.Management.Infrastructure.Generic;
using MPF.Core.Converters;
using MPF.Core.Utilities;
using RedumpLib.Data;
#if NET_FRAMEWORK
using IMAPI2;
#else
using Aaru.CommonTypes.Enums;
using AaruDevices = Aaru.Devices;
#endif
using SabreTools.RedumpLib.Data;
namespace MPF.Core.Data
{
/// <summary>
/// Represents information for a single drive
/// </summary>
/// <remarks>
/// TODO: Can the Aaru models be used instead of the ones I've created here?
/// </remarks>
public class Drive
{
#region Fields
/// <summary>
/// Represents drive type
/// </summary>
@@ -28,56 +27,63 @@ namespace MPF.Core.Data
/// <summary>
/// Drive partition format
/// </summary>
public string DriveFormat => driveInfo?.DriveFormat;
/// <summary>
/// Windows drive letter
/// </summary>
public char Letter => driveInfo?.Name[0] ?? '\0';
#if NET48
public string DriveFormat { get; private set; } = null;
#else
public string? DriveFormat { get; private set; } = null;
#endif
/// <summary>
/// Windows drive path
/// </summary>
public string Name => driveInfo?.Name;
#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
/// </summary>
public bool MarkedActive => driveInfo?.IsReady ?? false;
public bool MarkedActive { get; private set; } = false;
/// <summary>
/// Represents the total size of the drive
/// </summary>
public long TotalSize { get; private set; } = default;
/// <summary>
/// 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>
public string VolumeLabel
{
get
{
try
{
return driveInfo?.VolumeLabel;
}
catch
{
return null;
}
}
}
#if NET48
public string VolumeLabel { get; private set; } = null;
#else
public string? VolumeLabel { get; private set; } = null;
#endif
#endregion
#region Derived Fields
/// <summary>
/// Media label as read by Windows, formatted to avoid odd outputs
/// </summary>
#if NET48
public string FormattedVolumeLabel
#else
public string? FormattedVolumeLabel
#endif
{
get
{
string volumeLabel = Template.DiscNotDetected;
if (driveInfo.IsReady)
if (this.MarkedActive)
{
if (string.IsNullOrWhiteSpace(driveInfo.VolumeLabel))
if (string.IsNullOrWhiteSpace(this.VolumeLabel))
volumeLabel = "track";
else
volumeLabel = driveInfo.VolumeLabel;
volumeLabel = this.VolumeLabel;
}
foreach (char c in Path.GetInvalidFileNameChars())
@@ -88,166 +94,183 @@ namespace MPF.Core.Data
}
/// <summary>
/// DriveInfo object representing the drive, if possible
/// Read-only access to the drive letter
/// </summary>
private readonly DriveInfo driveInfo;
/// <remarks>Should only be used in UI applications</remarks>
public char? Letter => this.Name?[0] ?? '\0';
public Drive(InternalDriveType? driveType, DriveInfo driveInfo)
#endregion
/// <summary>
/// Protected constructor
/// </summary>
protected Drive() { }
/// <summary>
/// Create a new Drive object from a drive type and device path
/// </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
{
this.InternalDriveType = driveType;
this.driveInfo = driveInfo;
// Create a new, empty drive object
var drive = new Drive()
{
InternalDriveType = driveType,
};
// If we have an invalid device path, return null
if (string.IsNullOrWhiteSpace(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);
if (driveInfo == null || driveInfo == default)
return null;
// Fill in the rest of the data
drive.PopulateFromDriveInfo(driveInfo);
return drive;
}
/// <summary>
/// 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)
return;
// Populate the data fields
this.Name = driveInfo.Name;
this.MarkedActive = driveInfo.IsReady;
if (this.MarkedActive)
{
this.DriveFormat = driveInfo.DriveFormat;
this.TotalSize = driveInfo.TotalSize;
this.VolumeLabel = driveInfo.VolumeLabel;
}
else
{
this.DriveFormat = string.Empty;
this.TotalSize = default;
this.VolumeLabel = string.Empty;
}
}
#region Public Functionality
/// <summary>
/// Create a list of active drives matched to their volume labels
/// </summary>
/// <param name="ignoreFixedDrives">Ture to ignore fixed drives from population, false otherwise</param>
/// <param name="ignoreFixedDrives">True to ignore fixed drives from population, false otherwise</param>
/// <returns>Active drives, matched to labels, if possible</returns>
/// <remarks>
/// https://stackoverflow.com/questions/3060796/how-to-distinguish-between-usb-and-floppy-devices?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa
/// https://msdn.microsoft.com/en-us/library/aa394173(v=vs.85).aspx
/// </remarks>
public static List<Drive> CreateListOfDrives(bool ignoreFixedDrives)
{
var desiredDriveTypes = new List<DriveType>() { DriveType.CDRom };
if (!ignoreFixedDrives)
{
desiredDriveTypes.Add(DriveType.Fixed);
desiredDriveTypes.Add(DriveType.Removable);
}
// Get all supported drive types
var drives = DriveInfo.GetDrives()
.Where(d => desiredDriveTypes.Contains(d.DriveType))
.Select(d => new Drive(EnumConverter.ToInternalDriveType(d.DriveType), d))
.ToList();
// TODO: Management searcher stuff is not supported on other platforms
// Get the floppy drives and set the flag from removable
try
{
ManagementObjectSearcher searcher =
new ManagementObjectSearcher("root\\CIMV2",
"SELECT * FROM Win32_LogicalDisk");
var collection = searcher.Get();
foreach (ManagementObject queryObj in collection)
{
uint? mediaType = (uint?)queryObj["MediaType"];
if (mediaType != null && ((mediaType > 0 && mediaType < 11) || (mediaType > 12 && mediaType < 22)))
{
char devId = queryObj["DeviceID"].ToString()[0];
drives.ForEach(d => { if (d.Letter == devId) { d.InternalDriveType = Data.InternalDriveType.Floppy; } });
}
}
}
catch
{
// No-op
}
// Order the drives by drive letter
drives = drives.OrderBy(i => i.Letter).ToList();
var drives = GetDriveList(ignoreFixedDrives);
drives = drives.OrderBy(i => i == null ? "\0" : i.Name).ToList();
return drives;
}
/// <summary>
/// Get the current media type from drive letter
/// </summary>
/// <param name="drive"></param>
/// <param name="system"></param>
/// <returns></returns>
/// <remarks>
/// This may eventually be replaced by Aaru.Devices being able to be about 10x more accurate.
/// This will also end up making it so that IMAPI2 is no longer necessary. Unfortunately, that
/// will only work for .NET Core 3.1 and beyond.
/// </remarks>
public (MediaType?, string) GetMediaType()
#if NET48
public (MediaType?, string) GetMediaType(RedumpSystem? system)
#else
public (MediaType?, string?) GetMediaType(RedumpSystem? system)
#endif
{
// Take care of the non-optical stuff first
// TODO: See if any of these can be more granular, like Optical is
if (this.InternalDriveType == Data.InternalDriveType.Floppy)
return (MediaType.FloppyDisk, null);
else if (this.InternalDriveType == Data.InternalDriveType.HardDisk)
return (MediaType.HardDisk, null);
else if (this.InternalDriveType == Data.InternalDriveType.Removable)
return (MediaType.FlashDrive, null);
#if NET_FRAMEWORK
// Get the current drive information
string deviceId = null;
bool loaded = false;
try
switch (this.InternalDriveType)
{
// Get the device ID first
var searcher = new ManagementObjectSearcher(
"root\\CIMV2",
$"SELECT * FROM Win32_CDROMDrive WHERE Id = '{this.Letter}:\'");
foreach (ManagementObject queryObj in searcher.Get())
{
deviceId = (string)queryObj["DeviceID"];
loaded = (bool)queryObj["MediaLoaded"];
}
// If we got no valid device, we don't care and just return
if (deviceId == null)
return (null, "Device could not be found");
else if (!loaded)
return (null, "Device is not reporting media loaded");
MsftDiscMaster2 discMaster = new MsftDiscMaster2();
deviceId = deviceId.ToLower().Replace('\\', '#').Replace('/', '#');
string id = null;
foreach (var disc in discMaster)
{
if (disc.ToString().Contains(deviceId))
id = disc.ToString();
}
// If we couldn't find the drive, we don't care and return
if (id == null)
return (null, "Device ID could not be found");
// Create the required objects for reading from the drive
MsftDiscRecorder2 recorder = new MsftDiscRecorder2();
recorder.InitializeDiscRecorder(id);
MsftDiscFormat2Data dataWriter = new MsftDiscFormat2Data();
// If the recorder is not supported, just return
if (!dataWriter.IsRecorderSupported(recorder))
return (null, "IMAPI2 recorder not supported");
// Otherwise, set the recorder to get information from
dataWriter.Recorder = recorder;
var media = dataWriter.CurrentPhysicalMediaType;
return (media.IMAPIToMediaType(), null);
}
catch (Exception ex)
{
return (null, ex.Message);
}
#else
try
{
var device = new AaruDevices.Device(this.Name);
if (device.Error)
return (null, "Could not open device");
else if (device.Type != DeviceType.ATAPI && device.Type != DeviceType.SCSI)
return (null, "Device does not support media type detection");
// TODO: In order to get the disc type, Aaru.Core will need to be included as a
// package. Unfortunately, it currently has a conflict with one of the required libraries:
// System.Text.Encoding.CodePages (BOS uses >= 5.0.0, DotNetZip uses >= 4.5.0 && < 5.0.0)
}
catch (Exception ex)
{
return (null, ex.Message);
case Data.InternalDriveType.Floppy:
return (MediaType.FloppyDisk, null);
case Data.InternalDriveType.HardDisk:
return (MediaType.HardDisk, null);
case Data.InternalDriveType.Removable:
return (MediaType.FlashDrive, null);
}
return (null, "Media detection only supported on .NET Framework");
#endif
// Some systems should default to certain media types
switch (system)
{
// CD
case RedumpSystem.Panasonic3DOInteractiveMultiplayer:
case RedumpSystem.PhilipsCDi:
case RedumpSystem.SegaDreamcast:
case RedumpSystem.SegaSaturn:
case RedumpSystem.SonyPlayStation:
case RedumpSystem.VideoCD:
return (MediaType.CDROM, null);
// DVD
case RedumpSystem.DVDAudio:
case RedumpSystem.DVDVideo:
case RedumpSystem.MicrosoftXbox:
case RedumpSystem.MicrosoftXbox360:
return (MediaType.DVD, null);
// HD-DVD
case RedumpSystem.HDDVDVideo:
return (MediaType.HDDVD, null);
// Blu-ray
case RedumpSystem.BDVideo:
case RedumpSystem.MicrosoftXboxOne:
case RedumpSystem.MicrosoftXboxSeriesXS:
case RedumpSystem.SonyPlayStation3:
case RedumpSystem.SonyPlayStation4:
case RedumpSystem.SonyPlayStation5:
return (MediaType.BluRay, null);
// GameCube
case RedumpSystem.NintendoGameCube:
return (MediaType.NintendoGameCubeGameDisc, null);
// Wii
case RedumpSystem.NintendoWii:
return (MediaType.NintendoWiiOpticalDisc, null);
// WiiU
case RedumpSystem.NintendoWiiU:
return (MediaType.NintendoWiiUOpticalDisc, null);
// PSP
case RedumpSystem.SonyPlayStationPortable:
return (MediaType.UMD, null);
}
// Handle optical media by size and filesystem
if (this.TotalSize >= 0 && this.TotalSize <= 800_000_000 && (this.DriveFormat == "CDFS" || this.DriveFormat == "UDF"))
return (MediaType.CDROM, null);
else if (this.TotalSize > 800_000_000 && this.TotalSize <= 8_540_000_000 && (this.DriveFormat == "CDFS" || this.DriveFormat == "UDF"))
return (MediaType.DVD, null);
else if (this.TotalSize > 8_540_000_000)
return (MediaType.BluRay, null);
return (null, "Could not determine media type!");
}
/// <summary>
@@ -257,99 +280,82 @@ namespace MPF.Core.Data
/// <returns></returns>
public RedumpSystem? GetRedumpSystem(RedumpSystem? defaultValue)
{
string drivePath = $"{this.Letter}:\\";
// If we can't read the media in that drive, we can't do anything
if (!Directory.Exists(drivePath))
if (!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;
// Audio CD
if (this.FormattedVolumeLabel.Equals("Audio CD", StringComparison.OrdinalIgnoreCase))
{
return RedumpSystem.AudioCD;
}
// Check volume labels first
RedumpSystem? systemFromLabel = GetRedumpSystemFromVolumeLabel();
if (systemFromLabel != null)
return systemFromLabel;
// BD-Video
if (Directory.Exists(Path.Combine(drivePath, "BDMV")))
{
// Technically BD-Audio has this as well, but it's hard to split that out right now
return RedumpSystem.BDVideo;
}
// Get a list of files for quicker checking
#region Consoles
// DVD-Audio
// Bandai Playdia Quick Interactive System
try
{
if (Directory.Exists(Path.Combine(drivePath, "AUDIO_TS"))
&& Directory.EnumerateFiles(Path.Combine(drivePath, "AUDIO_TS")).Any())
List<string> files = Directory.EnumerateFiles(this.Name, "*", SearchOption.TopDirectoryOnly).ToList();
if (files.Any(f => f.EndsWith(".AJS", StringComparison.OrdinalIgnoreCase))
&& files.Any(f => f.EndsWith(".GLB", StringComparison.OrdinalIgnoreCase)))
{
return RedumpSystem.DVDAudio;
return RedumpSystem.BandaiPlaydiaQuickInteractiveSystem;
}
}
catch { }
// DVD-Video and Xbox
// Mattel Fisher-Price iXL
if (File.Exists(Path.Combine(this.Name, "iXL", "iXLUpdater.exe")))
{
return RedumpSystem.MattelFisherPriceiXL;
}
// Microsoft Xbox 360
try
{
if (Directory.Exists(Path.Combine(drivePath, "VIDEO_TS"))
&& Directory.EnumerateFiles(Path.Combine(drivePath, "VIDEO_TS")).Any())
if (Directory.Exists(Path.Combine(this.Name, "$SystemUpdate"))
&& Directory.EnumerateFiles(Path.Combine(this.Name, "$SystemUpdate")).Any()
&& this.TotalSize <= 500_000_000)
{
// TODO: Maybe add video track hashes to compare for Xbox and X360?
if (this.FormattedVolumeLabel.StartsWith("SEP13011042", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.MicrosoftXbox;
return RedumpSystem.DVDVideo;
return RedumpSystem.MicrosoftXbox360;
}
}
catch { }
// HD-DVD-Video
// Microsoft Xbox One
try
{
if (Directory.Exists(Path.Combine(drivePath, "HVDVD_TS"))
&& Directory.EnumerateFiles(Path.Combine(drivePath, "HVDVD_TS")).Any())
if (Directory.Exists(Path.Combine(this.Name, "MSXC"))
&& Directory.EnumerateFiles(Path.Combine(this.Name, "MSXC")).Any())
{
return RedumpSystem.HDDVDVideo;
return RedumpSystem.MicrosoftXboxOne;
}
}
catch
{
}
catch { }
// Sega Dreamcast
if (File.Exists(Path.Combine(drivePath, "IP.BIN")))
if (File.Exists(Path.Combine(this.Name, "IP.BIN")))
{
return RedumpSystem.SegaDreamcast;
}
// Sega Mega-CD / Sega-CD
if (File.Exists(Path.Combine(drivePath, "_BOOT", "IP.BIN"))
|| File.Exists(Path.Combine(drivePath, "_BOOT", "SP.BIN"))
|| File.Exists(Path.Combine(drivePath, "_BOOT", "SP_AS.BIN"))
|| File.Exists(Path.Combine(drivePath, "FILESYSTEM.BIN")))
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")))
{
return RedumpSystem.SegaMegaCDSegaCD;
}
// Sega Saturn
try
{
byte[] sector = ReadSector(0);
if (sector != null)
{
if (sector.StartsWith(Interface.SaturnSectorZeroStart))
return RedumpSystem.SegaSaturn;
}
}
catch { }
// Sony PlayStation and Sony PlayStation 2
string psxExePath = Path.Combine(drivePath, "PSX.EXE");
string systemCnfPath = Path.Combine(drivePath, "SYSTEM.CNF");
string psxExePath = Path.Combine(this.Name, "PSX.EXE");
string systemCnfPath = Path.Combine(this.Name, "SYSTEM.CNF");
if (File.Exists(systemCnfPath))
{
// Check for either BOOT or BOOT2
@@ -365,84 +371,232 @@ namespace MPF.Core.Data
}
// Sony PlayStation 3
if (this.FormattedVolumeLabel.Equals("PS3VOLUME", StringComparison.OrdinalIgnoreCase))
try
{
return RedumpSystem.SonyPlayStation3;
if (Directory.Exists(Path.Combine(this.Name, "PS3_GAME"))
|| Directory.Exists(Path.Combine(this.Name, "PS3_UPDATE"))
|| File.Exists(Path.Combine(this.Name, "PS3_DISC.SFB")))
{
return RedumpSystem.SonyPlayStation3;
}
}
catch { }
// Sony PlayStation 4
if (this.FormattedVolumeLabel.Equals("PS4VOLUME", StringComparison.OrdinalIgnoreCase))
// There are more possible paths that could be checked.
// There are some entries that can be found on most PS4 discs:
// "/app/GAME_SERIAL/app.pkg"
// "/bd/param.sfo"
// "/license/rif"
// There are also extra files that can be found on some discs:
// "/patch/GAME_SERIAL/patch.pkg" can be found in Redump entry 66816.
// Originally on disc as "/patch/CUSA11302/patch.pkg".
// 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 (File.Exists(Path.Combine(this.Name, "PS4", "UPDATE", "PS4UPDATE.PUP")))
{
return RedumpSystem.SonyPlayStation4;
}
// Sony PlayStation 5
if (this.FormattedVolumeLabel.Equals("PS5VOLUME", StringComparison.OrdinalIgnoreCase))
{
return RedumpSystem.SonyPlayStation5;
}
// V.Tech V.Flash / V.Smile Pro
if (File.Exists(Path.Combine(drivePath, "0SYSTEM")))
if (File.Exists(Path.Combine(this.Name, "0SYSTEM")))
{
return RedumpSystem.VTechVFlashVSmilePro;
}
#endregion
#region Computers
// Sharp X68000
if (File.Exists(Path.Combine(this.Name, "COMMAND.X")))
{
return RedumpSystem.SharpX68000;
}
#endregion
#region Video Formats
// BD-Video
if (Directory.Exists(Path.Combine(this.Name, "BDMV")))
{
// Technically BD-Audio has this as well, but it's hard to split that out right now
return RedumpSystem.BDVideo;
}
// DVD-Audio and DVD-Video
try
{
if (Directory.Exists(Path.Combine(this.Name, "AUDIO_TS"))
&& Directory.EnumerateFiles(Path.Combine(this.Name, "AUDIO_TS")).Any())
{
return RedumpSystem.DVDAudio;
}
else if (Directory.Exists(Path.Combine(this.Name, "VIDEO_TS"))
&& Directory.EnumerateFiles(Path.Combine(this.Name, "VIDEO_TS")).Any())
{
return RedumpSystem.DVDVideo;
}
}
catch { }
// HD-DVD-Video
try
{
if (Directory.Exists(Path.Combine(this.Name, "HVDVD_TS"))
&& Directory.EnumerateFiles(Path.Combine(this.Name, "HVDVD_TS")).Any())
{
return RedumpSystem.HDDVDVideo;
}
}
catch { }
// VCD
try
{
if (Directory.Exists(Path.Combine(drivePath, "VCD"))
&& Directory.EnumerateFiles(Path.Combine(drivePath, "VCD")).Any())
if (Directory.Exists(Path.Combine(this.Name, "VCD"))
&& Directory.EnumerateFiles(Path.Combine(this.Name, "VCD")).Any())
{
return RedumpSystem.VideoCD;
}
}
catch { }
#endregion
// Default return
return defaultValue;
}
/// <summary>
/// Read a sector with a specified size from the drive
/// Get the current system from the drive volume label
/// </summary>
/// <param name="num">Sector number, non-negative</param>
/// <param name="size">Size of a sector in bytes</param>
/// <returns>Byte array representing the sector, null on error</returns>
public byte[] ReadSector(long num, int size = 2048)
/// <returns>The system based on volume label, null if none detected</returns>
public RedumpSystem? GetRedumpSystemFromVolumeLabel()
{
// Missing drive leter is not supported
if (string.IsNullOrEmpty(this.driveInfo?.Name))
// If the volume label is empty, we can't do anything
if (string.IsNullOrWhiteSpace(this.VolumeLabel))
return null;
// We don't support negative sectors
if (num < 0)
return null;
// Audio CD
if (this.VolumeLabel.Equals("Audio CD", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.AudioCD;
// Wrap the following in case of device access errors
Stream fs = null;
// Microsoft Xbox
if (this.VolumeLabel.Equals("SEP13011042", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.MicrosoftXbox;
else if (this.VolumeLabel.Equals("SEP13011042072", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.MicrosoftXbox;
// Microsoft Xbox 360
if (this.VolumeLabel.Equals("XBOX360", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.MicrosoftXbox360;
else if (this.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))
// return RedumpSystem.MicrosoftXbox360; // Also for Xbox One?
//if (this.VolumeLabel.Equals("DVD_ROM", StringComparison.OrdinalIgnoreCase))
// return RedumpSystem.MicrosoftXbox360;
// Sega Mega-CD / Sega-CD
if (this.VolumeLabel.Equals("Sega_CD", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.SegaMegaCDSegaCD;
// Sony PlayStation 3
if (this.VolumeLabel.Equals("PS3VOLUME", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.SonyPlayStation3;
// Sony PlayStation 4
if (this.VolumeLabel.Equals("PS4VOLUME", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.SonyPlayStation4;
// Sony PlayStation 5
if (this.VolumeLabel.Equals("PS5VOLUME", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.SonyPlayStation5;
return null;
}
/// <summary>
/// Refresh the current drive information based on path
/// </summary>
public void RefreshDrive()
{
var driveInfo = DriveInfo.GetDrives().FirstOrDefault(d => d?.Name == this.Name);
this.PopulateFromDriveInfo(driveInfo);
}
#endregion
#region Helpers
/// <summary>
/// Get all current attached Drives
/// </summary>
/// <param name="ignoreFixedDrives">True to ignore fixed drives from population, false otherwise</param>
/// <returns>List of drives, null on error</returns>
/// <remarks>
/// https://stackoverflow.com/questions/3060796/how-to-distinguish-between-usb-and-floppy-devices?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa
/// https://msdn.microsoft.com/en-us/library/aa394173(v=vs.85).aspx
/// </remarks>
private static List<Drive> GetDriveList(bool ignoreFixedDrives)
{
var desiredDriveTypes = new List<DriveType>() { DriveType.CDRom };
if (!ignoreFixedDrives)
{
desiredDriveTypes.Add(DriveType.Fixed);
desiredDriveTypes.Add(DriveType.Removable);
}
// TODO: Reduce reliance on `DriveInfo`
// https://github.com/aaru-dps/Aaru/blob/5164a154e2145941472f2ee0aeb2eff3338ecbb3/Aaru.Devices/Windows/ListDevices.cs#L66
// Create an output drive list
var drives = new List<Drive>();
// Get all standard supported drive types
try
{
// Open the drive as a device
fs = File.OpenRead($"\\\\?\\{this.Letter}:");
// Seek to the start of the sector, if possible
long start = num * size;
fs.Seek(start, SeekOrigin.Begin);
// Read and return the sector
byte[] buffer = new byte[size];
fs.Read(buffer, 0, size);
return buffer;
drives = DriveInfo.GetDrives()
.Where(d => desiredDriveTypes.Contains(d.DriveType))
.Select(d => Create(EnumConverter.ToInternalDriveType(d.DriveType), d.Name) ?? new Drive())
.ToList();
}
catch
{
return null;
return drives;
}
finally
// Find and update all floppy drives
try
{
fs?.Dispose();
CimSession session = CimSession.Create(null);
var collection = session.QueryInstances("root\\CIMV2", "WQL", "SELECT * FROM Win32_LogicalDisk");
foreach (CimInstance instance in collection)
{
CimKeyedCollection<CimProperty> properties = instance.CimInstanceProperties;
uint? mediaType = properties["MediaType"]?.Value as uint?;
if (mediaType != null && ((mediaType > 0 && mediaType < 11) || (mediaType > 12 && mediaType < 22)))
{
char devId = (properties["Caption"].Value as string ?? string.Empty)[0];
drives.ForEach(d => { if (d?.Name != null && d.Name[0] == devId) { d.InternalDriveType = Data.InternalDriveType.Floppy; } });
}
}
}
catch
{
// No-op
}
return drives;
}
#endregion
}
}

View File

@@ -1,5 +1,21 @@
namespace MPF.Core.Data
{
/// <summary>
/// Available hashing types
/// </summary>
public enum Hash
{
CRC32,
CRC64,
MD5,
SHA1,
SHA256,
SHA384,
SHA512,
XxHash32,
XxHash64,
}
/// <summary>
/// Drive type for dumping
/// </summary>
@@ -20,12 +36,23 @@
// Dumping support
Aaru,
DD,
DiscImageCreator,
Redumper,
// Verification support only
CleanRip,
DCDumper,
UmdImageCreator,
}
/// <summary>
/// Log level for output
/// </summary>
public enum LogLevel
{
USER,
VERBOSE,
ERROR,
SECRET,
}
}

View File

@@ -8,25 +8,37 @@ 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
public string this[string key]
{
get
{
#if NET48
if (_keyValuePairs == null)
_keyValuePairs = new Dictionary<string, string>();
#else
_keyValuePairs ??= new Dictionary<string, string>();
#endif
key = key.ToLowerInvariant();
if (_keyValuePairs.ContainsKey(key))
return _keyValuePairs[key];
return null;
return string.Empty;
}
set
{
#if NET48
if (_keyValuePairs == null)
_keyValuePairs = new Dictionary<string, string>();
#else
_keyValuePairs ??= new Dictionary<string, string>();
#endif
key = key.ToLowerInvariant();
_keyValuePairs[key] = value;
@@ -99,15 +111,21 @@ namespace MPF.Core.Data
// Keys are case-insensitive by default
try
{
using (StreamReader sr = new StreamReader(stream))
using (var sr = new StreamReader(stream))
{
string section = string.Empty;
while (!sr.EndOfStream)
{
string line = sr.ReadLine().Trim();
var line = sr.ReadLine()?.Trim();
// Empty lines are skipped
if (string.IsNullOrWhiteSpace(line))
{
// No-op, we don't process empty lines
}
// Comments start with ';'
if (line.StartsWith(";"))
else if (line.StartsWith(";"))
{
// No-op, we don't process comments
}
@@ -119,7 +137,11 @@ namespace MPF.Core.Data
}
// 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('=');
@@ -179,7 +201,7 @@ namespace MPF.Core.Data
try
{
using (StreamWriter sw = new StreamWriter(stream))
using (var sw = new StreamWriter(stream))
{
// Order the dictionary by keys to link sections together
var orderedKeyValuePairs = _keyValuePairs.OrderBy(kvp => kvp.Key);
@@ -245,7 +267,9 @@ namespace MPF.Core.Data
public bool TryGetValue(string key, out string value)
{
return ((IDictionary<string, string>)_keyValuePairs).TryGetValue(key.ToLowerInvariant(), out value);
bool result = ((IDictionary<string, string>)_keyValuePairs).TryGetValue(key.ToLowerInvariant(), out var temp);
value = temp ?? string.Empty;
return result;
}
public void Add(KeyValuePair<string, string> item)

View File

@@ -1,42 +1,68 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Generic;
using MPF.Core.Converters;
using RedumpLib.Data;
using SabreTools.RedumpLib.Data;
namespace MPF.Core.Data
{
public class Options : IDictionary<string, string>, ICloneable
public class Options
{
private readonly Dictionary<string, string> _settings;
/// <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
/// </summary>
public bool FirstRun
{
get { return GetBooleanSetting(Settings, "FirstRun", true); }
set { Settings["FirstRun"] = value.ToString(); }
}
#region Internal Program
/// <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; }
get { return GetStringSetting(Settings, "AaruPath", "Programs\\Aaru\\Aaru.exe"); }
set { Settings["AaruPath"] = value; }
}
/// <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; }
get { return GetStringSetting(Settings, "DiscImageCreatorPath", "Programs\\Creator\\DiscImageCreator.exe"); }
set { Settings["DiscImageCreatorPath"] = value; }
}
/// <summary>
/// Path to dd for Windows
/// Path to Redumper
/// </summary>
public string DDPath
#if NET48
public string RedumperPath
#else
public string? RedumperPath
#endif
{
get { return GetStringSetting(_settings, "DDPath", "Programs\\DD\\dd.exe"); }
set { _settings["DDPath"] = value; }
get { return GetStringSetting(Settings, "RedumperPath", "Programs\\Redumper\\redumper.exe"); }
set { Settings["RedumperPath"] = value; }
}
/// <summary>
@@ -46,13 +72,13 @@ namespace MPF.Core.Data
{
get
{
string valueString = GetStringSetting(_settings, "InternalProgram", InternalProgram.DiscImageCreator.ToString());
var valueString = GetStringSetting(Settings, "InternalProgram", InternalProgram.DiscImageCreator.ToString());
var valueEnum = EnumConverter.ToInternalProgram(valueString);
return valueEnum == InternalProgram.NONE ? InternalProgram.DiscImageCreator : valueEnum;
}
set
{
_settings["InternalProgram"] = value.ToString();
Settings["InternalProgram"] = value.ToString();
}
}
@@ -65,8 +91,8 @@ namespace MPF.Core.Data
/// </summary>
public bool EnableDarkMode
{
get { return GetBooleanSetting(_settings, "EnableDarkMode", false); }
set { _settings["EnableDarkMode"] = value.ToString(); }
get { return GetBooleanSetting(Settings, "EnableDarkMode", false); }
set { Settings["EnableDarkMode"] = value.ToString(); }
}
/// <summary>
@@ -74,17 +100,30 @@ namespace MPF.Core.Data
/// </summary>
public bool CheckForUpdatesOnStartup
{
get { return GetBooleanSetting(_settings, "CheckForUpdatesOnStartup", true); }
set { _settings["CheckForUpdatesOnStartup"] = value.ToString(); }
get { return GetBooleanSetting(Settings, "CheckForUpdatesOnStartup", true); }
set { Settings["CheckForUpdatesOnStartup"] = value.ToString(); }
}
/// <summary>
/// Fast update label - Skips disc checks and updates path only
/// </summary>
public bool FastUpdateLabel
{
get { return GetBooleanSetting(Settings, "FastUpdateLabel", false); }
set { Settings["FastUpdateLabel"] = value.ToString(); }
}
/// <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; }
get { return GetStringSetting(Settings, "DefaultOutputPath", "ISO"); }
set { Settings["DefaultOutputPath"] = value; }
}
/// <summary>
@@ -94,16 +133,26 @@ namespace MPF.Core.Data
{
get
{
string valueString = GetStringSetting(_settings, "DefaultSystem", null);
var valueEnum = Extensions.ToRedumpSystem(valueString);
var valueString = GetStringSetting(Settings, "DefaultSystem", null);
var valueEnum = Extensions.ToRedumpSystem(valueString ?? string.Empty);
return valueEnum;
}
set
{
_settings["DefaultSystem"] = value.LongName();
Settings["DefaultSystem"] = value.LongName();
}
}
/// <summary>
/// Default output path for dumps
/// </summary>
/// <remarks>This is a hidden setting</remarks>
public bool ShowDebugViewMenuItem
{
get { return GetBooleanSetting(Settings, "ShowDebugViewMenuItem", false); }
set { Settings["ShowDebugViewMenuItem"] = value.ToString(); }
}
#endregion
#region Dumping Speeds
@@ -113,8 +162,8 @@ namespace MPF.Core.Data
/// </summary>
public int PreferredDumpSpeedCD
{
get { return GetInt32Setting(_settings, "PreferredDumpSpeedCD", 72); }
set { _settings["PreferredDumpSpeedCD"] = value.ToString(); }
get { return GetInt32Setting(Settings, "PreferredDumpSpeedCD", 24); }
set { Settings["PreferredDumpSpeedCD"] = value.ToString(); }
}
/// <summary>
@@ -122,8 +171,17 @@ namespace MPF.Core.Data
/// </summary>
public int PreferredDumpSpeedDVD
{
get { return GetInt32Setting(_settings, "PreferredDumpSpeedDVD", 24); }
set { _settings["PreferredDumpSpeedDVD"] = value.ToString(); }
get { return GetInt32Setting(Settings, "PreferredDumpSpeedDVD", 16); }
set { Settings["PreferredDumpSpeedDVD"] = value.ToString(); }
}
/// <summary>
/// Default HD-DVD dumping speed
/// </summary>
public int PreferredDumpSpeedHDDVD
{
get { return GetInt32Setting(Settings, "PreferredDumpSpeedHDDVD", 8); }
set { Settings["PreferredDumpSpeedHDDVD"] = value.ToString(); }
}
/// <summary>
@@ -131,8 +189,8 @@ namespace MPF.Core.Data
/// </summary>
public int PreferredDumpSpeedBD
{
get { return GetInt32Setting(_settings, "PreferredDumpSpeedBD", 16); }
set { _settings["PreferredDumpSpeedBD"] = value.ToString(); }
get { return GetInt32Setting(Settings, "PreferredDumpSpeedBD", 8); }
set { Settings["PreferredDumpSpeedBD"] = value.ToString(); }
}
#endregion
@@ -144,8 +202,8 @@ namespace MPF.Core.Data
/// </summary>
public bool AaruEnableDebug
{
get { return GetBooleanSetting(_settings, "AaruEnableDebug", false); }
set { _settings["AaruEnableDebug"] = value.ToString(); }
get { return GetBooleanSetting(Settings, "AaruEnableDebug", false); }
set { Settings["AaruEnableDebug"] = value.ToString(); }
}
/// <summary>
@@ -153,8 +211,8 @@ namespace MPF.Core.Data
/// </summary>
public bool AaruEnableVerbose
{
get { return GetBooleanSetting(_settings, "AaruEnableVerbose", false); }
set { _settings["AaruEnableVerbose"] = value.ToString(); }
get { return GetBooleanSetting(Settings, "AaruEnableVerbose", false); }
set { Settings["AaruEnableVerbose"] = value.ToString(); }
}
/// <summary>
@@ -162,8 +220,8 @@ namespace MPF.Core.Data
/// </summary>
public bool AaruForceDumping
{
get { return GetBooleanSetting(_settings, "AaruForceDumping", true); }
set { _settings["AaruForceDumping"] = value.ToString(); }
get { return GetBooleanSetting(Settings, "AaruForceDumping", true); }
set { Settings["AaruForceDumping"] = value.ToString(); }
}
/// <summary>
@@ -171,8 +229,8 @@ namespace MPF.Core.Data
/// </summary>
public int AaruRereadCount
{
get { return GetInt32Setting(_settings, "AaruRereadCount", 5); }
set { _settings["AaruRereadCount"] = value.ToString(); }
get { return GetInt32Setting(Settings, "AaruRereadCount", 5); }
set { Settings["AaruRereadCount"] = value.ToString(); }
}
/// <summary>
@@ -180,14 +238,32 @@ namespace MPF.Core.Data
/// </summary>
public bool AaruStripPersonalData
{
get { return GetBooleanSetting(_settings, "AaruStripPersonalData", false); }
set { _settings["AaruStripPersonalData"] = value.ToString(); }
get { return GetBooleanSetting(Settings, "AaruStripPersonalData", false); }
set { Settings["AaruStripPersonalData"] = value.ToString(); }
}
#endregion
#region DiscImageCreator
/// <summary>
/// Enable multi-sector read flag by default
/// </summary>
public bool DICMultiSectorRead
{
get { return GetBooleanSetting(Settings, "DICMultiSectorRead", false); }
set { Settings["DICMultiSectorRead"] = value.ToString(); }
}
/// <summary>
/// Include a default multi-sector read value
/// </summary>
public int DICMultiSectorReadValue
{
get { return GetInt32Setting(Settings, "DICMultiSectorReadValue", 0); }
set { Settings["DICMultiSectorReadValue"] = value.ToString(); }
}
/// <summary>
/// Enable overly-secure dumping flags by default
/// </summary>
@@ -198,8 +274,8 @@ namespace MPF.Core.Data
/// </remarks>
public bool DICParanoidMode
{
get { return GetBooleanSetting(_settings, "DICParanoidMode", false); }
set { _settings["DICParanoidMode"] = value.ToString(); }
get { return GetBooleanSetting(Settings, "DICParanoidMode", false); }
set { Settings["DICParanoidMode"] = value.ToString(); }
}
/// <summary>
@@ -207,8 +283,8 @@ namespace MPF.Core.Data
/// </summary>
public bool DICQuietMode
{
get { return GetBooleanSetting(_settings, "DICQuietMode", false); }
set { _settings["DICQuietMode"] = value.ToString(); }
get { return GetBooleanSetting(Settings, "DICQuietMode", false); }
set { Settings["DICQuietMode"] = value.ToString(); }
}
/// <summary>
@@ -216,8 +292,17 @@ namespace MPF.Core.Data
/// </summary>
public int DICRereadCount
{
get { return GetInt32Setting(_settings, "DICRereadCount", 20); }
set { _settings["DICRereadCount"] = value.ToString(); }
get { return GetInt32Setting(Settings, "DICRereadCount", 20); }
set { Settings["DICRereadCount"] = value.ToString(); }
}
/// <summary>
/// Default number of DVD/HD-DVD/BD rereads
/// </summary>
public int DICDVDRereadCount
{
get { return GetInt32Setting(Settings, "DICDVDRereadCount", 10); }
set { Settings["DICDVDRereadCount"] = value.ToString(); }
}
/// <summary>
@@ -225,8 +310,8 @@ namespace MPF.Core.Data
/// </summary>
public bool DICResetDriveAfterDump
{
get { return GetBooleanSetting(_settings, "DICResetDriveAfterDump", false); }
set { _settings["DICResetDriveAfterDump"] = value.ToString(); }
get { return GetBooleanSetting(Settings, "DICResetDriveAfterDump", false); }
set { Settings["DICResetDriveAfterDump"] = value.ToString(); }
}
/// <summary>
@@ -234,8 +319,57 @@ namespace MPF.Core.Data
/// </summary>
public bool DICUseCMIFlag
{
get { return GetBooleanSetting(_settings, "DICUseCMIFlag", false); }
set { _settings["DICUseCMIFlag"] = value.ToString(); }
get { return GetBooleanSetting(Settings, "DICUseCMIFlag", false); }
set { Settings["DICUseCMIFlag"] = value.ToString(); }
}
#endregion
#region Redumper
/// <summary>
/// Enable debug output while dumping by default
/// </summary>
public bool RedumperEnableDebug
{
get { return GetBooleanSetting(Settings, "RedumperEnableDebug", false); }
set { Settings["RedumperEnableDebug"] = value.ToString(); }
}
/// <summary>
/// Enable verbose output while dumping by default
/// </summary>
public bool RedumperEnableVerbose
{
get { return GetBooleanSetting(Settings, "RedumperEnableVerbose", false); }
set { Settings["RedumperEnableVerbose"] = value.ToString(); }
}
/// <summary>
/// Enable BE reading by default with Redumper
/// </summary>
public bool RedumperUseBEReading
{
get { return GetBooleanSetting(Settings, "RedumperUseBEReading", false); }
set { Settings["RedumperUseBEReading"] = value.ToString(); }
}
/// <summary>
/// Enable generic drive type by default with Redumper
/// </summary>
public bool RedumperUseGenericDriveType
{
get { return GetBooleanSetting(Settings, "RedumperUseGenericDriveType", false); }
set { Settings["RedumperUseGenericDriveType"] = value.ToString(); }
}
/// <summary>
/// Default number of rereads
/// </summary>
public int RedumperRereadCount
{
get { return GetInt32Setting(Settings, "RedumperRereadCount", 20); }
set { Settings["RedumperRereadCount"] = value.ToString(); }
}
#endregion
@@ -247,8 +381,17 @@ namespace MPF.Core.Data
/// </summary>
public bool ScanForProtection
{
get { return GetBooleanSetting(_settings, "ScanForProtection", true); }
set { _settings["ScanForProtection"] = value.ToString(); }
get { return GetBooleanSetting(Settings, "ScanForProtection", true); }
set { Settings["ScanForProtection"] = value.ToString(); }
}
/// <summary>
/// Output all found protections to a separate file in the directory
/// </summary>
public bool OutputSeparateProtectionFile
{
get { return GetBooleanSetting(Settings, "OutputSeparateProtectionFile", true); }
set { Settings["OutputSeparateProtectionFile"] = value.ToString(); }
}
/// <summary>
@@ -256,8 +399,8 @@ namespace MPF.Core.Data
/// </summary>
public bool AddPlaceholders
{
get { return GetBooleanSetting(_settings, "AddPlaceholders", true); }
set { _settings["AddPlaceholders"] = value.ToString(); }
get { return GetBooleanSetting(Settings, "AddPlaceholders", true); }
set { Settings["AddPlaceholders"] = value.ToString(); }
}
/// <summary>
@@ -265,8 +408,35 @@ namespace MPF.Core.Data
/// </summary>
public bool PromptForDiscInformation
{
get { return GetBooleanSetting(_settings, "PromptForDiscInformation", true); }
set { _settings["PromptForDiscInformation"] = value.ToString(); }
get { return GetBooleanSetting(Settings, "PromptForDiscInformation", true); }
set { Settings["PromptForDiscInformation"] = value.ToString(); }
}
/// <summary>
/// Pull all information from Redump if signed in
/// </summary>
public bool PullAllInformation
{
get { return GetBooleanSetting(Settings, "PullAllInformation", false); }
set { Settings["PullAllInformation"] = value.ToString(); }
}
/// <summary>
/// Enable tabs in all input fields
/// </summary>
public bool EnableTabsInInputFields
{
get { return GetBooleanSetting(Settings, "EnableTabsInInputFields", false); }
set { Settings["EnableTabsInInputFields"] = value.ToString(); }
}
/// <summary>
/// Limit outputs to Redump-supported values only
/// </summary>
public bool EnableRedumpCompatibility
{
get { return GetBooleanSetting(Settings, "EnableRedumpCompatibility", true); }
set { Settings["EnableRedumpCompatibility"] = value.ToString(); }
}
/// <summary>
@@ -274,8 +444,8 @@ namespace MPF.Core.Data
/// </summary>
public bool ShowDiscEjectReminder
{
get { return GetBooleanSetting(_settings, "ShowDiscEjectReminder", true); }
set { _settings["ShowDiscEjectReminder"] = value.ToString(); }
get { return GetBooleanSetting(Settings, "ShowDiscEjectReminder", true); }
set { Settings["ShowDiscEjectReminder"] = value.ToString(); }
}
/// <summary>
@@ -283,8 +453,8 @@ namespace MPF.Core.Data
/// </summary>
public bool EjectAfterDump
{
get { return GetBooleanSetting(_settings, "EjectAfterDump", false); }
set { _settings["EjectAfterDump"] = value.ToString(); }
get { return GetBooleanSetting(Settings, "EjectAfterDump", false); }
set { Settings["EjectAfterDump"] = value.ToString(); }
}
/// <summary>
@@ -292,8 +462,8 @@ namespace MPF.Core.Data
/// </summary>
public bool IgnoreFixedDrives
{
get { return GetBooleanSetting(_settings, "IgnoreFixedDrives", true); }
set { _settings["IgnoreFixedDrives"] = value.ToString(); }
get { return GetBooleanSetting(Settings, "IgnoreFixedDrives", true); }
set { Settings["IgnoreFixedDrives"] = value.ToString(); }
}
/// <summary>
@@ -301,8 +471,17 @@ namespace MPF.Core.Data
/// </summary>
public bool ToolsInSeparateWindow
{
get { return GetBooleanSetting(_settings, "ToolsInSeparateWindow", true); }
set { _settings["ToolsInSeparateWindow"] = value.ToString(); }
get { return GetBooleanSetting(Settings, "ToolsInSeparateWindow", true); }
set { Settings["ToolsInSeparateWindow"] = value.ToString(); }
}
/// <summary>
/// Add the dump filename as a suffix to the auto-generated files
/// </summary>
public bool AddFilenameSuffix
{
get { return GetBooleanSetting(Settings, "AddFilenameSuffix", false); }
set { Settings["AddFilenameSuffix"] = value.ToString(); }
}
/// <summary>
@@ -310,8 +489,8 @@ namespace MPF.Core.Data
/// </summary>
public bool OutputSubmissionJSON
{
get { return GetBooleanSetting(_settings, "OutputSubmissionJSON", false); }
set { _settings["OutputSubmissionJSON"] = value.ToString(); }
get { return GetBooleanSetting(Settings, "OutputSubmissionJSON", false); }
set { Settings["OutputSubmissionJSON"] = value.ToString(); }
}
/// <summary>
@@ -319,8 +498,8 @@ namespace MPF.Core.Data
/// </summary>
public bool IncludeArtifacts
{
get { return GetBooleanSetting(_settings, "IncludeArtifacts", false); }
set { _settings["IncludeArtifacts"] = value.ToString(); }
get { return GetBooleanSetting(Settings, "IncludeArtifacts", false); }
set { Settings["IncludeArtifacts"] = value.ToString(); }
}
/// <summary>
@@ -328,8 +507,17 @@ namespace MPF.Core.Data
/// </summary>
public bool CompressLogFiles
{
get { return GetBooleanSetting(_settings, "CompressLogFiles", true); }
set { _settings["CompressLogFiles"] = value.ToString(); }
get { return GetBooleanSetting(Settings, "CompressLogFiles", true); }
set { Settings["CompressLogFiles"] = value.ToString(); }
}
/// <summary>
/// Delete unnecessary files to reduce space
/// </summary>
public bool DeleteUnnecessaryFiles
{
get { return GetBooleanSetting(Settings, "DeleteUnnecessaryFiles", false); }
set { Settings["DeleteUnnecessaryFiles"] = value.ToString(); }
}
#endregion
@@ -341,8 +529,8 @@ namespace MPF.Core.Data
/// </summary>
public bool SkipMediaTypeDetection
{
get { return GetBooleanSetting(_settings, "SkipMediaTypeDetection", false); }
set { _settings["SkipMediaTypeDetection"] = value.ToString(); }
get { return GetBooleanSetting(Settings, "SkipMediaTypeDetection", false); }
set { Settings["SkipMediaTypeDetection"] = value.ToString(); }
}
/// <summary>
@@ -350,8 +538,8 @@ namespace MPF.Core.Data
/// </summary>
public bool SkipSystemDetection
{
get { return GetBooleanSetting(_settings, "SkipSystemDetection", false); }
set { _settings["SkipSystemDetection"] = value.ToString(); }
get { return GetBooleanSetting(Settings, "SkipSystemDetection", false); }
set { Settings["SkipSystemDetection"] = value.ToString(); }
}
#endregion
@@ -363,8 +551,8 @@ namespace MPF.Core.Data
/// </summary>
public bool ScanArchivesForProtection
{
get { return GetBooleanSetting(_settings, "ScanArchivesForProtection", true); }
set { _settings["ScanArchivesForProtection"] = value.ToString(); }
get { return GetBooleanSetting(Settings, "ScanArchivesForProtection", true); }
set { Settings["ScanArchivesForProtection"] = value.ToString(); }
}
/// <summary>
@@ -372,17 +560,8 @@ namespace MPF.Core.Data
/// </summary>
public bool ScanPackersForProtection
{
get { return GetBooleanSetting(_settings, "ScanPackersForProtection", false); }
set { _settings["ScanPackersForProtection"] = value.ToString(); }
}
/// <summary>
/// Force scanning all files for protection
/// </summary>
public bool ForceScanningForProtection
{
get { return GetBooleanSetting(_settings, "ForceScanningForProtection", false); }
set { _settings["ForceScanningForProtection"] = value.ToString(); }
get { return GetBooleanSetting(Settings, "ScanPackersForProtection", false); }
set { Settings["ScanPackersForProtection"] = value.ToString(); }
}
/// <summary>
@@ -390,8 +569,8 @@ namespace MPF.Core.Data
/// </summary>
public bool IncludeDebugProtectionInformation
{
get { return GetBooleanSetting(_settings, "IncludeDebugProtectionInformation", false); }
set { _settings["IncludeDebugProtectionInformation"] = value.ToString(); }
get { return GetBooleanSetting(Settings, "IncludeDebugProtectionInformation", false); }
set { Settings["IncludeDebugProtectionInformation"] = value.ToString(); }
}
#endregion
@@ -403,8 +582,8 @@ namespace MPF.Core.Data
/// </summary>
public bool VerboseLogging
{
get { return GetBooleanSetting(_settings, "VerboseLogging", true); }
set { _settings["VerboseLogging"] = value.ToString(); }
get { return GetBooleanSetting(Settings, "VerboseLogging", true); }
set { Settings["VerboseLogging"] = value.ToString(); }
}
/// <summary>
@@ -412,49 +591,36 @@ namespace MPF.Core.Data
/// </summary>
public bool OpenLogWindowAtStartup
{
get { return GetBooleanSetting(_settings, "OpenLogWindowAtStartup", true); }
set { _settings["OpenLogWindowAtStartup"] = value.ToString(); }
}
/// <summary>
/// Enable fancy formatting of log statements
/// Disables EnableProgressProcessing if disabled
/// </summary>
/// <remarks>
/// This is mainly for outputting redirected console outputs. Not many
/// other bits of the logs include any specially handled outputs.
/// </remarks>
public bool EnableLogFormatting
{
get { return GetBooleanSetting(_settings, "EnableLogFormatting", false); }
set { _settings["EnableLogFormatting"] = value.ToString(); }
}
/// <summary>
/// Enable progress bar updating based on log text
/// Disabled if EnableLogFormatting is disabled
/// </summary>
public bool EnableProgressProcessing
{
get { return GetBooleanSetting(_settings, "EnableProgressProcessing", false); }
set { _settings["EnableProgressProcessing"] = value.ToString(); }
get { return GetBooleanSetting(Settings, "OpenLogWindowAtStartup", true); }
set { Settings["OpenLogWindowAtStartup"] = value.ToString(); }
}
#endregion
#region Redump Login Information
#if NET48
public string RedumpUsername
#else
public string? RedumpUsername
#endif
{
get { return GetStringSetting(_settings, "RedumpUsername", ""); }
set { _settings["RedumpUsername"] = value; }
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 { return GetStringSetting(_settings, "RedumpPassword", ""); }
set { _settings["RedumpPassword"] = value; }
get
{
return GetStringSetting(Settings, "RedumpPassword", "");
}
set { Settings["RedumpPassword"] = value; }
}
/// <summary>
@@ -468,17 +634,47 @@ 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
{
this._settings = settings ?? new Dictionary<string, string>();
#if NET48
this.Settings = settings ?? new Dictionary<string, string>();
#else
this.Settings = settings ?? new Dictionary<string, string?>();
#endif
}
/// <summary>
/// Create a clone of the object
/// Constructor taking an existing Options object
/// </summary>
public object Clone()
/// <param name="source"></param>
#if NET48
public Options(Options source)
#else
public Options(Options? source)
#endif
{
return new Options(new Dictionary<string, string>(_settings));
#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
}
/// <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;
}
#region Helpers
@@ -490,11 +686,15 @@ 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>
private bool GetBooleanSetting(Dictionary<string, string> settings, string key, bool defaultValue)
#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))
{
if (Boolean.TryParse(settings[key], out bool value))
if (bool.TryParse(settings[key], out bool value))
return value;
else
return defaultValue;
@@ -512,11 +712,15 @@ 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>
private int GetInt32Setting(Dictionary<string, string> settings, string key, int defaultValue)
#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))
{
if (Int32.TryParse(settings[key], out int value))
if (int.TryParse(settings[key], out int value))
return value;
else
return defaultValue;
@@ -534,7 +738,11 @@ 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>
private string GetStringSetting(Dictionary<string, string> settings, string key, string defaultValue)
#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];
@@ -543,45 +751,5 @@ namespace MPF.Core.Data
}
#endregion
#region IDictionary implementations
public ICollection<string> Keys => _settings.Keys;
public ICollection<string> Values => _settings.Values;
public int Count => _settings.Count;
public bool IsReadOnly => ((IDictionary<string, string>)_settings).IsReadOnly;
public string this[string key]
{
get { return (_settings.ContainsKey(key) ? _settings[key] : null); }
set { _settings[key] = value; }
}
public bool ContainsKey(string key) => _settings.ContainsKey(key);
public void Add(string key, string value) => _settings.Add(key, value);
public bool Remove(string key) => _settings.Remove(key);
public bool TryGetValue(string key, out string value) => _settings.TryGetValue(key, out value);
public void Add(KeyValuePair<string, string> item) => _settings.Add(item.Key, item.Value);
public void Clear() => _settings.Clear();
public bool Contains(KeyValuePair<string, string> item) => ((IDictionary<string, string>)_settings).Contains(item);
public void CopyTo(KeyValuePair<string, string>[] array, int arrayIndex) => ((IDictionary<string, string>)_settings).CopyTo(array, arrayIndex);
public bool Remove(KeyValuePair<string, string> item) => ((IDictionary<string, string>)_settings).Remove(item);
public IEnumerator<KeyValuePair<string, string>> GetEnumerator() => _settings.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _settings.GetEnumerator();
#endregion
}
}

View File

@@ -42,7 +42,7 @@ namespace MPF.Core.Data
public void Enqueue(T item)
{
// Only accept new data when not cancelled
if (!this.TokenSource.IsCancellationRequested)
if (item != null && !this.TokenSource.IsCancellationRequested)
this.InternalQueue.Enqueue(item);
}
@@ -64,7 +64,7 @@ namespace MPF.Core.Data
}
// Get the next item from the queue
if (!this.InternalQueue.TryDequeue(out T nextItem))
if (!this.InternalQueue.TryDequeue(out var nextItem))
continue;
// Invoke the lambda, if possible

View File

@@ -30,7 +30,11 @@
/// 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
/// <summary>
/// Create a default failure result with no message
@@ -42,7 +46,11 @@
/// 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
/// <summary>
/// Results can be compared to boolean values based on the success value

View File

@@ -1,423 +0,0 @@
using RedumpLib.Data;
namespace MPF.Core.Data
{
/// <summary>
/// Contains information specific to an XGD disc
/// </summary>
/// <remarks>
/// XGD1 XeMID Format Information:
///
/// AABBBCCD
/// - AA => The two-ASCII-character publisher identifier (see GetPublisher for details)
/// - BBB => Game ID
/// - CC => Version number
/// - D => Region identifier (see GetRegion for details)
///
/// XGD2/3 XeMID Format Information:
///
/// AABCCCDDEFFGHH(IIIIIIII)
/// - AA => The two-ASCII-character publisher identifier (see GetPublisher for details)
/// - B => Platform identifier; 2 indicates Xbox 360.
/// - CCC => Game ID
/// - DD => SKU number (unique per SKU of a title)
/// - E => Region identifier (see GetRegion for details)
/// - FF => Base version; usually starts at 01 (can be 1 or 2 characters)
/// - G => Media type identifier (see GetMediaSubtype for details)
/// - HH => Disc number stored in [disc number][total discs] format
/// - IIIIIIII => 8-hex-digit certification submission identifier; usually on test discs only
/// </remarks>
public class XgdInfo
{
#region Fields
/// <summary>
/// Indicates whether the information in this object is fully instantiated or not
/// </summary>
public bool Initialized { get; private set; }
/// <summary>
/// Raw XeMID string that all other information is derived from
/// </summary>
public string XeMID { get; private set; }
/// <summary>
/// 2-character publisher identifier
/// </summary>
public string PublisherIdentifier { get; private set; }
/// <summary>
/// Platform disc is made for, 2 indicates Xbox 360
/// </summary>
public char? PlatformIdentifier { get; private set; }
/// <summary>
/// Game ID
/// </summary>
public string GameID { get; private set; }
/// <summary>
/// For XGD1: Internal version number
/// For XGD2/3: Title-specific SKU
/// </summary>
public string SKU { get; private set; }
/// <summary>
/// Region identifier character
/// </summary>
public char RegionIdentifier { get; private set; }
/// <summary>
/// Base version of executables, usually starts at 01
/// </summary>
/// <remarks>
/// TODO: Check if this is always 2 characters for XGD2/3
/// </remarks>
public string BaseVersion { get; private set; }
/// <summary>
/// Media subtype identifier
/// </summary>
public char MediaSubtypeIdentifier { get; private set; }
/// <summary>
/// Disc number stored in [disc number][total discs] format
/// </summary>
public string DiscNumberIdentifier { get; private set; }
/// <summary>
/// 8-hex-digit certification submission identifier; usually on test discs only
/// </summary>
public string CertificationSubmissionIdentifier { get; private set; }
#endregion
#region Auto-Generated Information
/// <summary>
/// Human-readable name derived from the publisher identifier
/// </summary>
public string PublisherName => GetPublisher(this.PublisherIdentifier);
/// <summary>
/// Internally represented region
/// </summary>
public Region? InternalRegion => GetRegion(this.RegionIdentifier);
/// <summary>
/// Human-readable subtype derived from the media identifier
/// </summary>
public string MediaSubtype => GetMediaSubtype(this.MediaSubtypeIdentifier);
#endregion
/// <summary>
/// Populate a set of XGD information from a Master ID (XeMID) string
/// </summary>
/// <param name="xemid">XeMID string representing the DMI information</param>
/// <param name="validate">True if value validation should be performed, false otherwise</param>
public XgdInfo(string xemid, bool validate = false)
{
this.Initialized = false;
if (string.IsNullOrWhiteSpace(xemid))
return;
this.XeMID = xemid.TrimEnd('\0');
if (string.IsNullOrWhiteSpace(this.XeMID))
return;
// XGD1 information is 8 characters
if (this.XeMID.Length == 8)
this.Initialized = ParseXGD1XeMID(this.XeMID, validate);
// XGD2/3 information is semi-variable length
else if (this.XeMID.Length == 13 || this.XeMID.Length == 14 || this.XeMID.Length == 21 || this.XeMID.Length == 22)
this.Initialized = ParseXGD23XeMID(this.XeMID, validate);
}
/// <summary>
/// Get the human-readable serial string
/// </summary>
/// <returns>Formatted serial string, null on error</returns>
public string GetSerial()
{
if (!this.Initialized)
return null;
try
{
// XGD1 doesn't use PlatformIdentifier
if (this.PlatformIdentifier == null)
return $"{this.PublisherIdentifier}-{this.GameID}";
// XGD2/3 uses a specific identifier
else if (this.PlatformIdentifier == '2')
return $"{this.PublisherIdentifier}-{this.PlatformIdentifier}{this.GameID}";
return null;
}
catch
{
return null;
}
}
/// <summary>
/// Get the human-readable version string
/// </summary>
/// <returns>Formatted version string, null on error</returns>
/// <remarks>This may differ for XGD2/3 in the future</remarks>
public string GetVersion()
{
if (!this.Initialized)
return null;
try
{
// XGD1 doesn't use PlatformIdentifier
if (this.PlatformIdentifier == null)
return $"1.{this.SKU}";
// XGD2/3 uses a specific identifier
else if (this.PlatformIdentifier == '2')
return $"1.{this.SKU}";
return null;
}
catch
{
return null;
}
}
/// <summary>
/// Parse an XGD1 XeMID string
/// </summary>
/// <param name="xemid">XeMID string to attempt to parse</param>
/// <param name="validate">True if value validation should be performed, false otherwise</param>
/// <returns>True if the XeMID could be parsed, false otherwise</returns>
private bool ParseXGD1XeMID(string xemid, bool validate)
{
if (xemid == null || xemid.Length != 8)
return false;
this.PublisherIdentifier = xemid.Substring(0, 2);
if (validate && string.IsNullOrEmpty(this.PublisherName))
return false;
this.GameID = xemid.Substring(2, 3);
this.SKU = xemid.Substring(5, 2);
this.RegionIdentifier = xemid[7];
if (validate && this.InternalRegion == null)
return false;
return true;
}
/// <summary>
/// Parse an XGD2/3 XeMID string
/// </summary>
/// <param name="xemid">XeMID string to attempt to parse</param>
/// <param name="validate">True if value validation should be performed, false otherwise</param>
/// <returns>True if the XeMID could be parsed, false otherwise</returns>
private bool ParseXGD23XeMID(string xemid, bool validate)
{
if (xemid == null
|| (xemid.Length != 13 && xemid.Length != 14
&& xemid.Length != 21 && xemid.Length != 22))
return false;
this.PublisherIdentifier = xemid.Substring(0, 2);
if (validate && string.IsNullOrEmpty(this.PublisherName))
return false;
this.PlatformIdentifier = xemid[2];
if (validate && this.PlatformIdentifier != '2')
return false;
this.GameID = xemid.Substring(3, 3);
this.SKU = xemid.Substring(6, 2);
this.RegionIdentifier = xemid[8];
if (validate && this.InternalRegion == null)
return false;
if (xemid.Length == 13 || xemid.Length == 21)
{
this.BaseVersion = xemid.Substring(9, 1);
this.MediaSubtypeIdentifier = xemid[10];
if (validate && string.IsNullOrEmpty(this.MediaSubtype))
return false;
this.DiscNumberIdentifier = xemid.Substring(11, 2);
}
else if (xemid.Length == 14 || xemid.Length == 22)
{
this.BaseVersion = xemid.Substring(9, 2);
this.MediaSubtypeIdentifier = xemid[11];
if (validate && string.IsNullOrEmpty(this.MediaSubtype))
return false;
this.DiscNumberIdentifier = xemid.Substring(12, 2);
}
if (xemid.Length == 21)
this.CertificationSubmissionIdentifier = xemid.Substring(13);
else if (xemid.Length == 22)
this.CertificationSubmissionIdentifier = xemid.Substring(14);
return true;
}
#region Helpers
/// <summary>
/// Determine the XGD type based on the XGD2/3 media type identifier character
/// </summary>
/// <param name="mediaTypeIdentifier">Character denoting the media type</param>
/// <returns>Media subtype as a string, if possible</returns>
private static string GetMediaSubtype(char mediaTypeIdentifier)
{
switch (mediaTypeIdentifier)
{
case 'F': return "XGD3";
case 'X': return "XGD2";
case 'Z': return "Games on Demand / Marketplace Demo";
default: return null;
}
}
/// <summary>
/// Get the full name of the publisher from the 2-character identifier
/// </summary>
/// <param name="publisherIdentifier">Case-sensitive 2-character identifier</param>
/// <returns>Publisher name, if possible</returns>
/// <see cref="https://xboxdevwiki.net/Xbe#Title_ID"/>
private static string GetPublisher(string publisherIdentifier)
{
switch (publisherIdentifier)
{
case "AC": return "Acclaim Entertainment";
case "AH": return "ARUSH Entertainment";
case "AQ": return "Aqua System";
case "AS": return "ASK";
case "AT": return "Atlus";
case "AV": return "Activision";
case "AY": return "Aspyr Media";
case "BA": return "Bandai";
case "BL": return "Black Box";
case "BM": return "BAM! Entertainment";
case "BR": return "Broccoli Co.";
case "BS": return "Bethesda Softworks";
case "BU": return "Bunkasha Co.";
case "BV": return "Buena Vista Games";
case "BW": return "BBC Multimedia";
case "BZ": return "Blizzard";
case "CC": return "Capcom";
case "CK": return "Kemco Corporation"; // TODO: Confirm
case "CM": return "Codemasters";
case "CV": return "Crave Entertainment";
case "DC": return "DreamCatcher Interactive";
case "DX": return "Davilex";
case "EA": return "Electronic Arts (EA)";
case "EC": return "Encore inc";
case "EL": return "Enlight Software";
case "EM": return "Empire Interactive";
case "ES": return "Eidos Interactive";
case "FI": return "Fox Interactive";
case "FS": return "From Software";
case "GE": return "Genki Co.";
case "GV": return "Groove Games";
case "HE": return "Tru Blu (Entertainment division of Home Entertainment Suppliers)";
case "HP": return "Hip games";
case "HU": return "Hudson Soft";
case "HW": return "Highwaystar";
case "IA": return "Mad Catz Interactive";
case "IF": return "Idea Factory";
case "IG": return "Infogrames";
case "IL": return "Interlex Corporation";
case "IM": return "Imagine Media";
case "IO": return "Ignition Entertainment";
case "IP": return "Interplay Entertainment";
case "IX": return "InXile Entertainment"; // TODO: Confirm
case "JA": return "Jaleco";
case "JW": return "JoWooD";
case "KB": return "Kemco"; // TODO: Confirm
case "KI": return "Kids Station Inc."; // TODO: Confirm
case "KN": return "Konami";
case "KO": return "KOEI";
case "KU": return "Kobi and / or GAE (formerly Global A Entertainment)"; // TODO: Confirm
case "LA": return "LucasArts";
case "LS": return "Black Bean Games (publishing arm of Leader S.p.A.)";
case "MD": return "Metro3D";
case "ME": return "Medix";
case "MI": return "Microïds";
case "MJ": return "Majesco Entertainment";
case "MM": return "Myelin Media";
case "MP": return "MediaQuest"; // TODO: Confirm
case "MS": return "Microsoft Game Studios";
case "MW": return "Midway Games";
case "MX": return "Empire Interactive"; // TODO: Confirm
case "NK": return "NewKidCo";
case "NL": return "NovaLogic";
case "NM": return "Namco";
case "OX": return "Oxygen Interactive";
case "PC": return "Playlogic Entertainment";
case "PL": return "Phantagram Co., Ltd.";
case "RA": return "Rage";
case "SA": return "Sammy";
case "SC": return "SCi Games";
case "SE": return "SEGA";
case "SN": return "SNK";
case "SS": return "Simon & Schuster";
case "SU": return "Success Corporation";
case "SW": return "Swing! Deutschland";
case "TA": return "Takara";
case "TC": return "Tecmo";
case "TD": return "The 3DO Company (or just 3DO)";
case "TK": return "Takuyo";
case "TM": return "TDK Mediactive";
case "TQ": return "THQ";
case "TS": return "Titus Interactive";
case "TT": return "Take-Two Interactive Software";
case "US": return "Ubisoft";
case "VC": return "Victor Interactive Software";
case "VN": return "Vivendi Universal (just took Interplays publishing rights)"; // TODO: Confirm
case "VU": return "Vivendi Universal Games";
case "VV": return "Vivendi Universal Games"; // TODO: Confirm
case "WE": return "Wanadoo Edition";
case "WR": return "Warner Bros. Interactive Entertainment"; // TODO: Confirm
case "XI": return "XPEC Entertainment and Idea Factory";
case "XK": return "Xbox kiosk disk?"; // TODO: Confirm
case "XL": return "Xbox special bundled or live demo disk?"; // TODO: Confirm
case "XM": return "Evolved Games"; // TODO: Confirm
case "XP": return "XPEC Entertainment";
case "XR": return "Panorama";
case "YB": return "YBM Sisa (South-Korea)";
case "ZD": return "Zushi Games (formerly Zoo Digital Publishing)";
default: return null;
}
}
/// <summary>
/// Determine the region based on the XGD serial character
/// </summary>
/// <param name="region">Character denoting the region</param>
/// <returns>Region, if possible</returns>
private static Region? GetRegion(char region)
{
switch (region)
{
case 'W': return Region.World;
case 'A': return Region.USA;
case 'J': return Region.JapanAsia;
case 'E': return Region.Europe;
case 'K': return Region.USAJapan;
case 'L': return Region.USAEurope;
case 'H': return Region.JapanEurope;
default: return null;
}
}
#endregion
}
}

View File

@@ -2,14 +2,15 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using BurnOutSharp;
using BinaryObjectScanner;
using MPF.Core.Data;
using MPF.Core.Modules;
using MPF.Core.Utilities;
using MPF.Modules;
using RedumpLib.Data;
using SabreTools.RedumpLib.Data;
namespace MPF.Library
namespace MPF.Core
{
/// <summary>
/// Represents the state of all settings to be used during dumping
@@ -19,14 +20,9 @@ namespace MPF.Library
#region Output paths
/// <summary>
/// Base output directory to write files to
/// Base output file path to write files to
/// </summary>
public string OutputDirectory { get; private set; }
/// <summary>
/// Base output filename for output
/// </summary>
public string OutputFilename { get; private set; }
public string OutputPath { get; private set; }
#endregion
@@ -35,7 +31,11 @@ namespace MPF.Library
/// <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
@@ -47,39 +47,60 @@ namespace MPF.Library
/// </summary>
public MediaType? Type { get; private set; }
/// <summary>
/// Currently selected dumping program
/// </summary>
public InternalProgram InternalProgram { get; private set; }
/// <summary>
/// Options object representing user-defined options
/// </summary>
public Options Options { get; private set; }
public Data.Options Options { get; private set; }
/// <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
#region Event Handlers
/// <summary>
/// Generic way of reporting a message
/// </summary>
#if NET48
public EventHandler<string> ReportStatus;
#else
public EventHandler<string>? ReportStatus;
#endif
/// <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>
private void OutputToLog(object proc, string args) => outputQueue.Enqueue(args);
#if NET48
private void OutputToLog(object proc, string args) => outputQueue?.Enqueue(args);
#else
private void OutputToLog(object? proc, string args) => outputQueue?.Enqueue(args);
#endif
/// <summary>
/// Process the outputs in the queue
/// </summary>
private void ProcessOutputs(string nextOutput) => ReportStatus.Invoke(this, nextOutput);
private void ProcessOutputs(string nextOutput) => ReportStatus?.Invoke(this, nextOutput);
#endregion
@@ -87,31 +108,40 @@ namespace MPF.Library
/// Constructor for a full DumpEnvironment object from user information
/// </summary>
/// <param name="options"></param>
/// <param name="outputDirectory"></param>
/// <param name="outputFilename"></param>
/// <param name="outputPath"></param>
/// <param name="drive"></param>
/// <param name="system"></param>
/// <param name="type"></param>
/// <param name="internalProgram"></param>
/// <param name="parameters"></param>
public DumpEnvironment(Options options,
string outputDirectory,
string outputFilename,
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
this.Options = options;
Options = options;
// Output paths
(this.OutputDirectory, this.OutputFilename) = InfoTool.NormalizeOutputPaths(outputDirectory, outputFilename);
OutputPath = InfoTool.NormalizeOutputPaths(outputPath, false);
// UI information
this.Drive = drive;
this.System = system ?? options.DefaultSystem;
this.Type = type ?? MediaType.NONE;
Drive = drive;
System = system ?? options.DefaultSystem;
Type = type ?? MediaType.NONE;
InternalProgram = internalProgram ?? options.InternalProgram;
// Dumping program
SetParameters(parameters);
}
@@ -122,45 +152,70 @@ namespace MPF.Library
/// 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
{
switch (Options.InternalProgram)
#if NET48
switch (InternalProgram)
{
// Dumping support
case InternalProgram.Aaru:
this.Parameters = new Modules.Aaru.Parameters(parameters) { ExecutablePath = Options.AaruPath };
break;
case InternalProgram.DD:
this.Parameters = new Modules.DD.Parameters(parameters) { ExecutablePath = Options.DDPath };
Parameters = new Modules.Aaru.Parameters(parameters) { ExecutablePath = Options.AaruPath };
break;
case InternalProgram.DiscImageCreator:
this.Parameters = new Modules.DiscImageCreator.Parameters(parameters) { ExecutablePath = Options.DiscImageCreatorPath };
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:
this.Parameters = new Modules.CleanRip.Parameters(parameters) { ExecutablePath = null };
Parameters = new Modules.CleanRip.Parameters(parameters) { ExecutablePath = null };
break;
case InternalProgram.DCDumper:
this.Parameters = null; // TODO: Create correct parameter type when supported
Parameters = null; // TODO: Create correct parameter type when supported
break;
case InternalProgram.UmdImageCreator:
this.Parameters = new Modules.UmdImageCreator.Parameters(parameters) { ExecutablePath = null };
Parameters = new Modules.UmdImageCreator.Parameters(parameters) { ExecutablePath = null };
break;
// This should never happen, but it needs a fallback
default:
this.Parameters = new Modules.DiscImageCreator.Parameters(parameters) { ExecutablePath = Options.DiscImageCreatorPath };
Parameters = new Modules.DiscImageCreator.Parameters(parameters) { ExecutablePath = Options.DiscImageCreatorPath };
break;
}
#else
Parameters = InternalProgram switch
{
// Dumping support
InternalProgram.Aaru => new Modules.Aaru.Parameters(parameters) { ExecutablePath = Options.AaruPath },
InternalProgram.DiscImageCreator => new Modules.DiscImageCreator.Parameters(parameters) { ExecutablePath = Options.DiscImageCreatorPath },
InternalProgram.Redumper => new Modules.Redumper.Parameters(parameters) { ExecutablePath = Options.RedumperPath },
// Verification support only
InternalProgram.CleanRip => new Modules.CleanRip.Parameters(parameters) { ExecutablePath = null },
InternalProgram.DCDumper => null, // TODO: Create correct parameter type when supported
InternalProgram.UmdImageCreator => new Modules.UmdImageCreator.Parameters(parameters) { ExecutablePath = null },
// This should never happen, but it needs a fallback
_ => new Modules.DiscImageCreator.Parameters(parameters) { ExecutablePath = Options.DiscImageCreatorPath },
};
#endif
// Set system and type
this.Parameters.System = this.System;
this.Parameters.Type = this.Type;
if (Parameters != null)
{
Parameters.System = System;
Parameters.Type = Type;
}
}
/// <summary>
@@ -168,7 +223,11 @@ namespace MPF.Library
/// </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)
@@ -178,26 +237,37 @@ namespace MPF.Library
return null;
// Set the proper parameters
string filename = OutputDirectory + Path.DirectorySeparatorChar + OutputFilename;
switch (Options.InternalProgram)
#if NET48
switch (InternalProgram)
{
case InternalProgram.Aaru:
Parameters = new Modules.Aaru.Parameters(System, Type, Drive.Letter, filename, driveSpeed, Options);
break;
case InternalProgram.DD:
Parameters = new Modules.DD.Parameters(System, Type, Drive.Letter, filename, driveSpeed, Options);
Parameters = new Modules.Aaru.Parameters(System, Type, Drive.Name, OutputPath, driveSpeed, Options);
break;
case InternalProgram.DiscImageCreator:
Parameters = new Modules.DiscImageCreator.Parameters(System, Type, Drive.Letter, filename, driveSpeed, Options);
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.Letter, filename, driveSpeed, Options);
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),
// This should never happen, but it needs a fallback
_ => new Modules.DiscImageCreator.Parameters(System, Type, Drive.Name, OutputPath, driveSpeed, Options),
};
#endif
// Generate and return the param string
return Parameters.GenerateParameters();
@@ -213,26 +283,42 @@ namespace MPF.Library
/// <summary>
/// Cancel an in-progress dumping process
/// </summary>
public void CancelDumping() => Parameters.KillInternalProgram();
public void CancelDumping() => Parameters?.KillInternalProgram();
/// <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)
#else
public async Task<Result> Run(IProgress<Result>? progress = null)
#endif
{
// If we don't have parameters
if (Parameters == null)
return Result.Failure("Error! Current configuration is not supported!");
// Check that we have the basics for dumping
Result result = IsValidForDump();
if (!result)
@@ -242,24 +328,24 @@ namespace MPF.Library
if (!Options.ToolsInSeparateWindow)
{
outputQueue = new ProcessingQueue<string>(ProcessOutputs);
Parameters.ReportStatus += OutputToLog;
if (Parameters.ReportStatus != null)
Parameters.ReportStatus += OutputToLog;
}
// Execute internal tool
progress?.Report(Result.Success($"Executing {Options.InternalProgram}... {(Options.ToolsInSeparateWindow ? "please wait!" : "see log for output!")}"));
Directory.CreateDirectory(OutputDirectory);
await Task.Run(() => Parameters.ExecuteInternalProgram(Options.ToolsInSeparateWindow));
progress?.Report(Result.Success($"{Options.InternalProgram} has finished!"));
progress?.Report(Result.Success($"Executing {InternalProgram}... {(Options.ToolsInSeparateWindow ? "please wait!" : "see log for output!")}"));
// Execute additional tools
progress?.Report(Result.Success("Running any additional tools... see log for output!"));
result = await Task.Run(() => ExecuteAdditionalTools());
progress?.Report(result);
var directoryName = Path.GetDirectoryName(OutputPath);
if (!string.IsNullOrWhiteSpace(directoryName))
Directory.CreateDirectory(directoryName);
await Task.Run(() => Parameters.ExecuteInternalProgram(Options.ToolsInSeparateWindow));
progress?.Report(Result.Success($"{InternalProgram} has finished!"));
// Remove event handler if needed
if (!Options.ToolsInSeparateWindow)
{
outputQueue.Dispose();
outputQueue?.Dispose();
Parameters.ReportStatus -= OutputToLog;
}
@@ -272,16 +358,29 @@ namespace MPF.Library
/// <param name="resultProgress">Optional result progress callback</param>
/// <param name="protectionProgress">Optional protection progress callback</param>
/// <param name="processUserInfo">Optional user prompt to deal with submission information</param>
/// <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)
Func<SubmissionInfo, (bool?, SubmissionInfo)> processUserInfo = null,
SubmissionInfo seedInfo = null)
#else
IProgress<Result>? resultProgress = null,
IProgress<ProtectionProgress>? protectionProgress = null,
Func<SubmissionInfo?, (bool?, SubmissionInfo?)>? processUserInfo = null,
SubmissionInfo? seedInfo = null)
#endif
{
resultProgress?.Report(Result.Success("Gathering submission information... please wait!"));
// Get the output directory and filename separately
var outputDirectory = Path.GetDirectoryName(OutputPath);
var outputFilename = Path.GetFileName(OutputPath);
// Check to make sure that the output had all the correct files
(bool foundFiles, List<string> missingFiles) = InfoTool.FoundAllFiles(this.OutputDirectory, this.OutputFilename, this.Parameters, false);
(bool foundFiles, List<string> missingFiles) = InfoTool.FoundAllFiles(outputDirectory, outputFilename, Parameters, false);
if (!foundFiles)
{
resultProgress?.Report(Result.Failure($"There were files missing from the output:\n{string.Join("\n", missingFiles)}"));
@@ -290,29 +389,36 @@ namespace MPF.Library
// Extract the information from the output files
resultProgress?.Report(Result.Success("Extracting output information from output files..."));
SubmissionInfo submissionInfo = await InfoTool.ExtractOutputInformation(
this.OutputDirectory,
this.OutputFilename,
this.Drive,
this.System,
this.Type,
this.Options,
this.Parameters,
var submissionInfo = await SubmissionInfoTool.ExtractOutputInformation(
OutputPath,
Drive,
System,
Type,
Options,
Parameters,
resultProgress,
protectionProgress);
resultProgress?.Report(Result.Success("Extracting information complete!"));
// Inject seed submission info data, if necessary
if (seedInfo != null)
{
resultProgress?.Report(Result.Success("Injecting user-supplied information..."));
SubmissionInfoTool.InjectSubmissionInformation(submissionInfo, seedInfo);
resultProgress?.Report(Result.Success("Information injection complete!"));
}
// Eject the disc automatically if configured to
if (Options.EjectAfterDump == true)
{
resultProgress?.Report(Result.Success($"Ejecting disc in drive {Drive.Letter}"));
resultProgress?.Report(Result.Success($"Ejecting disc in drive {Drive?.Name}"));
await EjectDisc();
}
// Reset the drive automatically if configured to
if (Options.InternalProgram == InternalProgram.DiscImageCreator && Options.DICResetDriveAfterDump)
if (InternalProgram == InternalProgram.DiscImageCreator && Options.DICResetDriveAfterDump)
{
resultProgress?.Report(Result.Success($"Resetting drive {Drive.Letter}"));
resultProgress?.Report(Result.Success($"Resetting drive {Drive?.Name}"));
await ResetDrive();
}
@@ -330,39 +436,75 @@ namespace MPF.Library
resultProgress?.Report(Result.Success("Disc information skipped!"));
}
// Process special fields for site codes
resultProgress?.Report(Result.Success("Processing site codes..."));
InfoTool.ProcessSpecialFields(submissionInfo);
resultProgress?.Report(Result.Success("Processing complete!"));
// Format the information for the text output
resultProgress?.Report(Result.Success("Formatting information..."));
List<string> formattedValues = InfoTool.FormatOutputData(submissionInfo);
resultProgress?.Report(Result.Success("Formatting complete!"));
(var formattedValues, var formatResult) = InfoTool.FormatOutputData(submissionInfo, Options);
if (formattedValues == null)
resultProgress?.Report(Result.Success(formatResult));
else
resultProgress?.Report(Result.Failure(formatResult));
// Get the filename suffix for auto-generated files
var filenameSuffix = Options.AddFilenameSuffix ? Path.GetFileNameWithoutExtension(outputFilename) : null;
// Write the text output
resultProgress?.Report(Result.Success("Writing information to !submissionInfo.txt..."));
bool success = InfoTool.WriteOutputData(this.OutputDirectory, formattedValues);
if (success)
resultProgress?.Report(Result.Success("Writing complete!"));
(bool txtSuccess, string txtResult) = InfoTool.WriteOutputData(outputDirectory, filenameSuffix, formattedValues);
if (txtSuccess)
resultProgress?.Report(Result.Success(txtResult));
else
resultProgress?.Report(Result.Failure("Writing could not complete!"));
resultProgress?.Report(Result.Failure(txtResult));
// Write the copy protection output
if (submissionInfo?.CopyProtection?.FullProtections != null && submissionInfo.CopyProtection.FullProtections.Any())
{
if (Options.ScanForProtection && Options.OutputSeparateProtectionFile)
{
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!"));
}
}
// Write the JSON output, if required
if (Options.OutputSubmissionJSON)
{
resultProgress?.Report(Result.Success("Writing information to !submissionInfo.json.gz..."));
success = InfoTool.WriteOutputData(this.OutputDirectory, submissionInfo);
if (success)
resultProgress?.Report(Result.Success($"Writing information to !submissionInfo.json{(Options.IncludeArtifacts ? ".gz" : string.Empty)}..."));
bool jsonSuccess = InfoTool.WriteOutputData(outputDirectory, filenameSuffix, submissionInfo, Options.IncludeArtifacts);
if (jsonSuccess)
resultProgress?.Report(Result.Success("Writing complete!"));
else
resultProgress?.Report(Result.Failure("Writing could not complete!"));
}
// Conpress the logs, if required
// Compress the logs, if required
if (Options.CompressLogFiles)
{
resultProgress?.Report(Result.Success("Compressing log files..."));
success = InfoTool.CompressLogFiles(this.OutputDirectory, this.OutputFilename, this.Parameters);
if (success)
resultProgress?.Report(Result.Success("Compression complete!"));
(bool compressSuccess, string compressResult) = InfoTool.CompressLogFiles(outputDirectory, filenameSuffix, outputFilename, Parameters);
if (compressSuccess)
resultProgress?.Report(Result.Success(compressResult));
else
resultProgress?.Report(Result.Failure("Compression could not complete!"));
resultProgress?.Report(Result.Failure(compressResult));
}
// Delete unnecessary files, if required
if (Options.DeleteUnnecessaryFiles)
{
resultProgress?.Report(Result.Success("Deleting unnecessary files..."));
(bool deleteSuccess, string deleteResult) = InfoTool.DeleteUnnecessaryFiles(outputDirectory, outputFilename, Parameters);
if (deleteSuccess)
resultProgress?.Report(Result.Success(deleteResult));
else
resultProgress?.Report(Result.Failure(deleteResult));
}
resultProgress?.Report(Result.Success("Submission information process complete!"));
@@ -375,7 +517,11 @@ namespace MPF.Library
/// <returns>True if the configuration is valid, false otherwise</returns>
internal bool ParametersValid()
{
bool parametersValid = Parameters.IsValid();
// Missing drive means it can never be valid
if (Drive == null)
return false;
bool parametersValid = Parameters?.IsValid() ?? false;
bool floppyValid = !(Drive.InternalDriveType == InternalDriveType.Floppy ^ Type == MediaType.FloppyDisk);
// TODO: HardDisk being in the Removable category is a hack, fix this later
@@ -385,18 +531,12 @@ namespace MPF.Library
return parametersValid && floppyValid && removableDiskValid;
}
/// <summary>
/// Run any additional tools given a DumpEnvironment
/// </summary>
/// <returns>Result instance with the outcome</returns>
private Result ExecuteAdditionalTools() => Result.Success("No external tools needed!");
/// <summary>
/// Run internal program async with an input set of parameters
/// </summary>
/// <param name="parameters"></param>
/// <returns>Standard output from commandline window</returns>
private async Task<string> ExecuteInternalProgram(BaseParameters parameters)
private static async Task<string> ExecuteInternalProgram(BaseParameters parameters)
{
Process childProcess;
string output = await Task.Run(() =>
@@ -435,16 +575,15 @@ namespace MPF.Library
private Result IsValidForDump()
{
// Validate that everything is good
if (!ParametersValid())
if (Parameters == null || !ParametersValid())
return Result.Failure("Error! Current configuration is not supported!");
// Fix the output paths, just in case
(OutputDirectory, OutputFilename) = InfoTool.NormalizeOutputPaths(OutputDirectory, OutputFilename);
OutputPath = InfoTool.NormalizeOutputPaths(OutputPath, false);
// Validate that the output path isn't on the dumping drive
string fullOutputPath = Path.GetFullPath(Path.Combine(OutputDirectory, OutputFilename));
if (fullOutputPath[0] == Drive.Letter)
return Result.Failure($"Error! Cannot output to same drive that is being dumped!");
if (Drive?.Name != null && OutputPath.StartsWith(Drive.Name))
return Result.Failure("Error! Cannot output to same drive that is being dumped!");
// Validate that the required program exists
if (!File.Exists(Parameters.ExecutablePath))
@@ -452,8 +591,8 @@ namespace MPF.Library
// Validate that the dumping drive doesn't contain the executable
string fullExecutablePath = Path.GetFullPath(Parameters.ExecutablePath);
if (fullExecutablePath[0] == Drive.Letter)
return Result.Failure("$Error! Cannot dump same drive that executable resides on!");
if (Drive?.Name != null && fullExecutablePath.StartsWith(Drive.Name))
return Result.Failure("Error! Cannot dump same drive that executable resides on!");
// Validate that the current configuration is supported
return Tools.GetSupportStatus(System, Type);
@@ -481,14 +620,18 @@ namespace MPF.Library
/// </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())
return null;
// Validate we're not trying to eject a non-optical
if (Drive.InternalDriveType != InternalDriveType.Optical)
if (Drive == null || Drive.InternalDriveType != InternalDriveType.Optical)
return null;
CancelDumping();
@@ -496,7 +639,7 @@ namespace MPF.Library
var parameters = new Modules.DiscImageCreator.Parameters(string.Empty)
{
BaseCommand = command,
DriveLetter = Drive.Letter.ToString(),
DrivePath = Drive.Name,
ExecutablePath = Options.DiscImageCreatorPath,
};

View File

@@ -1,35 +1,90 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Hashing;
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
{
CRC = 1 << 0,
MD5 = 1 << 1,
SHA1 = 1 << 2,
SHA256 = 1 << 3,
SHA384 = 1 << 4,
SHA512 = 1 << 5,
#region Properties
// Special combinations
Standard = CRC | MD5 | SHA1,
All = CRC | MD5 | SHA1 | SHA256 | SHA384 | SHA512,
}
/// <summary>
/// Async hashing class wraper
/// </summary>
public class Hasher
{
/// <summary>
/// Hash type associated with the current state
/// </summary>
#if NET48
public Hash HashType { get; private set; }
private IDisposable _hasher;
#else
public Hash HashType { get; init; }
#endif
/// <summary>
/// Current hash in bytes
/// </summary>
#if NET48
public byte[] CurrentHashBytes
#else
public byte[]? CurrentHashBytes
#endif
{
get
{
#if NET48
switch (_hasher)
{
case HashAlgorithm ha:
return ha.Hash;
case NonCryptographicHashAlgorithm ncha:
return ncha.GetCurrentHash().Reverse().ToArray();
default:
return null;
}
#else
return (_hasher) switch
{
HashAlgorithm ha => ha.Hash,
NonCryptographicHashAlgorithm ncha => ncha.GetCurrentHash().Reverse().ToArray(),
_ => null,
};
#endif
}
}
/// <summary>
/// Current hash as a string
/// </summary>
#if NET48
public string CurrentHashString => ByteArrayToString(CurrentHashBytes);
#else
public string? CurrentHashString => ByteArrayToString(CurrentHashBytes);
#endif
#endregion
#region Private Fields
/// <summary>
/// Internal hasher being used for processing
/// </summary>
/// <remarks>May be either a HashAlgorithm or NonCryptographicHashAlgorithm</remarks>
#if NET48
private object _hasher;
#else
private object? _hasher;
#endif
#endregion
#region Constructors
/// <summary>
/// Constructor
/// </summary>
/// <param name="hashType">Hash type to instantiate</param>
public Hasher(Hash hashType)
{
this.HashType = hashType;
@@ -41,56 +96,256 @@ namespace MPF.Core.Hashing
/// </summary>
private void GetHasher()
{
#if NET48
switch (HashType)
{
case Hash.CRC:
_hasher = new OptimizedCRC();
case Hash.CRC32:
_hasher = new Crc32();
break;
case Hash.CRC64:
_hasher = new Crc64();
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;
case Hash.XxHash32:
_hasher = new XxHash32();
break;
case Hash.XxHash64:
_hasher = new XxHash64();
break;
}
#else
_hasher = HashType switch
{
Hash.CRC32 => new Crc32(),
Hash.CRC64 => new Crc64(),
Hash.MD5 => MD5.Create(),
Hash.SHA1 => SHA1.Create(),
Hash.SHA256 => SHA256.Create(),
Hash.SHA384 => SHA384.Create(),
Hash.SHA512 => SHA512.Create(),
Hash.XxHash32 => new XxHash32(),
Hash.XxHash64 => new XxHash64(),
_ => null,
};
#endif
}
/// <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>
#if NET48
public static bool GetFileHashes(string filename, out long size, out string crc32, out string md5, out string sha1)
#else
public static bool GetFileHashes(string filename, out long size, out string? crc32, out string? md5, out string? sha1)
#endif
{
// 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>
#if NET48
public static Dictionary<Hash, string> GetFileHashes(string filename, out long size)
#else
public static Dictionary<Hash, string?>? GetFileHashes(string filename, out long size)
#endif
{
// 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;
// Create the output dictionary
#if NET48
var hashDict = new Dictionary<Hash, string>();
#else
var hashDict = new Dictionary<Hash, string?>();
#endif
// 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>
#if NET48
public static Dictionary<Hash, string> GetStreamHashes(Stream input)
#else
public static Dictionary<Hash, string?>? GetStreamHashes(Stream input)
#endif
{
// Create the output dictionary
#if NET48
var hashDict = new Dictionary<Hash, string>();
#else
var hashDict = new Dictionary<Hash, string?>();
#endif
try
{
// Get a list of hashers to run over the buffer
var hashers = new Dictionary<Hash, Hasher>
{
{ Hash.CRC32, new Hasher(Hash.CRC32) },
{ Hash.CRC64, new Hasher(Hash.CRC64) },
{ 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) },
{ Hash.XxHash32, new Hasher(Hash.XxHash32) },
{ Hash.XxHash64, new Hasher(Hash.XxHash64) },
};
// 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;
// Run hashes in parallel
Parallel.ForEach(hashers, h => h.Value.Process(buffer, current));
// 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();
Parallel.ForEach(hashers, h => h.Value.Terminate());
// Get the results
hashDict[Hash.CRC32] = hashers[Hash.CRC32].CurrentHashString;
hashDict[Hash.CRC64] = hashers[Hash.CRC64].CurrentHashString;
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;
hashDict[Hash.XxHash32] = hashers[Hash.XxHash32].CurrentHashString;
hashDict[Hash.XxHash64] = hashers[Hash.XxHash64].CurrentHashString;
hashDict[Hash.CRC64] = hashers[Hash.CRC64].CurrentHashString;
// Dispose of the hashers
loadBuffer.Dispose();
foreach (var hasher in hashers.Values)
{
hasher.Dispose();
}
return hashDict;
}
catch (IOException)
{
return null;
}
finally
{
input.Dispose();
}
}
public void Dispose()
{
_hasher.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.CRC:
(_hasher as OptimizedCRC).Update(buffer, 0, size);
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:
var bufferSpan = new ReadOnlySpan<byte>(buffer, 0, size);
ncha.Append(bufferSpan);
break;
}
}
@@ -98,57 +353,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 = new byte[0];
switch (HashType)
byte[] emptyBuffer = Array.Empty<byte>();
switch (_hasher)
{
case Hash.CRC:
(_hasher as OptimizedCRC).Update(emptyBuffer, 0, 0);
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>
public byte[] GetHash()
{
switch (HashType)
{
case Hash.CRC:
return BitConverter.GetBytes((_hasher as OptimizedCRC).Value).Reverse().ToArray();
#endregion
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>
public string GetHashString()
{
byte[] hash = GetHash();
if (hash == null)
return null;
return ByteArrayToString(hash);
}
#region Helpers
/// <summary>
/// Convert a byte array to a hex string
@@ -156,7 +375,11 @@ 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)
@@ -172,5 +395,7 @@ namespace MPF.Core.Hashing
return null;
}
}
#endregion
}
}

View File

@@ -1,154 +0,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
{
internal class OptimizedCRC : IDisposable
{
private const uint kCrcPoly = 0xEDB88320;
private const uint kInitial = 0xFFFFFFFF;
private const int CRC_NUM_TABLES = 8;
private static readonly uint[] Table;
static OptimizedCRC()
{
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 OptimizedCRC()
{
Init();
}
/// <summary>
/// Reset CRC
/// </summary>
public void Init()
{
UnsignedValue = kInitial;
}
public int Value
{
get { return (int)~UnsignedValue; }
}
public void Update(byte[] data, int offset, int count)
{
new ArraySegment<byte>(data, offset, count); // check arguments
if (count == 0)
{
return;
}
var table = OptimizedCRC.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;
}
static public int Compute(byte[] data, int offset, int count)
{
var crc = new OptimizedCRC();
crc.Update(data, offset, count);
return crc.Value;
}
static public int Compute(byte[] data)
{
return Compute(data, 0, data.Length);
}
static public int Compute(ArraySegment<byte> block)
{
return Compute(block.Array, block.Offset, block.Count);
}
public void Dispose()
{
UnsignedValue = 0;
}
}
}

View File

@@ -11,7 +11,11 @@ namespace MPF.Core.Hashing
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;
@@ -48,7 +52,8 @@ namespace MPF.Core.Hashing
}
try
{
SizeRead = _ds.Read(_buffer, 0, _size);
if (_buffer != null)
SizeRead = _ds.Read(_buffer, 0, _size);
}
catch (Exception)
{

2885
MPF.Core/InfoTool.cs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,65 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net48;netcoreapp3.1;net5.0</TargetFrameworks>
<PlatformTarget>x86</PlatformTarget>
<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-2021</Copyright>
<RepositoryUrl>https://github.com/SabreTools/MPF</RepositoryUrl>
<Version>2.2</Version>
<AssemblyVersion>$(Version)</AssemblyVersion>
<FileVersion>$(Version)</FileVersion>
<IncludeSource>true</IncludeSource>
<IncludeSymbols>true</IncludeSymbols>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Copyright>Copyright (c)2019-2023</Copyright>
<VersionPrefix>2.7.5</VersionPrefix>
</PropertyGroup>
<PropertyGroup>
<NrtRevisionFormat>$(Version)-{chash:8}</NrtRevisionFormat>
<NrtResolveSimpleAttributes>true</NrtResolveSimpleAttributes>
<NrtShowRevision>false</NrtShowRevision>
<PropertyGroup Condition="'$(TargetFramework)'!='net48'">
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='net48'">
<DefineConstants>NET_FRAMEWORK</DefineConstants>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net48'">
<COMReference Include="IMAPI2">
<Guid>{2735412F-7F64-5B0F-8F00-5D77AFBE261E}</Guid>
<VersionMajor>1</VersionMajor>
<VersionMinor>0</VersionMinor>
<Lcid>0</Lcid>
<WrapperTool>tlbimp</WrapperTool>
<Isolated>False</Isolated>
<EmbedInteropTypes>True</EmbedInteropTypes>
</COMReference>
<COMReference Include="IMAPI2FS">
<Guid>{2C941FD0-975B-59BE-A960-9A2A262853A5}</Guid>
<VersionMajor>1</VersionMajor>
<VersionMinor>0</VersionMinor>
<Lcid>0</Lcid>
<WrapperTool>tlbimp</WrapperTool>
<Isolated>False</Isolated>
<EmbedInteropTypes>True</EmbedInteropTypes>
</COMReference>
<ItemGroup>
<InternalsVisibleTo Include="MPF.Test" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\RedumpLib\RedumpLib.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="System.Management" Version="6.0.0-rc.1.21451.13" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'!='net48'">
<PackageReference Include="Aaru.Devices" Version="5.3.0-rc2" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Management" />
<PackageReference Include="BurnOutSharp" PrivateAssets="build; analyzers" ExcludeAssets="contentFiles" Version="2.9.0" GeneratePathProperty="true">
<IncludeAssets>runtime; compile; build; native; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Management.Infrastructure" Version="3.0.0" />
<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" />
</ItemGroup>
</Project>

View File

@@ -1,4 +1,4 @@
namespace MPF.Modules.Aaru
namespace MPF.Core.Modules.Aaru
{
/// <summary>
/// Top-level commands for Aaru

View File

@@ -1,6 +1,6 @@
using RedumpLib.Data;
using SabreTools.RedumpLib.Data;
namespace MPF.Modules.Aaru
namespace MPF.Core.Modules.Aaru
{
public static class Converters
{

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,14 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using MPF.Core.Data;
using MPF.Core.Hashing;
using MPF.Core.Utilities;
using RedumpLib.Data;
using SabreTools.RedumpLib.Data;
namespace MPF.Modules
namespace MPF.Core.Modules
{
public abstract class BaseParameters
{
@@ -21,7 +18,11 @@ namespace MPF.Modules
/// Geneeic way of reporting a message
/// </summary>
/// <param name="message">String value to report</param>
#if NET48
public EventHandler<string> ReportStatus;
#else
public EventHandler<string>? ReportStatus;
#endif
#endregion
@@ -30,12 +31,20 @@ namespace MPF.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 internal IEnumerable<string> Keys => flags.Keys;
/// <summary>
@@ -59,7 +68,11 @@ namespace MPF.Modules
/// <summary>
/// Process to track external program
/// </summary>
#if NET48
private Process process;
#else
private Process? process;
#endif
#endregion
@@ -68,18 +81,30 @@ namespace MPF.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
@@ -93,7 +118,11 @@ namespace MPF.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
@@ -116,7 +145,11 @@ namespace MPF.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))
@@ -128,15 +161,19 @@ namespace MPF.Modules
/// </summary>
/// <param name="system">RedumpSystem value to use</param>
/// <param name="type">MediaType value to use</param>
/// <param name="driveLetter">Drive letter to use</param>
/// <param name="drivePath">Drive path to use</param>
/// <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>
public BaseParameters(RedumpSystem? system, MediaType? type, char driveLetter, string filename, int? driveSpeed, Options options)
#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;
SetDefaultParameters(driveLetter, filename, driveSpeed, options);
SetDefaultParameters(drivePath, filename, driveSpeed, options);
}
#region Abstract Methods
@@ -153,10 +190,15 @@ namespace MPF.Modules
/// Generate a SubmissionInfo for the output files
/// </summary>
/// <param name="submissionInfo">Base submission info to fill in specifics for</param>
/// <param name="options">Options object representing user-defined options</param>
/// <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>
public abstract void GenerateSubmissionInfo(SubmissionInfo submissionInfo, string basePath, Drive drive, bool includeArtifacts);
#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
@@ -166,27 +208,54 @@ namespace MPF.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
@@ -230,18 +299,26 @@ namespace MPF.Modules
/// <summary>
/// Set default parameters for a given system and media type
/// </summary>
/// <param name="driveLetter">Drive letter to use</param>
/// <param name="drivePath">Drive path to use</param>
/// <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>
protected virtual void SetDefaultParameters(char driveLetter, string filename, int? driveSpeed, Options options) { }
#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>
protected virtual bool ValidateAndSetParameters(string parameters) => true;
#if NET48
protected virtual bool ValidateAndSetParameters(string parameters) => !string.IsNullOrWhiteSpace(parameters);
#else
protected virtual bool ValidateAndSetParameters(string? parameters) => !string.IsNullOrWhiteSpace(parameters);
#endif
#endregion
@@ -273,8 +350,8 @@ namespace MPF.Modules
// Start processing tasks, if necessary
if (!separateWindow)
{
Logging.OutputToLog(process.StandardOutput, this, ReportStatus);
Logging.OutputToLog(process.StandardError, this, ReportStatus);
_ = Logging.OutputToLog(process.StandardOutput, this, ReportStatus);
_ = Logging.OutputToLog(process.StandardError, this, ReportStatus);
}
process.WaitForExit();
@@ -315,7 +392,11 @@ namespace MPF.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;
@@ -330,7 +411,11 @@ namespace MPF.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))
@@ -343,7 +428,7 @@ namespace MPF.Modules
return BitConverter.ToString(bytes).Replace("-", string.Empty);
}
return string.Join("\n", File.ReadAllLines(filename));
return File.ReadAllText(filename);
}
/// <summary>
@@ -460,7 +545,11 @@ namespace MPF.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;
@@ -496,7 +585,11 @@ namespace MPF.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;
@@ -571,7 +664,11 @@ namespace MPF.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;
@@ -621,6 +718,7 @@ namespace MPF.Modules
string valuePart = commandParts[1];
this[longFlagString] = true;
(string value, long factor) = ExtractFactorFromValue(valuePart);
return (sbyte)(sbyte.Parse(value) * factor);
}
@@ -648,7 +746,11 @@ namespace MPF.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;
@@ -697,6 +799,7 @@ namespace MPF.Modules
string valuePart = commandParts[1];
this[longFlagString] = true;
(string value, long factor) = ExtractFactorFromValue(valuePart);
return (short)(short.Parse(value) * factor);
}
@@ -724,7 +827,11 @@ namespace MPF.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;
@@ -773,6 +880,7 @@ namespace MPF.Modules
string valuePart = commandParts[1];
this[longFlagString] = true;
(string value, long factor) = ExtractFactorFromValue(valuePart);
return (int)(int.Parse(value) * factor);
}
@@ -800,7 +908,11 @@ namespace MPF.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;
@@ -849,6 +961,7 @@ namespace MPF.Modules
string valuePart = commandParts[1];
this[longFlagString] = true;
(string value, long factor) = ExtractFactorFromValue(valuePart);
return long.Parse(value) * factor;
}
@@ -864,8 +977,12 @@ namespace MPF.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)
=> ProcessStringParameter(parts, null, flagString, ref i, missingAllowed);
#else
protected string? ProcessStringParameter(List<string> parts, string flagString, ref int i, bool missingAllowed = false)
#endif
=> ProcessStringParameter(parts, null, flagString, ref i, missingAllowed);
/// <summary>
/// Process a string parameter
@@ -876,7 +993,11 @@ namespace MPF.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;
@@ -931,6 +1052,88 @@ namespace MPF.Modules
return string.Empty;
}
/// <summary>
/// Process a byte parameter
/// </summary>
/// <param name="parts">List of parts to be referenced</param>
/// <param name="flagString">Flag string, if available</param>
/// <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>
protected byte? ProcessUInt8Parameter(List<string> parts, string flagString, ref int i, bool missingAllowed = false)
=> ProcessUInt8Parameter(parts, null, flagString, ref i, missingAllowed);
/// <summary>
/// Process a byte parameter
/// </summary>
/// <param name="parts">List of parts to be referenced</param>
/// <param name="shortFlagString">Short flag string, if available</param>
/// <param name="longFlagString">Long flag string, if available</param>
/// <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;
if (parts[i] == shortFlagString || parts[i] == longFlagString)
{
if (!IsFlagSupported(longFlagString))
{
return null;
}
else if (!DoesExist(parts, i + 1))
{
if (missingAllowed)
this[longFlagString] = true;
return null;
}
else if (IsFlagSupported(parts[i + 1]))
{
if (missingAllowed)
this[longFlagString] = true;
return null;
}
else if (!IsValidInt8(parts[i + 1]))
{
if (missingAllowed)
this[longFlagString] = true;
return null;
}
this[longFlagString] = true;
i++;
(string value, long factor) = ExtractFactorFromValue(parts[i]);
return (byte)(byte.Parse(value) * factor);
}
else if (parts[i].StartsWith(shortFlagString + "=") || parts[i].StartsWith(longFlagString + "="))
{
if (!IsFlagSupported(longFlagString))
return null;
string[] commandParts = parts[i].Split('=');
if (commandParts.Length != 2)
return null;
string valuePart = commandParts[1];
this[longFlagString] = true;
(string value, long factor) = ExtractFactorFromValue(valuePart);
return (byte)(byte.Parse(value) * factor);
}
return Byte.MinValue;
}
/// <summary>
/// Get yhe trimmed value and multiplication factor from a value
/// </summary>
@@ -995,393 +1198,49 @@ namespace MPF.Modules
#endregion
#region Common Information Extraction
#region Methods to Move
/// <summary>
/// Get hashes from an input file path
/// Get the hex contents of the PIC file
/// </summary>
/// <param name="filename">Path to the input file</param>
/// <returns>True if hashing was successful, false otherwise</returns>
protected static bool GetFileHashes(string filename, out long size, out string crc32, out string md5, out string sha1)
/// <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>
#if NET48
protected static string GetPIC(string picPath, int trimLength = -1)
#else
protected static string? GetPIC(string picPath, int trimLength = -1)
#endif
{
// Set all initial values
size = -1; crc32 = null; md5 = null; sha1 = null;
// If the file doesn't exist, we can't do anything
if (!File.Exists(filename))
return false;
// Set the file size
size = new FileInfo(filename).Length;
// Open the input file
var input = File.OpenRead(filename);
// If the file doesn't exist, we can't get the info
if (!File.Exists(picPath))
return null;
try
{
// Get a list of hashers to run over the buffer
List<Hasher> hashers = new List<Hasher>
{
new Hasher(Hash.CRC),
new Hasher(Hash.MD5),
new Hasher(Hash.SHA1),
new Hasher(Hash.SHA256),
new Hasher(Hash.SHA384),
new Hasher(Hash.SHA512),
};
var hex = GetFullFile(picPath, true);
if (hex == null)
return null;
// Initialize the hashing helpers
var loadBuffer = new ThreadLoadBuffer(input);
int buffersize = 3 * 1024 * 1024;
byte[] buffer0 = new byte[buffersize];
byte[] buffer1 = new byte[buffersize];
if (trimLength > -1)
#if NET48
hex = hex.Substring(0, trimLength);
#else
hex = hex[..trimLength];
#endif
/*
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 = size;
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;
// Run hashes in parallel
Parallel.ForEach(hashers, h => h.Process(buffer, current));
// 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();
Parallel.ForEach(hashers, h => h.Terminate());
// Get the results
crc32 = hashers.First(h => h.HashType == Hash.CRC).GetHashString();
md5 = hashers.First(h => h.HashType == Hash.MD5).GetHashString();
sha1 = hashers.First(h => h.HashType == Hash.SHA1).GetHashString();
//sha256 = hashers.First(h => h.HashType == Hash.SHA256).GetHashString();
//sha384 = hashers.First(h => h.HashType == Hash.SHA384).GetHashString();
//sha512 = hashers.First(h => h.HashType == Hash.SHA512).GetHashString();
// Dispose of the hashers
loadBuffer.Dispose();
hashers.ForEach(h => h.Dispose());
return true;
}
catch (IOException ex)
{
return false;
}
finally
{
input.Dispose();
}
return false;
}
/// <summary>
/// Get the split values for ISO-based media
/// </summary>
/// <param name="hashData">String representing the combined hash data</param>
/// <returns>True if extraction was successful, false otherwise</returns>
protected static bool GetISOHashValues(string hashData, out long size, out string crc32, out string md5, out string sha1)
{
size = -1; crc32 = null; md5 = null; sha1 = null;
if (string.IsNullOrWhiteSpace(hashData))
return false;
Regex hashreg = new Regex(@"<rom name="".*?"" size=""(.*?)"" crc=""(.*?)"" md5=""(.*?)"" sha1=""(.*?)""");
Match m = hashreg.Match(hashData);
if (m.Success)
{
Int64.TryParse(m.Groups[1].Value, out size);
crc32 = m.Groups[2].Value;
md5 = m.Groups[3].Value;
sha1 = m.Groups[4].Value;
return true;
}
else
{
return false;
}
}
/// <summary>
/// Get the EXE date from a PlayStation disc, if possible
/// </summary>
/// <param name="driveLetter">Drive letter to use to check</param>
/// <param name="serial">Internal disc serial, if possible</param>
/// <param name="region">Output region, if possible</param>
/// <param name="date">Output EXE date in "yyyy-mm-dd" format if possible, null on error</param>
/// <returns></returns>
protected static bool GetPlayStationExecutableInfo(char? driveLetter, out string serial, out Region? region, out string date)
{
serial = null; region = null; date = null;
// If there's no drive letter, we can't do this part
if (driveLetter == null)
return false;
// If the folder no longer exists, we can't do this part
string drivePath = driveLetter + ":\\";
if (!Directory.Exists(drivePath))
return false;
// Get the two paths that we will need to check
string psxExePath = Path.Combine(drivePath, "PSX.EXE");
string systemCnfPath = Path.Combine(drivePath, "SYSTEM.CNF");
// Try both of the common paths that contain information
string exeName = null;
// Read the CNF file as an INI file
var systemCnf = new IniFile(systemCnfPath);
string bootValue = string.Empty;
// PlayStation uses "BOOT" as the key
if (systemCnf.ContainsKey("BOOT"))
bootValue = systemCnf["BOOT"];
// PlayStation 2 uses "BOOT2" as the key
if (systemCnf.ContainsKey("BOOT2"))
bootValue = systemCnf["BOOT2"];
// If we had any boot value, parse it and get the executable name
if (!string.IsNullOrEmpty(bootValue))
{
var match = Regex.Match(bootValue, @"cdrom.?:\\?(.*)");
if (match != null && match.Groups.Count > 1)
{
exeName = match.Groups[1].Value;
exeName = exeName.Split(';')[0];
serial = exeName.Replace('_', '-').Replace(".", string.Empty);
}
}
// If the SYSTEM.CNF value can't be found, try PSX.EXE
if (string.IsNullOrWhiteSpace(exeName) && File.Exists(psxExePath))
exeName = "PSX.EXE";
// If neither can be found, we return false
if (string.IsNullOrWhiteSpace(exeName))
return false;
// Get the region, if possible
region = GetPlayStationRegion(exeName);
// Now that we have the EXE name, try to get the fileinfo for it
string exePath = Path.Combine(drivePath, exeName);
if (!File.Exists(exePath))
return false;
// Fix the Y2K timestamp issue
FileInfo fi = new FileInfo(exePath);
DateTime dt = new DateTime(fi.LastWriteTimeUtc.Year >= 1900 && fi.LastWriteTimeUtc.Year < 1920 ? 2000 + fi.LastWriteTimeUtc.Year % 100 : fi.LastWriteTimeUtc.Year,
fi.LastWriteTimeUtc.Month, fi.LastWriteTimeUtc.Day);
date = dt.ToString("yyyy-MM-dd");
return true;
}
/// <summary>
/// Get the version from a PlayStation 2 disc, if possible
/// </summary>
/// <param name="driveLetter">Drive letter to use to check</param>
/// <returns>Game version if possible, null on error</returns>
protected static string GetPlayStation2Version(char? driveLetter)
{
// If there's no drive letter, we can't do this part
if (driveLetter == null)
return null;
// If the folder no longer exists, we can't do this part
string drivePath = driveLetter + ":\\";
if (!Directory.Exists(drivePath))
return null;
// Get the SYSTEM.CNF path to check
string systemCnfPath = Path.Combine(drivePath, "SYSTEM.CNF");
// Try to parse the SYSTEM.CNF file
var systemCnf = new IniFile(systemCnfPath);
if (systemCnf.ContainsKey("VER"))
return systemCnf["VER"];
// If "VER" can't be found, we can't do much
return null;
}
/// <summary>
/// Get the version from a PlayStation 4 disc, if possible
/// </summary>
/// <param name="driveLetter">Drive letter to use to check</param>
/// <returns>Game version if possible, null on error</returns>
protected static string GetPlayStation4Version(char? driveLetter)
{
// If there's no drive letter, we can't do this part
if (driveLetter == null)
return null;
// If the folder no longer exists, we can't do this part
string drivePath = driveLetter + ":\\";
if (!Directory.Exists(drivePath))
return null;
// If we can't find param.sfo, we don't have a PlayStation 4 disc
string paramSfoPath = Path.Combine(drivePath, "bd", "param.sfo");
if (!File.Exists(paramSfoPath))
return null;
// Let's try reading param.sfo to find the version at the end of the file
try
{
using (BinaryReader br = new BinaryReader(File.OpenRead(paramSfoPath)))
{
br.BaseStream.Seek(-0x08, SeekOrigin.End);
return new string(br.ReadChars(5));
}
return Regex.Replace(hex, ".{32}", "$0\n", RegexOptions.Compiled);
}
catch
{
// We don't care what the error was
// We don't care what the error was right now
return null;
}
}
/// <summary>
/// Get the version from a PlayStation 5 disc, if possible
/// </summary>
/// <param name="driveLetter">Drive letter to use to check</param>
/// <returns>Game version if possible, null on error</returns>
protected static string GetPlayStation5Version(char? driveLetter)
{
// If there's no drive letter, we can't do this part
if (driveLetter == null)
return null;
// If the folder no longer exists, we can't do this part
string drivePath = driveLetter + ":\\";
if (!Directory.Exists(drivePath))
return null;
// If we can't find param.json, we don't have a PlayStation 5 disc
string paramJsonPath = Path.Combine(drivePath, "bd", "param.json");
if (!File.Exists(paramJsonPath))
return null;
// Let's try reading param.json to find the version in the unencrypted JSON
try
{
using (BinaryReader br = new BinaryReader(File.OpenRead(paramJsonPath)))
{
br.BaseStream.Seek(0x89E, SeekOrigin.Begin);
return new string(br.ReadChars(5));
}
}
catch
{
// We don't care what the error was
return null;
}
}
#endregion
#region Category Extraction
/// <summary>
/// Determine the category based on the UMDImageCreator string
/// </summary>
/// <param name="region">String representing the category</param>
/// <returns>Category, if possible</returns>
protected static DiscCategory? GetUMDCategory(string category)
{
switch (category)
{
case "GAME": return DiscCategory.Games;
case "VIDEO": return DiscCategory.Video;
case "AUDIO": return DiscCategory.Audio;
default: return null;
}
}
#endregion
#region Region Extraction
/// <summary>
/// Determine the region based on the PlayStation serial code
/// </summary>
/// <param name="serial">PlayStation serial code</param>
/// <returns>Region mapped from name, if possible</returns>
protected static Region? GetPlayStationRegion(string serial)
{
// Standardized "S" serials
if (serial.StartsWith("S"))
{
// string publisher = serial[0] + serial[1];
// char secondRegion = serial[3];
switch (serial[2])
{
case 'A': return Region.Asia;
case 'C': return Region.China;
case 'E': return Region.Europe;
case 'J': return Region.JapanKorea;
case 'K': return Region.Korea;
case 'P': return Region.Japan;
case 'U': return Region.USA;
}
}
// Japan-only special serial
else if (serial.StartsWith("PAPX"))
return Region.Japan;
// Region appears entirely random
else if (serial.StartsWith("PABX"))
return null;
// Japan-only special serial
else if (serial.StartsWith("PCBX"))
return Region.Japan;
// Single disc known, Japan
else if (serial.StartsWith("PDBX"))
return Region.Japan;
// Single disc known, Europe
else if (serial.StartsWith("PEBX"))
return Region.Europe;
return null;
}
#endregion
}
}
}

View File

@@ -2,10 +2,13 @@
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using MPF.Core.Converters;
using MPF.Core.Data;
using RedumpLib.Data;
using SabreTools.RedumpLib.Data;
namespace MPF.Modules.CleanRip
#pragma warning disable IDE0051 // Remove unused private members
namespace MPF.Core.Modules.CleanRip
{
/// <summary>
/// Represents a generic set of CleanRip parameters
@@ -20,11 +23,19 @@ namespace MPF.Modules.CleanRip
#endregion
/// <inheritdoc/>
#if NET48
public Parameters(string parameters) : base(parameters) { }
#else
public Parameters(string? parameters) : base(parameters) { }
#endif
/// <inheritdoc/>
public Parameters(RedumpSystem? system, MediaType? type, char driveLetter, string filename, int? driveSpeed, Options options)
: base(system, type, driveLetter, filename, driveSpeed, options)
#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)
{
}
@@ -33,7 +44,7 @@ namespace MPF.Modules.CleanRip
/// <inheritdoc/>
public override (bool, List<string>) CheckAllOutputFilesExist(string basePath, bool preCheck)
{
List<string> missingFiles = new List<string>();
var missingFiles = new List<string>();
switch (this.Type)
{
case MediaType.DVD: // Only added here to help users; not strictly correct
@@ -50,21 +61,41 @@ namespace MPF.Modules.CleanRip
break;
default:
return (false, missingFiles);
missingFiles.Add("Media and system combination not supported for CleanRip");
break;
}
return (!missingFiles.Any(), missingFiles);
}
/// <inheritdoc/>
public override void GenerateSubmissionInfo(SubmissionInfo info, string basePath, Drive drive, bool includeArtifacts)
#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
{
info.TracksAndWriteOffsets.ClrMameProData = GetCleanripDatfile(basePath + ".iso", basePath + "-dumpinfo.txt");
// Ensure that required sections exist
info = SubmissionInfoTool.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");
var datafile = GenerateCleanripDatafile(basePath + ".iso", basePath + "-dumpinfo.txt");
// Get the individual hash data, as per internal
if (GetISOHashValues(info.TracksAndWriteOffsets.ClrMameProData, out long size, out string crc32, out string md5, out string sha1))
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;
@@ -72,7 +103,7 @@ namespace MPF.Modules.CleanRip
// Dual-layer discs have the same size and layerbreak
if (size == 8511160320)
info.SizeAndChecksums.Layerbreak = 2084960;
}
}
// Extract info based generically on MediaType
switch (this.Type)
@@ -81,12 +112,23 @@ namespace MPF.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 string gcVersion))
if (GetGameCubeWiiInformation(basePath + "-dumpinfo.txt", out Region? gcRegion, out var gcVersion, out var gcName))
{
#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
}
break;
@@ -95,17 +137,23 @@ namespace MPF.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
if (File.Exists(basePath + ".bca"))
info.Artifacts["bca"] = GetBase64(GetFullFile(basePath + ".bca", binary: true));
info.Artifacts["bca"] = GetBase64(GetFullFile(basePath + ".bca", binary: true)) ?? string.Empty;
if (File.Exists(basePath + "-dumpinfo.txt"))
info.Artifacts["dumpinfo"] = GetBase64(GetFullFile(basePath + "-dumpinfo.txt"));
info.Artifacts["dumpinfo"] = GetBase64(GetFullFile(basePath + "-dumpinfo.txt")) ?? string.Empty;
}
}
/// <inheritdoc/>
public override List<string> GetLogFilePaths(string basePath)
{
List<string> logFiles = new List<string>();
var logFiles = new List<string>();
switch (this.Type)
{
case MediaType.DVD: // Only added here to help users; not strictly correct
@@ -126,13 +174,91 @@ namespace MPF.Modules.CleanRip
#region Information Extraction Methods
/// <summary>
/// Get a formatted datfile from the cleanrip output, if possible
/// </summary>
/// <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))
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
{
// 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
return null;
}
}
}
/// <summary>
/// Get the hex contents of the BCA file
/// </summary>
/// <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))
@@ -140,7 +266,10 @@ namespace MPF.Modules.CleanRip
try
{
string hex = GetFullFile(bcaPath, true);
var hex = GetFullFile(bcaPath, true);
if (hex == null)
return null;
return Regex.Replace(hex, ".{32}", "$0\n");
}
catch
@@ -156,13 +285,17 @@ namespace MPF.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))
return null;
using (StreamReader sr = File.OpenText(dumpinfo))
using (var sr = File.OpenText(dumpinfo))
{
long size = new FileInfo(iso).Length;
string crc = string.Empty;
@@ -172,19 +305,30 @@ namespace MPF.Modules.CleanRip
try
{
// Make sure this file is a dumpinfo
if (!sr.ReadLine().Contains("--File Generated by CleanRip"))
if (sr.ReadLine()?.Contains("--File Generated by CleanRip") != true)
return null;
// Read all lines and gather dat information
while (!sr.EndOfStream)
{
string line = sr.ReadLine().Trim();
if (line.StartsWith("CRC32"))
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}\" />";
@@ -203,34 +347,59 @@ namespace MPF.Modules.CleanRip
/// <param name="dumpinfo">Path to discinfo file</param>
/// <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>
/// <returns></returns>
private static bool GetGameCubeWiiInformation(string dumpinfo, out Region? region, out string version)
#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
{
region = null; version = null;
region = null; version = null; name = null;
// If the file doesn't exist, we can't get info from it
if (!File.Exists(dumpinfo))
return false;
using (StreamReader sr = File.OpenText(dumpinfo))
using (var sr = File.OpenText(dumpinfo))
{
try
{
// Make sure this file is a dumpinfo
if (!sr.ReadLine().Contains("--File Generated by CleanRip"))
if (sr.ReadLine()?.Contains("--File Generated by CleanRip") != true)
return false;
// Read all lines and gather dat information
while (!sr.EndOfStream)
{
string line = sr.ReadLine().Trim();
if (line.StartsWith("Version"))
var line = sr.ReadLine()?.Trim();
if (string.IsNullOrWhiteSpace(line))
{
version = line.Substring(9);
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"))
{
string serial = line.Substring(10);
#if NET48
string serial = line.Substring("Filename: ".Length);
#else
string serial = line["Filename: ".Length..];
#endif
// char gameType = serial[0];
// string gameid = serial[1] + serial[2];
@@ -245,7 +414,7 @@ namespace MPF.Modules.CleanRip
region = Region.Germany;
break;
case 'E':
region = Region.USA;
region = Region.UnitedStatesOfAmerica;
break;
case 'F':
region = Region.France;
@@ -257,7 +426,7 @@ namespace MPF.Modules.CleanRip
region = Region.Japan;
break;
case 'K':
region = Region.Korea;
region = Region.SouthKorea;
break;
case 'L':
region = Region.Europe; // Japanese import to Europe
@@ -266,22 +435,22 @@ namespace MPF.Modules.CleanRip
region = Region.Europe; // American import to Europe
break;
case 'N':
region = Region.USA; // Japanese import to USA
region = Region.UnitedStatesOfAmerica; // Japanese import to USA
break;
case 'P':
region = Region.Europe;
break;
case 'R':
region = Region.Russia;
region = Region.RussianFederation;
break;
case 'S':
region = Region.Spain;
break;
case 'Q':
region = Region.Korea; // Korea with Japanese language
region = Region.SouthKorea; // Korea with Japanese language
break;
case 'T':
region = Region.Korea; // Korea with English language
region = Region.SouthKorea; // Korea with English language
break;
case 'X':
region = null; // Not a real region code

View File

@@ -0,0 +1,145 @@
using System.Xml.Serialization;
namespace MPF.Core.Modules
{
[XmlRoot("datafile")]
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

@@ -1,4 +1,4 @@
namespace MPF.Modules.DiscImageCreator
namespace MPF.Core.Modules.DiscImageCreator
{
/// <summary>
/// Top-level commands for DiscImageCreator
@@ -26,6 +26,7 @@ namespace MPF.Modules.DiscImageCreator
public const string Sub = "sub";
public const string Swap = "swap";
public const string Tape = "tape";
public const string Version = "/v";
public const string XBOX = "xbox";
public const string XBOXSwap = "xboxswap";
public const string XGD2Swap = "xgd2swap";
@@ -44,12 +45,13 @@ namespace MPF.Modules.DiscImageCreator
public const string C2Opcode = "/c2";
public const string CopyrightManagementInformation = "/c";
public const string D8Opcode = "/d8";
public const string DatExpand = "/d";
public const string DisableBeep = "/q";
public const string DVDReread = "/rr";
public const string ExtractMicroSoftCabFile = "/mscf";
public const string Fix = "/fix";
public const string ForceUnitAccess = "/f";
public const string MultiSectorRead = "/mr";
public const string MultiSession = "/ms";
public const string NoFixSubP = "/np";
public const string NoFixSubQ = "/nq";
public const string NoFixSubQLibCrypt = "/nl";
@@ -57,6 +59,7 @@ namespace MPF.Modules.DiscImageCreator
public const string NoFixSubQSecuROM = "/ns";
public const string NoSkipSS = "/nss";
public const string PadSector = "/ps";
public const string Range = "/ra";
public const string Raw = "/raw";
public const string Resume = "/re";
public const string Reverse = "/r";

View File

@@ -1,6 +1,6 @@
using RedumpLib.Data;
using SabreTools.RedumpLib.Data;
namespace MPF.Modules.DiscImageCreator
namespace MPF.Core.Modules.DiscImageCreator
{
public static class Converters
{
@@ -48,7 +48,11 @@ namespace MPF.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)
{
@@ -86,7 +90,11 @@ namespace MPF.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)
{

View File

@@ -0,0 +1,74 @@
namespace MPF.Core.Modules.Redumper
{
/// <summary>
/// Top-level commands for Redumper
/// </summary>
public static class CommandStrings
{
public const string NONE = "";
public const string CD = "cd";
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 Dump = "dump";
public const string Info = "info";
public const string Protection = "protection";
public const string Refine = "refine";
public const string Split = "split";
public const string Verify = "verify";
public const string DVDKey = "dvdkey";
public const string DVDIsoKey = "dvdisokey";
}
/// <summary>
/// Dumping flags for Redumper
/// </summary>
public static class FlagStrings
{
// General
public const string HelpLong = "--help";
public const string HelpShort = "-h";
public const string Verbose = "--verbose";
public const string Debug = "--debug";
public const string Drive = "--drive";
public const string Speed = "--speed";
public const string Retries = "--retries";
public const string ImagePath = "--image-path";
public const string ImageName = "--image-name";
public const string Overwrite = "--overwrite";
// Drive Configuration
public const string DriveType = "--drive-type";
public const string DriveReadOffset = "--drive-read-offset";
public const string DriveC2Shift = "--drive-c2-shift";
public const string DrivePregapStart = "--drive-pregap-start";
public const string DriveReadMethod = "--drive-read-method";
public const string DriveSectorOrder = "--drive-sector-order";
// Drive Specific
public const string PlextorLeadinSkip = "--plextor-leadin-skip";
public const string PlextorLeadinRetries = "--plextor-leadin-retries";
public const string AsusSkipLeadout = "--asus-skip-leadout";
// Offset
public const string ForceOffset = "--force-offset";
public const string AudioSilenceThreshold = "--audio-silence-threshold";
public const string CorrectOffsetShift = "--correct-offset-shift";
public const string OffsetShiftRelocate = "--offset-shift-relocate";
// Split
public const string ForceSplit = "--force-split";
public const string LeaveUnchanged = "--leave-unchanged";
public const string ForceQTOC = "--force-qtoc";
public const string SkipFill = "--skip-fill";
public const string ISO9660Trim = "--iso9660-trim";
// Miscellaneous
public const string LBAStart = "--lba-start";
public const string LBAEnd = "--lba-end";
public const string RefineSubchannel = "--refine-subchannel";
public const string Skip = "--skip";
public const string DumpReadSize = "--dump-read-size";
public const string OverreadLeadout = "--overread-leadout";
}
}

View File

@@ -0,0 +1,36 @@
using SabreTools.RedumpLib.Data;
namespace MPF.Core.Modules.Redumper
{
public static class Converters
{
#region Cross-enumeration conversions
/// <summary>
/// Get the default extension for a given disc type
/// </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)
{
case MediaType.CDROM:
return ".bin";
case MediaType.DVD:
case MediaType.HDDVD:
case MediaType.BluRay:
return ".iso";
case MediaType.NONE:
default:
return null;
}
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,10 +2,12 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using MPF.Core.Converters;
using MPF.Core.Data;
using RedumpLib.Data;
using MPF.Core.Hashing;
using SabreTools.RedumpLib.Data;
namespace MPF.Modules.UmdImageCreator
namespace MPF.Core.Modules.UmdImageCreator
{
/// <summary>
/// Represents a generic set of UmdImageCreator parameters
@@ -20,11 +22,19 @@ namespace MPF.Modules.UmdImageCreator
#endregion
/// <inheritdoc/>
#if NET48
public Parameters(string parameters) : base(parameters) { }
#else
public Parameters(string? parameters) : base(parameters) { }
#endif
/// <inheritdoc/>
public Parameters(RedumpSystem? system, MediaType? type, char driveLetter, string filename, int? driveSpeed, Options options)
: base(system, type, driveLetter, filename, driveSpeed, options)
#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)
{
}
@@ -33,7 +43,7 @@ namespace MPF.Modules.UmdImageCreator
/// <inheritdoc/>
public override (bool, List<string>) CheckAllOutputFilesExist(string basePath, bool preCheck)
{
List<string> missingFiles = new List<string>();
var missingFiles = new List<string>();
switch (this.Type)
{
case MediaType.UMD:
@@ -52,35 +62,66 @@ namespace MPF.Modules.UmdImageCreator
break;
default:
return (false, missingFiles);
missingFiles.Add("Media and system combination not supported for UmdImageCreator");
break;
}
return (!missingFiles.Any(), missingFiles);
}
/// <inheritdoc/>
public override void GenerateSubmissionInfo(SubmissionInfo info, string basePath, Drive drive, bool includeArtifacts)
#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);
// 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:
info.Extras.PVD = GetPVD(basePath + "_mainInfo.txt") ?? "";
#if NET48
info.Extras.PVD = GetPVD(basePath + "_mainInfo.txt") ?? string.Empty;
#else
info.Extras!.PVD = GetPVD(basePath + "_mainInfo.txt") ?? string.Empty;
#endif
if (GetFileHashes(basePath + ".iso", out long filesize, out string crc32, out string md5, out string 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
info.SizeAndChecksums!.Size = filesize;
#endif
info.SizeAndChecksums.CRC32 = crc32;
info.SizeAndChecksums.MD5 = md5;
info.SizeAndChecksums.SHA1 = sha1;
}
if (GetUMDAuxInfo(basePath + "_disc.txt", out string title, out DiscCategory? umdcat, out string umdversion, out string umdlayer, out long umdsize))
if (GetUMDAuxInfo(basePath + "_disc.txt", out var title, out DiscCategory? umdcat, out var umdversion, out var umdlayer, out long umdsize))
{
info.CommonDiscInfo.Title = title ?? "";
#if NET48
info.CommonDiscInfo.Title = title ?? string.Empty;
info.CommonDiscInfo.Category = umdcat ?? DiscCategory.Games;
info.VersionAndEditions.Version = umdversion ?? "";
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))
info.SizeAndChecksums.Layerbreak = Int64.Parse(umdlayer ?? "-1");
@@ -92,26 +133,36 @@ namespace MPF.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
if (File.Exists(basePath + "_disc.txt"))
info.Artifacts["disc"] = GetBase64(GetFullFile(basePath + "_disc.txt"));
info.Artifacts["disc"] = GetBase64(GetFullFile(basePath + "_disc.txt")) ?? string.Empty;
if (File.Exists(basePath + "_drive.txt"))
info.Artifacts["drive"] = GetBase64(GetFullFile(basePath + "_drive.txt")) ?? string.Empty;
if (File.Exists(basePath + "_mainError.txt"))
info.Artifacts["mainError"] = GetBase64(GetFullFile(basePath + "_mainError.txt"));
info.Artifacts["mainError"] = GetBase64(GetFullFile(basePath + "_mainError.txt")) ?? string.Empty;
if (File.Exists(basePath + "_mainInfo.txt"))
info.Artifacts["mainInfo"] = GetBase64(GetFullFile(basePath + "_mainInfo.txt"));
info.Artifacts["mainInfo"] = GetBase64(GetFullFile(basePath + "_mainInfo.txt")) ?? string.Empty;
if (File.Exists(basePath + "_volDesc.txt"))
info.Artifacts["volDesc"] = GetBase64(GetFullFile(basePath + "_volDesc.txt"));
info.Artifacts["volDesc"] = GetBase64(GetFullFile(basePath + "_volDesc.txt")) ?? string.Empty;
}
}
/// <inheritdoc/>
public override List<string> GetLogFilePaths(string basePath)
{
List<string> logFiles = new List<string>();
var logFiles = new List<string>();
switch (this.Type)
{
case MediaType.UMD:
if (File.Exists($"{basePath}_disc.txt"))
logFiles.Add($"{basePath}_disc.txt");
if (File.Exists($"{basePath}_drive.txt"))
logFiles.Add($"{basePath}_drive.txt");
if (File.Exists($"{basePath}_mainError.txt"))
logFiles.Add($"{basePath}_mainError.txt");
if (File.Exists($"{basePath}_mainInfo.txt"))
@@ -134,21 +185,25 @@ namespace MPF.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 (StreamReader sr = File.OpenText(mainInfo))
using (var sr = File.OpenText(mainInfo))
{
try
{
// Make sure we're in the right sector
while (!sr.ReadLine().StartsWith("========== LBA[000016, 0x0000010]: Main Channel ==========")) ;
while (sr.ReadLine()?.StartsWith("========== LBA[000016, 0x0000010]: Main Channel ==========") == false) ;
// Fast forward to the PVD
while (!sr.ReadLine().StartsWith("0310")) ;
while (sr.ReadLine()?.StartsWith("0310") == false) ;
// Now that we're at the PVD, read each line in and concatenate
string pvd = "";
@@ -170,7 +225,11 @@ namespace MPF.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;
@@ -178,22 +237,28 @@ namespace MPF.Modules.UmdImageCreator
if (!File.Exists(disc))
return false;
using (StreamReader sr = File.OpenText(disc))
using (var sr = File.OpenText(disc))
{
try
{
// Loop through everything to get the first instance of each required field
string line = string.Empty;
var line = string.Empty;
while (!sr.EndOfStream)
{
line = sr.ReadLine().Trim();
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 = GetUMDCategory(line.Split(' ')[1]);
umdcat = InfoTool.GetUMDCategory(line.Split(' ')[1]);
else if (line.StartsWith("L0 length"))
umdlayer = line.Split(' ')[2];
else if (line.StartsWith("FileSize:"))
@@ -201,7 +266,7 @@ namespace MPF.Modules.UmdImageCreator
}
// If the L0 length is the size of the full disc, there's no layerbreak
if (Int64.Parse(umdlayer) * 2048 == umdsize)
if (Int64.TryParse(umdlayer, out long umdlayerValue) && umdlayerValue * 2048 == umdsize)
umdlayer = null;
return true;

View File

@@ -4,65 +4,107 @@ using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using BurnOutSharp;
using BurnOutSharp.External.psxt001z;
using BurnOutSharp.ProtectionType;
using MPF.Core.Data;
using BinaryObjectScanner;
using BinaryObjectScanner.Protection;
using psxt001z;
namespace MPF.Library
namespace MPF.Core
{
public static class Protection
{
/// <summary>
/// Run protection scan on a given dump environment
/// Run protection scan on a given path
/// </summary>
/// <param name="path">Path to scan for protection</param>
/// <param name="options">Options object that determines what to scan</param>
/// <param name="progress">Optional progress callback</param>
/// <returns>TCopy protection detected in the envirionment, if any</returns>
public static async Task<(bool, string)> RunProtectionScanOnPath(string path, Options options, IProgress<ProtectionProgress> progress = null)
/// <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
{
try
{
var found = await Task.Run(() =>
{
var scanner = new Scanner(progress)
{
IncludeDebug = options.IncludeDebugProtectionInformation,
ScanAllFiles = options.ForceScanningForProtection,
ScanArchives = options.ScanArchivesForProtection,
ScanPackers = options.ScanPackersForProtection,
};
var scanner = new 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);
});
if (found == null || found.Count() == 0)
return (true, "None found");
// If nothing was returned, return
if (found == null || !found.Any())
return (null, null);
// Get an ordered list of distinct found protections
var orderedDistinctProtections = found
// Filter out any empty protections
var filteredProtections = found
.Where(kvp => kvp.Value != null && kvp.Value.Any())
.SelectMany(kvp => kvp.Value)
.Distinct()
.OrderBy(p => p);
.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value.OrderBy(s => s).ToList());
// Sanitize and join protections for writing
string protections = SanitizeFoundProtections(orderedDistinctProtections);
return (true, protections);
// Return the filtered set of protections
return (filteredProtections, null);
}
catch (Exception ex)
{
return (false, ex.ToString());
return (null, ex.ToString());
}
}
/// <summary>
/// Format found protections to a deduplicated, ordered string
/// </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())
return "None found [OMIT FROM SUBMISSION]";
// Get an ordered list of distinct found protections
var orderedDistinctProtections = protections
.SelectMany(kvp => kvp.Value)
.Distinct()
.OrderBy(p => p);
// Sanitize and join protections for writing
string protectionString = SanitizeFoundProtections(orderedDistinctProtections);
if (string.IsNullOrWhiteSpace(protectionString))
return "None found [OMIT FROM SUBMISSION]";
return protectionString;
}
/// <summary>
/// Get the existence of an anti-modchip string from a PlayStation disc, if possible
/// </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(() =>
{
try
@@ -73,7 +115,7 @@ namespace MPF.Library
try
{
byte[] fileContent = File.ReadAllBytes(file);
string protection = antiModchip.CheckContents(file, fileContent, false, null, null);
var protection = antiModchip.CheckContents(file, fileContent, false);
if (!string.IsNullOrWhiteSpace(protection))
return true;
}
@@ -106,12 +148,21 @@ namespace MPF.Library
/// <param name="foundProtections">Enumerable of found protections</param>
public static string SanitizeFoundProtections(IEnumerable<string> foundProtections)
{
// EXCEPTIONS
if (foundProtections.Any(p => p.StartsWith("[Exception opening file")))
{
foundProtections = foundProtections.Where(p => !p.StartsWith("[Exception opening file"));
foundProtections = foundProtections
.Prepend("Exception occurred while scanning [RESCAN NEEDED]")
.OrderBy(p => p);
}
// ActiveMARK
if (foundProtections.Any(p => p == "ActiveMARK 5") && foundProtections.Any(p => p == "ActiveMARK"))
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
@@ -141,7 +192,7 @@ namespace MPF.Library
// 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")
@@ -177,80 +228,68 @@ namespace MPF.Library
// SafeCast
// TODO: Figure this one out
// Cactus Data Shield / SafeDisc
if (foundProtections.Any(p => p == "Cactus Data Shield 300 (Confirm presence of other CDS-300 files)"))
{
foundProtections = foundProtections
.Where(p => p != "Cactus Data Shield 300 (Confirm presence of other CDS-300 files)");
if (foundProtections.Any(p => !p.StartsWith("SafeDisc")))
foundProtections = foundProtections.Append("Cactus Data Shield 300");
}
// 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 != "SafeDisc")
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}", 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 1-3")
.Where(p => p != "SafeDisc 2")
.Where(p => p != "SafeDisc 3.20-4.xx (version removed)")
.Where(p => !p.StartsWith("SafeDisc (dplayerx.dll)"))
.Where(p => !p.StartsWith("SafeDisc (drvmgt.dll)"))
.Where(p => !p.StartsWith("SafeDisc (secdrv.sys)"))
.Where(p => p != "SafeDisc Lite");
.Where(p => p != "SafeDisc 2+");
}
else if (foundProtections.Any(p => p.StartsWith("SafeDisc (drvmgt.dll)")))
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 != "SafeDisc")
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}/+", RegexOptions.Compiled)))
.Where(p => p != "SafeDisc 1/Lite")
.Where(p => p != "SafeDisc 1-3")
.Where(p => p != "SafeDisc 2")
.Where(p => p != "SafeDisc 3.20-4.xx (version removed)")
.Where(p => !p.StartsWith("SafeDisc (dplayerx.dll)"))
.Where(p => !p.StartsWith("SafeDisc (secdrv.sys)"))
.Where(p => p != "SafeDisc Lite");
.Where(p => p != "SafeDisc 2+");
}
else if (foundProtections.Any(p => p.StartsWith("SafeDisc (secdrv.sys)")))
else if (foundProtections.Any(p => Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}/+", RegexOptions.Compiled)))
{
foundProtections = foundProtections.Where(p => p != "SafeDisc")
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 => p != "SafeDisc 1/Lite")
.Where(p => p != "SafeDisc 1-3")
.Where(p => p != "SafeDisc 2")
.Where(p => p != "SafeDisc 3.20-4.xx (version removed)")
.Where(p => !p.StartsWith("SafeDisc (dplayerx.dll)"))
.Where(p => p != "SafeDisc Lite");
.Where(p => p != "SafeDisc 2+");
}
else if (foundProtections.Any(p => p.StartsWith("SafeDisc (dplayerx.dll)")))
else if (foundProtections.Any(p => p.StartsWith("Macrovision Security Driver")))
{
foundProtections = foundProtections.Where(p => p != "SafeDisc")
foundProtections = foundProtections.Where(p => !p.StartsWith("Macrovision Protected Application"))
.Where(p => !p.StartsWith("Macrovision Protection File"))
.Where(p => p != "SafeDisc")
.Where(p => p != "SafeDisc 1/Lite")
.Where(p => p != "SafeDisc 1-3")
.Where(p => p != "SafeDisc 2")
.Where(p => p != "SafeDisc 3.20-4.xx (version removed)")
.Where(p => p != "SafeDisc Lite");
.Where(p => p != "SafeDisc 2+");
}
else if (foundProtections.Any(p => p == "SafeDisc 3.20-4.xx (version removed)"))
else if (foundProtections.Any(p => p == "SafeDisc 2+"))
{
foundProtections = foundProtections.Where(p => p != "SafeDisc")
.Where(p => p != "SafeDisc 1/Lite")
.Where(p => p != "SafeDisc 1-3")
.Where(p => p != "SafeDisc 2")
.Where(p => p != "SafeDisc Lite");
}
else if (foundProtections.Any(p => p == "SafeDisc 2"))
{
foundProtections = foundProtections.Where(p => p != "SafeDisc")
.Where(p => p != "SafeDisc 1/Lite")
.Where(p => p != "SafeDisc 1-3")
.Where(p => p != "SafeDisc Lite");
foundProtections = foundProtections.Where(p => !p.StartsWith("Macrovision Protected Application"))
.Where(p => !p.StartsWith("Macrovision Protection File"))
.Where(p => p != "SafeDisc");
}
else if (foundProtections.Any(p => p == "SafeDisc 1/Lite"))
{
foundProtections = foundProtections.Where(p => p != "SafeDisc")
.Where(p => p != "SafeDisc 1-3")
.Where(p => p != "SafeDisc Lite");
}
else if (foundProtections.Any(p => p == "SafeDisc Lite"))
{
foundProtections = foundProtections.Where(p => p != "SafeDisc")
.Where(p => p != "SafeDisc 1-3");
}
else if (foundProtections.Any(p => p == "SafeDisc 1-3"))
{
foundProtections = foundProtections.Where(p => p != "SafeDisc");
foundProtections = foundProtections.Where(p => !p.StartsWith("Macrovision Protected Application"))
.Where(p => !p.StartsWith("Macrovision Protection File"))
.Where(p => p != "SafeDisc");
}
}
@@ -263,7 +302,7 @@ namespace MPF.Library
// 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")

File diff suppressed because it is too large Load Diff

View File

@@ -3,13 +3,13 @@ using System.Collections.Generic;
using System.Linq;
using MPF.Core.Converters;
namespace MPF
namespace MPF.Core.UI.ComboBoxItems
{
/// <summary>
/// A generic combo box element
/// </summary>
/// <typeparam name="T">Enum type representing the possible values</typeparam>
public class Element<T> : IElement where T : struct, Enum
public class Element<T> : IEquatable<Element<T>>, IElement where T : struct, Enum
{
private readonly T Data;
@@ -47,5 +47,18 @@ namespace MPF
.OfType<T>()
.Select(e => new Element<T>(e));
}
/// <inheritdoc/>
#if NET48
public bool Equals(Element<T> other)
#else
public bool Equals(Element<T>? other)
#endif
{
if (other == null)
return false;
return Name == other.Name;
}
}
}

View File

@@ -1,4 +1,4 @@
namespace MPF
namespace MPF.Core.UI.ComboBoxItems
{
public interface IElement
{

View File

@@ -1,18 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using MPF.Core.Converters;
using MPF.Core.Utilities;
using RedumpLib.Data;
using SabreTools.RedumpLib.Data;
namespace MPF
namespace MPF.Core.UI.ComboBoxItems
{
/// <summary>
/// Represents a single item in the System combo box
/// </summary>
public class RedumpSystemComboBoxItem : IElement
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;
@@ -81,5 +83,18 @@ namespace MPF
return systemsValues;
}
/// <inheritdoc/>
#if NET48
public bool Equals(RedumpSystemComboBoxItem other)
#else
public bool Equals(RedumpSystemComboBoxItem? other)
#endif
{
if (other == null)
return false;
return Value == other.Value;
}
}
}

View File

@@ -0,0 +1,257 @@
using System.Collections.Generic;
using System.Linq;
using MPF.Core.Data;
using MPF.Core.UI.ComboBoxItems;
using MPF.Core.Utilities;
using SabreTools.RedumpLib.Data;
namespace MPF.Core.UI.ViewModels
{
public class DiscInformationViewModel
{
#region Fields
/// <summary>
/// Application-level Options object
/// </summary>
public Options Options { get; private set; }
/// <summary>
/// SubmissionInfo object to fill and save
/// </summary>
public SubmissionInfo SubmissionInfo { get; private set; }
#endregion
#region Lists
/// <summary>
/// List of available disc categories
/// </summary>
public List<Element<DiscCategory>> Categories { get; private set; } = Element<DiscCategory>.GenerateElements().ToList();
/// <summary>
/// List of available regions
/// </summary>
public List<Element<Region>> Regions { get; private set; } = Element<Region>.GenerateElements().ToList();
/// <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,
Region.AsiaEurope,
Region.AsiaUSA,
Region.Australia,
Region.AustraliaGermany,
Region.AustraliaNewZealand,
Region.Austria,
Region.AustriaSwitzerland,
Region.Belarus,
Region.Belgium,
Region.BelgiumNetherlands,
Region.Brazil,
Region.Bulgaria,
Region.Canada,
Region.China,
Region.Croatia,
Region.Czechia,
Region.Denmark,
Region.Estonia,
Region.Europe,
Region.EuropeAsia,
Region.EuropeAustralia,
Region.EuropeCanada,
Region.EuropeGermany,
Region.Export,
Region.Finland,
Region.France,
Region.FranceSpain,
Region.Germany,
Region.GreaterChina,
Region.Greece,
Region.Hungary,
Region.Iceland,
Region.India,
Region.Ireland,
Region.Israel,
Region.Italy,
Region.Japan,
Region.JapanAsia,
Region.JapanEurope,
Region.JapanKorea,
Region.JapanUSA,
Region.SouthKorea,
Region.LatinAmerica,
Region.Lithuania,
Region.Netherlands,
Region.NewZealand,
Region.Norway,
Region.Poland,
Region.Portugal,
Region.Romania,
Region.RussianFederation,
Region.Scandinavia,
Region.Serbia,
Region.Singapore,
Region.Slovakia,
Region.SouthAfrica,
Region.Spain,
Region.SpainPortugal,
Region.Sweden,
Region.Switzerland,
Region.Taiwan,
Region.Thailand,
Region.Turkey,
Region.UnitedArabEmirates,
Region.UnitedKingdom,
Region.UKAustralia,
Region.Ukraine,
Region.UnitedStatesOfAmerica,
Region.USAAsia,
Region.USAAustralia,
Region.USABrazil,
Region.USACanada,
Region.USAEurope,
Region.USAGermany,
Region.USAJapan,
Region.USAKorea,
Region.World,
};
/// <summary>
/// List of available languages
/// </summary>
public List<Element<Language>> Languages { get; private set; } = Element<Language>.GenerateElements().ToList();
/// <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,
Language.Arabic,
Language.Armenian,
Language.Basque,
Language.Belarusian,
Language.Bulgarian,
Language.Catalan,
Language.Chinese,
Language.Croatian,
Language.Czech,
Language.Danish,
Language.Dutch,
Language.English,
Language.Estonian,
Language.Finnish,
Language.French,
Language.Gaelic,
Language.German,
Language.Greek,
Language.Hebrew,
Language.Hindi,
Language.Hungarian,
Language.Icelandic,
Language.Indonesian,
Language.Italian,
Language.Japanese,
Language.Korean,
Language.Latin,
Language.Latvian,
Language.Lithuanian,
Language.Macedonian,
Language.Norwegian,
Language.Polish,
Language.Portuguese,
Language.Panjabi,
Language.Romanian,
Language.Russian,
Language.Serbian,
Language.Slovak,
Language.Slovenian,
Language.Spanish,
Language.Swedish,
Language.Tamil,
Language.Thai,
Language.Turkish,
Language.Ukrainian,
Language.Vietnamese,
};
/// <summary>
/// List of available languages
/// </summary>
public List<Element<LanguageSelection>> LanguageSelections { get; private set; } = Element<LanguageSelection>.GenerateElements().ToList();
#endregion
/// <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();
}
#region Helpers
/// <summary>
/// Load the current contents of the base SubmissionInfo to the UI
/// </summary>
/// TODO: Convert selected list item to binding
public void Load()
{
if (SubmissionInfo.CommonDiscInfo?.Languages != null)
Languages.ForEach(l => l.IsChecked = SubmissionInfo.CommonDiscInfo.Languages.Contains(l));
if (SubmissionInfo.CommonDiscInfo?.LanguageSelection != null)
LanguageSelections.ForEach(ls => ls.IsChecked = SubmissionInfo.CommonDiscInfo.LanguageSelection.Contains(ls));
}
/// <summary>
/// Save the current contents of the UI to the base SubmissionInfo
/// </summary>
/// TODO: Convert selected list item to binding
public void Save()
{
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.LanguageSelection = LanguageSelections.Where(ls => ls.IsChecked).Select(ls => ls?.Value).ToArray();
}
/// <summary>
/// Repopulate the list of Languages based on Redump support
/// </summary>
public void SetRedumpLanguages()
{
this.Languages = RedumpLanguages.Select(l => new Element<Language>(l)).ToList();
}
/// <summary>
/// Repopulate the list of Regions based on Redump support
/// </summary>
public void SetRedumpRegions()
{
this.Regions = RedumpRegions.Select(r => new Element<Region>(r)).ToList();
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,124 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using MPF.Core.Data;
using MPF.Core.UI.ComboBoxItems;
using SabreTools.RedumpLib.Web;
namespace MPF.Core.UI.ViewModels
{
public class OptionsViewModel : INotifyPropertyChanged
{
#region Fields
/// <summary>
/// Title for the window
/// </summary>
#if NET48
public string Title
#else
public string? Title
#endif
{
get => _title;
set
{
_title = value;
TriggerPropertyChanged(nameof(Title));
}
}
#if NET48
private string _title;
#else
private string? _title;
#endif
/// <summary>
/// Current set of options
/// </summary>
public Options Options { get; }
/// <summary>
/// Flag for if settings were saved or not
/// </summary>
public bool SavedSettings { get; set; }
/// <inheritdoc/>
#if NET48
public event PropertyChangedEventHandler PropertyChanged;
#else
public event PropertyChangedEventHandler? PropertyChanged;
#endif
#endregion
#region Lists
/// <summary>
/// List of available internal programs
/// </summary>
public List<Element<InternalProgram>> InternalPrograms => PopulateInternalPrograms();
/// <summary>
/// Current list of supported system profiles
/// </summary>
public List<RedumpSystemComboBoxItem> Systems => RedumpSystemComboBoxItem.GenerateElements().ToList();
#endregion
/// <summary>
/// Constructor
/// </summary>
public OptionsViewModel(Options baseOptions)
{
Options = new Options(baseOptions);
}
#region Population
/// <summary>
/// Get a complete list of supported internal programs
/// </summary>
private static List<Element<InternalProgram>> PopulateInternalPrograms()
{
var internalPrograms = new List<InternalProgram> { InternalProgram.DiscImageCreator, InternalProgram.Aaru, InternalProgram.Redumper };
return internalPrograms.Select(ip => new Element<InternalProgram>(ip)).ToList();
}
#endregion
#region UI Commands
/// <summary>
/// Test Redump login credentials
/// </summary>
#if NET48
public (bool?, string) TestRedumpLogin(string username, string password)
#else
public async Task<(bool?, string?)> TestRedumpLogin(string username, string password)
#endif
{
#if NET48
return RedumpWebClient.ValidateCredentials(username, password);
#else
return await RedumpHttpClient.ValidateCredentials(username, password);
#endif
}
#endregion
#region Property Updates
/// <summary>
/// Trigger a property changed event
/// </summary>
private void TriggerPropertyChanged(string propertyName)
{
// If the property change event is initialized
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}

400
MPF.Core/Utilities/Chime.cs Normal file
View File

@@ -0,0 +1,400 @@
#if FALSE
using System;
namespace MPF.Core.Utilities
{
/// <summary>
/// Methods to deal with outputting tones to the PC speaker
/// </summary>
public class Chime
{
/// <summary>
/// Standard duration to play a single tone
/// </summary>
private const int standardDurationMs = 200;
#region Octave 0
/// <summary>
/// Frequency representing C(0)
/// </summary>
private const int noteC0 = 16; // 16.35
/// <summary>
/// Frequency representing D(0)
/// </summary>
private const int noteD0 = 18; // 18.35
/// <summary>
/// Frequency representing E(0)
/// </summary>
private const int noteE0 = 21; // 20.60
/// <summary>
/// Frequency representing F(0)
/// </summary>
private const int noteF0 = 22; // 21.83
/// <summary>
/// Frequency representing G(0)
/// </summary>
private const int noteG0 = 25; // 24.50
/// <summary>
/// Frequency representing A(0)
/// </summary>
private const int noteA0 = 28; // 27.50
/// <summary>
/// Frequency representing B(0)
/// </summary>
private const int noteB0 = 31; // 30.87
#endregion
#region Octave 1
/// <summary>
/// Frequency representing C(1)
/// </summary>
private const int noteC1 = 33; // 32.70
/// <summary>
/// Frequency representing D(1)
/// </summary>
private const int noteD1 = 37; // 36.71
/// <summary>
/// Frequency representing E(1)
/// </summary>
private const int noteE1 = 41; // 41.20
/// <summary>
/// Frequency representing F(1)
/// </summary>
private const int noteF1 = 44; // 43.65
/// <summary>
/// Frequency representing G(1)
/// </summary>
private const int noteG1 = 49; // 49.00
/// <summary>
/// Frequency representing A(1)
/// </summary>
private const int noteA1 = 55; // 55.00
/// <summary>
/// Frequency representing B(1)
/// </summary>
private const int noteB1 = 62; // 61.74
#endregion
#region Octave 2
/// <summary>
/// Frequency representing C(2)
/// </summary>
private const int noteC2 = 65; // 65.41
/// <summary>
/// Frequency representing D(2)
/// </summary>
private const int noteD2 = 73; // 73.42
/// <summary>
/// Frequency representing E(2)
/// </summary>
private const int noteE2 = 82; // 82.41
/// <summary>
/// Frequency representing F(2)
/// </summary>
private const int noteF2 = 87; // 87.31
/// <summary>
/// Frequency representing G(2)
/// </summary>
private const int noteG2 = 98; // 98.00
/// <summary>
/// Frequency representing A(2)
/// </summary>
private const int noteA2 = 110; // 110.00
/// <summary>
/// Frequency representing B(2)
/// </summary>
private const int noteB2 = 123; // 123.47
#endregion
#region Octave 3
/// <summary>
/// Frequency representing C(3)
/// </summary>
private const int noteC3 = 131; // 130.81
/// <summary>
/// Frequency representing D(3)
/// </summary>
private const int noteD3 = 147; // 146.83
/// <summary>
/// Frequency representing E(3)
/// </summary>
private const int noteE3 = 165; // 164.81
/// <summary>
/// Frequency representing F(3)
/// </summary>
private const int noteF3 = 175; // 174.61
/// <summary>
/// Frequency representing G(3)
/// </summary>
private const int noteG3 = 196; // 196.00
/// <summary>
/// Frequency representing A(3)
/// </summary>
private const int noteA3 = 220; // 220.00
/// <summary>
/// Frequency representing B(3)
/// </summary>
private const int noteB3 = 247; // 246.94
#endregion
#region Octave 4
/// <summary>
/// Frequency representing C(4)
/// </summary>
private const int noteC4 = 262; // 261.63
/// <summary>
/// Frequency representing D(4)
/// </summary>
private const int noteD4 = 294; // 293.66
/// <summary>
/// Frequency representing E(4)
/// </summary>
private const int noteE4 = 330; // 329.63
/// <summary>
/// Frequency representing F(4)
/// </summary>
private const int noteF4 = 349; // 349.23
/// <summary>
/// Frequency representing G(4)
/// </summary>
private const int noteG4 = 392; // 392.00
/// <summary>
/// Frequency representing A(4)
/// </summary>
private const int noteA4 = 440; // 440.00
/// <summary>
/// Frequency representing B(4)
/// </summary>
private const int noteB4 = 494; // 493.88
#endregion
#region Octave 5
/// <summary>
/// Frequency representing C(5)
/// </summary>
private const int noteC5 = 523; // 523.25
/// <summary>
/// Frequency representing D(5)
/// </summary>
private const int noteD5 = 587; // 587.33
/// <summary>
/// Frequency representing E(5)
/// </summary>
private const int noteE5 = 659; // 659.25
/// <summary>
/// Frequency representing F(5)
/// </summary>
private const int noteF5 = 698; // 698.46
/// <summary>
/// Frequency representing G(5)
/// </summary>
private const int noteG5 = 783; // 783.99
/// <summary>
/// Frequency representing A(5)
/// </summary>
private const int noteA5 = 880; // 880.00
/// <summary>
/// Frequency representing B(5)
/// </summary>
private const int noteB5 = 988; // 987.77
#endregion
#region Octave 6
/// <summary>
/// Frequency representing C(6)
/// </summary>
private const int noteC6 = 1047; // 1046.50
/// <summary>
/// Frequency representing D(6)
/// </summary>
private const int noteD6 = 1175; // 1174.66
/// <summary>
/// Frequency representing E(6)
/// </summary>
private const int noteE6 = 1319; // 1318.51
/// <summary>
/// Frequency representing F(6)
/// </summary>
private const int noteF6 = 1397; // 1396.91
/// <summary>
/// Frequency representing G(6)
/// </summary>
private const int noteG6 = 1568; // 1567.98
/// <summary>
/// Frequency representing A(6)
/// </summary>
private const int noteA6 = 1760; // 1760.00
/// <summary>
/// Frequency representing B(6)
/// </summary>
private const int noteB6 = 1976; // 1975.53
#endregion
#region Octave 7
/// <summary>
/// Frequency representing C(7)
/// </summary>
private const int noteC7 = 2093; // 2093.00
/// <summary>
/// Frequency representing D(7)
/// </summary>
private const int noteD7 = 2349; // 2349.32
/// <summary>
/// Frequency representing E(7)
/// </summary>
private const int noteE7 = 2637; // 2637.02
/// <summary>
/// Frequency representing F(7)
/// </summary>
private const int noteF7 = 2794; // 2793.83
/// <summary>
/// Frequency representing G(7)
/// </summary>
private const int noteG7 = 3136; // 3135.96
/// <summary>
/// Frequency representing A(7)
/// </summary>
private const int noteA7 = 3520; // 3520.00
/// <summary>
/// Frequency representing B(7)
/// </summary>
private const int noteB7 = 3951; // 3951.07
#endregion
#region Octave 8
/// <summary>
/// Frequency representing C(8)
/// </summary>
private const int noteC8 = 4186; // 4186.01
/// <summary>
/// Frequency representing D(8)
/// </summary>
private const int noteD8 = 4699; // 4698.63
/// <summary>
/// Frequency representing E(8)
/// </summary>
private const int noteE8 = 5274; // 5274.04
/// <summary>
/// Frequency representing F(8)
/// </summary>
private const int noteF8 = 5588; // 5587.65
/// <summary>
/// Frequency representing G(8)
/// </summary>
private const int noteG8 = 6272; // 6271.93
/// <summary>
/// Frequency representing A(8)
/// </summary>
private const int noteA8 = 7040; // 7040.00
/// <summary>
/// Frequency representing B(8)
/// </summary>
private const int noteB8 = 7902; // 7902.13
#endregion
/// <summary>
/// Output a series of beeps for completion, similar to DiscImageCreator
/// </summary>
/// <param name="success">True if the upward series should play, false otherwise</param>
public static void StandardCompletion(bool success)
{
if (success)
{
Console.Beep(noteC4, standardDurationMs);
Console.Beep(noteD4, standardDurationMs);
Console.Beep(noteE4, standardDurationMs);
Console.Beep(noteF4, standardDurationMs);
Console.Beep(noteG4, standardDurationMs);
Console.Beep(noteA4, standardDurationMs);
Console.Beep(noteB4, standardDurationMs);
Console.Beep(noteC5, standardDurationMs);
}
else
{
Console.Beep(noteC5, standardDurationMs);
Console.Beep(noteB4, standardDurationMs);
Console.Beep(noteA4, standardDurationMs);
Console.Beep(noteG4, standardDurationMs);
Console.Beep(noteF4, standardDurationMs);
Console.Beep(noteE4, standardDurationMs);
Console.Beep(noteD4, standardDurationMs);
Console.Beep(noteC4, standardDurationMs);
}
}
}
}
#endif

View File

@@ -1,4 +1,8 @@
using RedumpLib.Data;
using System;
using System.Collections.Generic;
using MPF.Core.Converters;
using MPF.Core.Data;
using SabreTools.RedumpLib.Data;
namespace MPF.Core.Utilities
{
@@ -68,6 +72,7 @@ namespace MPF.Core.Utilities
case RedumpSystem.SonyPlayStation3:
case RedumpSystem.SonyPlayStation4:
//case RedumpSystem.SonyPlayStation5:
case RedumpSystem.SonyPlayStationPortable:
return true;
default:
return false;
@@ -90,11 +95,13 @@ namespace MPF.Core.Utilities
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:
@@ -102,26 +109,6 @@ namespace MPF.Core.Utilities
}
}
/// <summary>
/// Determine if a system is a marker value
/// </summary>
/// <param name="system">RedumpSystem value to check</param>
/// <returns>True if the system is a marker value, false otherwise</returns>
public static bool IsMarker(this RedumpSystem? system)
{
switch (system)
{
case RedumpSystem.MarkerArcadeEnd:
case RedumpSystem.MarkerComputerEnd:
case RedumpSystem.MarkerDiscBasedConsoleEnd:
// case RedumpSystem.MarkerOtherConsoleEnd:
case RedumpSystem.MarkerOtherEnd:
return true;
default:
return false;
}
}
/// <summary>
/// Determine if a system is considered XGD
/// </summary>
@@ -140,5 +127,23 @@ namespace MPF.Core.Utilities
return false;
}
}
/// <summary>
/// List all programs with their short usable names
/// </summary>
public static List<string> ListPrograms()
{
var programs = new List<string>();
foreach (var val in Enum.GetValues(typeof(InternalProgram)))
{
if (((InternalProgram)val) == InternalProgram.NONE)
continue;
programs.Add($"{((InternalProgram?)val).LongName()}");
}
return programs;
}
}
}

View File

@@ -14,7 +14,11 @@ 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)
#else
public static async Task OutputToLog(TextReader reader, object baseClass, EventHandler<string>? handler)
#endif
{
// Initialize the required variables
char[] buffer = new char[256];
@@ -63,7 +67,11 @@ 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)
#else
private static void ProcessNewLines(StringBuilder sb, string line, object baseClass, EventHandler<string>? handler)
#endif
{
line = line.Replace("\r\n", "\n");
var split = line.Split('\n');
@@ -105,7 +113,11 @@ 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)
#else
private static void ProcessCarriageReturns(StringBuilder sb, string line, object baseClass, EventHandler<string>? handler)
#endif
{
var split = line.Split('\r');

View File

@@ -0,0 +1,301 @@
using System;
using System.Collections.Generic;
using System.IO;
using MPF.Core.Converters;
using MPF.Core.Data;
using Newtonsoft.Json;
using SabreTools.RedumpLib.Data;
namespace MPF.Core.Utilities
{
public static class OptionsLoader
{
private const string ConfigurationPath = "config.json";
#region Arguments
/// <summary>
/// Process any standalone arguments for the program
/// </summary>
/// <returns>True if one of the arguments was processed, false otherwise</returns>
public static bool? ProcessStandaloneArguments(string[] args)
{
// Help options
if (args.Length == 0 || args[0] == "-h" || args[0] == "-?")
return false;
// List options
if (args[0] == "-lm" || args[0] == "--listmedia")
{
Console.WriteLine("Supported Media Types:");
foreach (string mediaType in Extensions.ListMediaTypes())
{
Console.WriteLine(mediaType);
}
Console.ReadLine();
return true;
}
else if (args[0] == "-lp" || args[0] == "--listprograms")
{
Console.WriteLine("Supported Programs:");
foreach (string program in EnumExtensions.ListPrograms())
{
Console.WriteLine(program);
}
Console.ReadLine();
return true;
}
else if (args[0] == "-ls" || args[0] == "--listsystems")
{
Console.WriteLine("Supported Systems:");
foreach (string system in Extensions.ListSystems())
{
Console.WriteLine(system);
}
Console.ReadLine();
return true;
}
return false;
}
/// <summary>
/// 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)
return (false, MediaType.NONE, null, "Invalid number of arguments");
// Check the MediaType
var mediaType = EnumConverter.ToMediaType(args[0].Trim('"'));
if (mediaType == MediaType.NONE)
return (false, MediaType.NONE, null, $"{args[0]} is not a recognized media type");
// Check the RedumpSystem
var knownSystem = Extensions.ToRedumpSystem(args[1].Trim('"'));
if (knownSystem == null)
return (false, MediaType.NONE, null, $"{args[1]} is not a recognized system");
return (true, mediaType, knownSystem, null);
}
/// <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()
{
RedumpUsername = null,
RedumpPassword = null,
InternalProgram = InternalProgram.NONE,
AddFilenameSuffix = false,
OutputSubmissionJSON = false,
CompressLogFiles = false,
DeleteUnnecessaryFiles = false,
};
// 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;
// If we have no arguments, just return
if (args == null || args.Length == 0)
return (options, null, null, 0);
// If we have an invalid start index, just return
if (startIndex < 0 || startIndex >= args.Length)
return (options, null, null, startIndex);
// Loop through the arguments and parse out values
for (; startIndex < args.Length; startIndex++)
{
// Use specific program
if (args[startIndex].StartsWith("-u=") || args[startIndex].StartsWith("--use="))
{
string internalProgram = args[startIndex].Split('=')[1];
options.InternalProgram = EnumConverter.ToInternalProgram(internalProgram);
}
else if (args[startIndex] == "-u" || args[startIndex] == "--use")
{
string internalProgram = args[startIndex + 1];
options.InternalProgram = EnumConverter.ToInternalProgram(internalProgram);
startIndex++;
}
// Redump login
else if (args[startIndex].StartsWith("-c=") || args[startIndex].StartsWith("--credentials="))
{
string[] credentials = args[startIndex].Split('=')[1].Split(';');
options.RedumpUsername = credentials[0];
options.RedumpPassword = credentials[1];
}
else if (args[startIndex] == "-c" || args[startIndex] == "--credentials")
{
options.RedumpUsername = args[startIndex + 1];
options.RedumpPassword = args[startIndex + 2];
startIndex += 2;
}
// Pull all information (requires Redump login)
else if (args[startIndex].Equals("-a") || args[startIndex].Equals("--pull-all"))
{
options.PullAllInformation = true;
}
// Use a device path for physical checks
else if (args[startIndex].StartsWith("-p=") || args[startIndex].StartsWith("--path="))
{
parsedPath = args[startIndex].Split('=')[1];
}
else if (args[startIndex] == "-p" || args[startIndex] == "--path")
{
parsedPath = args[startIndex + 1];
startIndex++;
}
// Scan for protection (requires device path)
else if (args[startIndex].Equals("-s") || args[startIndex].Equals("--scan"))
{
scan = true;
}
// Output protection to separate file (requires scan for protection)
else if (args[startIndex].Equals("-f") || args[startIndex].Equals("--protect-file"))
{
protectFile = 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);
}
else if (args[startIndex] == "-l" || args[startIndex] == "--load-seed")
{
string seedInfo = args[startIndex + 1];
info = SubmissionInfoTool.CreateFromFile(seedInfo);
startIndex++;
}
// Add filename suffix
else if (args[startIndex].Equals("-x") || args[startIndex].Equals("--suffix"))
{
options.AddFilenameSuffix = true;
}
// Output submission JSON
else if (args[startIndex].Equals("-j") || args[startIndex].Equals("--json"))
{
options.OutputSubmissionJSON = true;
}
// Compress log and extraneous files
else if (args[startIndex].Equals("-z") || args[startIndex].Equals("--zip"))
{
options.CompressLogFiles = true;
}
// Delete unnecessary files files
else if (args[startIndex].Equals("-d") || args[startIndex].Equals("--delete"))
{
options.DeleteUnnecessaryFiles = true;
}
// Default, we fall out
else
{
break;
}
}
// Now deal with the complex options
options.ScanForProtection = scan && !string.IsNullOrWhiteSpace(parsedPath);
options.OutputSeparateProtectionFile = scan && protectFile && !string.IsNullOrWhiteSpace(parsedPath);
return (options, info, parsedPath, startIndex);
}
/// <summary>
/// Return a list of supported arguments and descriptions
/// </summary>
public static List<string> PrintSupportedArguments()
{
var supportedArguments = new List<string>
{
"-u, --use <program> Dumping program output type [REQUIRED]",
"-c, --credentials <user> <pw> Redump username and password",
"-a, --pull-all Pull all information from Redump (requires --credentials)",
"-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)",
"-l, --load-seed <path> Load a seed submission JSON for user information",
"-x, --suffix Enable adding filename suffix",
"-j, --json Enable submission JSON output",
"-z, --zip Enable log file compression",
"-d, --delete Enable unnecessary file deletion",
};
return supportedArguments;
}
#endregion
#region Configuration
/// <summary>
/// Load the current set of options from the application configuration
/// </summary>
public static Options LoadFromConfig()
{
if (!File.Exists(ConfigurationPath))
{
_ = File.Create(ConfigurationPath);
return new Options();
}
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);
}
/// <summary>
/// Save the current set of options to the application configuration
/// </summary>
public static void SaveToConfig(Options options)
{
var serializer = JsonSerializer.Create();
var sw = new StreamWriter(ConfigurationPath) { AutoFlush = true };
var writer = new JsonTextWriter(sw) { Formatting = Formatting.Indented };
serializer.Serialize(writer, options.Settings, typeof(Dictionary<string, string>));
}
#endregion
}
}

View File

@@ -1,9 +1,8 @@
using System;
using System.Net;
using System.Reflection;
using MPF.Core.Data;
using Newtonsoft.Json.Linq;
using RedumpLib.Data;
using SabreTools.RedumpLib.Data;
namespace MPF.Core.Utilities
{
@@ -85,6 +84,7 @@ 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
@@ -117,6 +117,132 @@ namespace MPF.Core.Utilities
default:
return Result.Failure($"{type.LongName()} media are not supported for dumping");
}
#else
return type switch
{
// Fully supported types
MediaType.BluRay
or MediaType.CDROM
or MediaType.DVD
or MediaType.FloppyDisk
or MediaType.HardDisk
or MediaType.CompactFlash
or MediaType.SDCard
or MediaType.FlashDrive
or MediaType.HDDVD => Result.Success($"{type.LongName()} ready to dump"),
// Partially supported types
MediaType.GDROM
or MediaType.NintendoGameCubeGameDisc
or MediaType.NintendoWiiOpticalDisc => Result.Success($"{type.LongName()} partially supported for dumping"),
// Special case for other supported tools
MediaType.UMD => Result.Failure($"{type.LongName()} supported for submission info parsing"),
// Specifically unknown type
MediaType.NONE => Result.Failure($"Please select a valid media type"),
// 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>
public static bool ProgramSupportsMedia(InternalProgram program, MediaType? type)
{
// If the media type is not set, return false
if (type == null || type == MediaType.NONE)
return false;
#if NET48
switch (program)
{
case InternalProgram.Redumper:
switch (type)
{
// Formats considered at least partially dumpable by Redumper
case MediaType.BluRay:
case MediaType.CDROM:
case MediaType.DVD:
case MediaType.GDROM:
case MediaType.HDDVD:
return 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;
// All other formats considered unsupported
default:
return false;
}
// All other InternalPrograms are not used for dumping
default:
return false;
}
#else
return (program) switch
{
// 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,
// 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,
// 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,
// Default
_ => false,
};
#endif
}
#endregion
@@ -131,16 +257,23 @@ 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
{
// Get current assembly version
var assemblyVersion = Assembly.GetEntryAssembly().GetName().Version;
string version = $"{assemblyVersion.Major}.{assemblyVersion.Minor}" + (assemblyVersion.Build != 0 ? $".{assemblyVersion.Build}" : string.Empty);
var assemblyVersion = Assembly.GetEntryAssembly()?.GetName()?.Version;
if (assemblyVersion == null)
return (false, "Assembly version could not be determined", null);
string version = $"{assemblyVersion.Major}.{assemblyVersion.Minor}.{assemblyVersion.Build}";
// Get the latest tag from GitHub
(string tag, string url) = GetRemoteVersionAndUrl();
var (tag, url) = GetRemoteVersionAndUrl();
bool different = version != tag;
string message = $"Local version: {version}"
@@ -160,18 +293,38 @@ 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
{
var assemblyVersion = Attribute.GetCustomAttribute(Assembly.GetEntryAssembly(), typeof(AssemblyInformationalVersionAttribute)) as AssemblyInformationalVersionAttribute;
return assemblyVersion.InformationalVersion;
try
{
var assembly = Assembly.GetEntryAssembly();
if (assembly == null)
return null;
var assemblyVersion = Attribute.GetCustomAttribute(assembly, typeof(AssemblyInformationalVersionAttribute)) as AssemblyInformationalVersionAttribute;
return assemblyVersion?.InformationalVersion;
}
catch (Exception ex)
{
return ex.ToString();
}
}
/// <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
{
using (WebClient wc = new WebClient())
#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";
@@ -184,6 +337,27 @@ namespace MPF.Core.Utilities
return (latestTag, releaseUrl);
}
#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);
}
#endif
}
#endregion

View File

@@ -1,238 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
/// <remarks>
/// Information sourced from http://web.archive.org/web/20070221154246/http://www.goldenhawk.com/download/cdrwin.pdf
/// </remarks>
namespace MPF.CueSheets
{
/// <summary>
/// The audio or data files filetype
/// </summary>
public enum CueFileType
{
/// <summary>
/// Intel binary file (least significant byte first). Use for data files.
/// </summary>
BINARY,
/// <summary>
/// Motorola binary file (most significant byte first). Use for data files.
/// </summary>
MOTOROLA,
/// <summary>
/// Audio AIFF file (44.1KHz 16-bit stereo)
/// </summary>
AIFF,
/// <summary>
/// Audio WAVE file (44.1KHz 16-bit stereo)
/// </summary>
WAVE,
/// <summary>
/// Audio MP3 file (44.1KHz 16-bit stereo)
/// </summary>
MP3,
}
/// <summary>
/// Represents a single FILE in a cuesheet
/// </summary>
public class CueFile
{
/// <summary>
/// filename
/// </summary>
public string FileName { get; set; }
/// <summary>
/// filetype
/// </summary>
public CueFileType FileType { get; set; }
/// <summary>
/// List of TRACK in FILE
/// </summary>
public List<CueTrack> Tracks { get; set; }
/// <summary>
/// Create an empty FILE
/// </summary>
public CueFile()
{
}
/// <summary>
/// Fill a FILE from an array of lines
/// </summary>
/// <param name="fileName">File name to set</param>
/// <param name="fileType">File type to set</param>
/// <param name="cueLines">Lines array to pull from</param>
/// <param name="i">Reference to index in array</param>
/// <param name="throwOnError">True if errors throw an exception, false otherwise</param>
public CueFile(string fileName, string fileType, string[] cueLines, ref int i, bool throwOnError = false)
{
if (cueLines == null)
{
if (throwOnError)
throw new ArgumentNullException(nameof(cueLines));
return;
}
else if (i < 0 || i > cueLines.Length)
{
if (throwOnError)
throw new IndexOutOfRangeException();
return;
}
// Set the current fields
this.FileName = fileName.Trim('"');
this.FileType = GetFileType(fileType);
// Increment to start
i++;
for (; i < cueLines.Length; i++)
{
string line = cueLines[i].Trim();
string[] splitLine = line.Split(' ');
// If we have an empty line, we skip
if (string.IsNullOrWhiteSpace(line))
continue;
switch (splitLine[0])
{
// Read comments
case "REM":
// We ignore all comments for now
break;
// Read track information
case "TRACK":
if (splitLine.Length < 3)
{
if (throwOnError)
throw new FormatException($"TRACK line malformed: {line}");
continue;
}
if (this.Tracks == null)
this.Tracks = new List<CueTrack>();
var track = new CueTrack(splitLine[1], splitLine[2], cueLines, ref i);
if (track == default)
{
if (throwOnError)
throw new FormatException($"TRACK line malformed: {line}");
continue;
}
this.Tracks.Add(track);
break;
// Default means return
default:
i--;
return;
}
}
}
/// <summary>
/// Write the FILE out to a stream
/// </summary>
/// <param name="sw">StreamWriter to write to</param>
/// <param name="throwOnError">True if errors throw an exception, false otherwise</param>
public void Write(StreamWriter sw, bool throwOnError = false)
{
// If we don't have any tracks, it's invalid
if (this.Tracks == null)
{
if (throwOnError)
throw new ArgumentNullException(nameof(this.Tracks));
return;
}
else if (this.Tracks.Count == 0)
{
if (throwOnError)
throw new ArgumentException("No tracks provided to write");
return;
}
sw.WriteLine($"FILE \"{this.FileName}\" {FromFileType(this.FileType)}");
foreach (var track in Tracks)
{
track.Write(sw);
}
}
/// <summary>
/// Get the file type from a given string
/// </summary>
/// <param name="fileType">String to get value from</param>
/// <returns>CueFileType, if possible</returns>
private CueFileType GetFileType(string fileType)
{
switch (fileType.ToLowerInvariant())
{
case "binary":
return CueFileType.BINARY;
case "motorola":
return CueFileType.MOTOROLA;
case "aiff":
return CueFileType.AIFF;
case "wave":
return CueFileType.WAVE;
case "mp3":
return CueFileType.MP3;
default:
return CueFileType.BINARY;
}
}
/// <summary>
/// Get the string from a given file type
/// </summary>
/// <param name="fileType">CueFileType to get value from</param>
/// <returns>String, if possible (default BINARY)</returns>
private string FromFileType(CueFileType fileType)
{
switch (fileType)
{
case CueFileType.BINARY:
return "BINARY";
case CueFileType.MOTOROLA:
return "MOTOROLA";
case CueFileType.AIFF:
return "AIFF";
case CueFileType.WAVE:
return "WAVE";
case CueFileType.MP3:
return "MP3";
default:
return string.Empty;
}
}
}
}

View File

@@ -1,163 +0,0 @@
using System;
using System.IO;
using System.Linq;
/// <remarks>
/// Information sourced from http://web.archive.org/web/20070221154246/http://www.goldenhawk.com/download/cdrwin.pdf
/// </remarks>
namespace MPF.CueSheets
{
/// <summary>
/// Represents a single INDEX in a TRACK
/// </summary>
public class CueIndex
{
/// <summary>
/// INDEX number, between 0 and 99
/// </summary>
public int Index { get; set; }
/// <summary>
/// Starting time of INDEX in minutes
/// </summary>
public int Minutes { get; set; }
/// <summary>
/// Starting time of INDEX in seconds
/// </summary>
/// <remarks>There are 60 seconds in a minute</remarks>
public int Seconds { get; set; }
/// <summary>
/// Starting time of INDEX in frames.
/// </summary>
/// <remarks>There are 75 frames per second</remarks>
public int Frames { get; set; }
/// <summary>
/// Create an empty INDEX
/// </summary>
public CueIndex()
{
}
/// <summary>
/// Fill a INDEX from an array of lines
/// </summary>
/// <param name="index">Index to set</param>
/// <param name="startTime">Start time to set</param>
/// <param name="throwOnError">True if errors throw an exception, false otherwise</param>
public CueIndex(string index, string startTime, bool throwOnError = false)
{
// Set the current fields
if (!int.TryParse(index, out int parsedIndex))
{
if (throwOnError)
throw new ArgumentException($"Index was not a number: {index}");
return;
}
else if (parsedIndex < 0 || parsedIndex > 99)
{
if (throwOnError)
throw new IndexOutOfRangeException($"Index must be between 0 and 99: {parsedIndex}");
return;
}
// Ignore empty lines
if (string.IsNullOrWhiteSpace(startTime))
{
if (throwOnError)
throw new ArgumentException("Start time was null or whitespace");
return;
}
// Ignore lines that don't contain the correct information
if (startTime.Length != 8 || startTime.Count(c => c == ':') != 2)
{
if (throwOnError)
throw new FormatException($"Start time was not in a recognized format: {startTime}");
return;
}
// Split the line
string[] splitTime = startTime.Split(':');
if (splitTime.Length != 3)
{
if (throwOnError)
throw new FormatException($"Start time was not in a recognized format: {startTime}");
return;
}
// Parse the lengths
int[] lengthSegments = new int[3];
// Minutes
if (!int.TryParse(splitTime[0], out lengthSegments[0]))
{
if (throwOnError)
throw new FormatException($"Minutes segment was not a number: {splitTime[0]}");
return;
}
else if (lengthSegments[0] < 0)
{
if (throwOnError)
throw new IndexOutOfRangeException($"Minutes segment must be 0 or greater: {lengthSegments[0]}");
return;
}
// Seconds
if (!int.TryParse(splitTime[1], out lengthSegments[1]))
{
if (throwOnError)
throw new FormatException($"Seconds segment was not a number: {splitTime[1]}");
return;
}
else if (lengthSegments[1] < 0 || lengthSegments[1] > 60)
{
if (throwOnError)
throw new IndexOutOfRangeException($"Seconds segment must be between 0 and 60: {lengthSegments[1]}");
return;
}
// Frames
if (!int.TryParse(splitTime[2], out lengthSegments[2]))
{
if (throwOnError)
throw new FormatException($"Frames segment was not a number: {splitTime[2]}");
return;
}
else if (lengthSegments[2] < 0 || lengthSegments[2] > 75)
{
if (throwOnError)
throw new IndexOutOfRangeException($"Frames segment must be between 0 and 75: {lengthSegments[2]}");
return;
}
// Set the values
this.Index = parsedIndex;
this.Minutes = lengthSegments[0];
this.Seconds = lengthSegments[1];
this.Frames = lengthSegments[2];
}
/// <summary>
/// Write the INDEX out to a stream
/// </summary>
/// <param name="sw">StreamWriter to write to</param>
public void Write(StreamWriter sw)
{
sw.WriteLine($" INDEX {this.Index:D2} {this.Minutes:D2}:{this.Seconds:D2}:{this.Frames:D2}");
}
}
}

View File

@@ -1,250 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
/// <remarks>
/// Information sourced from http://web.archive.org/web/20070221154246/http://www.goldenhawk.com/download/cdrwin.pdf
/// </remarks>
namespace MPF.CueSheets
{
/// <summary>
/// Represents a single cuesheet
/// </summary>
public class CueSheet
{
/// <summary>
/// CATALOG
/// </summary>
public string Catalog { get; set; }
/// <summary>
/// CDTEXTFILE
/// </summary>
public string CdTextFile { get; set; }
/// <summary>
/// PERFORMER
/// </summary>
public string Performer { get; set; }
/// <summary>
/// SONGWRITER
/// </summary>
public string Songwriter { get; set; }
/// <summary>
/// TITLE
/// </summary>
public string Title { get; set; }
/// <summary>
/// List of FILE in cuesheet
/// </summary>
public List<CueFile> Files { get; set; }
/// <summary>
/// Create an empty cuesheet
/// </summary>
public CueSheet()
{
}
/// <summary>
/// Create a cuesheet from a file, if possible
/// </summary>
/// <param name="filename"></param>
/// <param name="throwOnError">True if errors throw an exception, false otherwise</param>
public CueSheet(string filename, bool throwOnError = false)
{
// Check that the file exists
if (!File.Exists(filename))
return;
// Check the extension
string ext = Path.GetExtension(filename).TrimStart('.');
if (!string.Equals(ext, "cue", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(ext, "txt", StringComparison.OrdinalIgnoreCase))
{
return;
}
// Open the file and begin reading
string[] cueLines = File.ReadAllLines(filename);
for (int i = 0; i < cueLines.Length; i++)
{
string line = cueLines[i].Trim();
// http://stackoverflow.com/questions/554013/regular-expression-to-split-on-spaces-unless-in-quotes
string[] splitLine = Regex
.Matches(line, @"[^\s""]+|""[^""]*""")
.Cast<Match>()
.Select(m => m.Groups[0].Value)
.ToArray();
// If we have an empty line, we skip
if (string.IsNullOrWhiteSpace(line))
continue;
switch (splitLine[0])
{
// Read comments
case "REM":
// We ignore all comments for now
break;
// Read MCN
case "CATALOG":
if (splitLine.Length < 2)
{
if (throwOnError)
throw new FormatException($"CATALOG line malformed: {line}");
continue;
}
this.Catalog = splitLine[1];
break;
// Read external CD-Text file path
case "CDTEXTFILE":
if (splitLine.Length < 2)
{
if (throwOnError)
throw new FormatException($"CDTEXTFILE line malformed: {line}");
continue;
}
this.CdTextFile = splitLine[1];
break;
// Read CD-Text enhanced performer
case "PERFORMER":
if (splitLine.Length < 2)
{
if (throwOnError)
throw new FormatException($"PERFORMER line malformed: {line}");
continue;
}
this.Performer = splitLine[1];
break;
// Read CD-Text enhanced songwriter
case "SONGWRITER":
if (splitLine.Length < 2)
{
if (throwOnError)
throw new FormatException($"SONGWRITER line malformed: {line}");
continue;
}
this.Songwriter = splitLine[1];
break;
// Read CD-Text enhanced title
case "TITLE":
if (splitLine.Length < 2)
{
if (throwOnError)
throw new FormatException($"TITLE line malformed: {line}");
continue;
}
this.Title = splitLine[1];
break;
// Read file information
case "FILE":
if (splitLine.Length < 3)
{
if (throwOnError)
throw new FormatException($"FILE line malformed: {line}");
continue;
}
if (this.Files == null)
this.Files = new List<CueFile>();
var file = new CueFile(splitLine[1], splitLine[2], cueLines, ref i);
if (file == default)
{
if (throwOnError)
throw new FormatException($"FILE line malformed: {line}");
continue;
}
this.Files.Add(file);
break;
}
}
}
/// <summary>
/// Write the cuesheet out to a file
/// </summary>
/// <param name="filename">File path to write to</param>
public void Write(string filename)
{
using (var fs = File.Open(filename, FileMode.Create, FileAccess.Write, FileShare.ReadWrite))
{
Write(fs);
}
}
/// <summary>
/// Write the cuesheet out to a stream
/// </summary>
/// <param name="stream">Stream to write to</param>
/// <param name="throwOnError">True if errors throw an exception, false otherwise</param>
public void Write(Stream stream, bool throwOnError = false)
{
// If we don't have any files, it's invalid
if (this.Files == null)
{
if (throwOnError)
throw new ArgumentNullException(nameof(this.Files));
return;
}
else if (this.Files.Count == 0)
{
if (throwOnError)
throw new ArgumentException("No files provided to write");
return;
}
using (var sw = new StreamWriter(stream, Encoding.ASCII, 1024, true))
{
if (!string.IsNullOrEmpty(this.Catalog))
sw.WriteLine($"CATALOG {this.Catalog}");
if (!string.IsNullOrEmpty(this.CdTextFile))
sw.WriteLine($"CDTEXTFILE {this.CdTextFile}");
if (!string.IsNullOrEmpty(this.Performer))
sw.WriteLine($"PERFORMER {this.Performer}");
if (!string.IsNullOrEmpty(this.Songwriter))
sw.WriteLine($"SONGWRITER {this.Songwriter}");
if (!string.IsNullOrEmpty(this.Title))
sw.WriteLine($"TITLE {this.Title}");
foreach (var file in Files)
{
file.Write(sw);
}
}
}
}
}

View File

@@ -1,554 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
/// <remarks>
/// Information sourced from http://web.archive.org/web/20070221154246/http://www.goldenhawk.com/download/cdrwin.pdf
/// </remarks>
namespace MPF.CueSheets
{
/// <summary>
/// Track datatype
/// </summary>
public enum CueTrackDataType
{
/// <summary>
/// AUDIO, Audio/Music (2352)
/// </summary>
AUDIO,
/// <summary>
/// CDG, Karaoke CD+G (2448)
/// </summary>
CDG,
/// <summary>
/// MODE1/2048, CD-ROM Mode1 Data (cooked)
/// </summary>
MODE1_2048,
/// <summary>
/// MODE1/2352 CD-ROM Mode1 Data (raw)
/// </summary>
MODE1_2352,
/// <summary>
/// MODE2/2336, CD-ROM XA Mode2 Data
/// </summary>
MODE2_2336,
/// <summary>
/// MODE2/2352, CD-ROM XA Mode2 Data
/// </summary>
MODE2_2352,
/// <summary>
/// CDI/2336, CD-I Mode2 Data
/// </summary>
CDI_2336,
/// <summary>
/// CDI/2352, CD-I Mode2 Data
/// </summary>
CDI_2352,
}
/// <summary>
/// Special subcode flags within a track
/// </summary>
[Flags]
public enum CueTrackFlag
{
/// <summary>
/// DCP, Digital copy permitted
/// </summary>
DCP = 1 << 0,
/// <summary>
/// 4CH, Four channel audio
/// </summary>
FourCH = 1 << 1,
/// <summary>
/// PRE, Pre-emphasis enabled (audio tracks only)
/// </summary>
PRE = 1 << 2,
/// <summary>
/// SCMS, Serial Copy Management System (not supported by all recorders)
/// </summary>
SCMS = 1 << 3,
/// <summary>
/// DATA, set for data files. This flag is set automatically based on the tracks filetype
/// </summary>
DATA = 1 << 4,
}
/// <summary>
/// Represents a single TRACK in a FILE
/// </summary>
public class CueTrack
{
/// <summary>
/// Track number. The range is 1 to 99.
/// </summary>
public int Number { get; set; }
/// <summary>
/// Track datatype
/// </summary>
public CueTrackDataType DataType { get; set; }
/// <summary>
/// FLAGS
/// </summary>
public CueTrackFlag Flags { get; set; }
/// <summary>
/// ISRC
/// </summary>
/// <remarks>12 characters in length</remarks>
public string ISRC { get; set; }
/// <summary>
/// PERFORMER
/// </summary>
public string Performer { get; set; }
/// <summary>
/// SONGWRITER
/// </summary>
public string Songwriter { get; set; }
/// <summary>
/// TITLE
/// </summary>
public string Title { get; set; }
/// <summary>
/// PREGAP
/// </summary>
public PreGap PreGap { get; set; }
/// <summary>
/// List of INDEX in TRACK
/// </summary>
/// <remarks>Must start with 0 or 1 and then sequential</remarks>
public List<CueIndex> Indices { get; set; }
/// <summary>
/// POSTGAP
/// </summary>
public PostGap PostGap { get; set; }
/// <summary>
/// Create an empty TRACK
/// </summary>
public CueTrack()
{
}
/// <summary>
/// Fill a TRACK from an array of lines
/// </summary>
/// <param name="number">Number to set</param>
/// <param name="dataType">Data type to set</param>
/// <param name="cueLines">Lines array to pull from</param>
/// <param name="i">Reference to index in array</param>
/// <param name="throwOnError">True if errors throw an exception, false otherwise</param>
public CueTrack(string number, string dataType, string[] cueLines, ref int i, bool throwOnError = false)
{
if (cueLines == null)
{
if (throwOnError)
throw new ArgumentNullException(nameof(cueLines));
return;
}
else if (i < 0 || i > cueLines.Length)
{
if (throwOnError)
throw new IndexOutOfRangeException();
return;
}
// Set the current fields
if (!int.TryParse(number, out int parsedNumber))
{
if (throwOnError)
throw new ArgumentException($"Number was not a number: {number}");
return;
}
else if (parsedNumber < 1 || parsedNumber > 99)
{
if (throwOnError)
throw new IndexOutOfRangeException($"Index must be between 1 and 99: {parsedNumber}");
return;
}
this.Number = parsedNumber;
this.DataType = GetDataType(dataType);
// Increment to start
i++;
for (; i < cueLines.Length; i++)
{
string line = cueLines[i].Trim();
string[] splitLine = line.Split(' ');
// If we have an empty line, we skip
if (string.IsNullOrWhiteSpace(line))
continue;
switch (splitLine[0])
{
// Read comments
case "REM":
// We ignore all comments for now
break;
// Read flag information
case "FLAGS":
if (splitLine.Length < 2)
{
if (throwOnError)
throw new FormatException($"FLAGS line malformed: {line}");
continue;
}
this.Flags = GetFlags(splitLine);
break;
// Read International Standard Recording Code
case "ISRC":
if (splitLine.Length < 2)
{
if (throwOnError)
throw new FormatException($"ISRC line malformed: {line}");
continue;
}
this.ISRC = splitLine[1];
break;
// Read CD-Text enhanced performer
case "PERFORMER":
if (splitLine.Length < 2)
{
if (throwOnError)
throw new FormatException($"PERFORMER line malformed: {line}");
continue;
}
this.Performer = splitLine[1];
break;
// Read CD-Text enhanced songwriter
case "SONGWRITER":
if (splitLine.Length < 2)
{
if (throwOnError)
throw new FormatException($"SONGWRITER line malformed: {line}");
continue;
}
this.Songwriter = splitLine[1];
break;
// Read CD-Text enhanced title
case "TITLE":
if (splitLine.Length < 2)
{
if (throwOnError)
throw new FormatException($"TITLE line malformed: {line}");
continue;
}
this.Title = splitLine[1];
break;
// Read pregap information
case "PREGAP":
if (splitLine.Length < 2)
{
if (throwOnError)
throw new FormatException($"PREGAP line malformed: {line}");
continue;
}
var pregap = new PreGap(splitLine[1]);
if (pregap == default)
{
if (throwOnError)
throw new FormatException($"PREGAP line malformed: {line}");
continue;
}
this.PreGap = pregap;
break;
// Read index information
case "INDEX":
if (splitLine.Length < 3)
{
if (throwOnError)
throw new FormatException($"INDEX line malformed: {line}");
continue;
}
if (this.Indices == null)
this.Indices = new List<CueIndex>();
var index = new CueIndex(splitLine[1], splitLine[2]);
if (index == default)
{
if (throwOnError)
throw new FormatException($"INDEX line malformed: {line}");
continue;
}
this.Indices.Add(index);
break;
// Read postgap information
case "POSTGAP":
if (splitLine.Length < 2)
{
if (throwOnError)
throw new FormatException($"POSTGAP line malformed: {line}");
continue;
}
var postgap = new PostGap(splitLine[1]);
if (postgap == default)
{
if (throwOnError)
throw new FormatException($"POSTGAP line malformed: {line}");
continue;
}
this.PostGap = postgap;
break;
// Default means return
default:
i--;
return;
}
}
}
/// <summary>
/// Write the TRACK out to a stream
/// </summary>
/// <param name="sw">StreamWriter to write to</param
/// <param name="throwOnError">True if errors throw an exception, false otherwise</param>
public void Write(StreamWriter sw, bool throwOnError = false)
{
// If we don't have any indices, it's invalid
if (this.Indices == null)
{
if (throwOnError)
throw new ArgumentNullException(nameof(this.Indices));
return;
}
else if (this.Indices.Count == 0)
{
if (throwOnError)
throw new ArgumentException("No indices provided to write");
return;
}
sw.WriteLine($" TRACK {this.Number:D2} {FromDataType(this.DataType)}");
if (this.Flags != 0)
sw.WriteLine($" FLAGS {FromFlags(this.Flags)}");
if (!string.IsNullOrEmpty(this.ISRC))
sw.WriteLine($"ISRC {this.ISRC}");
if (!string.IsNullOrEmpty(this.Performer))
sw.WriteLine($"PERFORMER {this.Performer}");
if (!string.IsNullOrEmpty(this.Songwriter))
sw.WriteLine($"SONGWRITER {this.Songwriter}");
if (!string.IsNullOrEmpty(this.Title))
sw.WriteLine($"TITLE {this.Title}");
if (this.PreGap != null)
this.PreGap.Write(sw);
foreach (var index in Indices)
{
index.Write(sw);
}
if (this.PostGap != null)
this.PostGap.Write(sw);
}
/// <summary>
/// Get the data type from a given string
/// </summary>
/// <param name="dataType">String to get value from</param>
/// <returns>CueTrackDataType, if possible (default AUDIO)</returns>
private CueTrackDataType GetDataType(string dataType)
{
switch (dataType.ToLowerInvariant())
{
case "audio":
return CueTrackDataType.AUDIO;
case "cdg":
return CueTrackDataType.CDG;
case "mode1/2048":
return CueTrackDataType.MODE1_2048;
case "mode1/2352":
return CueTrackDataType.MODE1_2352;
case "mode2/2336":
return CueTrackDataType.MODE2_2336;
case "mode2/2352":
return CueTrackDataType.MODE2_2352;
case "cdi/2336":
return CueTrackDataType.CDI_2336;
case "cdi/2352":
return CueTrackDataType.CDI_2352;
default:
return CueTrackDataType.AUDIO;
}
}
/// <summary>
/// Get the string from a given data type
/// </summary>
/// <param name="dataType">CueTrackDataType to get value from</param>
/// <returns>string, if possible</returns>
private string FromDataType(CueTrackDataType dataType)
{
switch (dataType)
{
case CueTrackDataType.AUDIO:
return "AUDIO";
case CueTrackDataType.CDG:
return "CDG";
case CueTrackDataType.MODE1_2048:
return "MODE1/2048";
case CueTrackDataType.MODE1_2352:
return "MODE1/2352";
case CueTrackDataType.MODE2_2336:
return "MODE2/2336";
case CueTrackDataType.MODE2_2352:
return "MODE2/2352";
case CueTrackDataType.CDI_2336:
return "CDI/2336";
case CueTrackDataType.CDI_2352:
return "CDI/2352";
default:
return string.Empty;
}
}
/// <summary>
/// Get the flag value for an array of strings
/// </summary>
/// <param name="flagStrings">Possible flags as strings</param>
/// <returns>CueTrackFlag value representing the strings, if possible</returns>
private CueTrackFlag GetFlags(string[] flagStrings)
{
CueTrackFlag flag = 0;
foreach (string flagString in flagStrings)
{
switch (flagString.ToLowerInvariant())
{
case "flags":
// No-op since this is the start of the line
break;
case "dcp":
flag |= CueTrackFlag.DCP;
break;
case "4ch":
flag |= CueTrackFlag.FourCH;
break;
case "pre":
flag |= CueTrackFlag.PRE;
break;
case "scms":
flag |= CueTrackFlag.SCMS;
break;
case "data":
flag |= CueTrackFlag.DATA;
break;
}
}
return flag;
}
/// <summary>
/// Get the string value for a set of track flags
/// </summary>
/// <param name="flags">CueTrackFlag to get value from</param>
/// <returns>String value representing the CueTrackFlag, if possible</returns>
private string FromFlags(CueTrackFlag flags)
{
string outputFlagString = string.Empty;
if (flags.HasFlag(CueTrackFlag.DCP))
outputFlagString += "DCP ";
if (flags.HasFlag(CueTrackFlag.FourCH))
outputFlagString += "4CH ";
if (flags.HasFlag(CueTrackFlag.PRE))
outputFlagString += "PRE ";
if (flags.HasFlag(CueTrackFlag.SCMS))
outputFlagString += "SCMS ";
if (flags.HasFlag(CueTrackFlag.DATA))
outputFlagString += "DATA ";
return outputFlagString.Trim();
}
}
}

View File

@@ -1,30 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net48;netcoreapp3.1;net5.0</TargetFrameworks>
<PlatformTarget>x86</PlatformTarget>
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
<Copyright>Copyright (c)2019-2021</Copyright>
<RepositoryUrl>https://github.com/SabreTools/MPF</RepositoryUrl>
<Version>2.2</Version>
<AssemblyVersion>$(Version)</AssemblyVersion>
<FileVersion>$(Version)</FileVersion>
<IncludeSource>true</IncludeSource>
<IncludeSymbols>true</IncludeSymbols>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup>
<NrtRevisionFormat>$(Version)-{chash:8}</NrtRevisionFormat>
<NrtResolveSimpleAttributes>true</NrtResolveSimpleAttributes>
<NrtShowRevision>false</NrtShowRevision>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Unclassified.NetRevisionTask" Version="0.4.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@@ -1,139 +0,0 @@
using System;
using System.IO;
using System.Linq;
/// <remarks>
/// Information sourced from http://web.archive.org/web/20070221154246/http://www.goldenhawk.com/download/cdrwin.pdf
/// </remarks>
namespace MPF.CueSheets
{
/// <summary>
/// Represents POSTGAP information of a track
/// </summary>
public class PostGap
{
/// <summary>
/// Length of POSTGAP in minutes
/// </summary>
public int Minutes { get; set; }
/// <summary>
/// Length of POSTGAP in seconds
/// </summary>
/// <remarks>There are 60 seconds in a minute</remarks>
public int Seconds { get; set; }
/// <summary>
/// Length of POSTGAP in frames.
/// </summary>
/// <remarks>There are 75 frames per second</remarks>
public int Frames { get; set; }
/// Create an empty POSTGAP
/// </summary>
public PostGap()
{
}
/// <summary>
/// Create a POSTGAP from a mm:ss:ff length
/// </summary>
/// <param name="length">String to get length information from</param>
/// <param name="throwOnError">True if errors throw an exception, false otherwise</param>
public PostGap(string length, bool throwOnError = false)
{
// Ignore empty lines
if (string.IsNullOrWhiteSpace(length))
{
if (throwOnError)
throw new ArgumentException("Length was null or whitespace");
return;
}
// Ignore lines that don't contain the correct information
if (length.Length != 8 || length.Count(c => c == ':') != 2)
{
if (throwOnError)
throw new FormatException($"Length was not in a recognized format: {length}");
return;
}
// Split the line
string[] splitLength = length.Split(':');
if (splitLength.Length != 3)
{
if (throwOnError)
throw new FormatException($"Length was not in a recognized format: {length}");
return;
}
// Parse the lengths
int[] lengthSegments = new int[3];
// Minutes
if (!int.TryParse(splitLength[0], out lengthSegments[0]))
{
if (throwOnError)
throw new FormatException($"Minutes segment was not a number: {splitLength[0]}");
return;
}
else if (lengthSegments[0] < 0)
{
if (throwOnError)
throw new IndexOutOfRangeException($"Minutes segment must be 0 or greater: {lengthSegments[0]}");
return;
}
// Seconds
if (!int.TryParse(splitLength[1], out lengthSegments[1]))
{
if (throwOnError)
throw new FormatException($"Seconds segment was not a number: {splitLength[1]}");
return;
}
else if (lengthSegments[1] < 0 || lengthSegments[1] > 60)
{
if (throwOnError)
throw new IndexOutOfRangeException($"Seconds segment must be between 0 and 60: {lengthSegments[1]}");
return;
}
// Frames
if (!int.TryParse(splitLength[2], out lengthSegments[2]))
{
if (throwOnError)
throw new FormatException($"Frames segment was not a number: {splitLength[2]}");
return;
}
else if (lengthSegments[2] < 0 || lengthSegments[2] > 75)
{
if (throwOnError)
throw new IndexOutOfRangeException($"Frames segment must be between 0 and 75: {lengthSegments[2]}");
return;
}
// Set the values
this.Minutes = lengthSegments[0];
this.Seconds = lengthSegments[1];
this.Frames = lengthSegments[2];
}
/// <summary>
/// Write the POSTGAP out to a stream
/// </summary>
/// <param name="sw">StreamWriter to write to</param>
public void Write(StreamWriter sw)
{
sw.WriteLine($" POSTGAP {this.Minutes:D2}:{this.Seconds:D2}:{this.Frames:D2}");
}
}
}

View File

@@ -1,140 +0,0 @@
using System;
using System.IO;
using System.Linq;
/// <remarks>
/// Information sourced from http://web.archive.org/web/20070221154246/http://www.goldenhawk.com/download/cdrwin.pdf
/// </remarks>
namespace MPF.CueSheets
{
/// <summary>
/// Represents PREGAP information of a track
/// </summary>
public class PreGap
{
/// <summary>
/// Length of PREGAP in minutes
/// </summary>
public int Minutes { get; set; }
/// <summary>
/// Length of PREGAP in seconds
/// </summary>
/// <remarks>There are 60 seconds in a minute</remarks>
public int Seconds { get; set; }
/// <summary>
/// Length of PREGAP in frames.
/// </summary>
/// <remarks>There are 75 frames per second</remarks>
public int Frames { get; set; }
/// <summary>
/// Create an empty PREGAP
/// </summary>
public PreGap()
{
}
/// <summary>
/// Create a PREGAP from a mm:ss:ff length
/// </summary>
/// <param name="length">String to get length information from</param>
/// <param name="throwOnError">True if errors throw an exception, false otherwise</param>
public PreGap(string length, bool throwOnError = false)
{
// Ignore empty lines
if (string.IsNullOrWhiteSpace(length))
{
if (throwOnError)
throw new ArgumentException("Length was null or whitespace");
return;
}
// Ignore lines that don't contain the correct information
if (length.Length != 8 || length.Count(c => c == ':') != 2)
{
if (throwOnError)
throw new FormatException($"Length was not in a recognized format: {length}");
return;
}
// Split the line
string[] splitLength = length.Split(':');
if (splitLength.Length != 3)
{
if (throwOnError)
throw new FormatException($"Length was not in a recognized format: {length}");
return;
}
// Parse the lengths
int[] lengthSegments = new int[3];
// Minutes
if (!int.TryParse(splitLength[0], out lengthSegments[0]))
{
if (throwOnError)
throw new FormatException($"Minutes segment was not a number: {splitLength[0]}");
return;
}
else if (lengthSegments[0] < 0)
{
if (throwOnError)
throw new IndexOutOfRangeException($"Minutes segment must be 0 or greater: {lengthSegments[0]}");
return;
}
// Seconds
if (!int.TryParse(splitLength[1], out lengthSegments[1]))
{
if (throwOnError)
throw new FormatException($"Seconds segment was not a number: {splitLength[1]}");
return;
}
else if (lengthSegments[1] < 0 || lengthSegments[1] > 60)
{
if (throwOnError)
throw new IndexOutOfRangeException($"Seconds segment must be between 0 and 60: {lengthSegments[1]}");
return;
}
// Frames
if (!int.TryParse(splitLength[2], out lengthSegments[2]))
{
if (throwOnError)
throw new FormatException($"Frames segment was not a number: {splitLength[2]}");
return;
}
else if (lengthSegments[2] < 0 || lengthSegments[2] > 75)
{
if (throwOnError)
throw new IndexOutOfRangeException($"Frames segment must be between 0 and 75: {lengthSegments[2]}");
return;
}
// Set the values
this.Minutes = lengthSegments[0];
this.Seconds = lengthSegments[1];
this.Frames = lengthSegments[2];
}
/// <summary>
/// Write the PREGAP out to a stream
/// </summary>
/// <param name="sw">StreamWriter to write to</param>
public void Write(StreamWriter sw)
{
sw.WriteLine($" PREGAP {this.Minutes:D2}:{this.Seconds:D2}:{this.Frames:D2}");
}
}
}

View File

@@ -1,4 +0,0 @@
using System.Runtime.CompilerServices;
// Anything marked as internal can be used by the test methods
[assembly: InternalsVisibleTo("MPF.Test")]

File diff suppressed because it is too large Load Diff

View File

@@ -1,50 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net48;netcoreapp3.1;net5.0</TargetFrameworks>
<PlatformTarget>x86</PlatformTarget>
<AssemblyName>MPF.Library</AssemblyName>
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
<Copyright>Copyright (c)2019-2021</Copyright>
<RepositoryUrl>https://github.com/SabreTools/MPF</RepositoryUrl>
<Version>2.2</Version>
<AssemblyVersion>$(Version)</AssemblyVersion>
<FileVersion>$(Version)</FileVersion>
<IncludeSource>true</IncludeSource>
<IncludeSymbols>true</IncludeSymbols>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup>
<NrtRevisionFormat>$(Version)-{chash:8}</NrtRevisionFormat>
<NrtResolveSimpleAttributes>true</NrtResolveSimpleAttributes>
<NrtShowRevision>false</NrtShowRevision>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='net48'">
<DefineConstants>NET_FRAMEWORK</DefineConstants>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MPF.Core\MPF.Core.csproj" />
<ProjectReference Include="..\MPF.Modules\MPF.Modules.csproj" />
<ProjectReference Include="..\RedumpLib\RedumpLib.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BurnOutSharp" PrivateAssets="build; analyzers" ExcludeAssets="contentFiles" Version="1.8.0" GeneratePathProperty="true">
<IncludeAssets>runtime; compile; build; native; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="System.IO.Compression" Version="4.3.0" />
<PackageReference Include="Unclassified.NetRevisionTask" Version="0.4.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Content Include="$(PkgBurnOutSharp)\content\**" PackagePath="contentFiles\any\any;content" CopyToOutputDirectory="Always" PackageCopyToOutput="true" />
</ItemGroup>
</Project>

View File

@@ -1,4 +0,0 @@
using System.Runtime.CompilerServices;
// Anything marked as internal can be used by the test methods
[assembly: InternalsVisibleTo("MPF.Test")]

View File

@@ -1,32 +0,0 @@
namespace MPF.Modules.DD
{
/// <summary>
/// Top-level commands for DD
/// </summary>
public static class CommandStrings
{
public const string NONE = "";
public const string List = "--list";
}
/// <summary>
/// Dumping flags for DD
/// </summary>
public static class FlagStrings
{
// Boolean flags
public const string Progress = "--progress";
public const string Size = "--size";
// Int64 flags
public const string BlockSize = "bs";
public const string Count = "count";
public const string Seek = "seek";
public const string Skip = "skip";
// String flags
public const string Filter = "--filter";
public const string InputFile = "if";
public const string OutputFile = "of";
}
}

View File

@@ -1,22 +0,0 @@
using RedumpLib.Data;
namespace MPF.Modules.DD
{
public static class Converters
{
#region Cross-enumeration conversions
/// <summary>
/// Get the default extension for a given disc type
/// </summary>
/// <param name="type">MediaType value to check</param>
/// <returns>Valid extension (with leading '.'), null on error</returns>
public static string Extension(MediaType? type)
{
// DD has a single, unified output format by default
return ".bin";
}
#endregion
}
}

View File

@@ -1,401 +0,0 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using MPF.Core.Data;
using RedumpLib.Data;
namespace MPF.Modules.DD
{
/// <summary>
/// Represents a generic set of DD parameters
/// </summary>
public class Parameters : BaseParameters
{
#region Generic Dumping Information
/// <inheritdoc/>
public override string InputPath => InputFileValue?.TrimStart('\\', '?');
/// <inheritdoc/>
public override string OutputPath => OutputFileValue;
/// <inheritdoc/>
/// <inheritdoc/>
public override int? Speed
{
get { return 1; }
set { }
}
#endregion
#region Metadata
/// <inheritdoc/>
public override InternalProgram InternalProgram => InternalProgram.DD;
#endregion
#region Flag Values
public long? BlockSizeValue { get; set; }
public long? CountValue { get; set; }
// fixed, removable, disk, partition
public string FilterValue { get; set; }
public string InputFileValue { get; set; }
public string OutputFileValue { get; set; }
public long? SeekValue { get; set; }
public long? SkipValue { get; set; }
#endregion
/// <inheritdoc/>
public Parameters(string parameters) : base(parameters) { }
/// <inheritdoc/>
public Parameters(RedumpSystem? system, MediaType? type, char driveLetter, string filename, int? driveSpeed, Options options)
: base(system, type, driveLetter, filename, driveSpeed, options)
{
}
#region BaseParameters Implementations
/// <inheritdoc/>
public override (bool, List<string>) CheckAllOutputFilesExist(string basePath, bool preCheck)
{
// TODO: Figure out what sort of output files are expected... just `.bin`?
return (true, new List<string>());
}
/// <inheritdoc/>
public override void GenerateSubmissionInfo(SubmissionInfo info, string basePath, Drive drive, bool includeArtifacts)
{
// TODO: Fill in submission info specifics for DD
string outputDirectory = Path.GetDirectoryName(basePath);
switch (this.Type)
{
// Determine type-specific differences
}
switch (this.System)
{
case RedumpSystem.KonamiPython2:
if (GetPlayStationExecutableInfo(drive?.Letter, out string pythonTwoSerial, out Region? pythonTwoRegion, out string pythonTwoDate))
{
info.CommonDiscInfo.Comments += $"{Constants.InternalSerialNameCommentField} {pythonTwoSerial}\n";
info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? pythonTwoRegion;
info.CommonDiscInfo.EXEDateBuildDate = pythonTwoDate;
}
info.VersionAndEditions.Version = GetPlayStation2Version(drive?.Letter) ?? "";
break;
case RedumpSystem.SonyPlayStation:
if (GetPlayStationExecutableInfo(drive?.Letter, out string playstationSerial, out Region? playstationRegion, out string playstationDate))
{
info.CommonDiscInfo.Comments += $"{Constants.InternalSerialNameCommentField} {playstationSerial}\n";
info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? playstationRegion;
info.CommonDiscInfo.EXEDateBuildDate = playstationDate;
}
break;
case RedumpSystem.SonyPlayStation2:
if (GetPlayStationExecutableInfo(drive?.Letter, out string playstationTwoSerial, out Region? playstationTwoRegion, out string playstationTwoDate))
{
info.CommonDiscInfo.Comments += $"{Constants.InternalSerialNameCommentField} {playstationTwoSerial}\n";
info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? playstationTwoRegion;
info.CommonDiscInfo.EXEDateBuildDate = playstationTwoDate;
}
info.VersionAndEditions.Version = GetPlayStation2Version(drive?.Letter) ?? "";
break;
case RedumpSystem.SonyPlayStation4:
info.VersionAndEditions.Version = GetPlayStation4Version(drive?.Letter) ?? "";
break;
case RedumpSystem.SonyPlayStation5:
info.VersionAndEditions.Version = GetPlayStation5Version(drive?.Letter) ?? "";
break;
}
}
/// <inheritdoc/>
public override string GenerateParameters()
{
List<string> parameters = new List<string>();
if (BaseCommand == null)
BaseCommand = CommandStrings.NONE;
if (!string.IsNullOrEmpty(BaseCommand))
parameters.Add(BaseCommand);
#region Boolean flags
// Progress
if (IsFlagSupported(FlagStrings.Progress))
{
if (this[FlagStrings.Progress] == true)
parameters.Add($"{FlagStrings.Progress}");
}
// Size
if (IsFlagSupported(FlagStrings.Size))
{
if (this[FlagStrings.Size] == true)
parameters.Add($"{FlagStrings.Size}");
}
#endregion
#region Int64 flags
// Block Size
if (IsFlagSupported(FlagStrings.BlockSize))
{
if (this[FlagStrings.BlockSize] == true && BlockSizeValue != null)
parameters.Add($"{FlagStrings.BlockSize}={BlockSizeValue}");
}
// Count
if (IsFlagSupported(FlagStrings.Count))
{
if (this[FlagStrings.Count] == true && CountValue != null)
parameters.Add($"{FlagStrings.Count}={CountValue}");
}
// Seek
if (IsFlagSupported(FlagStrings.Seek))
{
if (this[FlagStrings.Seek] == true && SeekValue != null)
parameters.Add($"{FlagStrings.Seek}={SeekValue}");
}
// Skip
if (IsFlagSupported(FlagStrings.Skip))
{
if (this[FlagStrings.Skip] == true && SkipValue != null)
parameters.Add($"{FlagStrings.Skip}={SkipValue}");
}
#endregion
#region String flags
// Filter
if (IsFlagSupported(FlagStrings.Filter))
{
if (this[FlagStrings.Filter] == true && FilterValue != null)
parameters.Add($"{FlagStrings.Filter}={FilterValue}");
}
// Input File
if (IsFlagSupported(FlagStrings.InputFile))
{
if (this[FlagStrings.InputFile] == true && InputFileValue != null)
parameters.Add($"{FlagStrings.InputFile}=\"{InputFileValue}\"");
else
return null;
}
// Output File
if (IsFlagSupported(FlagStrings.OutputFile))
{
if (this[FlagStrings.OutputFile] == true && OutputFileValue != null)
parameters.Add($"{FlagStrings.OutputFile}=\"{OutputFileValue}\"");
else
return null;
}
#endregion
return string.Join(" ", parameters);
}
/// <inheritdoc/>
public override Dictionary<string, List<string>> GetCommandSupport()
{
return new Dictionary<string, List<string>>()
{
[CommandStrings.NONE] = new List<string>()
{
FlagStrings.BlockSize,
FlagStrings.Count,
FlagStrings.Filter,
FlagStrings.InputFile,
FlagStrings.OutputFile,
FlagStrings.Progress,
FlagStrings.Seek,
FlagStrings.Size,
FlagStrings.Skip,
},
[CommandStrings.List] = new List<string>()
{
},
};
}
/// <inheritdoc/>
public override string GetDefaultExtension(MediaType? mediaType) => Converters.Extension(mediaType);
/// <inheritdoc/>
public override bool IsDumpingCommand()
{
switch (this.BaseCommand)
{
case CommandStrings.List:
return false;
default:
return true;
}
}
/// <inheritdoc/>
protected override void ResetValues()
{
BaseCommand = CommandStrings.NONE;
flags = new Dictionary<string, bool?>();
BlockSizeValue = null;
CountValue = null;
InputFileValue = null;
OutputFileValue = null;
SeekValue = null;
SkipValue = null;
}
/// <inheritdoc/>
protected override void SetDefaultParameters(char driveLetter, string filename, int? driveSpeed, Options options)
{
BaseCommand = CommandStrings.NONE;
this[FlagStrings.InputFile] = true;
InputFileValue = $"\\\\?\\{driveLetter}:";
this[FlagStrings.OutputFile] = true;
OutputFileValue = filename;
// TODO: Add more common block sizes
this[FlagStrings.BlockSize] = true;
switch (this.Type)
{
case MediaType.FloppyDisk:
BlockSizeValue = 1440 * 1024;
break;
default:
BlockSizeValue = 1024 * 1024 * 1024;
break;
}
this[FlagStrings.Progress] = true;
this[FlagStrings.Size] = true;
}
/// <inheritdoc/>
protected override bool ValidateAndSetParameters(string parameters)
{
BaseCommand = CommandStrings.NONE;
// The string has to be valid by itself first
if (string.IsNullOrWhiteSpace(parameters))
return false;
// Now split the string into parts for easier validation
// https://stackoverflow.com/questions/14655023/split-a-string-that-has-white-spaces-unless-they-are-enclosed-within-quotes
parameters = parameters.Trim();
List<string> parts = Regex.Matches(parameters, @"[\""].+?[\""]|[^ ]+")
.Cast<Match>()
.Select(m => m.Value)
.ToList();
// Determine what the commandline should look like given the first item
int start = 0;
if (parts[0] == CommandStrings.List)
{
BaseCommand = parts[0];
start = 1;
}
// Loop through all auxilary flags, if necessary
for (int i = start; i < parts.Count; i++)
{
// Flag read-out values
long? longValue = null;
string stringValue = null;
// Keep a count of keys to determine if we should break out to filename handling or not
int keyCount = Keys.Count();
#region Boolean flags
// Progress
ProcessFlagParameter(parts, FlagStrings.Progress, ref i);
// Size
ProcessFlagParameter(parts, FlagStrings.Size, ref i);
#endregion
#region Int64 flags
// Block Size
longValue = ProcessInt64Parameter(parts, FlagStrings.BlockSize, ref i);
if (longValue != null)
BlockSizeValue = longValue;
// Count
longValue = ProcessInt64Parameter(parts, FlagStrings.Count, ref i);
if (longValue != null)
CountValue = longValue;
// Seek
longValue = ProcessInt64Parameter(parts, FlagStrings.Seek, ref i);
if (longValue != null)
SeekValue = longValue;
// Skip
longValue = ProcessInt64Parameter(parts, FlagStrings.Skip, ref i);
if (longValue != null)
SkipValue = longValue;
#endregion
#region String flags
// Filter (fixed, removable, disk, partition)
stringValue = ProcessStringParameter(parts, FlagStrings.Filter, ref i);
if (!string.IsNullOrEmpty(stringValue))
FilterValue = stringValue;
// Input File
stringValue = ProcessStringParameter(parts, FlagStrings.InputFile, ref i);
if (!string.IsNullOrEmpty(stringValue))
InputFileValue = stringValue;
// Output File
stringValue = ProcessStringParameter(parts, FlagStrings.OutputFile, ref i);
if (!string.IsNullOrEmpty(stringValue))
OutputFileValue = stringValue;
#endregion
}
return true;
}
#endregion
}
}

View File

@@ -1,46 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net48;netcoreapp3.1;net5.0</TargetFrameworks>
<PlatformTarget>x86</PlatformTarget>
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
<Copyright>Copyright (c)2019-2021</Copyright>
<RepositoryUrl>https://github.com/SabreTools/MPF</RepositoryUrl>
<Version>2.2</Version>
<AssemblyVersion>$(Version)</AssemblyVersion>
<FileVersion>$(Version)</FileVersion>
<IncludeSource>true</IncludeSource>
<IncludeSymbols>true</IncludeSymbols>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup>
<NrtRevisionFormat>$(Version)-{chash:8}</NrtRevisionFormat>
<NrtResolveSimpleAttributes>true</NrtResolveSimpleAttributes>
<NrtShowRevision>false</NrtShowRevision>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='net48'">
<DefineConstants>NET_FRAMEWORK</DefineConstants>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MPF.Core\MPF.Core.csproj" />
<ProjectReference Include="..\MPF.CueSheets\MPF.CueSheets.csproj" />
<ProjectReference Include="..\RedumpLib\RedumpLib.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net48'">
<ProjectReference Include="..\CICMMetadata\CICMMetadataEditor\CICMMetadataEditor\CICMMetadataEditor.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Memory" Version="4.5.4" />
<PackageReference Include="Unclassified.NetRevisionTask" Version="0.4.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@@ -2,9 +2,6 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
#if NET_FRAMEWORK
using IMAPI2;
#endif
using MPF.Core.Converters;
using MPF.Core.Data;
using Xunit;
@@ -25,35 +22,6 @@ namespace MPF.Test.Core.Converters
DriveType.Removable,
};
#if NET_FRAMEWORK
/// <summary>
/// IMAPI_MEDIA_PHYSICAL_TYPE values that map to MediaType
/// </summary>
private static readonly IMAPI_MEDIA_PHYSICAL_TYPE[] _mappableImapiTypes = new IMAPI_MEDIA_PHYSICAL_TYPE[]
{
IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_UNKNOWN,
IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_CDROM,
IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_CDR,
IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_CDRW,
IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_DVDROM,
IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_DVDRAM,
IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_DVDPLUSR,
IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_DVDPLUSRW,
IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_DVDPLUSR_DUALLAYER,
IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_DVDDASHR,
IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_DVDDASHRW,
IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_DVDDASHR_DUALLAYER,
IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_DISK,
IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_DVDPLUSRW_DUALLAYER,
IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_HDDVDROM,
IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_HDDVDR,
IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_HDDVDRAM,
IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_BDROM,
IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_BDR,
IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_BDRE,
};
#endif
/// <summary>
/// Check that every supported DriveType maps to an InternalDriveType
/// </summary>
@@ -71,64 +39,41 @@ namespace MPF.Test.Core.Converters
Assert.NotNull(actual);
}
#if NET_FRAMEWORK
/// <summary>
/// Check that every supported IMAPI_MEDIA_PHYSICAL_TYPE maps to an MediaType
/// </summary>
/// <param name="imapiType">IMAPI_MEDIA_PHYSICAL_TYPE value to check</param>
/// <param name="expectNull">True to expect a null mapping, false otherwise</param>
[Theory]
[MemberData(nameof(GenerateImapiTypeMappingTestData))]
public void IMAPIToMediaTypeTest(IMAPI_MEDIA_PHYSICAL_TYPE imapiType, bool expectNull)
{
var actual = imapiType.IMAPIToMediaType();
if (expectNull)
Assert.Null(actual);
else
Assert.NotNull(actual);
}
#endif
/// <summary>
/// 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, true });
}
return testData;
}
#if NET_FRAMEWORK
/// <summary>
/// Generate a test set of IMAPI_MEDIA_PHYSICAL_TYPE values
/// </summary>
/// <returns>MemberData-compatible list of IMAPI_MEDIA_PHYSICAL_TYPE values</returns>
public static List<object[]> GenerateImapiTypeMappingTestData()
{
var testData = new List<object[]>() { new object[] { null, false } };
foreach (IMAPI_MEDIA_PHYSICAL_TYPE imapiType in Enum.GetValues(typeof(IMAPI_MEDIA_PHYSICAL_TYPE)))
{
if (_mappableImapiTypes.Contains(imapiType))
testData.Add(new object[] { imapiType, false });
else
testData.Add(new object[] { imapiType, true });
}
return testData;
}
#else
testData.Add(new object?[] { driveType, false });
#endif
else
#if NET48
testData.Add(new object[] { driveType, true });
#else
testData.Add(new object?[] { driveType, true });
#endif
}
#endregion
return testData;
}
#endregion
#region Convert to Long Name
@@ -150,18 +95,30 @@ 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
}
return testData;
}
#endregion
#endregion
// TODO: Add from-string tests
}

View File

@@ -1,115 +0,0 @@
using MPF.Core.Data;
using Xunit;
namespace MPF.Test.Core.Data
{
public class XgdInfoTests
{
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData("1234567")]
[InlineData("1234567\0")]
[InlineData("123456789")]
public void UnmatchedStringTests(string invalidString)
{
XgdInfo xgdInfo = new XgdInfo(invalidString);
Assert.False(xgdInfo.Initialized);
}
[Theory]
[InlineData("AV00100W", "AV", "001", "00", 'W')]
[InlineData("AV00100W\0", "AV", "001", "00", 'W')]
public void XGD1ValidTests(string validString, string publisher, string gameId, string version, char regionIdentifier)
{
XgdInfo xgdInfo = new XgdInfo(validString, validate: true);
Assert.True(xgdInfo.Initialized);
Assert.Equal(publisher, xgdInfo.PublisherIdentifier);
Assert.Equal(gameId, xgdInfo.GameID);
Assert.Equal(version, xgdInfo.SKU);
Assert.Equal(regionIdentifier, xgdInfo.RegionIdentifier);
}
[Theory]
// Invalid publisher identifier
[InlineData("ZZ000000")]
[InlineData("ZZ000000\0")]
// Invalid region identifier
[InlineData("AV00000Z")]
[InlineData("AV00000Z\0")]
public void XGD1InvalidTests(string invalidString)
{
XgdInfo xgdInfo = new XgdInfo(invalidString, validate: true);
Assert.False(xgdInfo.Initialized);
}
[Theory]
[InlineData("AV200100W0F11", "AV", "001", "00", 'W', "0", 'F', "11", null)]
[InlineData("AV200100W0F11\0", "AV", "001", "00", 'W', "0", 'F', "11", null)]
[InlineData("AV200100W01F11", "AV", "001", "00", 'W', "01", 'F', "11", null)]
[InlineData("AV200100W01F11\0", "AV", "001", "00", 'W', "01", 'F', "11", null)]
[InlineData("AV200100W0F11DEADBEEF", "AV", "001", "00", 'W', "0", 'F', "11", "DEADBEEF")]
[InlineData("AV200100W0F11DEADBEEF\0", "AV", "001", "00", 'W', "0", 'F', "11", "DEADBEEF")]
[InlineData("AV200100W01F11DEADBEEF", "AV", "001", "00", 'W', "01", 'F', "11", "DEADBEEF")]
[InlineData("AV200100W01F11DEADBEEF\0", "AV", "001", "00", 'W', "01", 'F', "11", "DEADBEEF")]
public void XGD23ValidTests(string validString, string publisher, string gameId, string sku, char regionIdentifier, string baseVersion, char mediaSubtype, string discNumber, string cert)
{
XgdInfo xgdInfo = new XgdInfo(validString, validate: true);
Assert.True(xgdInfo.Initialized);
Assert.Equal(publisher, xgdInfo.PublisherIdentifier);
Assert.Equal('2', xgdInfo.PlatformIdentifier);
Assert.Equal(gameId, xgdInfo.GameID);
Assert.Equal(sku, xgdInfo.SKU);
Assert.Equal(regionIdentifier, xgdInfo.RegionIdentifier);
Assert.Equal(baseVersion, xgdInfo.BaseVersion);
Assert.Equal(mediaSubtype, xgdInfo.MediaSubtypeIdentifier);
Assert.Equal(discNumber, xgdInfo.DiscNumberIdentifier);
Assert.Equal(cert, xgdInfo.CertificationSubmissionIdentifier);
}
[Theory]
// Invalid publisher identifier
[InlineData("ZZ00000000000")]
[InlineData("ZZ00000000000\0")]
[InlineData("ZZ000000000000")]
[InlineData("ZZ000000000000\0")]
[InlineData("ZZ0000000000000000000")]
[InlineData("ZZ0000000000000000000\0")]
[InlineData("ZZ00000000000000000000")]
[InlineData("ZZ00000000000000000000\0")]
// Invalid platform identifier
[InlineData("AV90000000000")]
[InlineData("AV90000000000\0")]
[InlineData("AV900000000000")]
[InlineData("AV900000000000\0")]
[InlineData("AV9000000000000000000")]
[InlineData("AV9000000000000000000\0")]
[InlineData("AV90000000000000000000")]
[InlineData("AV90000000000000000000\0")]
// Invalid region identifier
[InlineData("AV200000Z0000")]
[InlineData("AV200000Z0000\0")]
[InlineData("AV200000Z00000")]
[InlineData("AV200000Z00000\0")]
[InlineData("AV200000Z000000000000")]
[InlineData("AV200000Z000000000000\0")]
[InlineData("AV200000Z0000000000000")]
[InlineData("AV200000Z0000000000000\0")]
// Invalid media subtype identifier
[InlineData("AV200000W0A00")]
[InlineData("AV200000W0A00\0")]
[InlineData("AV200000W00A00")]
[InlineData("AV200000W00A00\0")]
[InlineData("AV200000W00A000000000")]
[InlineData("AV200000W00A000000000\0")]
[InlineData("AV200000W00A0000000000")]
[InlineData("AV200000W00A0000000000\0")]
public void XGD23InvalidTests(string invalidString)
{
XgdInfo xgdInfo = new XgdInfo(invalidString, validate: true);
Assert.False(xgdInfo.Initialized);
}
}
}

View File

@@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using MPF.Core.Utilities;
using RedumpLib.Data;
using SabreTools.RedumpLib.Data;
using Xunit;
namespace MPF.Test.Core.Utilities
@@ -31,10 +31,12 @@ namespace MPF.Test.Core.Utilities
RedumpSystem.AtariJaguarCDInteractiveMultimediaSystem,
RedumpSystem.AudioCD,
RedumpSystem.DVDAudio,
RedumpSystem.HasbroiONEducationalGamingSystem,
RedumpSystem.HasbroVideoNow,
RedumpSystem.HasbroVideoNowColor,
RedumpSystem.HasbroVideoNowJr,
RedumpSystem.HasbroVideoNowXP,
RedumpSystem.PlayStationGameSharkUpdates,
RedumpSystem.PhilipsCDi,
RedumpSystem.SuperAudioCD,
};
@@ -58,6 +60,7 @@ namespace MPF.Test.Core.Utilities
RedumpSystem.SonyPlayStation2,
RedumpSystem.SonyPlayStation3,
RedumpSystem.SonyPlayStation4,
RedumpSystem.SonyPlayStationPortable,
};
/// <summary>
@@ -140,15 +143,31 @@ 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
else
#if NET48
testData.Add(new object[] { mediaType, false });
#else
testData.Add(new object?[] { mediaType, false });
#endif
}
return testData;
@@ -158,15 +177,31 @@ 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
else
#if NET48
testData.Add(new object[] { redumpSystem, false });
#else
testData.Add(new object?[] { redumpSystem, false });
#endif
}
return testData;
@@ -176,15 +211,31 @@ 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
else
#if NET48
testData.Add(new object[] { redumpSystem, false });
#else
testData.Add(new object?[] { redumpSystem, false });
#endif
}
return testData;
@@ -194,15 +245,31 @@ 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
else
#if NET48
testData.Add(new object[] { redumpSystem, false });
#else
testData.Add(new object?[] { redumpSystem, false });
#endif
}
return testData;
@@ -212,15 +279,31 @@ 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
else
#if NET48
testData.Add(new object[] { redumpSystem, false });
#else
testData.Add(new object?[] { redumpSystem, false });
#endif
}
return testData;

View File

@@ -1,7 +1,6 @@
using System.IO;
using MPF.Core;
using MPF.Core.Data;
using MPF.Library;
using RedumpLib.Data;
using SabreTools.RedumpLib.Data;
using Xunit;
namespace MPF.Test.Library
@@ -18,11 +17,13 @@ namespace MPF.Test.Library
public void ParametersValidTest(string parameters, char letter, bool isFloppy, MediaType? mediaType, bool expected)
{
var options = new Options() { InternalProgram = InternalProgram.DiscImageCreator };
var drive = isFloppy
? new Drive(InternalDriveType.Floppy, new DriveInfo(letter.ToString()))
: new Drive(InternalDriveType.Optical, new DriveInfo(letter.ToString()));
var env = new DumpEnvironment(options, string.Empty, string.Empty, drive, RedumpSystem.IBMPCcompatible, mediaType, parameters);
// TODO: This relies on creating real objects for the drive. Can we mock this out instead?
var drive = isFloppy
? Drive.Create(InternalDriveType.Floppy, letter.ToString())
: Drive.Create(InternalDriveType.Optical, letter.ToString());
var env = new DumpEnvironment(options, string.Empty, drive, RedumpSystem.IBMPCcompatible, mediaType, null, parameters);
bool actual = env.ParametersValid();
Assert.Equal(expected, actual);

View File

@@ -1,5 +1,7 @@
using MPF.Library;
using RedumpLib.Data;
using System.Collections.Generic;
using System.IO;
using MPF.Core;
using SabreTools.RedumpLib.Data;
using Xunit;
namespace MPF.Test.Library
@@ -40,26 +42,183 @@ namespace MPF.Test.Library
long layerbreak,
long layerbreak2,
long layerbreak3,
#if NET48
string expected)
#else
string? expected)
#endif
{
string actual = InfoTool.GetFixedMediaType(mediaType, size, layerbreak, layerbreak2, layerbreak3);
// TODO: Add tests around BDU
var actual = InfoTool.GetFixedMediaType(mediaType, null, size, layerbreak, layerbreak2, layerbreak3);
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(null, null, null, null)]
[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")]
public void NormalizeOutputPathsTest(string outputDirectory, string outputFilename, string expectedOutputDirectory, string expectedOutputFilename)
[InlineData(null, "")]
[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
{
(string actualOutputDirectory, string actualOutputFilename) = InfoTool.NormalizeOutputPaths(outputDirectory, outputFilename);
Assert.Equal(expectedOutputDirectory, actualOutputDirectory);
Assert.Equal(expectedOutputFilename, actualOutputFilename);
if (!string.IsNullOrWhiteSpace(expectedPath))
expectedPath = Path.GetFullPath(expectedPath);
string actualPath = InfoTool.NormalizeOutputPaths(outputPath, true);
Assert.Equal(expectedPath, actualPath);
}
[Fact]
public void ProcessSpecialFieldsCompleteTest()
{
// Create a new SubmissionInfo object
var info = new SubmissionInfo()
{
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",
},
}
};
// Process the special fields
InfoTool.ProcessSpecialFields(info);
// Validate the basics
Assert.NotNull(info.CommonDiscInfo.Comments);
Assert.Null(info.CommonDiscInfo.CommentsSpecialFields);
Assert.NotNull(info.CommonDiscInfo.Contents);
Assert.Null(info.CommonDiscInfo.ContentsSpecialFields);
// Split the values
string[] splitComments = info.CommonDiscInfo.Comments.Split('\n');
string[] splitContents = info.CommonDiscInfo.Contents.Split('\n');
// Validate the lines
Assert.Equal(3, splitComments.Length);
Assert.Equal(5, splitContents.Length);
}
[Fact]
public void ProcessSpecialFieldsNullObjectTest()
{
// Create a new SubmissionInfo object
var info = new SubmissionInfo()
{
CommonDiscInfo = null,
};
// Process the special fields
InfoTool.ProcessSpecialFields(info);
// Validate
Assert.Null(info.CommonDiscInfo);
}
[Fact]
public void ProcessSpecialFieldsNullCommentsContentsTest()
{
// Create a new SubmissionInfo object
var info = new SubmissionInfo()
{
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",
},
}
};
// Process the special fields
InfoTool.ProcessSpecialFields(info);
// Validate the basics
Assert.NotNull(info.CommonDiscInfo.Comments);
Assert.Null(info.CommonDiscInfo.CommentsSpecialFields);
Assert.NotNull(info.CommonDiscInfo.Contents);
Assert.Null(info.CommonDiscInfo.ContentsSpecialFields);
// Split the values
string[] splitComments = info.CommonDiscInfo.Comments.Split('\n');
string[] splitContents = info.CommonDiscInfo.Contents.Split('\n');
// Validate the lines
Assert.Single(splitComments);
Assert.Equal(2, splitContents.Length);
}
[Fact]
public void ProcessSpecialFieldsNullDictionariesTest()
{
// Create a new SubmissionInfo object
var info = new SubmissionInfo()
{
CommonDiscInfo = new CommonDiscInfoSection()
{
Comments = "This is a comments line\n[T:ISBN] ISBN Value",
CommentsSpecialFields = null,
Contents = "This is a contents line\n[T:GF] Game Footage",
ContentsSpecialFields = null,
}
};
// Process the special fields
InfoTool.ProcessSpecialFields(info);
// Validate the basics
Assert.NotNull(info.CommonDiscInfo.Comments);
Assert.Null(info.CommonDiscInfo.CommentsSpecialFields);
Assert.NotNull(info.CommonDiscInfo.Contents);
Assert.Null(info.CommonDiscInfo.ContentsSpecialFields);
// Split the values
string[] splitComments = info.CommonDiscInfo.Comments.Split('\n');
string[] splitContents = info.CommonDiscInfo.Contents.Split('\n');
// Validate the lines
Assert.Equal(2, splitComments.Length);
Assert.Equal(2, splitContents.Length);
}
}
}

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using MPF.Library;
using MPF.Core;
using Xunit;
namespace MPF.Test.Library
@@ -178,43 +178,6 @@ namespace MPF.Test.Library
Assert.Equal("Anything Else Protection", sanitized);
}
[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
[InlineData(4)]
[InlineData(5)]
[InlineData(6)]
[InlineData(7)]
[InlineData(8)]
public void SanitizeFoundProtectionsSafeDiscTest(int skip)
{
List<string> protections = new List<string>()
{
"SafeDisc 1.20.000",
"SafeDisc (drvmgt.dll) 1.2.0",
"SafeDisc (secdrv.sys) 1.2.0",
"SafeDisc (dplayerx.dll) 1.2.0",
"SafeDisc 3.20-4.xx (version removed)",
"SafeDisc 2",
"SafeDisc 1/Lite",
"SafeDisc Lite",
"SafeDisc 1-3",
"SafeDisc",
};
// Safeguard for the future
if (skip >= protections.Count)
throw new ArgumentException("Invalid skip value", nameof(skip));
// The list is in order of preference
protections = protections.Skip(skip).ToList();
string sanitized = Protection.SanitizeFoundProtections(protections);
Assert.Equal(protections[0], sanitized);
}
[Theory]
[InlineData(0)]
[InlineData(1)]

View File

@@ -1,56 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net48;netcoreapp3.1;net5.0-windows</TargetFrameworks>
<PlatformTarget>x86</PlatformTarget>
<TargetFrameworks>net48;net6.0-windows;net7.0-windows</TargetFrameworks>
<IsPackable>false</IsPackable>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='net48'">
<DefineConstants>NET_FRAMEWORK</DefineConstants>
<PropertyGroup Condition="'$(TargetFramework)'!='net48'">
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net48'">
<COMReference Include="IMAPI2">
<Guid>{2735412F-7F64-5B0F-8F00-5D77AFBE261E}</Guid>
<VersionMajor>1</VersionMajor>
<VersionMinor>0</VersionMinor>
<Lcid>0</Lcid>
<WrapperTool>tlbimp</WrapperTool>
<Isolated>False</Isolated>
<EmbedInteropTypes>True</EmbedInteropTypes>
</COMReference>
<COMReference Include="IMAPI2FS">
<Guid>{2C941FD0-975B-59BE-A960-9A2A262853A5}</Guid>
<VersionMajor>1</VersionMajor>
<VersionMinor>0</VersionMinor>
<Lcid>0</Lcid>
<WrapperTool>tlbimp</WrapperTool>
<Isolated>False</Isolated>
<EmbedInteropTypes>True</EmbedInteropTypes>
</COMReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MPF\MPF.csproj" />
<ProjectReference Include="..\MPF.Library\MPF.Library.csproj" />
<ProjectReference Include="..\MPF.Modules\MPF.Modules.csproj" />
<ProjectReference Include="..\RedumpLib\RedumpLib.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeCoverage" Version="16.11.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="Microsoft.CodeCoverage" Version="17.7.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="SabreTools.RedumpLib" Version="1.1.1" />
<PackageReference Include="xunit" Version="2.5.3" />
<PackageReference Include="xunit.abstractions" Version="2.0.3" />
<PackageReference Include="xunit.analyzers" Version="0.10.0" />
<PackageReference Include="xunit.assert" Version="2.4.1" />
<PackageReference Include="xunit.core" Version="2.4.1" />
<PackageReference Include="xunit.extensibility.core" Version="2.4.1" />
<PackageReference Include="xunit.extensibility.execution" Version="2.4.1" />
<PackageReference Include="xunit.runner.console" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.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">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@@ -1,7 +1,8 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using MPF.Core.Data;
using MPF.Modules.DiscImageCreator;
using RedumpLib.Data;
using MPF.Core.Modules.DiscImageCreator;
using SabreTools.RedumpLib.Data;
using Xunit;
namespace MPF.Test.Modules
@@ -19,38 +20,122 @@ namespace MPF.Test.Modules
[InlineData(RedumpSystem.RawThrillsVarious, MediaType.GDROM, null)]
public void ParametersFromSystemAndTypeTest(RedumpSystem? knownSystem, MediaType? mediaType, string expected)
{
var options = new Options { };
var actual = new Parameters(knownSystem, mediaType, 'D', "disc.bin", 16, options);
var options = new Options();
var actual = new Parameters(knownSystem, mediaType, "D:\\", "disc.bin", 16, options);
Assert.Equal(expected, actual.BaseCommand);
}
[Theory]
[InlineData(RedumpSystem.AppleMacintosh, MediaType.LaserDisc, true, 20, null, null)]
[InlineData(RedumpSystem.NintendoGameCube, MediaType.NintendoGameCubeGameDisc, false, 20, null, new string[] { FlagStrings.Raw })]
[InlineData(RedumpSystem.IBMPCcompatible, MediaType.DVD, false, 20, null, new string[] { FlagStrings.CopyrightManagementInformation, FlagStrings.ScanFileProtect })]
/* paranoid mode tests */
[InlineData(RedumpSystem.IBMPCcompatible, MediaType.CDROM, true, 1000, 2, new string[] { FlagStrings.C2Opcode, FlagStrings.NoFixSubQSecuROM, FlagStrings.ScanFileProtect, FlagStrings.ScanSectorProtect, FlagStrings.SubchannelReadLevel })]
[InlineData(RedumpSystem.AppleMacintosh, MediaType.CDROM, false, 20, null, new string[] { FlagStrings.C2Opcode, FlagStrings.NoFixSubQSecuROM, FlagStrings.ScanFileProtect, FlagStrings.ScanSectorProtect, FlagStrings.SubchannelReadLevel })]
[InlineData(RedumpSystem.IBMPCcompatible, MediaType.DVD, true, 500, null, new string[] { FlagStrings.CopyrightManagementInformation, FlagStrings.ScanFileProtect })]
[InlineData(RedumpSystem.HDDVDVideo, MediaType.HDDVD, true, 500, null, new string[] { FlagStrings.CopyrightManagementInformation })]
[InlineData(RedumpSystem.IBMPCcompatible, MediaType.DVD, false, 500, null, new string[] { FlagStrings.CopyrightManagementInformation, FlagStrings.ScanFileProtect })]
[InlineData(RedumpSystem.HDDVDVideo, MediaType.HDDVD, false, 500, null, new string[] { FlagStrings.CopyrightManagementInformation })]
/* reread c2 */
[InlineData(RedumpSystem.SegaDreamcast, MediaType.GDROM, false, 1000, null, new string[] { FlagStrings.C2Opcode })]
[InlineData(RedumpSystem.SegaDreamcast, MediaType.GDROM, false, -1, null, new string[] { FlagStrings.C2Opcode })]
public void ParametersFromOptionsTest(RedumpSystem? knownSystem, MediaType? mediaType, bool paranoid, int rereadC2, int? subchannelLevel, string[] expected)
[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)
{
var options = new Options { DICParanoidMode = paranoid, DICRereadCount = rereadC2 };
var actual = new Parameters(knownSystem, mediaType, 'D', "disc.bin", 16, options);
var options = new Options();
var actual = new Parameters(knownSystem, mediaType, "D:\\", "disc.bin", 16, options);
var expectedSet = new HashSet<string>(expected ?? Array.Empty<string>());
HashSet<string> actualSet = GenerateUsedKeys(actual);
Assert.Equal(expectedSet, actualSet);
}
[Theory]
[InlineData(RedumpSystem.SegaDreamcast, MediaType.GDROM, 1000, new string[] { FlagStrings.C2Opcode })]
[InlineData(RedumpSystem.SegaDreamcast, MediaType.GDROM, -1, new string[] { FlagStrings.C2Opcode })]
public void ParametersFromOptionsC2RereadTest(RedumpSystem? knownSystem, MediaType? mediaType, int rereadC2, string[] expected)
{
var options = new Options { DICRereadCount = rereadC2 };
var actual = new Parameters(knownSystem, mediaType, "D:\\", "disc.bin", 16, options);
var expectedSet = new HashSet<string>(expected ?? Array.Empty<string>());
HashSet<string> actualSet = GenerateUsedKeys(actual);
HashSet<string> expectedSet = new HashSet<string>(expected ?? new string[0]);
HashSet<string> actualSet = new HashSet<string>(actual.Keys ?? new string[0]);
Assert.Equal(expectedSet, actualSet);
if (rereadC2 == -1 || !knownSystem.MediaTypes().Contains(mediaType))
Assert.Null(actual.C2OpcodeValue[0]);
else
Assert.Equal(rereadC2, actual.C2OpcodeValue[0]);
Assert.Equal(subchannelLevel, actual.SubchannelReadLevelValue);
}
[Theory]
[InlineData(RedumpSystem.IBMPCcompatible, MediaType.DVD, 1000, new string[] { FlagStrings.DVDReread })]
[InlineData(RedumpSystem.IBMPCcompatible, MediaType.DVD, -1, new string[] { FlagStrings.DVDReread })]
[InlineData(RedumpSystem.IBMPCcompatible, MediaType.BluRay, 1000, new string[] { FlagStrings.DVDReread })]
[InlineData(RedumpSystem.IBMPCcompatible, MediaType.BluRay, -1, new string[] { FlagStrings.DVDReread })]
public void ParametersFromOptionsDVDRereadTest(RedumpSystem? knownSystem, MediaType? mediaType, int rereadDVDBD, string[] expected)
{
var options = new Options { DICDVDRereadCount = rereadDVDBD };
var actual = new Parameters(knownSystem, mediaType, "D:\\", "disc.bin", 16, options);
var expectedSet = new HashSet<string>(expected ?? Array.Empty<string>());
HashSet<string> actualSet = GenerateUsedKeys(actual);
Assert.Equal(expectedSet, actualSet);
if (rereadDVDBD == -1 || !knownSystem.MediaTypes().Contains(mediaType))
Assert.Null(actual.DVDRereadValue);
else
Assert.Equal(rereadDVDBD, actual.DVDRereadValue);
}
[Theory]
[InlineData(RedumpSystem.BDVideo, MediaType.BluRay, true, new string[] { FlagStrings.DVDReread })]
[InlineData(RedumpSystem.BDVideo, MediaType.BluRay, false, new string[] { FlagStrings.DVDReread })]
[InlineData(RedumpSystem.IBMPCcompatible, MediaType.CDROM, true, new string[] { FlagStrings.C2Opcode, FlagStrings.NoFixSubQSecuROM, FlagStrings.MultiSectorRead, FlagStrings.ScanFileProtect })]
[InlineData(RedumpSystem.IBMPCcompatible, MediaType.CDROM, false, new string[] { FlagStrings.C2Opcode, FlagStrings.NoFixSubQSecuROM, FlagStrings.ScanFileProtect })]
[InlineData(RedumpSystem.IBMPCcompatible, MediaType.DVD, true, new string[] { FlagStrings.DVDReread })]
[InlineData(RedumpSystem.IBMPCcompatible, MediaType.DVD, false, new string[] { FlagStrings.DVDReread })]
[InlineData(RedumpSystem.HDDVDVideo, MediaType.HDDVD, true, new string[] { FlagStrings.DVDReread })]
[InlineData(RedumpSystem.HDDVDVideo, MediaType.HDDVD, false, new string[] { FlagStrings.DVDReread })]
public void ParametersFromOptionsMultiSectorReadTest(RedumpSystem? knownSystem, MediaType? mediaType, bool multiSectorRead, string[] expected)
{
var options = new Options { DICMultiSectorRead = multiSectorRead };
var actual = new Parameters(knownSystem, mediaType, "D:\\", "disc.bin", 16, options);
var expectedSet = new HashSet<string>(expected ?? Array.Empty<string>());
HashSet<string> actualSet = GenerateUsedKeys(actual);
Assert.Equal(expectedSet, actualSet);
if (expectedSet.Count != 1 && multiSectorRead)
Assert.Equal(0, actual.MultiSectorReadValue);
}
[Theory]
[InlineData(RedumpSystem.BDVideo, MediaType.BluRay, true, new string[] { FlagStrings.DVDReread })]
[InlineData(RedumpSystem.BDVideo, MediaType.BluRay, false, new string[] { FlagStrings.DVDReread })]
[InlineData(RedumpSystem.IBMPCcompatible, MediaType.CDROM, true, new string[] { FlagStrings.C2Opcode, FlagStrings.NoFixSubQSecuROM, FlagStrings.ScanFileProtect, FlagStrings.ScanSectorProtect, FlagStrings.SubchannelReadLevel })]
[InlineData(RedumpSystem.IBMPCcompatible, MediaType.CDROM, false, new string[] { FlagStrings.C2Opcode, FlagStrings.NoFixSubQSecuROM, FlagStrings.ScanFileProtect })]
[InlineData(RedumpSystem.IBMPCcompatible, MediaType.DVD, true, new string[] { FlagStrings.DVDReread, FlagStrings.ScanFileProtect })]
[InlineData(RedumpSystem.IBMPCcompatible, MediaType.DVD, false, new string[] { FlagStrings.DVDReread })]
[InlineData(RedumpSystem.HDDVDVideo, MediaType.HDDVD, true, new string[] { FlagStrings.DVDReread })]
[InlineData(RedumpSystem.HDDVDVideo, MediaType.HDDVD, false, new string[] { FlagStrings.DVDReread })]
public void ParametersFromOptionsParanoidModeTest(RedumpSystem? knownSystem, MediaType? mediaType, bool paranoidMode, string[] expected)
{
var options = new Options { DICParanoidMode = paranoidMode };
var actual = new Parameters(knownSystem, mediaType, "D:\\", "disc.bin", 16, options);
var expectedSet = new HashSet<string>(expected ?? Array.Empty<string>());
HashSet<string> actualSet = GenerateUsedKeys(actual);
Assert.Equal(expectedSet, actualSet);
if (paranoidMode)
{
if (actualSet.Contains(FlagStrings.ScanSectorProtect))
Assert.True(actual[FlagStrings.ScanSectorProtect]);
if (actualSet.Contains(FlagStrings.SubchannelReadLevel))
{
Assert.True(actual[FlagStrings.SubchannelReadLevel]);
Assert.Equal(2, actual.SubchannelReadLevelValue);
}
}
else
{
if (actualSet.Contains(FlagStrings.ScanSectorProtect))
Assert.False(actual[FlagStrings.ScanSectorProtect]);
if (actualSet.Contains(FlagStrings.SubchannelReadLevel))
Assert.False(actual[FlagStrings.SubchannelReadLevel]);
Assert.Null(actual.SubchannelReadLevelValue);
}
}
[Theory]
@@ -76,9 +161,13 @@ 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
{
string actual = MPF.Modules.DiscImageCreator.Converters.Extension(mediaType);
var actual = Converters.Extension(mediaType);
Assert.Equal(expected, actual);
}
@@ -102,7 +191,7 @@ namespace MPF.Test.Modules
[InlineData(CommandStrings.XBOX, MediaType.DVD)]
public void BaseCommandToMediaTypeTest(string command, MediaType? expected)
{
MediaType? actual = MPF.Modules.DiscImageCreator.Converters.ToMediaType(command);
MediaType? actual = Converters.ToMediaType(command);
Assert.Equal(expected, actual);
}
@@ -126,11 +215,11 @@ namespace MPF.Test.Modules
[InlineData(CommandStrings.XBOX, RedumpSystem.MicrosoftXbox)]
public void BaseCommandToRedumpSystemTest(string command, RedumpSystem? expected)
{
RedumpSystem? actual = MPF.Modules.DiscImageCreator.Converters.ToRedumpSystem(command);
RedumpSystem? actual = Converters.ToRedumpSystem(command);
Assert.Equal(expected, actual);
}
#endregion
#endregion
[Fact]
public void DiscImageCreatorAudioParametersTest()
@@ -142,7 +231,7 @@ namespace MPF.Test.Modules
Assert.NotNull(parametersObject);
// Validate that the same set of parameters are generated on the output
string newParameters = parametersObject.GenerateParameters();
var newParameters = parametersObject.GenerateParameters();
Assert.NotNull(newParameters);
Assert.Equal(originalParameters, newParameters);
}
@@ -157,9 +246,29 @@ namespace MPF.Test.Modules
Assert.NotNull(parametersObject);
// Validate that the same set of parameters are generated on the output
string newParameters = parametersObject.GenerateParameters();
var newParameters = parametersObject.GenerateParameters();
Assert.NotNull(newParameters);
Assert.Equal(originalParameters, newParameters);
}
/// <summary>
/// Generate a HashSet of keys that are considered to be set
/// </summary>
/// <param name="parameters">Parameters object to get keys from</param>
/// <returns>HashSet representing the strings</returns>
private static HashSet<string> GenerateUsedKeys(Parameters parameters)
{
var usedKeys = new HashSet<string>();
if (parameters?.Keys == null)
return usedKeys;
foreach (string key in parameters.Keys)
{
if (parameters[key] == true)
usedKeys.Add(key);
}
return usedKeys;
}
}
}

View File

@@ -1,7 +1,8 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using RedumpLib.Data;
using SabreTools.RedumpLib.Data;
using Xunit;
namespace MPF.Test.RedumpLib
@@ -17,12 +18,17 @@ namespace MPF.Test.RedumpLib
private static readonly DiscType?[] _mappableDiscTypes = new DiscType?[]
{
DiscType.BD25,
DiscType.BD33,
DiscType.BD50,
DiscType.BD66,
DiscType.BD100,
DiscType.BD128,
DiscType.CD,
DiscType.DVD5,
DiscType.DVD9,
DiscType.GDROM,
DiscType.HDDVDSL,
DiscType.HDDVDDL,
DiscType.NintendoGameCubeGameDisc,
DiscType.NintendoWiiOpticalDiscSL,
DiscType.NintendoWiiOpticalDiscDL,
@@ -89,15 +95,31 @@ 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;
@@ -107,12 +129,24 @@ 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;
@@ -122,16 +156,32 @@ 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;
@@ -150,7 +200,7 @@ namespace MPF.Test.RedumpLib
[MemberData(nameof(GenerateDiscCategoryTestData))]
public void DiscCategoryLongNameTest(DiscCategory? discCategory, bool expectNull)
{
string actual = discCategory.LongName();
var actual = discCategory.LongName();
if (expectNull)
Assert.Null(actual);
@@ -162,12 +212,24 @@ 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;
@@ -186,7 +248,7 @@ namespace MPF.Test.RedumpLib
[MemberData(nameof(GenerateDiscTypeTestData))]
public void DiscTypeLongNameTest(DiscType? discType, bool expectNull)
{
string actual = discType.LongName();
var actual = discType.LongName();
if (expectNull)
Assert.Null(actual);
@@ -198,15 +260,31 @@ 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;
@@ -225,7 +303,7 @@ namespace MPF.Test.RedumpLib
[MemberData(nameof(GenerateLanguageTestData))]
public void LanguageLongNameTest(Language? language, bool expectNull)
{
string actual = language.LongName();
var actual = language.LongName();
if (expectNull)
Assert.Null(actual);
@@ -242,7 +320,7 @@ namespace MPF.Test.RedumpLib
[MemberData(nameof(GenerateLanguageTestData))]
public void LanguageShortNameTest(Language? language, bool expectNull)
{
string actual = language.ShortName();
var actual = language.ShortName();
if (expectNull)
Assert.Null(actual);
@@ -250,16 +328,109 @@ namespace MPF.Test.RedumpLib
Assert.NotNull(actual);
}
/// <summary>
/// Ensure that every Language that has an ISO 639-1 code is unique
/// </summary>
[Fact]
public void LanguageNoDuplicateTwoLetterCodeTest()
{
var fullLanguages = Enum.GetValues(typeof(Language)).Cast<Language?>().ToList();
var filteredLanguages = new Dictionary<string, Language?>();
int totalCount = 0;
foreach (Language? language in fullLanguages)
{
var code = language.TwoLetterCode();
if (string.IsNullOrWhiteSpace(code))
continue;
// Throw if the code already exists
if (filteredLanguages.ContainsKey(code))
throw new DuplicateNameException($"Code {code} already in dictionary");
filteredLanguages[code] = language;
totalCount++;
}
Assert.Equal(totalCount, filteredLanguages.Count);
}
/// <summary>
/// Ensure that every Language that has a standard/bibliographic ISO 639-2 code is unique
/// </summary>
[Fact]
public void LanguageNoDuplicateThreeLetterCodeTest()
{
var fullLanguages = Enum.GetValues(typeof(Language)).Cast<Language?>().ToList();
var filteredLanguages = new Dictionary<string, Language?>();
int totalCount = 0;
foreach (Language? language in fullLanguages)
{
var code = language.ThreeLetterCode();
if (string.IsNullOrWhiteSpace(code))
continue;
// Throw if the code already exists
if (filteredLanguages.ContainsKey(code))
throw new DuplicateNameException($"Code {code} already in dictionary");
filteredLanguages[code] = language;
totalCount++;
}
Assert.Equal(totalCount, filteredLanguages.Count);
}
/// <summary>
/// Ensure that every Language that has a terminology ISO 639-2 code is unique
/// </summary>
[Fact]
public void LanguageNoDuplicateThreeLetterCodeAltTest()
{
var fullLanguages = Enum.GetValues(typeof(Language)).Cast<Language?>().ToList();
var filteredLanguages = new Dictionary<string, Language?>();
int totalCount = 0;
foreach (Language? language in fullLanguages)
{
var code = language.ThreeLetterCodeAlt();
if (string.IsNullOrWhiteSpace(code))
continue;
// Throw if the code already exists
if (filteredLanguages.ContainsKey(code))
throw new DuplicateNameException($"Code {code} already in dictionary");
filteredLanguages[code] = language;
totalCount++;
}
Assert.Equal(totalCount, filteredLanguages.Count);
}
/// <summary>
/// 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;
@@ -278,7 +449,7 @@ namespace MPF.Test.RedumpLib
[MemberData(nameof(GenerateLanguageSelectionTestData))]
public void LanguageSelectionLongNameTest(LanguageSelection? languageSelection, bool expectNull)
{
string actual = languageSelection.LongName();
var actual = languageSelection.LongName();
if (expectNull)
Assert.Null(actual);
@@ -290,12 +461,24 @@ 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;
@@ -314,7 +497,7 @@ namespace MPF.Test.RedumpLib
[MemberData(nameof(GenerateMediaTypeTestData))]
public void MediaTypeLongNameTest(MediaType? mediaType, bool expectNull)
{
string actual = mediaType.LongName();
var actual = mediaType.LongName();
if (expectNull)
Assert.Null(actual);
@@ -331,7 +514,7 @@ namespace MPF.Test.RedumpLib
[MemberData(nameof(GenerateMediaTypeTestData))]
public void MediaTypeShortNameTest(MediaType? mediaType, bool expectNull)
{
string actual = mediaType.ShortName();
var actual = mediaType.ShortName();
if (expectNull)
Assert.Null(actual);
@@ -343,12 +526,24 @@ 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;
@@ -367,7 +562,7 @@ namespace MPF.Test.RedumpLib
[MemberData(nameof(GenerateRegionTestData))]
public void RegionLongNameTest(Region? region, bool expectNull)
{
string actual = region.LongName();
var actual = region.LongName();
if (expectNull)
Assert.Null(actual);
@@ -384,7 +579,7 @@ namespace MPF.Test.RedumpLib
[MemberData(nameof(GenerateRegionTestData))]
public void RegionShortNameTest(Region? region, bool expectNull)
{
string actual = region.ShortName();
var actual = region.ShortName();
if (expectNull)
Assert.Null(actual);
@@ -392,16 +587,120 @@ namespace MPF.Test.RedumpLib
Assert.NotNull(actual);
}
/// <summary>
/// Ensure that every Language that has an ISO 639-1 code is unique
/// </summary>
[Fact]
public void RegionNoDuplicateShortNameTest()
{
var fullRegions = Enum.GetValues(typeof(Region)).Cast<Region?>().ToList();
var filteredRegions = new Dictionary<string, Region?>();
int totalCount = 0;
foreach (Region? region in fullRegions)
{
var code = region.ShortName();
if (string.IsNullOrWhiteSpace(code))
continue;
// Throw if the code already exists
if (filteredRegions.ContainsKey(code))
throw new DuplicateNameException($"Code {code} already in dictionary");
filteredRegions[code] = region;
totalCount++;
}
Assert.Equal(totalCount, filteredRegions.Count);
}
/// <summary>
/// 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;
}
#endregion
#region Site Code
/// <summary>
/// Check that every SiteCode has a long name provided
/// </summary>
/// <param name="siteCode">SiteCode value to check</param>
/// <param name="expectNull">True to expect a null value, false otherwise</param>
[Theory]
[MemberData(nameof(GenerateSiteCodeTestData))]
public void SiteCodeLongNameTest(SiteCode? siteCode, bool expectNull)
{
var actual = siteCode.LongName();
if (expectNull)
Assert.Null(actual);
else
Assert.NotNull(actual);
}
/// <summary>
/// Check that every SiteCode has a short name provided
/// </summary>
/// <param name="siteCode">SiteCode value to check</param>
/// <param name="expectNull">True to expect a null value, false otherwise</param>
[Theory]
[MemberData(nameof(GenerateSiteCodeTestData))]
public void SiteCodeShortNameTest(SiteCode? siteCode, bool expectNull)
{
var actual = siteCode.ShortName();
if (expectNull)
Assert.Null(actual);
else
Assert.NotNull(actual);
}
/// <summary>
/// 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;
@@ -411,17 +710,6 @@ namespace MPF.Test.RedumpLib
#region System
/// <summary>
/// RedumpSystem values that are considered markers and not real systems
/// </summary>
private static readonly RedumpSystem?[] _markerSystemTypes = new RedumpSystem?[]
{
RedumpSystem.MarkerArcadeEnd,
RedumpSystem.MarkerComputerEnd,
RedumpSystem.MarkerDiscBasedConsoleEnd,
RedumpSystem.MarkerOtherEnd,
};
/// <summary>
/// Check that every RedumpSystem has a long name provided
/// </summary>
@@ -431,7 +719,7 @@ namespace MPF.Test.RedumpLib
[MemberData(nameof(GenerateRedumpSystemTestData))]
public void RedumpSystemLongNameTest(RedumpSystem? redumpSystem, bool expectNull)
{
string actual = redumpSystem.LongName();
var actual = redumpSystem.LongName();
if (expectNull)
Assert.Null(actual);
@@ -466,16 +754,28 @@ 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 (_markerSystemTypes.Contains(redumpSystem))
if (redumpSystem.IsMarker())
continue;
#if NET48
testData.Add(new object[] { redumpSystem, false });
#else
testData.Add(new object?[] { redumpSystem, false });
#endif
}
return testData;
@@ -494,7 +794,7 @@ namespace MPF.Test.RedumpLib
[MemberData(nameof(GenerateSystemCategoryTestData))]
public void SystemCategoryLongNameTest(SystemCategory? systemCategory, bool expectNull)
{
string actual = systemCategory.LongName();
var actual = systemCategory.LongName();
if (expectNull)
Assert.Null(actual);
@@ -506,15 +806,31 @@ 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;
@@ -545,12 +861,24 @@ 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,7 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using RedumpLib.Data;
using SabreTools.RedumpLib.Data;
using Xunit;
namespace MPF.Test.RedumpLib
@@ -42,7 +42,8 @@ namespace MPF.Test.RedumpLib
var submissionInfo = new SubmissionInfo()
{
SchemaVersion = 1,
MatchedIDs = new List<int> { 0, 1, 2, 3 },
FullyMatchedID = 3,
PartiallyMatchedIDs = new List<int> { 0, 1, 2, 3 },
Added = DateTime.UtcNow,
LastModified = DateTime.UtcNow,
@@ -79,8 +80,24 @@ namespace MPF.Test.RedumpLib
Barcode = "UPC Barcode",
EXEDateBuildDate = "19xx-xx-xx",
ErrorsCount = "0",
Comments = "[T:ISBN] ISBN\r\nComment data",
Contents = "Demos:\r\n- Game Demo 1",
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",
},
},
VersionAndEditions = new VersionAndEditionsSection()
@@ -124,7 +141,7 @@ namespace MPF.Test.RedumpLib
DumpersAndStatus = new DumpersAndStatusSection()
{
Status = DumpStatus.TwoOrMoHumanReadablesGreen,
Status = DumpStatus.TwoOrMoreGreen,
Dumpers = new string[] { "Dumper1", "Dumper2" },
OtherDumpers = "Dumper3",
},
@@ -148,6 +165,16 @@ namespace MPF.Test.RedumpLib
SHA1 = "SHA1",
},
DumpingInfo = new DumpingInfoSection()
{
DumpingProgram = "DiscImageCreator 20500101",
DumpingDate = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss"),
Manufacturer = "ATAPI",
Model = "Optical Drive",
Firmware = "1.23",
ReportedDiscType = "CD-R",
},
Artifacts = new Dictionary<string, string>()
{
["Sample Artifact"] = "Sample Data",

View File

@@ -1,6 +1,6 @@
using System.Linq;
using MPF.Core.Data;
using RedumpLib.Data;
using SabreTools.RedumpLib.Data;
using Xunit;
namespace MPF.Test.Data

View File

@@ -2,22 +2,19 @@
using System.Collections.Generic;
using System.Linq;
using System.Windows.Media;
using static MPF.Core.Data.Interface;
namespace MPF
namespace MPF.UI.Core
{
/// <summary>
/// Variables for UI elements
/// </summary>
public static class Constants
{
// Private lists of known drive speed ranges
private 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 };
private static IReadOnlyList<int> DVD { get; } = CD.Where(s => s <= 24).ToList();
private static IReadOnlyList<int> BD { get; } = CD.Where(s => s <= 16).ToList();
// Create collections for UI based on known drive speeds
public static DoubleCollection SpeedsForCDAsCollection { get; } = GetDoubleCollectionFromIntList(CD);
public static DoubleCollection SpeedsForDVDAsCollection { get; } = GetDoubleCollectionFromIntList(DVD);
public static DoubleCollection SpeedsForHDDVDAsCollection { get; } = GetDoubleCollectionFromIntList(HDDVD);
public static DoubleCollection SpeedsForBDAsCollection { get; } = GetDoubleCollectionFromIntList(BD);
private static DoubleCollection GetDoubleCollectionFromIntList(IReadOnlyList<int> list)
=> new DoubleCollection(list.Select(i => Convert.ToDouble(i)).ToList());

View File

@@ -0,0 +1,64 @@
using System;
using System.Globalization;
using System.Windows.Data;
using MPF.Core.Data;
using MPF.Core.UI.ComboBoxItems;
using SabreTools.RedumpLib.Data;
namespace MPF.UI.Core
{
internal class ElementConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
switch (value)
{
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);
// Null values are treated as a system value
default:
return 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)
{
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;
}
}
}
}

View File

@@ -18,7 +18,7 @@ namespace WPFCustomMessageBox
/// Global parameter to enable (true) or disable (false) the removal of the title bar icon.
/// If you are using a custom window style, the icon removal may cause issues like displaying two title bar (the default windows one and the custom one).
/// </summary>
public static bool RemoveTitleBarIcon = true;
private static readonly bool RemoveTitleBarIcon = true;
/// <summary>
/// Displays a message box that has a message and returns a result.
@@ -173,7 +173,11 @@ 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)
{
@@ -515,11 +519,15 @@ 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;
CustomMessageBoxWindow msg = new CustomMessageBoxWindow(owner, messageBoxText, caption, buttonLayout, icon, RemoveTitleBarIcon);
var msg = new CustomMessageBoxWindow(owner, messageBoxText, caption, buttonLayout, icon, RemoveTitleBarIcon);
if (!string.IsNullOrEmpty(okButtonText))
msg.OkButtonText = okButtonText;
if (!string.IsNullOrEmpty(cancelButtonText))
@@ -545,11 +553,15 @@ 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;
CustomMessageBoxWindow msg = new CustomMessageBoxWindow(owner, messageBoxText, caption, buttonLayout, icon, RemoveTitleBarIcon);
var msg = new CustomMessageBoxWindow(owner, messageBoxText, caption, buttonLayout, icon, RemoveTitleBarIcon);
if (!string.IsNullOrEmpty(yesButtonText))
msg.YesButtonText = yesButtonText;
if (!string.IsNullOrEmpty(noButtonText))
@@ -566,14 +578,14 @@ namespace WPFCustomMessageBox
{
if (timeout.HasValue && timeout.Value <= 0)
{
throw new ArgumentOutOfRangeException("timeout", string.Format("Timeout must be greater than 0."));
throw new ArgumentOutOfRangeException(nameof(timeout), string.Format("Timeout must be greater than 0."));
}
if (timeout.HasValue)
{
//System.Threading.Timer timer = null;
//timer = new System.Threading.Timer(s => { msg.Close(); timer.Dispose(); }, null, timeout.Value, System.Threading.Timeout.Infinite);
System.Timers.Timer timer = new System.Timers.Timer(timeout.Value) { AutoReset = false };
var timer = new System.Timers.Timer(timeout.Value) { AutoReset = false };
timer.Elapsed += delegate {
Application.Current.Dispatcher.Invoke(new Action(() =>
{

View File

@@ -4,7 +4,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
WindowStartupLocation="CenterScreen"
WindowStyle="None"
ShowInTaskbar="False"
ResizeMode="NoResize" SizeToContent="WidthAndHeight"
TextOptions.TextFormattingMode="Display" TextOptions.TextRenderingMode="ClearType" UseLayoutRounding="True"
Title="" MinHeight="155" MaxWidth="470" MinWidth="154"

View File

@@ -10,9 +10,13 @@ namespace WPFCustomMessageBox
/// </summary>
internal partial class CustomMessageBoxWindow : Window
{
private bool _removeTitleBarIcon = true;
private readonly bool _removeTitleBarIcon = true;
#if NET48
public string Caption
#else
public string? Caption
#endif
{
get
{
@@ -24,7 +28,11 @@ namespace WPFCustomMessageBox
}
}
#if NET48
public string Message
#else
public string? Message
#endif
{
get
{
@@ -36,7 +44,11 @@ namespace WPFCustomMessageBox
}
}
#if NET48
public string OkButtonText
#else
public string? OkButtonText
#endif
{
get
{
@@ -48,7 +60,11 @@ namespace WPFCustomMessageBox
}
}
#if NET48
public string CancelButtonText
#else
public string? CancelButtonText
#endif
{
get
{
@@ -60,7 +76,11 @@ namespace WPFCustomMessageBox
}
}
#if NET48
public string YesButtonText
#else
public string? YesButtonText
#endif
{
get
{
@@ -72,7 +92,11 @@ namespace WPFCustomMessageBox
}
}
#if NET48
public string NoButtonText
#else
public string? NoButtonText
#endif
{
get
{
@@ -86,13 +110,20 @@ namespace WPFCustomMessageBox
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)
#endif
{
InitializeComponent();
_removeTitleBarIcon = removeTitleBarIcon;
Focusable = true;
ShowActivated = true;
ShowInTaskbar = true;
if (owner != null)
if (owner != null && owner.IsLoaded)
{
Owner = owner;
WindowStartupLocation = WindowStartupLocation.CenterOwner;
@@ -106,7 +137,7 @@ namespace WPFCustomMessageBox
if (image.HasValue)
DisplayImage(image.Value);
else
Image_MessageBox.Visibility = System.Windows.Visibility.Collapsed;
Image_MessageBox.Visibility = Visibility.Collapsed;
}
protected override void OnSourceInitialized(EventArgs e)
@@ -123,39 +154,39 @@ namespace WPFCustomMessageBox
{
case MessageBoxButton.OKCancel:
// Hide all but OK, Cancel
Button_OK.Visibility = System.Windows.Visibility.Visible;
Button_OK.Visibility = Visibility.Visible;
Button_OK.Focus();
Button_Cancel.Visibility = System.Windows.Visibility.Visible;
Button_Cancel.Visibility = Visibility.Visible;
Button_Yes.Visibility = System.Windows.Visibility.Collapsed;
Button_No.Visibility = System.Windows.Visibility.Collapsed;
Button_Yes.Visibility = Visibility.Collapsed;
Button_No.Visibility = Visibility.Collapsed;
break;
case MessageBoxButton.YesNo:
// Hide all but Yes, No
Button_Yes.Visibility = System.Windows.Visibility.Visible;
Button_Yes.Visibility = Visibility.Visible;
Button_Yes.Focus();
Button_No.Visibility = System.Windows.Visibility.Visible;
Button_No.Visibility = Visibility.Visible;
Button_OK.Visibility = System.Windows.Visibility.Collapsed;
Button_Cancel.Visibility = System.Windows.Visibility.Collapsed;
Button_OK.Visibility = Visibility.Collapsed;
Button_Cancel.Visibility = Visibility.Collapsed;
break;
case MessageBoxButton.YesNoCancel:
// Hide only OK
Button_Yes.Visibility = System.Windows.Visibility.Visible;
Button_Yes.Visibility = Visibility.Visible;
Button_Yes.Focus();
Button_No.Visibility = System.Windows.Visibility.Visible;
Button_Cancel.Visibility = System.Windows.Visibility.Visible;
Button_No.Visibility = Visibility.Visible;
Button_Cancel.Visibility = Visibility.Visible;
Button_OK.Visibility = System.Windows.Visibility.Collapsed;
Button_OK.Visibility = Visibility.Collapsed;
break;
default:
// Hide all but OK
Button_OK.Visibility = System.Windows.Visibility.Visible;
Button_OK.Visibility = Visibility.Visible;
Button_OK.Focus();
Button_Yes.Visibility = System.Windows.Visibility.Collapsed;
Button_No.Visibility = System.Windows.Visibility.Collapsed;
Button_Cancel.Visibility = System.Windows.Visibility.Collapsed;
Button_Yes.Visibility = Visibility.Collapsed;
Button_No.Visibility = Visibility.Collapsed;
Button_Cancel.Visibility = Visibility.Collapsed;
break;
}
}
@@ -184,7 +215,7 @@ namespace WPFCustomMessageBox
}
Image_MessageBox.Source = icon.ToImageSource();
Image_MessageBox.Visibility = System.Windows.Visibility.Visible;
Image_MessageBox.Visibility = Visibility.Visible;
}
private void Button_OK_Click(object sender, RoutedEventArgs e)

View File

@@ -37,7 +37,7 @@ namespace WPFCustomMessageBox
const int SWP_NOMOVE = 0x0002;
const int SWP_NOZORDER = 0x0004;
const int SWP_FRAMECHANGED = 0x0020;
const uint WM_SETICON = 0x0080;
//const uint WM_SETICON = 0x0080;
internal static ImageSource ToImageSource(this Icon icon)
@@ -60,7 +60,11 @@ 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;
@@ -84,7 +88,7 @@ namespace WPFCustomMessageBox
IntPtr hwnd = new WindowInteropHelper(window).Handle;
// Change the extended window style to not show a window icon
int extendedStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
SetWindowLong(hwnd, GWL_EXSTYLE, extendedStyle | WS_EX_DLGMODALFRAME);
_ = SetWindowLong(hwnd, GWL_EXSTYLE, extendedStyle | WS_EX_DLGMODALFRAME);
// Update the window's non-client area to reflect the changes
SetWindowPos(hwnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
}

View File

Before

Width:  |  Height:  |  Size: 423 KiB

After

Width:  |  Height:  |  Size: 423 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@@ -0,0 +1,45 @@
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<PropertyGroup>
<TargetFrameworks>net48;net6.0-windows;net7.0-windows</TargetFrameworks>
<RuntimeIdentifiers>win-x64</RuntimeIdentifiers>
<UseWindowsForms>true</UseWindowsForms>
<UseWPF>true</UseWPF>
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
<Copyright>Copyright (c)2019-2023</Copyright>
<VersionPrefix>2.7.5</VersionPrefix>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)'!='net48'">
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Resource Include="Images\ring-code-guide-1-layer.png" />
<Resource Include="Images\ring-code-guide-2-layer.png" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MPF.Core\MPF.Core.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net48'">
<Reference Include="PresentationFramework.Aero" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SabreTools.RedumpLib" Version="1.1.1" />
</ItemGroup>
<ItemGroup>
<Page Update="UserControls\UserInput.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
</Page>
<Page Update="Windows\DiscInformationWindow.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
</Page>
<Page Update="Windows\RingCodeGuideWindow.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
</Page>
</ItemGroup>
</Project>

524
MPF.UI.Core/Theme.cs Normal file
View File

@@ -0,0 +1,524 @@
using System.Windows;
using System.Windows.Media;
namespace MPF.UI.Core
{
/// <summary>
/// Represents all required mapping values for the UI
/// </summary>
public abstract class Theme
{
#region Application-Wide
/// <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
#region Button
/// <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
#region ComboBox
/// <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
#region CustomMessageBox
/// <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
#region MenuItem
/// <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
#region ProgressBar
/// <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
#region ScrollViewer
/// <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
#region TabItem
/// <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
#region TextBox
/// <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
/// <summary>
/// Apply the theme to the current application
/// </summary>
public void Apply()
{
// Handle application-wide resources
Application.Current.Resources[SystemColors.ActiveBorderBrushKey] = this.ActiveBorderBrush;
Application.Current.Resources[SystemColors.ControlBrushKey] = this.ControlBrush;
Application.Current.Resources[SystemColors.ControlTextBrushKey] = this.ControlTextBrush;
Application.Current.Resources[SystemColors.GrayTextBrushKey] = this.GrayTextBrush;
Application.Current.Resources[SystemColors.WindowBrushKey] = this.WindowBrush;
Application.Current.Resources[SystemColors.WindowTextBrushKey] = this.WindowTextBrush;
// Handle Button-specific resources
Application.Current.Resources["Button.Disabled.Background"] = this.Button_Disabled_Background;
Application.Current.Resources["Button.MouseOver.Background"] = this.Button_MouseOver_Background;
Application.Current.Resources["Button.Pressed.Background"] = this.Button_Pressed_Background;
Application.Current.Resources["Button.Static.Background"] = this.Button_Static_Background;
// Handle ComboBox-specific resources
Application.Current.Resources["ComboBox.Disabled.Background"] = this.ComboBox_Disabled_Background;
Application.Current.Resources["ComboBox.Disabled.Editable.Background"] = this.ComboBox_Disabled_Editable_Background;
Application.Current.Resources["ComboBox.Disabled.Editable.Button.Background"] = this.ComboBox_Disabled_Editable_Button_Background;
Application.Current.Resources["ComboBox.MouseOver.Background"] = this.ComboBox_MouseOver_Background;
Application.Current.Resources["ComboBox.MouseOver.Editable.Background"] = this.ComboBox_MouseOver_Editable_Background;
Application.Current.Resources["ComboBox.MouseOver.Editable.Button.Background"] = this.ComboBox_MouseOver_Editable_Button_Background;
Application.Current.Resources["ComboBox.Pressed.Background"] = this.ComboBox_Pressed_Background;
Application.Current.Resources["ComboBox.Pressed.Editable.Background"] = this.ComboBox_Pressed_Editable_Background;
Application.Current.Resources["ComboBox.Pressed.Editable.Button.Background"] = this.ComboBox_Pressed_Editable_Button_Background;
Application.Current.Resources["ComboBox.Static.Background"] = this.ComboBox_Static_Background;
Application.Current.Resources["ComboBox.Static.Editable.Background"] = this.ComboBox_Static_Editable_Background;
Application.Current.Resources["ComboBox.Static.Editable.Button.Background"] = this.ComboBox_Static_Editable_Button_Background;
// Handle CustomMessageBox-specific resources
Application.Current.Resources["CustomMessageBox.Static.Background"] = this.CustomMessageBox_Static_Background;
// Handle MenuItem-specific resources
Application.Current.Resources["MenuItem.SubMenu.Background"] = this.MenuItem_SubMenu_Background;
Application.Current.Resources["MenuItem.SubMenu.Border"] = this.MenuItem_SubMenu_Border;
// Handle ProgressBar-specific resources
Application.Current.Resources["ProgressBar.Background"] = this.ProgressBar_Background;
// Handle ScrollViewer-specific resources
Application.Current.Resources["ScrollViewer.ScrollBar.Background"] = this.ScrollViewer_ScrollBar_Background;
// Handle TabItem-specific resources
Application.Current.Resources["TabItem.Selected.Background"] = this.TabItem_Selected_Background;
Application.Current.Resources["TabItem.Static.Background"] = this.TabItem_Static_Background;
Application.Current.Resources["TabItem.Static.Border"] = this.TabItem_Static_Border;
// Handle TextBox-specific resources
Application.Current.Resources["TextBox.Static.Background"] = this.TextBox_Static_Background;
}
}
/// <summary>
/// Default light-mode theme
/// </summary>
public class LightModeTheme : Theme
{
public LightModeTheme()
{
// Handle application-wide resources
this.ActiveBorderBrush = null;
this.ControlBrush = null;
this.ControlTextBrush = null;
this.GrayTextBrush = null;
this.WindowBrush = null;
this.WindowTextBrush = null;
// Handle Button-specific resources
this.Button_Disabled_Background = new SolidColorBrush(Color.FromArgb(0xFF, 0xF4, 0xF4, 0xF4));
this.Button_MouseOver_Background = new SolidColorBrush(Color.FromArgb(0xFF, 0xBE, 0xE6, 0xFD));
this.Button_Pressed_Background = new SolidColorBrush(Color.FromArgb(0xFF, 0xC4, 0xE5, 0xF6));
this.Button_Static_Background = new SolidColorBrush(Color.FromArgb(0xFF, 0xDD, 0xDD, 0xDD));
// Handle ComboBox-specific resources
this.ComboBox_Disabled_Background = new SolidColorBrush(Color.FromArgb(0xFF, 0xF0, 0xF0, 0xF0));
this.ComboBox_Disabled_Editable_Background = new SolidColorBrush(Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF));
this.ComboBox_Disabled_Editable_Button_Background = Brushes.Transparent;
this.ComboBox_MouseOver_Background = new LinearGradientBrush(
Color.FromArgb(0xFF, 0xEC, 0xF4, 0xFC),
Color.FromArgb(0xFF, 0xDC, 0xEC, 0xFC),
new Point(0, 0),
new Point(0, 1));
this.ComboBox_MouseOver_Editable_Background = new SolidColorBrush(Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF));
this.ComboBox_MouseOver_Editable_Button_Background = new LinearGradientBrush(
Color.FromArgb(0xFF, 0xEB, 0xF4, 0xFC),
Color.FromArgb(0xFF, 0xDC, 0xEC, 0xFC),
new Point(0, 0),
new Point(0, 1));
this.ComboBox_Pressed_Background = new LinearGradientBrush(
Color.FromArgb(0xFF, 0xDA, 0xEC, 0xFC),
Color.FromArgb(0xFF, 0xC4, 0xE0, 0xFC),
new Point(0, 0),
new Point(0, 1));
this.ComboBox_Pressed_Editable_Background = new SolidColorBrush(Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF));
this.ComboBox_Pressed_Editable_Button_Background = new LinearGradientBrush(
Color.FromArgb(0xFF, 0xDA, 0xEB, 0xFC),
Color.FromArgb(0xFF, 0xC4, 0xE0, 0xFC),
new Point(0, 0),
new Point(0, 1));
this.ComboBox_Static_Background = new LinearGradientBrush(
Color.FromArgb(0xFF, 0xF0, 0xF0, 0xF0),
Color.FromArgb(0xFF, 0xE5, 0xE5, 0xE5),
new Point(0, 0),
new Point(0, 1));
this.ComboBox_Static_Editable_Background = new SolidColorBrush(Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF));
this.ComboBox_Static_Editable_Button_Background = Brushes.Transparent;
// Handle CustomMessageBox-specific resources
this.CustomMessageBox_Static_Background = null;
// Handle MenuItem-specific resources
this.MenuItem_SubMenu_Background = new SolidColorBrush(Color.FromArgb(0xFF, 0xF0, 0xF0, 0xF0));
this.MenuItem_SubMenu_Border = Brushes.DarkGray;
// Handle ProgressBar-specific resources
this.ProgressBar_Background = new SolidColorBrush(Color.FromArgb(0xFF, 0xE6, 0xE6, 0xE6));
// Handle ScrollViewer-specific resources
this.ScrollViewer_ScrollBar_Background = Brushes.LightGray;
// Handle TabItem-specific resources
this.TabItem_Selected_Background = new SolidColorBrush(Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF));
this.TabItem_Static_Background = new LinearGradientBrush(
Color.FromArgb(0xFF, 0xF0, 0xF0, 0xF0),
Color.FromArgb(0xFF, 0xE5, 0xE5, 0xE5),
new Point(0, 0),
new Point(0, 1));
this.TabItem_Static_Border = Brushes.DarkGray;
// Handle TextBox-specific resources
this.TextBox_Static_Background = new SolidColorBrush(Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF));
}
}
/// <summary>
/// Default dark-mode theme
/// </summary>
public class DarkModeTheme : Theme
{
public DarkModeTheme()
{
// Setup needed brushes
var darkModeBrush = new SolidColorBrush { Color = Color.FromArgb(0xff, 0x20, 0x20, 0x20) };
// Handle application-wide resources
this.ActiveBorderBrush = Brushes.Black;
this.ControlBrush = darkModeBrush;
this.ControlTextBrush = Brushes.White;
this.GrayTextBrush = Brushes.DarkGray;
this.WindowBrush = darkModeBrush;
this.WindowTextBrush = Brushes.White;
// Handle Button-specific resources
this.Button_Disabled_Background = darkModeBrush;
this.Button_MouseOver_Background = darkModeBrush;
this.Button_Pressed_Background = darkModeBrush;
this.Button_Static_Background = darkModeBrush;
// Handle ComboBox-specific resources
this.ComboBox_Disabled_Background = darkModeBrush;
this.ComboBox_Disabled_Editable_Background = darkModeBrush;
this.ComboBox_Disabled_Editable_Button_Background = darkModeBrush;
this.ComboBox_MouseOver_Background = darkModeBrush;
this.ComboBox_MouseOver_Editable_Background = darkModeBrush;
this.ComboBox_MouseOver_Editable_Button_Background = darkModeBrush;
this.ComboBox_Pressed_Background = darkModeBrush;
this.ComboBox_Pressed_Editable_Background = darkModeBrush;
this.ComboBox_Pressed_Editable_Button_Background = darkModeBrush;
this.ComboBox_Static_Background = darkModeBrush;
this.ComboBox_Static_Editable_Background = darkModeBrush;
this.ComboBox_Static_Editable_Button_Background = darkModeBrush;
// Handle CustomMessageBox-specific resources
this.CustomMessageBox_Static_Background = darkModeBrush;
// Handle MenuItem-specific resources
this.MenuItem_SubMenu_Background = darkModeBrush;
this.MenuItem_SubMenu_Border = Brushes.DarkGray;
// Handle ProgressBar-specific resources
this.ProgressBar_Background = darkModeBrush;
// Handle ScrollViewer-specific resources
this.ScrollViewer_ScrollBar_Background = darkModeBrush;
// Handle TabItem-specific resources
this.TabItem_Selected_Background = darkModeBrush;
this.TabItem_Static_Background = darkModeBrush;
this.TabItem_Static_Border = Brushes.DarkGray;
// Handle TextBox-specific resources
this.TextBox_Static_Background = darkModeBrush;
}
}
}

View File

@@ -1,9 +1,8 @@
<UserControl x:Class="MPF.UserControls.LogOutput"
<UserControl x:Class="MPF.UI.Core.UserControls.LogOutput"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MPF.UserControls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>

View File

@@ -0,0 +1,228 @@
using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Threading;
using MPF.Core.Data;
namespace MPF.UI.Core.UserControls
{
public partial class LogOutput : UserControl
{
/// <summary>
/// Document representing the text
/// </summary>
internal FlowDocument Document { get; private set; }
/// <summary>
/// Queue of items that need to be logged
/// </summary>
internal ProcessingQueue<LogLine> LogQueue { get; private set; }
/// <summary>
/// Paragraph backing the log
/// </summary>
private readonly Paragraph _paragraph;
/// <summary>
/// Cached value of the last line written
/// </summary>
#if NET48
private Run lastLine = null;
#else
private Run? lastLine = null;
#endif
public LogOutput()
{
InitializeComponent();
// Update the internal state
Document = new FlowDocument()
{
Background = new SolidColorBrush(Color.FromArgb(0xFF, 0x20, 0x20, 0x20))
};
_paragraph = new Paragraph();
Document.Blocks.Add(_paragraph);
// Setup the processing queue
LogQueue = new ProcessingQueue<LogLine>(ProcessLogLine);
// Add handlers
OutputViewer.SizeChanged += OutputViewerSizeChanged;
Output.TextChanged += OnTextChanged;
ClearButton.Click += OnClearButton;
SaveButton.Click += OnSaveButton;
// Update the internal state
Output.Document = Document;
}
#region Logging
/// <summary>
/// Enqueue text to the log with formatting
/// </summary>
/// <param name="logLevel">LogLevel for the log</param>
/// <param name="text">Text to write to the log</param>
public void EnqueueLog(LogLevel logLevel, string text)
{
// Null text gets ignored
if (text == null)
return;
// Enqueue the text
LogQueue.Enqueue(new LogLine(text, logLevel));
}
/// <summary>
/// Log line wrapper
/// </summary>
internal readonly struct LogLine
{
public readonly string Text;
public readonly LogLevel LogLevel;
public LogLine(string text, LogLevel logLevel)
{
this.Text = text;
this.LogLevel = logLevel;
}
/// <summary>
/// Get the foreground Brush for the current LogLevel
/// </summary>
/// <returns>Brush representing the color</returns>
public Brush GetForegroundColor()
{
switch (this.LogLevel)
{
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;
}
}
/// <summary>
/// Generate a Run object from the current LogLine
/// </summary>
/// <returns>Run object based on internal values</returns>
public Run GenerateRun()
{
return new Run { Text = this.Text, Foreground = GetForegroundColor() };
}
}
/// <summary>
/// Process the log lines in the queue
/// </summary>
/// <param name="nextLogLine">LogLine item to process</param>
internal void ProcessLogLine(LogLine nextLogLine)
{
// Null text gets ignored
string nextText = nextLogLine.Text;
if (nextText == null)
return;
try
{
if (nextText.StartsWith("\r"))
ReplaceLastLine(nextLogLine);
else
AppendToTextBox(nextLogLine);
}
catch (Exception ex)
{
// In the event that something fails horribly, we want to log
AppendToTextBox(new LogLine(ex.ToString(), LogLevel.ERROR));
}
}
/// <summary>
/// Append log line to the log text box
/// </summary>
/// <param name="logLine">LogLine value to append</param>
private void AppendToTextBox(LogLine logLine)
{
Dispatcher.Invoke(() =>
{
var run = logLine.GenerateRun();
_paragraph.Inlines.Add(run);
lastLine = run;
});
}
/// <summary>
/// Replace the last line written to the log text box
/// </summary>
/// <param name="logLine">LogLine value to append</param>
private void ReplaceLastLine(LogLine logLine)
{
Dispatcher.Invoke(() =>
{
#if NET48
if (lastLine == null) lastLine = new Run();
#else
lastLine ??= new Run();
#endif
lastLine.Text = logLine.Text;
lastLine.Foreground = logLine.GetForegroundColor();
});
}
#endregion
#region Helpers
/// <summary>
/// Clear all inlines of the paragraph
/// </summary>
private void ClearInlines() => _paragraph.Inlines.Clear();
/// <summary>
/// Save all inlines to console.log
/// </summary>
private void SaveInlines()
{
using (var sw = new StreamWriter(File.OpenWrite("console.log")))
{
foreach (var inline in _paragraph.Inlines)
{
if (inline is Run run)
sw.Write(run.Text);
}
}
}
/// <summary>
/// Scroll the current view to the bottom
/// </summary>
public void ScrollToBottom() => OutputViewer.ScrollToBottom();
#endregion
#region EventHandlers
private void OnClearButton(object sender, EventArgs e)
=> ClearInlines();
private void OnSaveButton(object sender, EventArgs e)
=> SaveInlines();
private void OnTextChanged(object sender, TextChangedEventArgs e)
=> ScrollToBottom();
private void OutputViewerSizeChanged(object sender, SizeChangedEventArgs e)
=> ScrollToBottom();
#endregion
}
}

View File

@@ -1,4 +1,4 @@
<UserControl x:Class="MPF.UserControls.UserInput"
<UserControl x:Class="MPF.UI.Core.UserControls.UserInput"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@@ -6,7 +6,7 @@
d:DesignHeight="450" d:DesignWidth="800" x:Name="userInput">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="0.75*" />
<ColumnDefinition Width="1.25*" />
</Grid.ColumnDefinitions>
@@ -22,7 +22,9 @@
AcceptsTab="{Binding ElementName=userInput, Path=Tab}"
AcceptsReturn="{Binding ElementName=userInput, Path=Enter}"
TextWrapping="{Binding ElementName=userInput, Path=TextWrapping}"
IsReadOnly="{Binding ElementName=userInput, Path=IsReadOnly}"
VerticalContentAlignment="{Binding ElementName=userInput, Path=VerticalContentAlignmentValue}"
VerticalScrollBarVisibility="{Binding ElementName=userInput, Path=ScrollBarVisibility}"/>
HorizontalScrollBarVisibility="{Binding ElementName=userInput, Path=HorizontalScrollBarVisibility}"
VerticalScrollBarVisibility="{Binding ElementName=userInput, Path=VerticalScrollBarVisibility}"/>
</Grid>
</UserControl>

View File

@@ -1,7 +1,7 @@
using System.Windows;
using System.Windows.Controls;
namespace MPF.UserControls
namespace MPF.UI.Core.UserControls
{
/// <summary>
/// Interaction logic for UserInput.xaml
@@ -28,11 +28,17 @@ namespace MPF.UserControls
public static readonly DependencyProperty TextWrappingProperty =
DependencyProperty.Register("TextWrapping", typeof(TextWrapping), typeof(UserInput));
public static readonly DependencyProperty IsReadOnlyProperty =
DependencyProperty.Register("IsReadOnly", typeof(bool), typeof(UserInput));
public static readonly DependencyProperty VerticalContentAlignmentValueProperty =
DependencyProperty.Register("VerticalContentAlignmentValue", typeof(VerticalAlignment), typeof(UserInput));
public static readonly DependencyProperty ScrollBarVisibilityProperty =
DependencyProperty.Register("ScrollBarVisibility", typeof(ScrollBarVisibility), typeof(UserInput));
public static readonly DependencyProperty HorizontalScrollBarVisibilityProperty =
DependencyProperty.Register("HorizontalScrollBarVisibility", typeof(ScrollBarVisibility), typeof(UserInput));
public static readonly DependencyProperty VerticalScrollBarVisibilityProperty =
DependencyProperty.Register("VerticalScrollBarVisibility", typeof(ScrollBarVisibility), typeof(UserInput));
#endregion
@@ -74,21 +80,32 @@ namespace MPF.UserControls
set => SetValue(TextWrappingProperty, value);
}
public bool IsReadOnly
{
get => (bool)GetValue(IsReadOnlyProperty);
set => SetValue(IsReadOnlyProperty, value);
}
public VerticalAlignment VerticalContentAlignmentValue
{
get => (VerticalAlignment)GetValue(VerticalContentAlignmentValueProperty);
set => SetValue(VerticalContentAlignmentValueProperty, value);
}
public ScrollBarVisibility ScrollBarVisibility
public ScrollBarVisibility HorizontalScrollBarVisibility
{
get => (ScrollBarVisibility)GetValue(ScrollBarVisibilityProperty);
set => SetValue(ScrollBarVisibilityProperty, value);
get => (ScrollBarVisibility)GetValue(HorizontalScrollBarVisibilityProperty);
set => SetValue(HorizontalScrollBarVisibilityProperty, value);
}
public ScrollBarVisibility VerticalScrollBarVisibility
{
get => (ScrollBarVisibility)GetValue(VerticalScrollBarVisibilityProperty);
set => SetValue(VerticalScrollBarVisibilityProperty, value);
}
#endregion
public UserInput()
{
// Set default values
@@ -96,8 +113,10 @@ namespace MPF.UserControls
Tab = false;
Enter = false;
TextWrapping = TextWrapping.NoWrap;
IsReadOnly = false;
VerticalContentAlignmentValue = VerticalAlignment.Center;
ScrollBarVisibility = ScrollBarVisibility.Auto;
HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden;
VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
InitializeComponent();
}

View File

@@ -0,0 +1,480 @@
<windows:WindowBase x:Class="MPF.UI.Core.Windows.DiscInformationWindow"
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:controls="clr-namespace:MPF.UI.Core.UserControls"
xmlns:core="clr-namespace:MPF.UI.Core"
xmlns:redump="clr-namespace:SabreTools.RedumpLib.Data;assembly=SabreTools.RedumpLib"
xmlns:windows="clr-namespace:MPF.UI.Core.Windows"
mc:Ignorable="d"
Title="Disc Information" Width="515" WindowStyle="None"
WindowStartupLocation="CenterOwner" ResizeMode="CanMinimize" SizeToContent="Height"
BorderBrush="DarkGray" BorderThickness="2">
<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">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="25"/>
<ColumnDefinition Width="115"/>
<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>
<TextBlock TextAlignment="Center"><Bold>Disc Information</Bold></TextBlock>
</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}}"
BorderBrush="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
Template="{DynamicResource CustomMenuItemTemplate}"/>
<MenuItem Header="Close" Click="CloseButtonClick" Width="185"
Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
Foreground="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"
BorderBrush="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
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>
<TabControl Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
Foreground="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"
Style="{DynamicResource CustomTabControlStyle}">
<TabItem x:Name="CommonInfo" Header="Common Info" Style="{DynamicResource CustomTabItemStyle}">
<ScrollViewer CanContentScroll="False" VerticalScrollBarVisibility="Auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MaxHeight="500">
<StackPanel Orientation="Vertical">
<GroupBox Header="Common Disc Information" Margin="5" Padding="5" BorderThickness="1" BorderBrush="LightGray"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<StackPanel Orientation="Vertical">
<controls:UserInput Label="Title"
Text="{Binding SubmissionInfo.CommonDiscInfo.Title, Mode=TwoWay}"/>
<controls:UserInput x:Name="AlternativeTitleTextBox" Label="Alternative Title"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)AlternativeTitle], Mode=TwoWay}"/>
<controls:UserInput Label="Foreign Title (Non-Latin)"
Text="{Binding SubmissionInfo.CommonDiscInfo.ForeignTitleNonLatin, Mode=TwoWay}"/>
<controls:UserInput x:Name="AlternativeForeignTitleTextBox" Label="Alternative Foreign Title"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)AlternativeForeignTitle], Mode=TwoWay}"/>
<controls:UserInput Label="Disc Number / Letter"
Text="{Binding SubmissionInfo.CommonDiscInfo.DiscNumberLetter, Mode=TwoWay}"/>
<controls:UserInput Label="Disc Title"
Text="{Binding SubmissionInfo.CommonDiscInfo.DiscTitle, Mode=TwoWay}"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.75*" />
<ColumnDefinition Width="1.25*" />
</Grid.ColumnDefinitions>
<Label x:Name="CategoryLabel" Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Right" Content="Category" />
<ComboBox x:Name="CategoryComboBox" Grid.Row="0" Grid.Column="1" Height="22" HorizontalAlignment="Stretch"
ItemsSource="{Binding Categories}" SelectedItem="{Binding SubmissionInfo.CommonDiscInfo.Category, Converter={StaticResource ElementConverter}, Mode=TwoWay}"
Style="{DynamicResource CustomComboBoxStyle}" />
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.75*" />
<ColumnDefinition Width="1.25*" />
</Grid.ColumnDefinitions>
<Label x:Name="RegionLabel" Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Right" Content="Region" />
<ComboBox x:Name="RegionComboBox" Grid.Row="0" Grid.Column="1" Height="22" HorizontalAlignment="Stretch"
ItemsSource="{Binding Regions}" SelectedItem="{Binding SubmissionInfo.CommonDiscInfo.Region, Converter={StaticResource ElementConverter}, Mode=TwoWay}"
Style="{DynamicResource CustomComboBoxStyle}" />
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.75*" />
<ColumnDefinition Width="1.25*" />
</Grid.ColumnDefinitions>
<Label x:Name="LanguagesLabel" Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Right" Content="Languages" />
<ComboBox x:Name="LanguagesComboBox" Grid.Row="0" Grid.Column="1" Height="24" HorizontalAlignment="Stretch"
ItemsSource="{Binding Languages}"
SelectedIndex="0" Style="{DynamicResource CustomComboBoxStyle}">
<ComboBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}" IsChecked="{Binding IsChecked}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
<Grid x:Name="LanguageSelectionGrid" Visibility="Collapsed">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.75*" />
<ColumnDefinition Width="1.25*" />
</Grid.ColumnDefinitions>
<Label x:Name="LanguageSelectionLabel" Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Right" Content="Language Selection Via" />
<ComboBox x:Name="LanguageSelectionComboBox" Grid.Row="0" Grid.Column="1" Height="24" HorizontalAlignment="Stretch"
ItemsSource="{Binding LanguageSelections}"
SelectedIndex="0" Style="{DynamicResource CustomComboBoxStyle}">
<ComboBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}" IsChecked="{Binding IsChecked}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
<controls:UserInput Label="Serial" Text="{Binding SubmissionInfo.CommonDiscInfo.Serial, Mode=TwoWay}"/>
<controls:UserInput Label="Barcode" Text="{Binding SubmissionInfo.CommonDiscInfo.Barcode, Mode=TwoWay}"/>
</StackPanel>
</GroupBox>
<GroupBox Header="Version and Editions" Margin="5" Padding="5" BorderThickness="1" BorderBrush="LightGray"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<StackPanel Orientation="Vertical">
<controls:UserInput Label="Version" Text="{Binding SubmissionInfo.VersionAndEditions.Version, Mode=TwoWay}"/>
<controls:UserInput Label="Edition" Text="{Binding SubmissionInfo.VersionAndEditions.OtherEditions, Mode=TwoWay}"/>
</StackPanel>
</GroupBox>
</StackPanel>
</ScrollViewer>
</TabItem>
<TabItem Header="Additional Info" Style="{DynamicResource CustomTabItemStyle}">
<ScrollViewer CanContentScroll="False" VerticalScrollBarVisibility="Auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MaxHeight="500">
<StackPanel Orientation="Vertical">
<GroupBox Header="Extras" Margin="5" Padding="5" BorderThickness="1" BorderBrush="LightGray"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<StackPanel Orientation="Vertical">
<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="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!"
Text="{Binding SubmissionInfo.CopyProtection.Protection, Mode=TwoWay}" TextHeight="75" TextWrapping="Wrap"
VerticalContentAlignmentValue="Top" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.75*" />
<ColumnDefinition Width="1.25*" />
</Grid.ColumnDefinitions>
<Label/>
<CheckBox Grid.Column="1" x:Name="PostgapTypeCheckBox" Content="Postgap type (Form 2)" Margin="0,4"
IsChecked="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)PostgapType], Mode=TwoWay}"/>
</Grid>
<controls:UserInput x:Name="SeriesTextBox" Label="Series"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)Series], Mode=TwoWay}"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.75*" />
<ColumnDefinition Width="1.25*" />
</Grid.ColumnDefinitions>
<Label/>
<CheckBox Grid.Column="1" x:Name="VCDCheckBox" Content="V-CD" Margin="0,4"
IsChecked="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)VCD], Mode=TwoWay}"/>
</Grid>
</StackPanel>
</GroupBox>
<GroupBox Header="Physical Identifiers" Margin="5" Padding="5" BorderThickness="1" BorderBrush="LightGray"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<StackPanel Orientation="Vertical">
<controls:UserInput x:Name="BBFCRegistrationNumberTextBox" Label="BBFC Reg. No."
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)BBFCRegistrationNumber], Mode=TwoWay}"/>
<controls:UserInput x:Name="DiscHologramIDTextBox" Label="Disc Hologram ID"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)DiscHologramID], Mode=TwoWay}"/>
<controls:UserInput x:Name="DNASDiscIDTextBox" Label="DNAS Disc ID"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)DNASDiscID], Mode=TwoWay}"/>
<controls:UserInput x:Name="ISBNTextBox" Label="ISBN"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)ISBN], Mode=TwoWay}"/>
<controls:UserInput x:Name="ISSNTextBox" Label="ISSN"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)ISSN], Mode=TwoWay}"/>
<controls:UserInput x:Name="PPNTextBox" Label="PPN"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)PPN], Mode=TwoWay}"/>
<controls:UserInput x:Name="VFCCodeTextBox" Label="VFC code"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)VFCCode], Mode=TwoWay}"/>
</StackPanel>
</GroupBox>
<GroupBox Header="Publisher Identifiers" Margin="5" Padding="5" BorderThickness="1" BorderBrush="LightGray"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<StackPanel Orientation="Vertical">
<controls:UserInput x:Name="AcclaimIDTextBox" Label="Acclaim ID"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)AcclaimID], Mode=TwoWay}"/>
<controls:UserInput x:Name="ActivisionIDTextBox" Label="Activision ID"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)ActivisionID], Mode=TwoWay}"/>
<controls:UserInput x:Name="BandaiIDTextBox" Label="Bandai ID"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)BandaiID], Mode=TwoWay}"/>
<controls:UserInput x:Name="CDProjektIDTextBox" Label="CD Projekt ID"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)CDProjektID], Mode=TwoWay}"/>
<controls:UserInput x:Name="ElectronicArtsIDTextBox" Label="Electronic Arts ID"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)ElectronicArtsID], Mode=TwoWay}"/>
<controls:UserInput x:Name="FoxInteractiveIDTextBox" Label="Fox Interactive ID"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)FoxInteractiveID], Mode=TwoWay}"/>
<controls:UserInput x:Name="GTInteractiveIDTextBox" Label="GT Interactive ID"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)GTInteractiveID], Mode=TwoWay}"/>
<controls:UserInput x:Name="JASRACIDTextBox" Label="JASRAC ID"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)JASRACID], Mode=TwoWay}"/>
<controls:UserInput x:Name="KingRecordsIDTextBox" Label="King Records ID"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)KingRecordsID], Mode=TwoWay}"/>
<controls:UserInput x:Name="KoeiIDTextBox" Label="Koei ID"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)KoeiID], Mode=TwoWay}"/>
<controls:UserInput x:Name="KonamiIDTextBox" Label="Konami ID"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)KonamiID], Mode=TwoWay}"/>
<controls:UserInput x:Name="LucasArtsIDTextBox" Label="LucasArts ID"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)LucasArtsID], Mode=TwoWay}"/>
<controls:UserInput x:Name="MicrosoftIDTextBox" Label="Microsoft ID"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)MicrosoftID], Mode=TwoWay}"/>
<controls:UserInput x:Name="NaganoIDTextBox" Label="Nagano ID"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)NaganoID], Mode=TwoWay}"/>
<controls:UserInput x:Name="NamcoIDTextBox" Label="Namco ID"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)NamcoID], Mode=TwoWay}"/>
<controls:UserInput x:Name="NipponIchiSoftwareIDTextBox" Label="Nippon Ichi Software ID"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)NipponIchiSoftwareID], Mode=TwoWay}"/>
<controls:UserInput x:Name="OriginIDTextBox" Label="Origin ID"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)OriginID], Mode=TwoWay}"/>
<controls:UserInput x:Name="PonyCanyonIDTextBox" Label="Pony Canyon ID"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)PonyCanyonID], Mode=TwoWay}"/>
<controls:UserInput x:Name="SegaIDTextBox" Label="Sega ID"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)SegaID], Mode=TwoWay}"/>
<controls:UserInput x:Name="SelenIDTextBox" Label="Selen ID"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)SelenID], Mode=TwoWay}"/>
<controls:UserInput x:Name="SierraIDTextBox" Label="Sierra ID"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)SierraID], Mode=TwoWay}"/>
<controls:UserInput x:Name="TaitoIDTextBox" Label="Taito ID"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)TaitoID], Mode=TwoWay}"/>
<controls:UserInput x:Name="UbisoftIDTextBox" Label="Ubisoft ID"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)UbisoftID], Mode=TwoWay}"/>
<controls:UserInput x:Name="ValveIDTextBox" Label="Valve ID"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)ValveID], Mode=TwoWay}"/>
</StackPanel>
</GroupBox>
</StackPanel>
</ScrollViewer>
</TabItem>
<TabItem Header="Contents" Style="{DynamicResource CustomTabItemStyle}">
<ScrollViewer CanContentScroll="False" VerticalScrollBarVisibility="Auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MaxHeight="500">
<StackPanel Orientation="Vertical">
<controls:UserInput x:Name="GeneralContent" Label="General"
Text="{Binding SubmissionInfo.CommonDiscInfo.Contents, Mode=TwoWay}" TextHeight="50"
Enter="True" TextWrapping="Wrap" VerticalContentAlignmentValue="Top"/>
<!-- Games -->
<controls:UserInput x:Name="GamesTextBox" Label="Games" TextHeight="50"
Enter="True" TextWrapping="Wrap" VerticalContentAlignmentValue="Top"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.ContentsSpecialFields[(redump:SiteCode)Games], Mode=TwoWay}"/>
<controls:UserInput x:Name="NetYarozeGamesTextBox" Label="Net Yaroze Games" TextHeight="50"
Enter="True" TextWrapping="Wrap" VerticalContentAlignmentValue="Top"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.ContentsSpecialFields[(redump:SiteCode)NetYarozeGames], Mode=TwoWay}"/>
<!-- Demos -->
<controls:UserInput x:Name="PlayableDemosTextBox" Label="Playable Demos" TextHeight="50"
Enter="True" TextWrapping="Wrap" VerticalContentAlignmentValue="Top"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.ContentsSpecialFields[(redump:SiteCode)PlayableDemos], Mode=TwoWay}"/>
<controls:UserInput x:Name="RollingDemosTextBox" Label="Rolling Demos" TextHeight="50"
Enter="True" TextWrapping="Wrap" VerticalContentAlignmentValue="Top"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.ContentsSpecialFields[(redump:SiteCode)RollingDemos], Mode=TwoWay}"/>
<controls:UserInput x:Name="TechDemosTextBox" Label="Tech Demos" TextHeight="50"
Enter="True" TextWrapping="Wrap" VerticalContentAlignmentValue="Top"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.ContentsSpecialFields[(redump:SiteCode)TechDemos], Mode=TwoWay}"/>
<!-- Video -->
<controls:UserInput x:Name="GameFootageTextBox" Label="Game Footage" TextHeight="50"
Enter="True" TextWrapping="Wrap" VerticalContentAlignmentValue="Top"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.ContentsSpecialFields[(redump:SiteCode)GameFootage], Mode=TwoWay}"/>
<controls:UserInput x:Name="VideosTextBox" Label="Videos" TextHeight="50"
Enter="True" TextWrapping="Wrap" VerticalContentAlignmentValue="Top"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.ContentsSpecialFields[(redump:SiteCode)Videos], Mode=TwoWay}"/>
<!-- Miscellaneous -->
<controls:UserInput x:Name="PatchesTextBox" Label="Patches" TextHeight="50"
Enter="True" TextWrapping="Wrap" VerticalContentAlignmentValue="Top"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.ContentsSpecialFields[(redump:SiteCode)Patches], Mode=TwoWay}"/>
<controls:UserInput x:Name="SavegamesTextBox" Label="Savegames" TextHeight="50"
Enter="True" TextWrapping="Wrap" VerticalContentAlignmentValue="Top"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.ContentsSpecialFields[(redump:SiteCode)Savegames], Mode=TwoWay}"/>
<controls:UserInput x:Name="ExtrasTextBox" Label="Extras" TextHeight="50"
Enter="True" TextWrapping="Wrap" VerticalContentAlignmentValue="Top"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.ContentsSpecialFields[(redump:SiteCode)Extras], Mode=TwoWay}"/>
</StackPanel>
</ScrollViewer>
</TabItem>
<TabItem Header="Ringcodes" Style="{DynamicResource CustomTabItemStyle}">
<ScrollViewer CanContentScroll="False" VerticalScrollBarVisibility="Auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MaxHeight="500">
<StackPanel Orientation="Vertical">
<GroupBox x:Name="L0Info" Header="Data/L0 Info" Margin="5" Padding="5" BorderThickness="1" BorderBrush="LightGray"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<StackPanel Orientation="Vertical">
<controls:UserInput x:Name="L0MasteringRing" Label="Data/L0 Mastering Ring"
Text="{Binding SubmissionInfo.CommonDiscInfo.Layer0MasteringRing, Mode=TwoWay}"/>
<controls:UserInput x:Name="L0MasteringSID" Label="Data/L0 Mastering SID"
Text="{Binding SubmissionInfo.CommonDiscInfo.Layer0MasteringSID, Mode=TwoWay}"/>
<controls:UserInput x:Name="L0Toolstamp" Label="Data/L0 Toolstamp/Mastering Code"
Text="{Binding SubmissionInfo.CommonDiscInfo.Layer0ToolstampMasteringCode, Mode=TwoWay}"/>
<controls:UserInput x:Name="L0MouldSID" Label="Data/L0 Mould SID"
Text="{Binding SubmissionInfo.CommonDiscInfo.Layer0MouldSID, Mode=TwoWay}"/>
<controls:UserInput x:Name="L0AdditionalMould" Label="Data/L0 Additional Mould"
Text="{Binding SubmissionInfo.CommonDiscInfo.Layer0AdditionalMould, Mode=TwoWay}"/>
</StackPanel>
</GroupBox>
<GroupBox x:Name="L1Info" Header="Label/L1 Info" Margin="5" Padding="5" BorderThickness="1" BorderBrush="LightGray"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<StackPanel Orientation="Vertical">
<controls:UserInput x:Name="L1MasteringRing" Label="Label/L1 Mastering Ring"
Text="{Binding SubmissionInfo.CommonDiscInfo.Layer1MasteringRing, Mode=TwoWay}"/>
<controls:UserInput x:Name="L1MasteringSID" Label="Label/L1 Mastering SID"
Text="{Binding SubmissionInfo.CommonDiscInfo.Layer1MasteringSID, Mode=TwoWay}"/>
<controls:UserInput x:Name="L1Toolstamp" Label="Label/L1 Toolstamp/Mastering Code"
Text="{Binding SubmissionInfo.CommonDiscInfo.Layer1ToolstampMasteringCode, Mode=TwoWay}"/>
<controls:UserInput x:Name="L1MouldSID" Label="Label/L1 Mould SID"
Text="{Binding SubmissionInfo.CommonDiscInfo.Layer1MouldSID, Mode=TwoWay}"/>
<controls:UserInput x:Name="L1AdditionalMould" Label="Label/L1 Additional Mould"
Text="{Binding SubmissionInfo.CommonDiscInfo.Layer1AdditionalMould, Mode=TwoWay}"/>
</StackPanel>
</GroupBox>
<GroupBox x:Name="L2Info" Header="L2 Info" Visibility="Collapsed" Margin="5" Padding="5" BorderThickness="1" BorderBrush="LightGray"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<StackPanel Orientation="Vertical">
<controls:UserInput x:Name="L2MasteringRing" Label="L2 Mastering Ring"
Text="{Binding SubmissionInfo.CommonDiscInfo.Layer2MasteringRing, Mode=TwoWay}"/>
<controls:UserInput x:Name="L2MasteringSID" Label="L2 Mastering SID"
Text="{Binding SubmissionInfo.CommonDiscInfo.Layer2MasteringSID, Mode=TwoWay}"/>
<controls:UserInput x:Name="L2Toolstamp" Label="L2 Toolstamp/Mastering Code"
Text="{Binding SubmissionInfo.CommonDiscInfo.Layer2ToolstampMasteringCode, Mode=TwoWay}"/>
</StackPanel>
</GroupBox>
<GroupBox x:Name="L3Info" Header="L3 Info" Visibility="Collapsed" Margin="5" Padding="5" BorderThickness="1" BorderBrush="LightGray"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<StackPanel Orientation="Vertical">
<controls:UserInput x:Name="L3MasteringRing" Label="L3 Mastering Ring"
Text="{Binding SubmissionInfo.CommonDiscInfo.Layer3MasteringRing, Mode=TwoWay}"/>
<controls:UserInput x:Name="L3MasteringSID" Label="L3 Mastering SID"
Text="{Binding SubmissionInfo.CommonDiscInfo.Layer3MasteringSID, Mode=TwoWay}"/>
<controls:UserInput x:Name="L3Toolstamp" Label="L3 Toolstamp/Mastering Code"
Text="{Binding SubmissionInfo.CommonDiscInfo.Layer3ToolstampMasteringCode, Mode=TwoWay}"/>
</StackPanel>
</GroupBox>
</StackPanel>
</ScrollViewer>
</TabItem>
<TabItem Header="Read-Only Info" Style="{DynamicResource CustomTabItemStyle}">
<ScrollViewer CanContentScroll="False" VerticalScrollBarVisibility="Auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MaxHeight="500">
<StackPanel Orientation="Vertical">
<controls:UserInput x:Name="FullyMatchedID" Label="Fully Matched ID" IsReadOnly="True"
Text="{Binding SubmissionInfo.FullyMatchedID, Mode=TwoWay}"/>
<controls:UserInput x:Name="PartiallyMatchedIDs" Label="Partially Matched ID(s)" IsReadOnly="True"/>
<controls:UserInput x:Name="AntiModchip" Label="Anti-Modchip" IsReadOnly="True"
Text="{Binding SubmissionInfo.CopyProtection.AntiModchip, Mode=TwoWay}"/>
<controls:UserInput x:Name="DiscOffset" Label="Disc Offset" IsReadOnly="True"
Text="{Binding SubmissionInfo.TracksAndWriteOffsets.OtherWriteOffsets, Mode=TwoWay}"/>
<controls:UserInput x:Name="DMIHash" Label="DMI Hash" IsReadOnly="True"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)DMIHash], Mode=TwoWay}"/>
<controls:UserInput x:Name="EDC" Label="EDC" IsReadOnly="True"
Text="{Binding SubmissionInfo.EDC.EDC, Mode=TwoWay}"/>
<controls:UserInput x:Name="ErrorsCount" Label="Error(s) Count" IsReadOnly="True"
Text="{Binding SubmissionInfo.CommonDiscInfo.ErrorsCount, Mode=TwoWay}"/>
<controls:UserInput x:Name="EXEDateBuildDate" Label="EXE/Build Date" IsReadOnly="True"
Text="{Binding SubmissionInfo.CommonDiscInfo.EXEDateBuildDate, Mode=TwoWay}"/>
<controls:UserInput x:Name="Filename" Label="Filename" IsReadOnly="True"
TextHeight="75" TextWrapping="NoWrap"
VerticalContentAlignmentValue="Top" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)Filename], Mode=TwoWay}"/>
<controls:UserInput x:Name="Header" Label="Header" IsReadOnly="True"
Text="{Binding SubmissionInfo.Extras.Header, Mode=TwoWay}" TextHeight="75" TextWrapping="NoWrap"
VerticalContentAlignmentValue="Top" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"/>
<controls:UserInput x:Name="InternalName" Label="Internal Names" IsReadOnly="True"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)InternalName], Mode=TwoWay}"/>
<controls:UserInput x:Name="InternalSerialName" Label="Internal Serial" IsReadOnly="True"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)InternalSerialName], Mode=TwoWay}"/>
<controls:UserInput x:Name="Multisession" Label="Multisession" IsReadOnly="True" TextHeight="75" TextWrapping="NoWrap"
VerticalContentAlignmentValue="Top" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)Multisession], Mode=TwoWay}"/>
<controls:UserInput x:Name="LibCrypt" Label="LibCrypt" IsReadOnly="True"
Text="{Binding SubmissionInfo.CopyProtection.LibCrypt, Mode=TwoWay}"/>
<controls:UserInput x:Name="LibCryptData" Label="LibCrypt Data" IsReadOnly="True"
Text="{Binding SubmissionInfo.CopyProtection.LibCryptData, Mode=TwoWay}" TextHeight="75" TextWrapping="NoWrap"
VerticalContentAlignmentValue="Top" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"/>
<controls:UserInput x:Name="PFIHash" Label="PFI Hash" IsReadOnly="True"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)PFIHash], Mode=TwoWay}"/>
<controls:UserInput x:Name="PIC" Label="PIC" IsReadOnly="True"
Text="{Binding SubmissionInfo.Extras.PIC, Mode=TwoWay}" TextHeight="75" TextWrapping="NoWrap"
VerticalContentAlignmentValue="Top" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"/>
<controls:UserInput x:Name="PVD" Label="PVD" IsReadOnly="True"
Text="{Binding SubmissionInfo.Extras.PVD, Mode=TwoWay}" TextHeight="75" TextWrapping="NoWrap"
VerticalContentAlignmentValue="Top" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"/>
<controls:UserInput x:Name="RingNonZeroDataStart" Label="Ring Non-Zero Data Start" IsReadOnly="True"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)RingNonZeroDataStart], Mode=TwoWay}"/>
<controls:UserInput x:Name="SecuROMData" Label="SecuROM Data" IsReadOnly="True"
Text="{Binding SubmissionInfo.CopyProtection.SecuROMData, Mode=TwoWay}" TextHeight="75" TextWrapping="NoWrap"
VerticalContentAlignmentValue="Top" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"/>
<controls:UserInput x:Name="SSHash" Label="Security Sector Hash" IsReadOnly="True"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)SSHash], Mode=TwoWay}"/>
<controls:UserInput x:Name="SecuritySectorRanges" Label="Security Sector Ranges" IsReadOnly="True"
Text="{Binding SubmissionInfo.Extras.SecuritySectorRanges, Mode=TwoWay}" TextHeight="75" TextWrapping="NoWrap"
VerticalContentAlignmentValue="Top" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"/>
<controls:UserInput x:Name="SSVersion" Label="Security Sector Version" IsReadOnly="True"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)SSVersion], Mode=TwoWay}"/>
<controls:UserInput x:Name="UniversalHash" Label="Universal Hash (SHA-1)" IsReadOnly="True"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)UniversalHash], Mode=TwoWay}"/>
<controls:UserInput x:Name="VolumeLabel" Label="Volume Label" IsReadOnly="True"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)VolumeLabel], Mode=TwoWay}"/>
<controls:UserInput x:Name="XeMID" Label="XeMID" IsReadOnly="True"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)XeMID], Mode=TwoWay}"/>
<controls:UserInput x:Name="XMID" Label="XMID" IsReadOnly="True"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)XMID], Mode=TwoWay}"/>
<!-- TODO: Add track/checksum data once there's a reasonable way to do so -->
</StackPanel>
</ScrollViewer>
</TabItem>
</TabControl>
<!-- Accept / Cancel -->
<GroupBox Margin="5,5,5,5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<UniformGrid Columns="3" Margin="5,5,5,5" Height="28">
<Button Name="AcceptButton" Height="25" Width="80" Content="Accept"
Style="{DynamicResource CustomButtonStyle}" />
<Button Name="CancelButton" Height="25" Width="80" Content="Cancel"
Style="{DynamicResource CustomButtonStyle}" />
<Button Name="RingCodeGuideButton" Grid.Column="2" Height="25" Width="120" Content="Ring Code Guide"
Style="{DynamicResource CustomButtonStyle}" />
</UniformGrid>
</GroupBox>
</StackPanel>
</Grid>
</windows:WindowBase>

View File

@@ -0,0 +1,402 @@
using System.Linq;
using System.Windows;
using MPF.Core.Data;
using MPF.Core.UI.ViewModels;
using MPF.Core.Utilities;
using SabreTools.RedumpLib.Data;
namespace MPF.UI.Core.Windows
{
/// <summary>
/// Interaction logic for DiscInformationWindow.xaml
/// </summary>
public partial class DiscInformationWindow : WindowBase
{
/// <summary>
/// Read-only access to the current disc information view model
/// </summary>
public DiscInformationViewModel DiscInformationViewModel => DataContext as DiscInformationViewModel ?? new DiscInformationViewModel(new Options(), new SubmissionInfo());
/// <summary>
/// Constructor
/// </summary>
#if NET48
public DiscInformationWindow(Options options, SubmissionInfo submissionInfo)
#else
public DiscInformationWindow(Options options, SubmissionInfo? submissionInfo)
#endif
{
InitializeComponent();
DataContext = new DiscInformationViewModel(options, submissionInfo);
DiscInformationViewModel.Load();
// Limit lists, if necessary
if (options.EnableRedumpCompatibility)
{
DiscInformationViewModel.SetRedumpRegions();
DiscInformationViewModel.SetRedumpLanguages();
}
// Add handlers
AcceptButton.Click += OnAcceptClick;
CancelButton.Click += OnCancelClick;
RingCodeGuideButton.Click += OnRingCodeGuideClick;
// Update UI with new values
ManipulateFields(options, submissionInfo);
}
#region Helpers
/// <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)
EnableTabsInInputFields();
// Hide read-only fields that don't have values set
HideReadOnlyFields(submissionInfo);
// Different media types mean different fields available
UpdateFromDiscType(submissionInfo);
// Different systems mean different fields available
UpdateFromSystemType(submissionInfo);
}
/// <summary>
/// Enable tab entry on supported fields
/// </summary>
/// TODO: See if these can be done by binding
private void EnableTabsInInputFields()
{
// Additional Information
CommentsTextBox.Tab = true;
// Contents
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;
// L0
L0MasteringRing.Tab = true;
L0MasteringSID.Tab = true;
L0Toolstamp.Tab = true;
L0MouldSID.Tab = true;
L0AdditionalMould.Tab = true;
// L1
L1MasteringRing.Tab = true;
L1MasteringSID.Tab = true;
L1Toolstamp.Tab = true;
L1MouldSID.Tab = true;
L1AdditionalMould.Tab = true;
// L2
L2MasteringRing.Tab = true;
L2MasteringSID.Tab = true;
L2Toolstamp.Tab = true;
// L3
L3MasteringRing.Tab = true;
L3MasteringSID.Tab = true;
L3Toolstamp.Tab = true;
}
/// <summary>
/// Hide any optional, read-only fields if they don't have a value
/// </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 (submissionInfo.FullyMatchedID == null)
FullyMatchedID.Visibility = Visibility.Collapsed;
if (submissionInfo.PartiallyMatchedIDs == null)
PartiallyMatchedIDs.Visibility = Visibility.Collapsed;
else
PartiallyMatchedIDs.Text = string.Join(", ", submissionInfo.PartiallyMatchedIDs);
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.IsNullOrWhiteSpace(submissionInfo.CommonDiscInfo?.ErrorsCount))
ErrorsCount.Visibility = Visibility.Collapsed;
if (string.IsNullOrWhiteSpace(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))
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.IsNullOrWhiteSpace(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))
PIC.Visibility = Visibility.Collapsed;
if (string.IsNullOrWhiteSpace(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))
SecuROMData.Visibility = Visibility.Collapsed;
if (submissionInfo.CommonDiscInfo?.CommentsSpecialFields?.ContainsKey(SiteCode.SSHash) != true)
SSHash.Visibility = Visibility.Collapsed;
if (string.IsNullOrWhiteSpace(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;
}
/// <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;
bool reverseOrder = system.HasReversedRingcodes();
switch (submissionInfo?.CommonDiscInfo?.Media)
{
case DiscType.CD:
case DiscType.GDROM:
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";
L1Info.Header = "Label Side";
L1MasteringRing.Visibility = Visibility.Collapsed;
L1MasteringSID.Visibility = Visibility.Collapsed;
L1Toolstamp.Visibility = Visibility.Collapsed;
L1MouldSID.Label = "Mould SID";
L1AdditionalMould.Label = "Additional Mould";
break;
case DiscType.DVD5:
case DiscType.DVD9:
case DiscType.HDDVDSL:
case DiscType.HDDVDDL:
case DiscType.BD25:
case DiscType.BD33:
case DiscType.BD50:
case DiscType.BD66:
case DiscType.BD100:
case DiscType.BD128:
case DiscType.NintendoGameCubeGameDisc:
case DiscType.NintendoWiiOpticalDiscSL:
case DiscType.NintendoWiiOpticalDiscDL:
case DiscType.NintendoWiiUOpticalDiscSL:
// Quad-layer discs
if (submissionInfo?.SizeAndChecksums?.Layerbreak3 != default(long))
{
L2Info.Visibility = Visibility.Visible;
L3Info.Visibility = Visibility.Visible;
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";
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";
L2Info.Header = "Layer 2";
L2MasteringRing.Label = "Mastering Ring";
L2MasteringSID.Label = "Mastering SID";
L2Toolstamp.Label = "Toolstamp/Mastering Code";
L3Info.Header = reverseOrder ? "Layer 3 (Inner)" : "Layer 3 (Outer)";
L3MasteringRing.Label = "Mastering Ring";
L3MasteringSID.Label = "Mastering SID";
L3Toolstamp.Label = "Toolstamp/Mastering Code";
}
// Triple-layer discs
else if (submissionInfo?.SizeAndChecksums?.Layerbreak2 != default(long))
{
L2Info.Visibility = Visibility.Visible;
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";
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";
L2Info.Header = reverseOrder ? "Layer 2 (Inner)" : "Layer 2 (Outer)";
L2MasteringRing.Label = "Mastering Ring";
L2MasteringSID.Label = "Mastering SID";
L2Toolstamp.Label = "Toolstamp/Mastering Code";
}
// Double-layer discs
else if (submissionInfo?.SizeAndChecksums?.Layerbreak != default(long))
{
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";
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";
}
// Single-layer discs
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";
L1Info.Header = "Label Side";
L1MasteringRing.Label = "Mastering Ring";
L1MasteringSID.Label = "Mastering SID";
L1Toolstamp.Label = "Toolstamp/Mastering Code";
L1MouldSID.Label = "Mould SID";
L1AdditionalMould.Label = "Additional Mould";
}
break;
// All other media we assume to have no rings
default:
L0Info.Visibility = Visibility.Collapsed;
L1Info.Visibility = Visibility.Collapsed;
L2Info.Visibility = Visibility.Collapsed;
L3Info.Visibility = Visibility.Collapsed;
break;
}
}
/// <summary>
/// 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.SonyPlayStation2:
LanguageSelectionGrid.Visibility = Visibility.Visible;
break;
}
}
#endregion
#region Event Handlers
/// <summary>
/// Handler for AcceptButton Click event
/// </summary>
private void OnAcceptClick(object sender, RoutedEventArgs e)
{
DiscInformationViewModel.Save();
DialogResult = true;
Close();
}
/// <summary>
/// Handler for CancelButton Click event
/// </summary>
private void OnCancelClick(object sender, RoutedEventArgs e)
{
DialogResult = false;
Close();
}
/// <summary>
/// Handler for RingCodeGuideButton Click event
/// </summary>
private void OnRingCodeGuideClick(object sender, RoutedEventArgs e)
{
var ringCodeGuideWindow = new RingCodeGuideWindow()
{
Owner = this,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
};
ringCodeGuideWindow.Show();
}
#endregion
}
}

View File

@@ -1,30 +1,33 @@
<windows:WindowBase x:Class="MPF.Windows.MainWindow"
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:controls="clr-namespace:MPF.UserControls"
xmlns:viewModels="clr-namespace:MPF.GUI.ViewModels"
xmlns:windows="clr-namespace:MPF.Windows"
mc:Ignorable="d"
Title="Media Preservation Frontend" Width="600" WindowStyle="None"
WindowStartupLocation="CenterScreen" ResizeMode="CanMinimize" SizeToContent="Height"
BorderBrush="DarkGray" BorderThickness="2">
<WindowChrome.WindowChrome>
<WindowChrome CaptionHeight="0" ResizeBorderThickness="0" />
</WindowChrome.WindowChrome>
<coreWindows:WindowBase x:Class="MPF.UI.Core.Windows.MainWindow"
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:controls="clr-namespace:MPF.UI.Core.UserControls"
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="Media Preservation Frontend" Width="600" WindowStyle="None"
WindowStartupLocation="CenterScreen" ResizeMode="CanMinimize" SizeToContent="Height"
BorderBrush="DarkGray" BorderThickness="2">
<Window.DataContext>
<viewModels:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<core:ElementConverter x:Key="ElementConverter" />
</Window.Resources>
<WindowChrome.WindowChrome>
<WindowChrome CaptionHeight="0" ResizeBorderThickness="0" />
</WindowChrome.WindowChrome>
<Grid>
<StackPanel Orientation="Vertical">
<Grid Margin="0,2,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="25"/>
<ColumnDefinition Width="115"/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
@@ -52,7 +55,12 @@
<MenuItem x:Name="OptionsMenuItem" Header="_Options" HorizontalAlignment="Left" Width="185"
Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
Foreground="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"
Template="{DynamicResource CustomMenuItemTemplate}" />
Template="{DynamicResource CustomMenuItemTemplate}"
IsEnabled="{Binding OptionsMenuItemEnabled}"/>
<MenuItem x:Name="DebugViewMenuItem" Header="_Debug Info Window" HorizontalAlignment="Left" Width="185"
Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
Foreground="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"
Template="{DynamicResource CustomMenuItemTemplate}" Visibility="Collapsed" />
</MenuItem>
<MenuItem x:Name="HelpMenuItem" Header="_Help"
Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
@@ -119,7 +127,9 @@
</Grid.RowDefinitions>
<Label x:Name="SystemMediaTypeLabel" Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Content="System/Media Type" />
<ComboBox x:Name="SystemTypeComboBox" Grid.Row="0" Grid.Column="1" Height="22" Width="250" HorizontalAlignment="Left" ItemsSource="{Binding Systems}" SelectedIndex="0" Style="{DynamicResource CustomComboBoxStyle}">
<ComboBox x:Name="SystemTypeComboBox" Grid.Row="0" 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>
@@ -130,18 +140,21 @@
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
<ComboBox x:Name="MediaTypeComboBox" Grid.Row="0" Grid.Column="1" Height="22" Width="140" HorizontalAlignment="Right" Style="{DynamicResource CustomComboBoxStyle}" />
<ComboBox x:Name="MediaTypeComboBox" Grid.Row="0" 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="OutputFilenameLabel" Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Content="Output Filename"/>
<TextBox x:Name="OutputFilenameTextBox" Grid.Row="1" Grid.Column="1" Height="22" VerticalContentAlignment="Center" />
<Label x:Name="OutputPathLabel" Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Content="Output Path"/>
<TextBox x:Name="OutputPathTextBox" Grid.Row="1" Grid.Column="1" Height="22" Width="345" HorizontalAlignment="Left" VerticalContentAlignment="Center"
Text="{Binding OutputPath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding OutputPathTextBoxEnabled}" />
<Button x:Name="OutputPathBrowseButton" Grid.Row="1" Grid.Column="1" Height="22" Width="50" HorizontalAlignment="Right" Content="Browse"
IsEnabled="{Binding OutputPathBrowseButtonEnabled}" Style="{DynamicResource CustomButtonStyle}"/>
<Label x:Name="OutputDirectoryLabel" Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Content="Output Directory"/>
<TextBox x:Name="OutputDirectoryTextBox" Grid.Row="2" Grid.Column="1" Height="22" Width="345" HorizontalAlignment="Left" VerticalContentAlignment="Center" />
<Button x:Name="OutputDirectoryBrowseButton" Grid.Row="2" Grid.Column="1" Height="22" Width="50" HorizontalAlignment="Right" Content="Browse"
Style="{DynamicResource CustomButtonStyle}"/>
<Label x:Name="DriveLetterLabel" Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Content="Drive Letter"/>
<ComboBox x:Name="DriveLetterComboBox" Grid.Row="3" Grid.Column="1" Height="22" Width="60" HorizontalAlignment="Left" Style="{DynamicResource CustomComboBoxStyle}">
<Label x:Name="DriveLetterLabel" Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Content="Drive Letter"/>
<ComboBox x:Name="DriveLetterComboBox" Grid.Row="2" Grid.Column="1" Height="22" Width="60" HorizontalAlignment="Left"
ItemsSource="{Binding Drives}" SelectedItem="{Binding Path=CurrentDrive, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding DriveLetterComboBoxEnabled}" Style="{DynamicResource CustomComboBoxStyle}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Letter}" />
@@ -149,35 +162,51 @@
</ComboBox.ItemTemplate>
</ComboBox>
<Label x:Name="DriveSpeedLabel" Grid.Row="4" Grid.Column="0" VerticalAlignment="Center" Content="Drive Speed"/>
<ComboBox x:Name="DriveSpeedComboBox" Grid.Row="4" Grid.Column="1" Height="22" Width="60" HorizontalAlignment="Left" Style="{DynamicResource CustomComboBoxStyle}" />
<Label x:Name="DriveSpeedLabel" Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Content="Drive Speed"/>
<ComboBox x:Name="DriveSpeedComboBox" Grid.Row="3" Grid.Column="1" Height="22" Width="60" HorizontalAlignment="Left"
ItemsSource="{Binding DriveSpeeds}" SelectedItem="{Binding Path=DriveSpeed, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding DriveSpeedComboBoxEnabled}" Style="{DynamicResource CustomComboBoxStyle}" />
<Label x:Name="DumpingProgramLabel" Grid.Row="4" Grid.Column="0" VerticalAlignment="Center" Content="Dumping Program"/>
<ComboBox x:Name="DumpingProgramComboBox" Grid.Row="4" 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}" />
<Label x:Name="ParametersLabel" Grid.Row="5" Grid.Column="0" VerticalAlignment="Center" Content="Parameters"/>
<TextBox x:Name="ParametersTextBox" Grid.Row="5" Grid.Column="1" Height="22" Width="370" HorizontalAlignment="Left" IsEnabled="False" VerticalContentAlignment="Center" />
<CheckBox x:Name="EnableParametersCheckBox" Grid.Row="5" Grid.Column="1" Height="22" HorizontalAlignment="Right" IsChecked="False" />
<TextBox x:Name="ParametersTextBox" Grid.Row="5" Grid.Column="1" Height="22" Width="370" HorizontalAlignment="Left" VerticalContentAlignment="Center"
Text="{Binding Parameters, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding ParametersCheckBoxEnabled}"/>
<CheckBox x:Name="EnableParametersCheckBox" Grid.Row="5" Grid.Column="1" Height="22" HorizontalAlignment="Right"
IsChecked="{Binding ParametersCheckBoxEnabled, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding EnableParametersCheckBoxEnabled}"/>
</Grid>
</GroupBox>
<GroupBox Margin="5,5,5,5" HorizontalAlignment="Stretch" Header="Controls">
<UniformGrid Columns="3" Margin="5,5,5,5">
<Button x:Name="StartStopButton" Height="22" Width="125" VerticalAlignment="Center" HorizontalAlignment="Center" IsDefault="True" Content="Start Dumping"
IsEnabled="False" Style="{DynamicResource CustomButtonStyle}" />
<UniformGrid Columns="4" Margin="5,5,5,5">
<Button x:Name="StartStopButton" Height="22" Width="125" VerticalAlignment="Center" HorizontalAlignment="Center" IsDefault="True"
Content="{Binding StartStopButtonText, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding StartStopButtonEnabled}" Style="{DynamicResource CustomButtonStyle}" />
<Button x:Name="MediaScanButton" Height="22" Width="125" VerticalAlignment="Center" HorizontalAlignment="Center" Content="Scan for discs"
Style="{DynamicResource CustomButtonStyle}" />
IsEnabled="{Binding MediaScanButtonEnabled}" Style="{DynamicResource CustomButtonStyle}" />
<Button x:Name="UpdateVolumeLabel" Height="22" Width="125" VerticalAlignment="Center" HorizontalAlignment="Center" Content="Update Label"
IsEnabled="{Binding UpdateVolumeLabelEnabled}" Style="{DynamicResource CustomButtonStyle}" />
<Button x:Name="CopyProtectScanButton" Height="22" Width="125" VerticalAlignment="Center" HorizontalAlignment="Center" Content="Scan for protection"
Style="{DynamicResource CustomButtonStyle}" />
IsEnabled="{Binding CopyProtectScanButtonEnabled}" Style="{DynamicResource CustomButtonStyle}" />
</UniformGrid>
</GroupBox>
<GroupBox Margin="5,5,5,5" HorizontalAlignment="Stretch" Header="Status">
<UniformGrid Margin="5,5,5,5" Grid.ColumnSpan="2">
<Label x:Name="StatusLabel" VerticalAlignment="Center" HorizontalAlignment="Center" Content="Waiting for media..." />
<TextBlock x:Name="StatusLabel" VerticalAlignment="Center" HorizontalAlignment="Center"
Text="{Binding Status, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</UniformGrid>
</GroupBox>
<Expander Margin="5,5 5 5" MaxHeight="300" HorizontalAlignment="Stretch" Header="Log Output" x:Name="LogPanel">
<Expander Margin="5,5 5 5" MaxHeight="300" HorizontalAlignment="Stretch" Header="Log Output" x:Name="LogPanel"
IsExpanded="{Binding LogPanelExpanded}">
<controls:LogOutput x:Name="LogOutput"/>
</Expander>
</StackPanel>
</Grid>
</windows:WindowBase>
</coreWindows:WindowBase>

View File

@@ -0,0 +1,470 @@
using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using MPF.Core;
using MPF.Core.UI.ViewModels;
using SabreTools.RedumpLib.Data;
using WPFCustomMessageBox;
using WinForms = System.Windows.Forms;
namespace MPF.UI.Core.Windows
{
public partial class MainWindow : WindowBase
{
/// <summary>
/// Read-only access to the current main view model
/// </summary>
public MainViewModel MainViewModel => DataContext as MainViewModel ?? new MainViewModel();
/// <summary>
/// Constructor
/// </summary>
public MainWindow() => InitializeComponent();
/// <summary>
/// Handler for MainWindow OnContentRendered event
/// </summary>
protected override void OnContentRendered(EventArgs e)
{
base.OnContentRendered(e);
// Disable buttons until we load fully
MainViewModel.StartStopButtonEnabled = false;
MainViewModel.MediaScanButtonEnabled = false;
MainViewModel.UpdateVolumeLabelEnabled = false;
MainViewModel.CopyProtectScanButtonEnabled = false;
// Add the click handlers to the UI
AddEventHandlers();
// Display the debug option in the menu, if necessary
if (MainViewModel.Options.ShowDebugViewMenuItem)
DebugViewMenuItem.Visibility = Visibility.Visible;
MainViewModel.Init(LogOutput.EnqueueLog, DisplayUserMessage, ShowDiscInformationWindow);
// Set the UI color scheme according to the options
ApplyTheme();
// Check for updates, if necessary
if (MainViewModel.Options.CheckForUpdatesOnStartup)
CheckForUpdates(showIfSame: false);
// Handle first-run, if necessary
if (MainViewModel.Options.FirstRun)
{
// Show the options window
ShowOptionsWindow("Welcome to MPF, Explore the Options");
}
}
#region UI Functionality
/// <summary>
/// Add all event handlers
/// </summary>
public void AddEventHandlers()
{
// Menu Bar Click
AboutMenuItem.Click += AboutClick;
AppExitMenuItem.Click += AppExitClick;
CheckForUpdatesMenuItem.Click += CheckForUpdatesClick;
DebugViewMenuItem.Click += DebugViewClick;
OptionsMenuItem.Click += OptionsMenuItemClick;
// User Area Click
CopyProtectScanButton.Click += CopyProtectScanButtonClick;
EnableParametersCheckBox.Click += EnableParametersCheckBoxClick;
MediaScanButton.Click += MediaScanButtonClick;
UpdateVolumeLabel.Click += UpdateVolumeLabelClick;
OutputPathBrowseButton.Click += OutputPathBrowseButtonClick;
StartStopButton.Click += StartStopButtonClick;
// User Area SelectionChanged
SystemTypeComboBox.SelectionChanged += SystemTypeComboBoxSelectionChanged;
MediaTypeComboBox.SelectionChanged += MediaTypeComboBoxSelectionChanged;
DriveLetterComboBox.SelectionChanged += DriveLetterComboBoxSelectionChanged;
DriveSpeedComboBox.SelectionChanged += DriveSpeedComboBoxSelectionChanged;
DumpingProgramComboBox.SelectionChanged += DumpingProgramComboBoxSelectionChanged;
// User Area TextChanged
OutputPathTextBox.TextChanged += OutputPathTextBoxTextChanged;
}
/// <summary>
/// Browse for an output file path
/// </summary>
public void BrowseFile()
{
// Get the current path, if possible
string currentPath = MainViewModel.OutputPath;
if (string.IsNullOrWhiteSpace(currentPath) && !string.IsNullOrWhiteSpace(MainViewModel.Options.DefaultOutputPath))
currentPath = Path.Combine(MainViewModel.Options.DefaultOutputPath, "track.bin");
else if (string.IsNullOrWhiteSpace(currentPath))
currentPath = "track.bin";
if (string.IsNullOrWhiteSpace(currentPath))
currentPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "track.bin");
// Get the full path
currentPath = Path.GetFullPath(currentPath);
// Get the directory
var directory = Path.GetDirectoryName(currentPath);
// Get the filename
string filename = Path.GetFileName(currentPath);
WinForms.FileDialog fileDialog = new WinForms.SaveFileDialog
{
FileName = filename,
InitialDirectory = directory,
};
WinForms.DialogResult result = fileDialog.ShowDialog();
if (result == WinForms.DialogResult.OK)
{
MainViewModel.OutputPath = fileDialog.FileName;
}
}
/// <summary>
/// Check for available updates
/// </summary>
/// <param name="showIfSame">True to show the box even if it's the same, false to only show if it's different</param>
public void CheckForUpdates(bool showIfSame)
{
(bool different, string message, var url) = MainViewModel.CheckForUpdates();
// If we have a new version, put it in the clipboard
if (different)
Clipboard.SetText(url);
if (showIfSame || different)
CustomMessageBox.Show(this, message, "Version Update Check", MessageBoxButton.OK, different ? MessageBoxImage.Exclamation : MessageBoxImage.Information);
}
/// <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
MessageBoxButton button;
switch (optionCount)
{
case 1:
button = MessageBoxButton.OK;
break;
case 2:
button = MessageBoxButton.YesNo;
break;
case 3:
button = MessageBoxButton.YesNoCancel;
break;
// This should not happen, but default to "OK"
default:
button = MessageBoxButton.OK;
break;
}
// 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)
{
case MessageBoxResult.OK:
case MessageBoxResult.Yes:
return true;
case MessageBoxResult.No:
return false;
case MessageBoxResult.Cancel:
case MessageBoxResult.None:
default:
return null;
}
}
/// <summary>
/// Build a dummy SubmissionInfo and display it for testing
/// </summary>
public void ShowDebugDiscInfoWindow()
{
var submissionInfo = MainViewModel.CreateDebugSubmissionInfo();
var result = ShowDiscInformationWindow(submissionInfo);
InfoTool.ProcessSpecialFields(result.Item2);
}
/// <summary>
/// Show the disc information window
/// </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);
var discInformationWindow = new DiscInformationWindow(MainViewModel.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)
#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 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)
{
Focusable = true,
Owner = this,
ShowActivated = true,
ShowInTaskbar = true,
Title = title ?? "Options",
WindowStartupLocation = WindowStartupLocation.CenterOwner,
};
optionsWindow.Closed += delegate { this.Activate(); };
optionsWindow.Closed += OnOptionsUpdated;
optionsWindow.Show();
}
/// <summary>
/// Set the UI color scheme according to the options
/// </summary>
private void ApplyTheme()
{
Theme theme;
if (MainViewModel.Options.EnableDarkMode)
theme = new DarkModeTheme();
else
theme = new LightModeTheme();
theme.Apply();
}
#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);
if (optionsWindow?.OptionsViewModel == null)
return;
bool savedSettings = optionsWindow.OptionsViewModel.SavedSettings;
var options = optionsWindow.OptionsViewModel.Options;
MainViewModel.UpdateOptions(savedSettings, options);
// Set the UI color scheme according to the options
ApplyTheme();
}
#region Menu Bar
/// <summary>
/// Handler for AboutMenuItem Click event
/// </summary>
public void AboutClick(object sender, RoutedEventArgs e)
{
string aboutText = MainViewModel.CreateAboutText();
CustomMessageBox.Show(this, aboutText, "About", MessageBoxButton.OK, MessageBoxImage.Information);
}
/// <summary>
/// Handler for AppExitMenuItem Click event
/// </summary>
public void AppExitClick(object sender, RoutedEventArgs e) =>
Application.Current.Shutdown();
/// <summary>
/// Handler for CheckForUpdatesMenuItem Click event
/// </summary>
public void CheckForUpdatesClick(object sender, RoutedEventArgs e)
=> CheckForUpdates(showIfSame: true);
/// <summary>
/// Handler for DebugViewMenuItem Click event
/// </summary>
public void DebugViewClick(object sender, RoutedEventArgs e) =>
ShowDebugDiscInfoWindow();
/// <summary>
/// Handler for OptionsMenuItem Click event
/// </summary>
public void OptionsMenuItemClick(object sender, RoutedEventArgs e) =>
ShowOptionsWindow();
#endregion
#region User Area
/// <summary>
/// Handler for CopyProtectScanButton Click event
/// </summary>
public async void CopyProtectScanButtonClick(object sender, RoutedEventArgs e)
{
var (output, error) = await MainViewModel.ScanAndShowProtection();
if (!MainViewModel.LogPanelExpanded)
{
if (!string.IsNullOrEmpty(output) && string.IsNullOrEmpty(error))
CustomMessageBox.Show(this, output, "Detected Protection(s)", MessageBoxButton.OK, MessageBoxImage.Information);
else
CustomMessageBox.Show(this, "An exception occurred, see the log for details", "Error!", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
/// <summary>
/// Handler for DriveLetterComboBox SelectionChanged event
/// </summary>
public void DriveLetterComboBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (MainViewModel.CanExecuteSelectionChanged)
MainViewModel.InitializeUIValues(removeEventHandlers: true, rescanDrives: false);
}
/// <summary>
/// Handler for DriveSpeedComboBox SelectionChanged event
/// </summary>
public void DriveSpeedComboBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (MainViewModel.CanExecuteSelectionChanged)
MainViewModel.EnsureDiscInformation();
}
/// <summary>
/// Handler for DumpingProgramComboBox SelectionChanged event
/// </summary>
public void DumpingProgramComboBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (MainViewModel.CanExecuteSelectionChanged)
MainViewModel.ChangeDumpingProgram();
}
/// <summary>
/// Handler for EnableParametersCheckBox Click event
/// </summary>
public void EnableParametersCheckBoxClick(object sender, RoutedEventArgs e)
{
if (MainViewModel.CanExecuteSelectionChanged)
MainViewModel.ToggleParameters();
}
/// <summary>
/// Handler for MediaScanButton Click event
/// </summary>
public void MediaScanButtonClick(object sender, RoutedEventArgs e) =>
MainViewModel.InitializeUIValues(removeEventHandlers: true, rescanDrives: true);
/// <summary>
/// Handler for MediaTypeComboBox SelectionChanged event
/// </summary>
public void MediaTypeComboBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (MainViewModel.CanExecuteSelectionChanged)
MainViewModel.ChangeMediaType(e.RemovedItems, e.AddedItems);
}
/// <summary>
/// Handler for OutputPathBrowseButton Click event
/// </summary>
public void OutputPathBrowseButtonClick(object sender, RoutedEventArgs e)
{
BrowseFile();
MainViewModel.EnsureDiscInformation();
}
/// <summary>
/// Handler for OutputPathTextBox TextChanged event
/// </summary>
public void OutputPathTextBoxTextChanged(object sender, TextChangedEventArgs e)
{
if (MainViewModel.CanExecuteSelectionChanged)
MainViewModel.EnsureDiscInformation();
}
/// <summary>
/// Handler for StartStopButton Click event
/// </summary>
public void StartStopButtonClick(object sender, RoutedEventArgs e) =>
MainViewModel.ToggleStartStop();
/// <summary>
/// Handler for SystemTypeComboBox SelectionChanged event
/// </summary>
public void SystemTypeComboBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (MainViewModel.CanExecuteSelectionChanged)
MainViewModel.ChangeSystem();
}
/// <summary>
/// Handler for UpdateVolumeLabel Click event
/// </summary>
public void UpdateVolumeLabelClick(object sender, RoutedEventArgs e)
{
if (MainViewModel.CanExecuteSelectionChanged)
{
if (MainViewModel.Options.FastUpdateLabel)
MainViewModel.FastUpdateLabel(removeEventHandlers: true);
else
MainViewModel.InitializeUIValues(removeEventHandlers: true, rescanDrives: false);
}
}
#endregion
#endregion // Event Handlers
}
}

Some files were not shown because too many files have changed in this diff Show More