Compare commits

...

267 Commits
3.1.5 ... 3.2.1

Author SHA1 Message Date
Matt Nadareski
00089b799c Bump version 2024-08-05 12:23:58 -04:00
Matt Nadareski
0f98f03999 Update changelog 2024-07-26 10:12:22 -04:00
Deterous
22f6f39a91 Enable loading seed JSON (#731)
* Enable loading seed JSON

* Safer function call
2024-07-26 10:11:39 -04:00
Matt Nadareski
e9a9011dbd Update RedumpLib to 1.4.1 2024-07-24 11:45:12 -04:00
Matt Nadareski
40bbb3d1c8 Update changelog 2024-07-19 10:55:55 -04:00
John Veness
a48dad817b Remove old --protect-file mentions (#728)
The --protect-file option itself was removed in e0482aad78
2024-07-19 10:55:23 -04:00
Matt Nadareski
9f76dcc5fd Add include artifacts flag for check, sanitize options (fixes #726) 2024-07-19 10:20:45 -04:00
Matt Nadareski
4356561a8a Update changelog 2024-07-19 10:16:30 -04:00
John Veness
347c522d62 Change to generic wording in report (#727) 2024-07-19 10:15:59 -04:00
Matt Nadareski
998ecec261 Remove RedumpLib tests 2024-07-16 13:08:28 -04:00
Matt Nadareski
ab8e775df0 Fix broken test logic 2024-07-16 13:03:58 -04:00
Matt Nadareski
246e6b8bfd Update changelog 2024-07-16 13:01:52 -04:00
John Veness
c7f69de18f Fix logic for deducing region from PlayStation ISN (#724)
* Deduce PS2 region from Redumper serial

* Fix logic for deducing region from PlayStation ISN

* Fix logic again

* Fix logic again again

* Fix this too

* Undo Redumper change

* Reorder functions

* Fix OR/AND logic
2024-07-16 13:00:11 -04:00
John Veness
d4a98d7712 Fix URLs in github issue templates (#717) 2024-07-15 16:42:01 -04:00
Deterous
2983266e8a Fix parameters after extension change (#721) 2024-07-06 23:48:34 -04:00
Deterous
1baef4440a Don't set MediaType if parameters ambiguous (#720) 2024-07-06 22:28:04 -04:00
Matt Nadareski
7cd25dae1c Remove now-unncessary names 2024-06-26 03:26:08 -04:00
Matt Nadareski
38f9b7234b Bring Check and CLI in parity with param processing 2024-06-26 03:24:44 -04:00
Matt Nadareski
2d7ea1bed9 Fix CLI help text alignment 2024-06-25 19:31:01 -04:00
Matt Nadareski
806a69c280 Simplify custom parameters warning 2024-06-25 17:31:38 -04:00
Matt Nadareski
a92159b8cb Try out custom options classes 2024-06-25 17:29:32 -04:00
Matt Nadareski
9451629461 Add some custom CLI parameters 2024-06-25 17:11:24 -04:00
Matt Nadareski
51a1f0cc8e Move some Check-specific methods 2024-06-25 16:48:24 -04:00
Matt Nadareski
2830641b8a Move GetDefaultSpeedForMediaType to common location 2024-06-25 16:42:23 -04:00
Matt Nadareski
7c87a22dcc Fix minimum number of args checks 2024-06-25 16:37:26 -04:00
Matt Nadareski
dd5b5d4c7d Use speed for CLI from configuration 2024-06-25 16:32:41 -04:00
Deterous
bc5e73d371 Custom theme colors (#712)
* Custom theme colors

* Use Convert.ToByte
2024-06-25 16:27:38 -04:00
Matt Nadareski
f47a55b723 Ensure tracks are assigned in Aaru 2024-06-24 15:00:13 -04:00
Matt Nadareski
c4d014e480 Add CLI status output on runtime 2024-06-24 14:22:15 -04:00
Matt Nadareski
69b1d2f7ad Blindly assume the path exists 2024-06-24 12:17:57 -04:00
Matt Nadareski
7c295ca2f4 Try to make config safer for CLI 2024-06-24 11:55:28 -04:00
Matt Nadareski
5af3aad68a Dispose of stream when creating config 2024-06-24 11:43:22 -04:00
Matt Nadareski
f150483e84 Load options before anything else 2024-06-24 11:38:13 -04:00
Matt Nadareski
92ef962f42 Allow custom parameters for CLI 2024-06-24 11:22:18 -04:00
Matt Nadareski
859e53f843 Save default config values for CLI 2024-06-24 11:04:48 -04:00
Matt Nadareski
e70d70ca22 Add CLI information to README 2024-06-24 10:46:40 -04:00
Matt Nadareski
c2cf8147d3 Add CLI build status to README 2024-06-24 10:38:45 -04:00
Matt Nadareski
cf32f38c0e Add preliminary MPF.CLI 2024-06-24 10:37:05 -04:00
Matt Nadareski
1f92ff08d6 Seal all theme classes 2024-06-24 09:45:04 -04:00
Matt Nadareski
6aaf076434 Separate themes into own namespace and files 2024-06-24 09:44:06 -04:00
Deterous
6865b23aa7 Purple (#711) 2024-06-23 21:46:15 -04:00
Matt Nadareski
e1c13982bd Remove empty gitmodules 2024-06-20 15:42:56 -04:00
Matt Nadareski
9cddcc2eae Update README with new build matricies 2024-06-20 14:40:42 -04:00
Matt Nadareski
33932fad47 Enable last runtime by default 2024-06-20 14:38:06 -04:00
Matt Nadareski
470e5c69fe Add flag values to script settings 2024-06-20 14:12:16 -04:00
Matt Nadareski
3aae2990a3 Better support build matricies 2024-06-20 13:56:28 -04:00
Matt Nadareski
503933e67f Add osx-arm64 to libraries 2024-06-20 12:21:28 -04:00
Matt Nadareski
1a99fd9e71 Forgot to upload packages to release 2024-06-20 12:10:04 -04:00
Matt Nadareski
affc175bda Fix nuget package naming 2024-06-20 12:06:09 -04:00
Matt Nadareski
fe4c88d3ad Add Linux ARM64 as target by default 2024-06-20 12:00:26 -04:00
Matt Nadareski
9c830c9755 Address build warnings for packages 2024-06-20 11:54:40 -04:00
Matt Nadareski
fc300465f8 Add nuget packing for processors and contexts 2024-06-20 11:51:28 -04:00
Matt Nadareski
de1032a099 Bump version 2024-06-20 11:33:58 -04:00
Matt Nadareski
aa22b9fbff Fix excluding programs in nix script 2024-06-18 15:28:35 -04:00
Matt Nadareski
a41f0d6237 Update BinaryObjectScanner to 3.1.12 2024-06-17 16:23:55 -04:00
Matt Nadareski
9d5dfaaa68 Make match sets immutable 2024-06-12 14:49:16 -04:00
Matt Nadareski
7f08684e9a Fix... something with Linux publish script 2024-06-12 14:04:56 -04:00
Matt Nadareski
8c2ad6eca5 Move track full matching to separate loop 2024-06-12 13:36:52 -04:00
Matt Nadareski
dad108de52 Simplify DIC DMI location finding 2024-06-03 21:03:16 -04:00
Matt Nadareski
df3bf1f7c5 Handle Redumper .atip and .pma files 2024-06-03 20:53:41 -04:00
Matt Nadareski
0e355b906c Handle Redumper .asus files 2024-06-03 20:41:53 -04:00
Matt Nadareski
1d472bf777 Add update parameter to unzip 2024-06-03 13:08:45 -04:00
Matt Nadareski
a7e0ac0806 Fix UI build workflow 2024-06-03 12:06:09 -04:00
Matt Nadareski
5a208926a5 Update to DIC 20240601 2024-06-01 23:21:39 -04:00
Matt Nadareski
d812ea7e2b Add PS3 info extraction for DIC 2024-05-31 10:41:56 -04:00
Matt Nadareski
f19111a1b0 Clean up some ProcessSystem cases 2024-05-31 00:07:40 -04:00
Matt Nadareski
a36f7d7df4 Fix setting Python 2 version on invalid 2024-05-31 00:03:40 -04:00
Matt Nadareski
c5e8de6c1a Fix setting PS1-5 version on invalid 2024-05-30 12:28:31 -04:00
Matt Nadareski
6ebcca104f Fix logic for PS1-5 system information 2024-05-29 13:07:10 -04:00
Matt Nadareski
3f048c5243 Rearrange test classes to match new format 2024-05-28 15:30:16 -04:00
Matt Nadareski
dffebc5d43 Move ToRedumper* back to EnumExtensions 2024-05-28 15:20:28 -04:00
Matt Nadareski
37f2cf5bab Ensure setting defaults are consistent 2024-05-28 15:16:28 -04:00
Matt Nadareski
9865f88a6f Fix one DIC parameter test 2024-05-28 14:51:43 -04:00
Matt Nadareski
90d4d0d029 Move Redumper enums to a better place 2024-05-28 14:41:51 -04:00
Matt Nadareski
68c3d7c4fa Remove magic strings from settings reading 2024-05-28 14:37:15 -04:00
Matt Nadareski
503a6a8cdc Create Frontend.Tools namespace 2024-05-28 14:19:59 -04:00
Matt Nadareski
c10b3d28bd Remove Core library, fix build 2024-05-28 14:16:00 -04:00
Matt Nadareski
d349ef8a9d Combine remaining Core into Frontend 2024-05-28 14:12:36 -04:00
Matt Nadareski
3137a543a7 Decouple execution contexts from Options class 2024-05-28 14:07:18 -04:00
Matt Nadareski
7b832049e8 Move StringEventArgs to Frontend 2024-05-28 13:47:06 -04:00
Matt Nadareski
6566db5913 Remove unused reporter delegate 2024-05-28 13:46:00 -04:00
Matt Nadareski
8c70f19959 Move ResultEventArgs to Frontend 2024-05-28 13:44:57 -04:00
Matt Nadareski
898069c799 Move Drive to Frontend 2024-05-28 13:15:31 -04:00
Matt Nadareski
01e991c5fd emove Drive dependency from GenerateSubmissionInfo 2024-05-28 13:07:16 -04:00
Matt Nadareski
3b21fa62a0 Hacky move of DIC-specific code 2024-05-28 13:00:39 -04:00
Matt Nadareski
12a13a2ffa Slight readability cleanup in DIC 2024-05-28 12:50:01 -04:00
Matt Nadareski
074d2c031c Treat KP2 like PS2 in DIC 2024-05-28 12:21:02 -04:00
Matt Nadareski
e33588451d Move EnumExtensions to Frontend 2024-05-28 12:00:05 -04:00
Matt Nadareski
f2ba433859 Merge VersionTool into FrontendTool 2024-05-28 11:57:53 -04:00
Matt Nadareski
b266467c33 Clear out InfoTool and remove 2024-05-28 11:53:15 -04:00
Matt Nadareski
8eece24d9a Create FrontendTool and move some methods to it 2024-05-28 11:51:10 -04:00
Matt Nadareski
bb644e9a8b Move InfoTool to root of Core 2024-05-28 11:45:00 -04:00
Matt Nadareski
c07ca9f39c Move VersionTool to root of Core 2024-05-28 11:40:17 -04:00
Matt Nadareski
bb92c43b35 Rename Tools to VersionTool 2024-05-28 11:38:58 -04:00
Matt Nadareski
b9d6a13e20 Move ProgramSupportsMedia to MainViewModel 2024-05-28 11:36:58 -04:00
Matt Nadareski
891499710f Centralize PS1/2 region detection 2024-05-28 11:32:34 -04:00
Matt Nadareski
1328a373ea Clean up usings 2024-05-28 11:22:42 -04:00
Matt Nadareski
4a59ce1d90 Move PS3 helpers to ProcessingTool 2024-05-28 11:19:33 -04:00
Matt Nadareski
7a74042aef Move Xbox/X360 helpers to ProcessingTool 2024-05-28 11:13:17 -04:00
Matt Nadareski
2f7abee51b Move output writing to DumpEnvironment 2024-05-28 11:02:09 -04:00
Matt Nadareski
a63c844ed1 Move drive-reading methods to Drive 2024-05-28 10:56:34 -04:00
Matt Nadareski
91a0e85e24 Centralize dumping program information gathering 2024-05-28 10:34:33 -04:00
Matt Nadareski
c9a67b1b51 Move ToInternalProgram to Options 2024-05-28 10:16:50 -04:00
Matt Nadareski
3d932705bc Move DoesSupportDriveSpeed to DumpEnvironment 2024-05-28 10:15:32 -04:00
Matt Nadareski
80cde96614 Move ListPrograms to OptionsLoader 2024-05-28 10:12:06 -04:00
Matt Nadareski
aae81035c1 Move ToMediaType to OptionsLoader 2024-05-28 10:10:50 -04:00
Matt Nadareski
d08716045a Move ToRedumper* methods to Options 2024-05-28 10:08:53 -04:00
Matt Nadareski
f34999e308 Move ProtectionTool to Frontend 2024-05-28 00:37:42 -04:00
Matt Nadareski
028f7d5788 Slight cleanup of InfoTool 2024-05-28 00:32:28 -04:00
Matt Nadareski
c34aeb6e45 Move GetCopyProtection to ProtectionTool 2024-05-28 00:31:27 -04:00
Matt Nadareski
bdb367c2c9 Call psxt001z direct from DIC processor 2024-05-28 00:29:56 -04:00
Matt Nadareski
63e6ce121a Move GetSupportStatus to DumpEnvironment 2024-05-28 00:19:34 -04:00
Matt Nadareski
ac072618c4 Remove unused byte array helper methods 2024-05-28 00:15:43 -04:00
Matt Nadareski
7a640c58ee Create ProcessingTool and move some methods 2024-05-28 00:13:17 -04:00
Matt Nadareski
5ad75c80d1 Move LogLevel enum to Frontend 2024-05-27 23:52:55 -04:00
Matt Nadareski
78d648d90b Move ProcessingQueue to Frontend 2024-05-27 23:48:48 -04:00
Matt Nadareski
d415a8f161 Move ConsoleLogger to Check CLI 2024-05-27 23:40:19 -04:00
Matt Nadareski
87ba8d573d Tools always run in separate window 2024-05-27 23:34:42 -04:00
Matt Nadareski
55a84fc911 Update Redumper to build 371 2024-05-27 22:13:55 -04:00
Matt Nadareski
f9351ff058 Standardize PS1-5 outputs and parsing 2024-05-27 21:45:06 -04:00
Matt Nadareski
e0482aad78 Make protection file output required 2024-05-27 13:58:06 -04:00
Matt Nadareski
9243020cd6 Fix build scripts 2024-05-23 21:50:47 -04:00
Matt Nadareski
616f3624b7 Rename main application to MPF.UI 2024-05-23 21:40:42 -04:00
Matt Nadareski
aff981171a Merge UI.Core into main application 2024-05-23 21:35:02 -04:00
Matt Nadareski
4816c5ab6a Move Aaru CICM code to Processors 2024-05-23 21:23:02 -04:00
Matt Nadareski
77f9b048fb Clean up Core dependencies 2024-05-23 21:18:05 -04:00
Matt Nadareski
846db2f602 Move Aaru CICM code to Core 2024-05-23 21:01:04 -04:00
Matt Nadareski
6a21ca9f86 Fix up visual solution 2024-05-23 20:53:54 -04:00
Matt Nadareski
9613cae204 Rename Core.* libraries 2024-05-23 15:40:12 -04:00
Matt Nadareski
59102a8330 Split Core.ExecutionContexts into separate library 2024-05-23 15:25:40 -04:00
Matt Nadareski
52f51cf1ab Split Core.Processors into separate library 2024-05-23 15:20:53 -04:00
Matt Nadareski
98ae16f7ae Split Core.Frontend into separate library 2024-05-23 15:15:43 -04:00
Matt Nadareski
c0d8a87c44 Decouple Frontend from execution contexts 2024-05-23 15:07:25 -04:00
Matt Nadareski
7a120d155a Move Options to root of Core 2024-05-23 15:04:13 -04:00
Matt Nadareski
d99da089ef Move EnumExtensions to root of core 2024-05-23 14:42:07 -04:00
Matt Nadareski
d76cd346d4 Move SubmissionGenerator to Core.Frontend 2024-05-23 14:37:40 -04:00
Matt Nadareski
5082ca57c4 Rename SubmissionInfoTool to SubmissionGenerator 2024-05-23 14:36:27 -04:00
Matt Nadareski
c31eeb001a Decouple InfoTool from processors 2024-05-23 14:34:32 -04:00
Matt Nadareski
bef4bf175c Move Logging to Core.Frontend 2024-05-23 14:27:58 -04:00
Matt Nadareski
ac744a1e6d Move OptionsLoader to Core.Frontend 2024-05-23 14:26:16 -04:00
Matt Nadareski
13d7d83dbb Remove useless using statement 2024-05-23 14:23:39 -04:00
Matt Nadareski
7608c08e7c Move Options to Core.Frontend 2024-05-23 14:21:15 -04:00
Matt Nadareski
c5c180a9c6 Move DumpEnvironment to Core.Frontend 2024-05-23 14:17:40 -04:00
Matt Nadareski
9bce6aea1a Rename Core.UI namespace to Core.Frontend 2024-05-23 14:16:32 -04:00
Matt Nadareski
7cd84e2e9a Move GetRedumpSystem to MainViewModel 2024-05-23 14:12:50 -04:00
Matt Nadareski
b8d7bbc72e Make FormattedVolumeLabel a method 2024-05-23 14:07:42 -04:00
Matt Nadareski
a60f11135e Clean up EnumExtensions 2024-05-23 14:02:02 -04:00
Matt Nadareski
c2664a1d2d Move ToInternalDriveType to Drive 2024-05-23 14:01:47 -04:00
Matt Nadareski
e5632634d0 Move Enumerations to root of Core 2024-05-23 13:51:14 -04:00
Matt Nadareski
daa3261c16 Move Options to root of Core 2024-05-23 13:47:14 -04:00
Matt Nadareski
dbf7150a31 Move Drive to root of Core 2024-05-23 13:45:21 -04:00
Matt Nadareski
893fd34d36 Move ProtectionTool to Core.Utilities 2024-05-23 13:42:53 -04:00
Matt Nadareski
e1961612c0 Rename Protection to ProtectionTool 2024-05-23 13:41:43 -04:00
Matt Nadareski
0f1b23056c Fix build 2024-05-23 13:40:59 -04:00
Matt Nadareski
26254e6b32 Move SubmissionInfoTool to Core.Utilities 2024-05-23 13:39:35 -04:00
Matt Nadareski
505fbf2567 Move InfoTool to Core.Utilities 2024-05-23 13:38:05 -04:00
Matt Nadareski
1bb38ea987 Move processing queue to root of Core 2024-05-23 13:34:05 -04:00
Matt Nadareski
a3144b1537 Move event args to root of Core 2024-05-23 13:33:02 -04:00
Matt Nadareski
ad90e2b6f9 Merge EnumConverter and EnumExtensions 2024-05-23 13:29:31 -04:00
Matt Nadareski
705060fa70 Remove firmware output for Redumper 2024-05-22 21:30:43 -04:00
Matt Nadareski
f8e8c02fcf Remove options from UI 2024-05-22 20:48:11 -04:00
Matt Nadareski
eacee24d45 Remove automatic eject and reset options 2024-05-22 20:46:17 -04:00
Matt Nadareski
d980fffa09 Clean up usings 2024-05-22 16:56:43 -04:00
Matt Nadareski
853b8689b4 Make RunProtectionScanOnPath signature easier to read 2024-05-22 16:56:14 -04:00
Matt Nadareski
7e4089f79c Move GetLibCryptDetected back to DIC processor 2024-05-22 16:54:04 -04:00
Matt Nadareski
54ee2829f1 Make GetCopyProtection signature easier to read 2024-05-22 16:46:50 -04:00
Matt Nadareski
f89cac5400 Remove unnecessary GetAntiModchipDetected method 2024-05-22 16:45:28 -04:00
Matt Nadareski
b003203aef Reduce complexity of ProcessSystem method 2024-05-22 16:44:18 -04:00
Matt Nadareski
4e5c9a242e Simplify RequiredProgramsExist logic 2024-05-22 16:34:01 -04:00
Matt Nadareski
5edb70745a Make drive private to DumpEnvironment 2024-05-22 16:24:01 -04:00
Matt Nadareski
f474b339ac Make system private to DumpEnvironment 2024-05-22 16:20:17 -04:00
Matt Nadareski
bb9a344938 Make media type private to DumpEnvironment 2024-05-22 16:16:56 -04:00
Matt Nadareski
75ad9eae28 Make context private to DumpEnvironment 2024-05-22 16:13:14 -04:00
Matt Nadareski
2b3b029545 Make processor private to DumpEnvironment 2024-05-22 15:59:43 -04:00
Matt Nadareski
9843644dfc Remove other reference to execution context 2024-05-22 15:46:57 -04:00
Matt Nadareski
54103a1d7e Execution context is not needed to extract info 2024-05-22 15:45:16 -04:00
Matt Nadareski
5da277ae64 Move GetRedumpSystemFromVolumeLabel to InfoTool 2024-05-22 15:28:49 -04:00
Matt Nadareski
51461a958d Remove use of "this" in Drive 2024-05-22 15:26:55 -04:00
Matt Nadareski
6d1fd9d47d Seal all execution contexts 2024-05-22 15:14:48 -04:00
Matt Nadareski
6b6f888dc3 Use proper private variable naming in ProcessingQueue 2024-05-22 15:04:47 -04:00
Matt Nadareski
1c6a9da9c8 Remove use of "this" in ProcessingQueue 2024-05-22 15:03:55 -04:00
Matt Nadareski
c335cd2869 Make StringEventArgs internally consistent 2024-05-22 15:01:52 -04:00
Matt Nadareski
dbe521b719 Better handle interface constants 2024-05-22 14:59:57 -04:00
Matt Nadareski
9486cdeedb Reduce accessors for DumpEnvironment 2024-05-22 14:51:36 -04:00
Matt Nadareski
2b9b186be0 Rename Result to ResultEventArgs for consistency 2024-05-22 14:49:45 -04:00
Matt Nadareski
73a78c786f Make implicit Result bidirectional 2024-05-22 14:35:28 -04:00
Matt Nadareski
786f2177bd Make implicit StringEventArgs bidirectional 2024-05-22 14:33:45 -04:00
Matt Nadareski
ddaf5e35f3 Make StringEventArgs more complete 2024-05-22 14:29:37 -04:00
Matt Nadareski
b39542b651 Use StringEventArgs more broadly 2024-05-22 14:24:16 -04:00
Matt Nadareski
4479733421 Separate out StringEventArgs 2024-05-22 14:14:18 -04:00
Matt Nadareski
6907e5b6ac Clean up usings 2024-05-22 14:08:41 -04:00
Matt Nadareski
81f672ca42 Move EnumConverter to Core.Data 2024-05-22 14:05:57 -04:00
Matt Nadareski
611c33f302 Reduce processing queue sleep time 2024-05-21 21:30:01 -04:00
Matt Nadareski
9cffc80982 Fix net20, net35, and net40 2024-05-21 21:11:04 -04:00
Matt Nadareski
3ba4db8f0a Remove unused byte array constant 2024-05-21 21:00:04 -04:00
Matt Nadareski
26daa46486 Move string contents for UI to view model 2024-05-21 20:57:57 -04:00
Matt Nadareski
51b14874c7 Remove Chime 2024-05-21 20:54:36 -04:00
Matt Nadareski
a6014e1b58 Clean up usings after moving methods 2024-05-21 20:50:44 -04:00
Matt Nadareski
e4237fedef Remove another redundant GetFullFile 2024-05-21 20:49:26 -04:00
Matt Nadareski
8d334b7228 Move GetBase64 to InfoTool 2024-05-21 20:48:40 -04:00
Matt Nadareski
bb95112559 Remove duplicate GetFullFile method 2024-05-21 20:46:48 -04:00
Matt Nadareski
6e798aa565 Separate out copy protection run 2024-05-21 20:43:06 -04:00
Matt Nadareski
94f8d9709a Move PlayStation drive use mostly to helper 2024-05-21 20:34:37 -04:00
Matt Nadareski
37a2e5c957 Handle version like category 2024-05-21 19:54:33 -04:00
Matt Nadareski
e163b174ac Make GetLogFilePaths required 2024-05-21 19:53:40 -04:00
Matt Nadareski
db92acfdcc Reduce surface area of generation method 2024-05-21 17:09:24 -04:00
Matt Nadareski
26e65b428b Separate out artifact generation 2024-05-21 17:05:07 -04:00
Matt Nadareski
03c55216ca Move constants into related classes 2024-05-21 16:46:22 -04:00
Matt Nadareski
70ae5dd787 Slight tweak to Result class variables 2024-05-21 16:43:11 -04:00
Matt Nadareski
e2a5cf968d Remove odd code from Result class 2024-05-21 16:42:36 -04:00
Matt Nadareski
60f43de605 Split constants files into component parts 2024-05-21 16:38:53 -04:00
Matt Nadareski
4205a0baef Make options internal to dump environment 2024-05-21 16:32:58 -04:00
Matt Nadareski
a52ac9f7b5 Invert using statement in dump environment 2024-05-21 16:26:32 -04:00
Matt Nadareski
346dab0899 Remove dcdumper until further notice 2024-05-21 16:25:04 -04:00
Matt Nadareski
c3bfd02310 Make some methods required for override 2024-05-21 16:16:39 -04:00
Matt Nadareski
d688fc6975 Slight tweak to deal with net20 2024-05-21 16:07:41 -04:00
Matt Nadareski
521b8d656b Remove unnecessary field in execution contexts 2024-05-21 16:05:37 -04:00
Matt Nadareski
9456301168 Split some processing code 2024-05-21 16:01:51 -04:00
Matt Nadareski
fad425da29 Use Logiqx model instead of internal one 2024-05-21 15:42:38 -04:00
Matt Nadareski
6b177c618d Remove redundant BinaryReaderExtensions class 2024-05-21 15:25:50 -04:00
Matt Nadareski
ca26307dbf Fix subfolder issue from previous 2024-05-21 15:09:19 -04:00
Matt Nadareski
8a7079a159 Simplify mv command in build config 2024-05-21 15:08:50 -04:00
Matt Nadareski
c4814fc950 Update Redumper to build 329 2024-05-21 14:40:01 -04:00
Matt Nadareski
7deaa9e7af Update launch JSON 2024-05-21 14:34:00 -04:00
Matt Nadareski
1ad4738b60 Update to DIC 20240401 2024-05-21 14:32:19 -04:00
Matt Nadareski
4a82baa5d1 Ensure check-only implementations still work 2024-05-21 13:41:53 -04:00
Matt Nadareski
1c1740010d Rename Parameters to ExecutionContext 2024-05-21 13:37:09 -04:00
Matt Nadareski
955fc4b8a0 Simplify access within processors 2024-05-21 13:14:15 -04:00
Matt Nadareski
fad9fa5f72 Remove now-unneeded parameters classes 2024-05-21 13:12:11 -04:00
Matt Nadareski
9dc976e423 Migrate processor functionality 2024-05-21 13:10:02 -04:00
Matt Nadareski
c2c92b54d9 Seal XBC processor 2024-05-21 12:48:26 -04:00
Matt Nadareski
77ccdb0032 Move DataFile to Core.Data 2024-05-21 12:47:26 -04:00
Matt Nadareski
4e3046fadd Create currently-unused processors 2024-05-21 12:46:08 -04:00
Matt Nadareski
70114ee59e Fix overwriting placeholders in Redumper 2024-05-21 12:11:19 -04:00
Matt Nadareski
4bb02b88fc Fix dictionary error in Redumper parsing 2024-05-21 11:03:44 -04:00
Matt Nadareski
f97e293ad2 Bump version 2024-05-19 19:48:51 -04:00
Deterous
2a040effde Prefer PlayStation info from Redumper logs (#702)
* Prefer PlayStation info from Redumper logs

* Restrict parsing PS data to info field

* Typo

* Readd version field
2024-05-19 19:45:25 -04:00
Matt Nadareski
862e676590 Update BinaryObjectScanner to 3.1.12 2024-05-18 22:30:27 -04:00
Deterous
bffa70bcc9 Add Xbox Backup Creator support to MPF.Check (#701)
* Initial XBC support

* Complete but untested XBC support

* Update changelog

* Fix SS recreation bug

* Parse XeMID from DMI

* Nitpicks
2024-05-16 23:11:33 -04:00
Matt Nadareski
bb596c49f4 Use IO implementation of IniFile 2024-05-16 12:35:39 -04:00
Matt Nadareski
917986530b Remove now-unused Hash enum 2024-05-16 12:29:48 -04:00
Matt Nadareski
14bc7609c5 Update BinaryObjectScanner to 3.1.11 2024-05-15 20:44:15 -04:00
Matt Nadareski
a2361c34bc Update RedumpLib and related 2024-05-15 17:17:23 -04:00
Matt Nadareski
3d29eeb3c3 Add site code listing to Check 2024-05-15 15:49:40 -04:00
Matt Nadareski
c908a55ce6 Get volume label from UIC outputs 2024-05-15 11:57:34 -04:00
Deterous
c2b3932363 Trim PIC for XboxOne/XboxSX (#700) 2024-05-14 10:34:11 -04:00
Deterous
b4d47aea37 Fix XboxOne/XboxSX Filename bug (#698) 2024-05-10 09:13:38 -04:00
Deterous
f8d3ae7bc7 Fix CleanRip not pulling info (#697) 2024-05-10 08:09:20 -04:00
Matt Nadareski
9f50277888 Update Redumper to build 325 2024-05-09 09:32:06 -04:00
Matt Nadareski
96f826994a Bump version 2024-05-09 08:56:53 -04:00
Matt Nadareski
eda3c97465 Note 2024-05-08 23:36:27 -04:00
Matt Nadareski
ff380451db Note 2024-05-08 23:35:35 -04:00
Matt Nadareski
a9ee6667d0 Add _PFI.bin support for UIC (fixes #696) 2024-05-07 19:17:48 -04:00
Matt Nadareski
54415241d2 Critical update to BinaryObjectScanner 3.1.10 2024-05-07 09:03:42 -04:00
Matt Nadareski
79d2957ede Omit false positives on formatting protections 2024-05-06 22:58:23 -04:00
Matt Nadareski
0b5d52da7d Note 2024-05-06 22:04:26 -04:00
Matt Nadareski
274ad9fc9a Note 2024-05-06 22:01:39 -04:00
Matt Nadareski
a2217b536b Note 2024-05-06 21:51:14 -04:00
Deterous
43e7883ac9 Option for default Redumper leadin retries (#693)
* Option for default Redumper leadin retries

* Gate custom default leadin retries behind option

* typo

* False by default

* change wording

* whitespace?

* better whitespace?
2024-05-02 23:41:07 -04:00
Matt Nadareski
c37d098eee Bump version 2024-04-28 20:17:48 -04:00
Matt Nadareski
17c2ca6fa8 Critical update to BinaryObjectScanner 3.1.9 2024-04-28 19:57:10 -04:00
Matt Nadareski
4b2d30bc01 Bump version 2024-04-27 19:37:49 -04:00
Matt Nadareski
ec8b65a7fa Update packages 2024-04-26 22:21:41 -04:00
Matt Nadareski
ec5611f5ff Update packages 2024-04-24 18:12:05 -04:00
Deterous
3e350b666b Custom non-redump Redumper options (#691)
* Gate advanced Redumper options in UI, add read method + sector order

* Update changelog

* Fixed-width text/combo boxes
2024-04-16 10:11:09 -04:00
Deterous
e83f69fc3e Define better default categories (#689) 2024-04-08 09:37:31 -04:00
Deterous
6ecbbb6978 Fix parameter parsing for = symbol (#687) 2024-04-05 21:27:57 -04:00
141 changed files with 14978 additions and 14593 deletions

View File

@@ -10,7 +10,7 @@ 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](hhttps://github.com/SabreTools/BinaryObjectScanner/issues) instead.
- Is it copy protection related? If so, report the issue [here](https://github.com/SabreTools/BinaryObjectScanner/issues) instead.
- Check [previous issues](https://github.com/SabreTools/MPF/issues) to see if any of those are related to what you're about to ask for.
If none of those apply, then continue...

View File

@@ -10,7 +10,7 @@ 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](hhttps://github.com/SabreTools/BinaryObjectScanner/issues) instead.
- Is it copy protection related? If so, report the issue [here](https://github.com/SabreTools/BinaryObjectScanner/issues) instead.
- Check [previous issues](https://github.com/SabreTools/MPF/issues) to see if any of those are related to what you're about to ask for.
If none of those apply, then continue...

View File

@@ -10,7 +10,7 @@ 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](hhttps://github.com/SabreTools/BinaryObjectScanner/issues) instead.
- Is it copy protection related? If so, report the issue [here](https://github.com/SabreTools/BinaryObjectScanner/issues) instead.
- Check multiple discs to help narrow down the issue
- Check the Options to see if changing any of those affects your issue.

View File

@@ -11,7 +11,7 @@ jobs:
strategy:
matrix:
project: [MPF.Check]
runtime: [win-x86, win-x64, linux-x64, osx-x64] #[win-x86, win-x64, win-arm64, linux-x64, linux-arm64, osx-x64]
runtime: [win-x86, win-x64, win-arm64, linux-x64, linux-arm64, osx-x64, osx-arm64]
framework: [net8.0] #[net20, net35, net40, net452, net472, net48, netcoreapp3.1, net5.0, net6.0, net7.0, net8.0]
steps:

50
.github/workflows/build_cli.yml vendored Normal file
View File

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

61
.github/workflows/build_nupkg.yml vendored Normal file
View File

@@ -0,0 +1,61 @@
name: Nuget Pack
on:
push:
branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Restore dependencies
run: dotnet restore
- name: Pack
run: dotnet pack
- name: Upload Execution Contexts package
uses: actions/upload-artifact@v4
with:
name: 'Execution Contexts Package'
path: 'MPF.ExecutionContexts/bin/Release/*.nupkg'
- name: Upload Execution Contexts to rolling
uses: ncipollo/release-action@v1.14.0
with:
allowUpdates: True
artifacts: 'MPF.ExecutionContexts/bin/Release/*.nupkg'
body: 'Last built commit: ${{ github.sha }}'
name: 'Rolling Release'
prerelease: True
replacesArtifacts: True
tag: "rolling"
updateOnlyUnreleased: True
- name: Upload Processors package
uses: actions/upload-artifact@v4
with:
name: 'Processors Package'
path: 'MPF.Processors/bin/Release/*.nupkg'
- name: Upload Execution Contexts to rolling
uses: ncipollo/release-action@v1.14.0
with:
allowUpdates: True
artifacts: 'MPF.Processors/bin/Release/*.nupkg'
body: 'Last built commit: ${{ github.sha }}'
name: 'Rolling Release'
prerelease: True
replacesArtifacts: True
tag: "rolling"
updateOnlyUnreleased: True

View File

@@ -10,7 +10,7 @@ jobs:
strategy:
matrix:
project: [MPF]
project: [MPF.UI]
runtime: [win-x86, win-x64]
framework: [net8.0-windows] #[net40, net452, net472, net48, netcoreapp3.1, net5.0-windows, net6.0-windows, net7.0-windows, net8.0-windows]
@@ -28,12 +28,19 @@ jobs:
- name: Build
run: dotnet publish ${{ matrix.project }}/${{ matrix.project }}.csproj -f ${{ matrix.framework }} -r ${{ matrix.runtime }} -c Debug --self-contained true --version-suffix ${{ github.sha }} ${{ (startsWith(matrix.framework, 'net5') || startsWith(matrix.framework, 'net6') || startsWith(matrix.framework, 'net7') || startsWith(matrix.framework, 'net8')) && '-p:PublishSingleFile=true' || ''}}
- name: Bundle DiscImageCreator
run: |
wget https://github.com/user-attachments/files/15521936/DiscImageCreator_20240601.zip
unzip -u DiscImageCreator_20240601.zip
mkdir -p MPF.UI/bin/Debug/${{ matrix.framework }}/${{ matrix.runtime }}/publish/Programs/Creator
mv Release_ANSI/* MPF.UI/bin/Debug/${{ matrix.framework }}/${{ matrix.runtime }}/publish/Programs/Creator/
- name: Bundle Redumper
run: |
wget https://github.com/superg/redumper/releases/download/build_311/redumper-2024.01.08_build311-win64.zip
unzip redumper-2024.01.08_build311-win64.zip
mkdir -p MPF/bin/Debug/${{ matrix.framework }}/${{ matrix.runtime }}/publish/Programs/Redumper
mv redumper-2024.01.08_build311-win64/bin/redumper.exe MPF/bin/Debug/${{ matrix.framework }}/${{ matrix.runtime }}/publish/Programs/Redumper/
wget https://github.com/superg/redumper/releases/download/build_371/redumper-2024.05.27_build371-win64.zip
unzip redumper-2024.05.27_build371-win64.zip
mkdir -p MPF.UI/bin/Debug/${{ matrix.framework }}/${{ matrix.runtime }}/publish/Programs/Redumper
mv redumper-2024.05.27_build371-win64/bin/redumper.exe MPF.UI/bin/Debug/${{ matrix.framework }}/${{ matrix.runtime }}/publish/Programs/Redumper/
- name: Archive build
run: zip -r ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_debug.zip ${{ matrix.project }}/bin/Debug/${{ matrix.framework }}/${{ matrix.runtime }}/publish/

0
.gitmodules vendored
View File

5
.vscode/launch.json vendored
View File

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

View File

@@ -1,3 +1,275 @@
### 3.2.1 (2024-08-05)
- Add nuget packing for processors and contexts
- Address build warnings for packages
- Add Linux ARM64 as target by default
- Fix nuget package naming
- Forgot to upload packages to release
- Add `osx-arm64` to libraries
- Better support build matricies
- Show script settings
- Add flag values to script settings
- Enable last runtime by default
- Update README with new build matricies
- Remove empty gitmodules
- Purple
- Separate themes into own namespace and files
- Seal all theme classes
- Add preliminary MPF.CLI
- Add CLI build status to README
- Add CLI information to README
- Save default config values for CLI
- Allow custom parameters for CLI
- Load options before anything else
- Dispose of stream when creating config
- Try to make config safer for CLI
- Blindly assume the path exists
- Add CLI status output on runtime
- Ensure tracks are assigned in Aaru
- Custom theme colors
- Use speed for CLI from configuration
- Fix minimum number of args checks
- Move GetDefaultSpeedForMediaType to common location
- Move some Check-specific methods
- Add some custom CLI parameters
- Try out custom options classes
- Simplify custom parameters warning
- Fix CLI help text alignment
- Bring Check and CLI in parity with param processing
- Remove now-unncessary names
- Don't set MediaType if parameters ambiguous
- Fix parameters after extension change
- Fix logic for deducing region from PlayStation ISN (JohnVeness)
- Fix broken test logic
- Remove RedumpLib tests
- Change to generic wording in report (JohnVeness)
- Add include artifacts flag for check, sanitize options
- Remove old --protect-file mentions (JohnVeness)
- Update RedumpLib to 1.4.1
- Enable loading seed JSON (Deterous)
### 3.2.0 (2024-06-20)
- Create currently-unused processors
- Move DataFile to Core.Data
- Seal XBC processor
- Migrate processor functionality
- Remove now-unneeded parameters classes
- Simplify access within processors
- Rename Parameters to ExecutionContext
- Ensure check-only implementations still work
- Update to DIC 20240401
- Update Redumper to build 329
- Simplify mv command in build config
- Fix subfolder issue from previous
- Remove redundant BinaryReaderExtensions class
- Use Logiqx model instead of internal one
- Split some processing code
- Remove unnecessary field in execution contexts
- Slight tweak to deal with net20
- Make some methods required for override
- Remove dcdumper until further notice
- Invert using statement in dump environment
- Make options internal to dump environment
- Split constants files into component parts
- Remove odd code from Result class
- Slight tweak to Result class variables
- Move constants into related classes
- Separate out artifact generation
- Reduce surface area of generation method
- Make GetLogFilePaths required
- Handle version like category
- Move PlayStation drive use mostly to helper
- Separate out copy protection run
- Remove duplicate GetFullFile method
- Move GetBase64 to InfoTool
- Remove another redundant GetFullFile
- Clean up usings after moving methods
- Remove Chime
- Move string contents for UI to view model
- Remove unused byte array constant
- Fix net20, net35, and net40
- Reduce processing queue sleep time
- Move EnumConverter to Core.Data
- Clean up usings
- Separate out StringEventArgs
- Use StringEventArgs more broadly
- Make StringEventArgs more complete
- Make implicit StringEventArgs bidirectional
- Make implicit Result bidirectional
- Rename Result to ResultEventArgs for consistency
- Reduce accessors for DumpEnvironment
- Better handle interface constants
- Make StringEventArgs internally consistent
- Remove use of "this" in ProcessingQueue
- Use proper private variable naming in ProcessingQueue
- Seal all execution contexts
- Remove use of "this" in Drive
- Move GetRedumpSystemFromVolumeLabel to InfoTool
- Execution context is not needed to extract info
- Remove other reference to execution context
- Make processor private to DumpEnvironment
- Make context private to DumpEnvironment
- Make media type private to DumpEnvironment
- Make system private to DumpEnvironment
- Make drive private to DumpEnvironment
- Simplify RequiredProgramsExist logic
- Reduce complexity of ProcessSystem method
- Remove unnecessary GetAntiModchipDetected method
- Make GetCopyProtection signature easier to read
- Move GetLibCryptDetected back to DIC processor
- Make RunProtectionScanOnPath signature easier to read
- Clean up usings
- Remove automatic eject and reset options
- Remove options from UI
- Remove firmware output for Redumper (Deterous)
- Merge EnumConverter and EnumExtensions
- Move event args to root of Core
- Move processing queue to root of Core
- Move InfoTool to Core.Utilities
- Move SubmissionInfoTool to Core.Utilities
- Fix build
- Rename Protection to ProtectionTool
- Move ProtectionTool to Core.Utilities
- Move Drive to root of Core
- Move Options to root of Core
- Move Enumerations to root of Core
- Move ToInternalDriveType to Drive
- Clean up EnumExtensions
- Make FormattedVolumeLabel a method
- Move GetRedumpSystem to MainViewModel
- Rename Core.UI namespace to Core.Frontend
- Move DumpEnvironment to Core.Frontend
- Move Options to Core.Frontend
- Remove useless using statement
- Move OptionsLoader to Core.Frontend
- Move Logging to Core.Frontend
- Decouple InfoTool from processors
- Rename SubmissionInfoTool to SubmissionGenerator
- Move SubmissionGenerator to Core.Frontend
- Move EnumExtensions to root of core
- Move Options to root of Core
- Decouple Frontend from execution contexts
- Split Core.Frontend into separate library
- Split Core.Processors into separate library
- Split Core.ExecutionContexts into separate library
- Rename Core.* libraries
- Fix up visual solution
- Move Aaru CICM code to Core
- Clean up Core dependencies
- Move Aaru CICM code to Processors
- Merge UI.Core into main application
- Rename main application to MPF.UI
- Fix build scripts
- Make protection file output required
- Standardize PS1-5 outputs and parsing (Deterous)
- Update Redumper to build 371
- Tools always run in separate window
- Move ConsoleLogger to Check CLI
- Move ProcessingQueue to Frontend
- Move LogLevel enum to Frontend
- Create ProcessingTool and move some methods
- Remove unused byte array helper methods
- Move GetSupportStatus to DumpEnvironment
- Call psxt001z direct from DIC processor
- Move GetCopyProtection to ProtectionTool
- Slight cleanup of InfoTool
- Move ProtectionTool to Frontend
- Move ToRedumper* methods to Options
- Move ToMediaType to OptionsLoader
- Move ListPrograms to OptionsLoader
- Move DoesSupportDriveSpeed to DumpEnvironment
- Move ToInternalProgram to Options
- Centralize dumping program information gathering
- Move drive-reading methods to Drive
- Move output writing to DumpEnvironment
- Move Xbox/X360 helpers to ProcessingTool
- Move PS3 helpers to ProcessingTool
- Clean up usings
- Centralize PS1/2 region detection
- Move ProgramSupportsMedia to MainViewModel
- Rename Tools to VersionTool
- Move VersionTool to root of Core
- Move InfoTool to root of Core
- Create FrontendTool and move some methods to it
- Clear out InfoTool and remove
- Merge VersionTool into FrontendTool
- Move EnumExtensions to Frontend
- Treat KP2 like PS2 in DIC
- Slight readability cleanup in DIC
- Hacky move of DIC-specific code
- Remove Drive dependency from GenerateSubmissionInfo
- Move Drive to Frontend
- Move ResultEventArgs to Frontend
- Remove unused reporter delegate
- Move StringEventArgs to Frontend
- Decouple execution contexts from Options class
- Combine remaining Core into Frontend
- Remove Core library, fix build
- Create Frontend.Tools namespace
- Remove magic strings from settings reading
- Move Redumper enums to a better place
- Fix one DIC parameter test
- Ensure setting defaults are consistent
- Move ToRedumper* back to EnumExtensions
- Rearrange test classes to match new format
- Fix logic for PS1-5 system information
- Fix setting PS1-5 version on invalid
- Fix setting Python 2 version on invalid
- Clean up some ProcessSystem cases
- Add PS3 info extraction for DIC
- Update to DIC 20240601
- Fix UI build workflow
- Add update parameter to unzip
- Handle Redumper .asus files
- Handle Redumper .atip and .pma files
- Simplify DIC DMI location finding
- Move track full matching to separate loop
- Fix... something with Linux publish script
- Make match sets immutable
- Update BinaryObjectScanner to 3.1.12
- Fix excluding programs in nix script
### 3.1.9a (2024-05-21)
- Fix dictionary error in Redumper parsing
- Fix overwriting placeholders in Redumper
### 3.1.9 (2024-05-19)
- Update Redumper to build 325
- Fix CleanRip not pulling info (Deterous)
- Fix XboxOne/XboxSX Filename bug (Deterous)
- Trim PIC for XboxOne/XboxSX (Deterous)
- Get volume label from UIC outputs
- Add site code listing to Check
- Update RedumpLib and related
- Update BinaryObjectScanner to 3.1.11
- Remove now-unused Hash enum
- Use IO implementation of IniFile
- Add Xbox Backup Creator support to MPF.Check (Deterous)
- Update BinaryObjectScanner to 3.1.12
- Prefer PlayStation info from Redumper logs (Deterous)
### 3.1.8 (2024-05-09)
- Option for default Redumper leadin retries (Deterous)
- Omit false positives on formatting protections
- Critical update to BinaryObjectScanner 3.1.10
- Add _PFI.bin support for UIC
### 3.1.7 (2024-04-28)
- Critical update to BinaryObjectScanner 3.1.9
### 3.1.6 (2024-04-27)
- Fix parameter parsing for `=` symbol (Deterous)
- Define better default categories (Deterous)
- Custom non-redump Redumper options (Deterous)
- Update packages
- Update packages
### 3.1.5 (2024-04-05)
- Handle `.0.physical` files from Redumpers

View File

@@ -1,15 +1,15 @@
using System;
using BinaryObjectScanner;
using MPF.Core.Data;
using MPF.Frontend;
namespace MPF.Core
namespace MPF.CLI
{
public static class ConsoleLogger
{
/// <summary>
/// Simple process counter to write to console
/// </summary>
public static void ProgressUpdated(object? sender, Result value)
public static void ProgressUpdated(object? sender, ResultEventArgs value)
{
Console.WriteLine(value.Message);
}

61
MPF.CLI/MPF.CLI.csproj Normal file
View File

@@ -0,0 +1,61 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Assembly Properties -->
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
<OutputType>Exe</OutputType>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<VersionPrefix>3.2.1</VersionPrefix>
<!-- Package Properties -->
<Title>MPF CLI</Title>
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
<Description>CLI frontend for various dumping programs</Description>
<Copyright>Copyright (c) Matt Nadareski 2019-2024</Copyright>
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
<RepositoryUrl>https://github.com/SabreTools/MPF</RepositoryUrl>
<RepositoryType>git</RepositoryType>
</PropertyGroup>
<!-- Support All Frameworks -->
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net4`))">
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`))">
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`))">
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(RuntimeIdentifier.StartsWith(`osx-arm`))">
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
</PropertyGroup>
<!-- Support for old .NET versions -->
<ItemGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net40`))">
<PackageReference Include="MinAsyncBridge" Version="0.12.4" />
<PackageReference Include="MinTasksExtensionsBridge" Version="0.3.4" />
<PackageReference Include="MinThreadingBridge" Version="0.11.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MPF.Frontend\MPF.Frontend.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BinaryObjectScanner" PrivateAssets="build; analyzers" ExcludeAssets="contentFiles" Version="3.1.13" GeneratePathProperty="true">
<IncludeAssets>runtime; compile; build; native; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="SabreTools.RedumpLib" Version="1.4.1" />
</ItemGroup>
<ItemGroup>
<Content Include="$(PkgBinaryObjectScanner)\content\**" PackagePath="contentFiles\any\any;content" CopyToOutputDirectory="Always" PackageCopyToOutput="true" />
</ItemGroup>
</Project>

305
MPF.CLI/Program.cs Normal file
View File

@@ -0,0 +1,305 @@
using System;
using System.IO;
#if NET40
using System.Threading;
using System.Threading.Tasks;
#endif
using BinaryObjectScanner;
using MPF.Frontend;
using MPF.Frontend.Tools;
using SabreTools.RedumpLib.Data;
using SabreTools.RedumpLib.Web;
namespace MPF.CLI
{
public class Program
{
public static void Main(string[] args)
{
// Load options from the config file
var options = OptionsLoader.LoadFromConfig();
if (options.FirstRun)
{
// Application paths
options.AaruPath = "FILL ME IN";
options.DiscImageCreatorPath = "FILL ME IN";
options.RedumperPath = "FILL ME IN";
options.InternalProgram = InternalProgram.NONE;
// Reset first run
options.FirstRun = false;
OptionsLoader.SaveToConfig(options, saveDefault: true);
}
// Try processing the standalone arguments
bool? standaloneProcessed = OptionsLoader.ProcessStandaloneArguments(args);
if (standaloneProcessed != false)
{
if (standaloneProcessed == null)
DisplayHelp();
return;
}
// Check for the minimum number of arguments
if (args.Length < 4)
{
DisplayHelp("Not enough arguments have been provided, exiting...");
return;
}
// Try processing the common arguments
(bool success, MediaType mediaType, RedumpSystem? knownSystem, var error) = OptionsLoader.ProcessCommonArguments(args);
if (!success)
{
DisplayHelp(error);
return;
}
// Validate the internal program
switch (options.InternalProgram)
{
case InternalProgram.Aaru:
if (!File.Exists(options.AaruPath))
{
DisplayHelp("A path needs to be supplied for Aaru, exiting...");
return;
}
break;
case InternalProgram.DiscImageCreator:
if (!File.Exists(options.DiscImageCreatorPath))
{
DisplayHelp("A path needs to be supplied for DIC, exiting...");
return;
}
break;
case InternalProgram.Redumper:
if (!File.Exists(options.RedumperPath))
{
DisplayHelp("A path needs to be supplied for Redumper, exiting...");
return;
}
break;
default:
DisplayHelp($"{options.InternalProgram} is not a supported dumping program, exiting...");
break;
}
// Make new Progress objects
var resultProgress = new Progress<ResultEventArgs>();
resultProgress.ProgressChanged += ConsoleLogger.ProgressUpdated;
var protectionProgress = new Progress<ProtectionProgress>();
protectionProgress.ProgressChanged += ConsoleLogger.ProgressUpdated;
// Validate the supplied credentials
(bool? _, string? message) = RedumpClient.ValidateCredentials(options.RedumpUsername ?? string.Empty, options.RedumpPassword ?? string.Empty).GetAwaiter().GetResult();
if (!string.IsNullOrEmpty(message))
Console.WriteLine(message);
// Process any custom parameters
(CommandOptions opts, int startIndex) = LoadFromArguments(args, options, startIndex: 2);
// Ensure we have the values we need
if (opts.CustomParams == null && (opts.DevicePath == null || opts.DevicePath == null))
{
DisplayHelp("Both a device path and file path need to be supplied, exiting...");
return;
}
// Get the speed from the options
int speed = opts.DriveSpeed ?? FrontendTool.GetDefaultSpeedForMediaType(mediaType, options);
// Populate an environment
var drive = Drive.Create(null, opts.DevicePath ?? string.Empty);
var env = new DumpEnvironment(options, opts.FilePath, drive, knownSystem, mediaType, options.InternalProgram, parameters: null);
// Process the parameters
string? paramStr = opts.CustomParams ?? env.GetFullParameters(speed);
if (string.IsNullOrEmpty(paramStr))
{
DisplayHelp("No valid environment could be created, exiting...");
return;
}
env.SetExecutionContext(paramStr);
// Invoke the dumping program
Console.WriteLine($"Invoking {options.InternalProgram} using '{paramStr}'");
var dumpResult = env.Run(resultProgress).GetAwaiter().GetResult();
Console.WriteLine(dumpResult.Message);
if (!dumpResult)
return;
// If it was not a dumping command
if (!env.IsDumpingCommand())
{
Console.WriteLine("Execution not recognized as dumping command, skipping processing...");
return;
}
// Finally, attempt to do the output dance
#if NET40
var verifyTask = env.VerifyAndSaveDumpOutput(resultProgress, protectionProgress);
verifyTask.Wait();
var verifyResult = verifyTask.Result;
#else
var verifyResult = env.VerifyAndSaveDumpOutput(resultProgress, protectionProgress).ConfigureAwait(false).GetAwaiter().GetResult();
#endif
Console.WriteLine(verifyResult.Message);
}
/// <summary>
/// Display help for MPF.CLI
/// </summary>
/// <param name="error">Error string to prefix the help text with</param>
private static void DisplayHelp(string? error = null)
{
if (error != null)
Console.WriteLine(error);
Console.WriteLine("Usage:");
Console.WriteLine("MPF.CLI <mediatype> <system> [options]");
Console.WriteLine();
Console.WriteLine("Standalone Options:");
Console.WriteLine("-h, -? Show this help text");
Console.WriteLine("-lc, --listcodes List supported comment/content site codes");
Console.WriteLine("-lm, --listmedia List supported media types");
Console.WriteLine("-ls, --listsystems List supported system types");
Console.WriteLine("-lp, --listprograms List supported dumping program outputs");
Console.WriteLine();
Console.WriteLine("CLI Options:");
Console.WriteLine("-u, --use <program> Override default dumping program");
Console.WriteLine("-d, --device <devicepath> Physical drive path (Required if no custom parameters set)");
Console.WriteLine("-f, --file \"<filepath>\" Output file path (Required if no custom parameters set)");
Console.WriteLine("-s, --speed <speed> Override default dumping speed");
Console.WriteLine("-c, --custom \"<params>\" Custom parameters to use");
Console.WriteLine();
Console.WriteLine("Custom parameters, if used, will fully replace the default parameters.");
Console.WriteLine("All parameters need to be supplied if doing this.");
Console.WriteLine();
}
/// <summary>
/// Load the current set of options from application arguments
/// </summary>
private static (CommandOptions, int) LoadFromArguments(string[] args, Frontend.Options options, int startIndex = 0)
{
// Create return values
var opts = new CommandOptions();
// If we have no arguments, just return
if (args == null || args.Length == 0)
return (opts, 0);
// If we have an invalid start index, just return
if (startIndex < 0 || startIndex >= args.Length)
return (opts, 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 = Frontend.Options.ToInternalProgram(internalProgram);
}
else if (args[startIndex] == "-u" || args[startIndex] == "--use")
{
string internalProgram = args[startIndex + 1];
options.InternalProgram = Frontend.Options.ToInternalProgram(internalProgram);
startIndex++;
}
// Use a device path
else if (args[startIndex].StartsWith("-d=") || args[startIndex].StartsWith("--device="))
{
opts.DevicePath = args[startIndex].Split('=')[1].Trim('"');
}
else if (args[startIndex] == "-d" || args[startIndex] == "--device")
{
opts.DevicePath = args[startIndex + 1].Trim('"');
startIndex++;
}
// Use a file path
else if (args[startIndex].StartsWith("-f=") || args[startIndex].StartsWith("--file="))
{
opts.FilePath = args[startIndex].Split('=')[1].Trim('"');
}
else if (args[startIndex] == "-f" || args[startIndex] == "--file")
{
opts.FilePath = args[startIndex + 1].Trim('"');
startIndex++;
}
// Set an override speed
else if (args[startIndex].StartsWith("-s=") || args[startIndex].StartsWith("--speed="))
{
if (!int.TryParse(args[startIndex].Split('=')[1].Trim('"'), out int speed))
speed = -1;
opts.DriveSpeed = speed;
}
else if (args[startIndex] == "-s" || args[startIndex] == "--speed")
{
if (!int.TryParse(args[startIndex + 1].Trim('"'), out int speed))
speed = -1;
opts.DriveSpeed = speed;
startIndex++;
}
// Use a custom parameters
else if (args[startIndex].StartsWith("-c=") || args[startIndex].StartsWith("--custom="))
{
opts.CustomParams = args[startIndex].Split('=')[1].Trim('"');
}
else if (args[startIndex] == "-c" || args[startIndex] == "--custom")
{
opts.CustomParams = args[startIndex + 1].Trim('"');
startIndex++;
}
// Default, we fall out
else
{
break;
}
}
return (opts, startIndex);
}
/// <summary>
/// Represents commandline options
/// </summary>
private class CommandOptions
{
/// <summary>
/// Path to the device to dump
/// </summary>
/// <remarks>Required if custom parameters are not set</remarks>
public string? DevicePath { get; set; } = null;
/// <summary>
/// Path to the output file
/// </summary>
/// <remarks>Required if custom parameters are not set</remarks>
public string? FilePath { get; set; } = null;
/// <summary>
/// Override drive speed
/// </summary>
public int? DriveSpeed { get; set; } = null;
/// <summary>
/// Custom parameters for dumping
/// </summary>
public string? CustomParams { get; set; } = null;
}
}
}

View File

@@ -0,0 +1,25 @@
using System;
using BinaryObjectScanner;
using MPF.Frontend;
namespace MPF.Check
{
public static class ConsoleLogger
{
/// <summary>
/// Simple process counter to write to console
/// </summary>
public static void ProgressUpdated(object? sender, ResultEventArgs value)
{
Console.WriteLine(value.Message);
}
/// <summary>
/// Simple process counter to write to console
/// </summary>
public static void ProgressUpdated(object? sender, ProtectionProgress value)
{
Console.WriteLine($"{value.Percentage * 100:N2}%: {value.Filename} - {value.Protection}");
}
}
}

View File

@@ -1,45 +1,65 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Assembly Properties -->
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
<OutputType>Exe</OutputType>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<VersionPrefix>3.1.5</VersionPrefix>
<PropertyGroup>
<!-- Assembly Properties -->
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
<OutputType>Exe</OutputType>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<VersionPrefix>3.2.1</VersionPrefix>
<!-- Package Properties -->
<Title>MPF Check</Title>
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
<Description>Validator for various dumping programs</Description>
<Copyright>Copyright (c) Matt Nadareski 2019-2024</Copyright>
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
<RepositoryUrl>https://github.com/SabreTools/MPF</RepositoryUrl>
<RepositoryType>git</RepositoryType>
</PropertyGroup>
<!-- Package Properties -->
<Title>MPF Check</Title>
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
<Description>Validator for various dumping programs</Description>
<Copyright>Copyright (c) Matt Nadareski 2019-2024</Copyright>
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
<RepositoryUrl>https://github.com/SabreTools/MPF</RepositoryUrl>
<RepositoryType>git</RepositoryType>
</PropertyGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<!-- Support All Frameworks -->
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net4`))">
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`))">
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`))">
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(RuntimeIdentifier.StartsWith(`osx-arm`))">
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MPF.Core\MPF.Core.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BinaryObjectScanner" PrivateAssets="build; analyzers" ExcludeAssets="contentFiles" Version="3.1.5" GeneratePathProperty="true">
<IncludeAssets>runtime; compile; build; native; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="SabreTools.RedumpLib" Version="1.3.6" />
</ItemGroup>
<ItemGroup>
<Content Include="$(PkgBinaryObjectScanner)\content\**" PackagePath="contentFiles\any\any;content" CopyToOutputDirectory="Always" PackageCopyToOutput="true" />
</ItemGroup>
<!-- Support for old .NET versions -->
<ItemGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net40`))">
<PackageReference Include="MinAsyncBridge" Version="0.12.4" />
<PackageReference Include="MinTasksExtensionsBridge" Version="0.3.4" />
<PackageReference Include="MinThreadingBridge" Version="0.11.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MPF.Frontend\MPF.Frontend.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BinaryObjectScanner" PrivateAssets="build; analyzers" ExcludeAssets="contentFiles" Version="3.1.13" GeneratePathProperty="true">
<IncludeAssets>runtime; compile; build; native; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="SabreTools.RedumpLib" Version="1.4.1" />
</ItemGroup>
<ItemGroup>
<Content Include="$(PkgBinaryObjectScanner)\content\**" PackagePath="contentFiles\any\any;content" CopyToOutputDirectory="Always" PackageCopyToOutput="true" />
</ItemGroup>
</Project>

View File

@@ -1,9 +1,13 @@
using System;
using System.IO;
#if NET40
using System.Threading;
using System.Threading.Tasks;
#endif
using BinaryObjectScanner;
using MPF.Core;
using MPF.Core.Data;
using MPF.Core.Utilities;
using MPF.Frontend;
using MPF.Frontend.Tools;
using SabreTools.RedumpLib;
using SabreTools.RedumpLib.Data;
using SabreTools.RedumpLib.Web;
@@ -13,6 +17,19 @@ namespace MPF.Check
{
public static void Main(string[] args)
{
// Create a default options object
var options = new Frontend.Options()
{
RedumpUsername = null,
RedumpPassword = null,
InternalProgram = InternalProgram.NONE,
AddFilenameSuffix = false,
OutputSubmissionJSON = false,
IncludeArtifacts = false,
CompressLogFiles = false,
DeleteUnnecessaryFiles = false,
};
// Try processing the standalone arguments
bool? standaloneProcessed = OptionsLoader.ProcessStandaloneArguments(args);
if (standaloneProcessed != false)
@@ -31,7 +48,7 @@ namespace MPF.Check
}
// Loop through and process options
(var options, var seedInfo, var path, int startIndex) = OptionsLoader.LoadFromArguments(args, startIndex: 2);
(CommandOptions opts, int startIndex) = LoadFromArguments(args, options, startIndex: 2);
if (options.InternalProgram == InternalProgram.NONE)
{
DisplayHelp("A program name needs to be provided");
@@ -39,17 +56,13 @@ namespace MPF.Check
}
// Make new Progress objects
var resultProgress = new Progress<Result>();
var resultProgress = new Progress<ResultEventArgs>();
resultProgress.ProgressChanged += ConsoleLogger.ProgressUpdated;
var protectionProgress = new Progress<ProtectionProgress>();
protectionProgress.ProgressChanged += ConsoleLogger.ProgressUpdated;
// Validate the supplied credentials
#if NETFRAMEWORK
(bool? _, string? message) = RedumpWebClient.ValidateCredentials(options.RedumpUsername ?? string.Empty, options.RedumpPassword ?? string.Empty);
#else
(bool? _, string? message) = RedumpHttpClient.ValidateCredentials(options.RedumpUsername ?? string.Empty, options.RedumpPassword ?? string.Empty).ConfigureAwait(false).GetAwaiter().GetResult();
#endif
(bool? _, string? message) = RedumpClient.ValidateCredentials(options.RedumpUsername ?? string.Empty, options.RedumpPassword ?? string.Empty).GetAwaiter().GetResult();
if (!string.IsNullOrEmpty(message))
Console.WriteLine(message);
@@ -68,19 +81,13 @@ namespace MPF.Check
// Now populate an environment
Drive? drive = null;
if (!string.IsNullOrEmpty(path))
drive = Drive.Create(null, path!);
if (!string.IsNullOrEmpty(opts.DevicePath))
drive = Drive.Create(null, opts.DevicePath!);
var env = new DumpEnvironment(options, filepath, drive, knownSystem, mediaType, internalProgram: null, parameters: null);
// Finally, attempt to do the output dance
#if NET40
var resultTask = env.VerifyAndSaveDumpOutput(resultProgress, protectionProgress);
resultTask.Wait();
var result = resultTask.Result;
#else
var result = env.VerifyAndSaveDumpOutput(resultProgress, protectionProgress).ConfigureAwait(false).GetAwaiter().GetResult();
#endif
var result = env.VerifyAndSaveDumpOutput(resultProgress, protectionProgress, seedInfo: opts.Seed).GetAwaiter().GetResult();
Console.WriteLine(result.Message);
}
}
@@ -95,22 +102,181 @@ namespace MPF.Check
Console.WriteLine(error);
Console.WriteLine("Usage:");
Console.WriteLine("MPF.Check.exe <mediatype> <system> [options] </path/to/output.cue/iso> ...");
Console.WriteLine("MPF.Check <mediatype> <system> [options] </path/to/output.cue/iso> ...");
Console.WriteLine();
Console.WriteLine("Standalone Options:");
Console.WriteLine("-h, -? Show this help text");
Console.WriteLine("-lc, --listcodes List supported comment/content site codes");
Console.WriteLine("-lm, --listmedia List supported media types");
Console.WriteLine("-ls, --listsystems List supported system types");
Console.WriteLine("-lp, --listprograms List supported dumping program outputs");
Console.WriteLine();
Console.WriteLine("Check Options:");
var supportedArguments = OptionsLoader.PrintSupportedArguments();
foreach (string argument in supportedArguments)
{
Console.WriteLine(argument);
}
Console.WriteLine("-u, --use <program> Dumping program output type [REQUIRED]");
Console.WriteLine("-c, --credentials <user> <pw> Redump username and password");
Console.WriteLine(" --pull-all Pull all information from Redump (requires --credentials)");
Console.WriteLine("-p, --path <drivepath> Physical drive path for additional checks");
Console.WriteLine("-s, --scan Enable copy protection scan (requires --path)");
Console.WriteLine(" --hide-drive-letters Hide drive letters from scan output (requires --scan)");
Console.WriteLine("-l, --load-seed <path> Load a seed submission JSON for user information");
Console.WriteLine("-x, --suffix Enable adding filename suffix");
Console.WriteLine("-j, --json Enable submission JSON output");
Console.WriteLine(" --include-artifacts Include artifacts in JSON (requires --json)");
Console.WriteLine("-z, --zip Enable log file compression");
Console.WriteLine("-d, --delete Enable unnecessary file deletion");
Console.WriteLine();
}
/// <summary>
/// Load the current set of options from application arguments
/// </summary>
private static (CommandOptions, int) LoadFromArguments(string[] args, Frontend.Options options, int startIndex = 0)
{
// Create return values
var opts = new CommandOptions();
// These values require multiple parts to be active
bool scan = false, hideDriveLetters = false;
// If we have no arguments, just return
if (args == null || args.Length == 0)
return (opts, 0);
// If we have an invalid start index, just return
if (startIndex < 0 || startIndex >= args.Length)
return (opts, 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 = Frontend.Options.ToInternalProgram(internalProgram);
}
else if (args[startIndex] == "-u" || args[startIndex] == "--use")
{
string internalProgram = args[startIndex + 1];
options.InternalProgram = Frontend.Options.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("--pull-all"))
{
options.PullAllInformation = true;
}
// Use a device path for physical checks
else if (args[startIndex].StartsWith("-p=") || args[startIndex].StartsWith("--path="))
{
opts.DevicePath = args[startIndex].Split('=')[1];
}
else if (args[startIndex] == "-p" || args[startIndex] == "--path")
{
opts.DevicePath = args[startIndex + 1];
startIndex++;
}
// Scan for protection (requires device path)
else if (args[startIndex].Equals("-s") || args[startIndex].Equals("--scan"))
{
scan = true;
}
// Hide drive letters from scan output (requires --scan)
else if (args[startIndex].Equals("--hide-drive-letters"))
{
hideDriveLetters = true;
}
// Include seed info file
else if (args[startIndex].StartsWith("-l=") || args[startIndex].StartsWith("--load-seed="))
{
string seedInfo = args[startIndex].Split('=')[1];
opts.Seed = Builder.CreateFromFile(seedInfo);
}
else if (args[startIndex] == "-l" || args[startIndex] == "--load-seed")
{
string seedInfo = args[startIndex + 1];
opts.Seed = Builder.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;
}
// Output submission JSON
else if (args[startIndex].Equals("--include-artifacts"))
{
options.IncludeArtifacts = 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.IsNullOrEmpty(opts.DevicePath);
options.HideDriveLetters = hideDriveLetters && scan && !string.IsNullOrEmpty(opts.DevicePath);
return (opts, startIndex);
}
/// <summary>
/// Represents commandline options
/// </summary>
private class CommandOptions
{
/// <summary>
/// Seed submission info from an input file
/// </summary>
public SubmissionInfo? Seed { get; set; } = null;
/// <summary>
/// Path to the device to scan
/// </summary>
public string? DevicePath { get; set; } = null;
}
}
}

View File

@@ -1,69 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using SabreTools.RedumpLib.Data;
namespace MPF.Core.Data
{
/// <summary>
/// Constant values for UI
/// </summary>
public static class Interface
{
// Button values
public const string StartDumping = "Start Dumping";
public const string StopDumping = "Stop Dumping";
// Byte arrays for signatures
public static readonly byte[] SaturnSectorZeroStart = [0x53, 0x45, 0x47, 0x41, 0x20, 0x53, 0x45, 0x47, 0x41, 0x53, 0x41, 0x54, 0x55, 0x52, 0x4E, 0x20];
// Lists of known drive speed ranges
#if NET20 || NET35 || NET40
public static IList<int> CD { get; } = new List<int> { 1, 2, 3, 4, 6, 8, 12, 16, 20, 24, 32, 40, 44, 48, 52, 56, 72 };
public static IList<int> DVD { get; } = CD.Where(s => s <= 24).ToList();
public static IList<int> HDDVD { get; } = CD.Where(s => s <= 24).ToList();
public static IList<int> BD { get; } = CD.Where(s => s <= 16).ToList();
public static IList<int> Unknown { get; } = new List<int> { 1 };
#else
public static IReadOnlyList<int> CD { get; } = new List<int> { 1, 2, 3, 4, 6, 8, 12, 16, 20, 24, 32, 40, 44, 48, 52, 56, 72 };
public static IReadOnlyList<int> DVD { get; } = CD.Where(s => s <= 24).ToList();
public static IReadOnlyList<int> HDDVD { get; } = CD.Where(s => s <= 24).ToList();
public static IReadOnlyList<int> BD { get; } = CD.Where(s => s <= 16).ToList();
public static IReadOnlyList<int> Unknown { get; } = new List<int> { 1 };
#endif
/// <summary>
/// Get list of all drive speeds for a given MediaType
/// </summary>
/// <param name="type">MediaType? that represents the current item</param>
/// <returns>Read-only list of drive speeds</returns>
#if NET20 || NET35 || NET40
public static IList<int> GetSpeedsForMediaType(MediaType? type)
#else
public static IReadOnlyList<int> GetSpeedsForMediaType(MediaType? type)
#endif
{
return type switch
{
MediaType.CDROM
or MediaType.GDROM => CD,
MediaType.DVD
or MediaType.NintendoGameCubeGameDisc
or MediaType.NintendoWiiOpticalDisc => DVD,
MediaType.HDDVD => HDDVD,
MediaType.BluRay => BD,
_ => Unknown,
};
}
}
/// <summary>
/// Template field values for submission info
/// </summary>
public static class Template
{
public const string RequiredValue = "(REQUIRED)";
public const string RequiredIfExistsValue = "(REQUIRED, IF EXISTS)";
public const string OptionalValue = "(OPTIONAL)";
public const string DiscNotDetected = "Disc Not Detected";
}
}

View File

@@ -1,718 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
#if NET462_OR_GREATER || NETCOREAPP
using Microsoft.Management.Infrastructure;
using Microsoft.Management.Infrastructure.Generic;
#endif
using MPF.Core.Converters;
using 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>
public InternalDriveType? InternalDriveType { get; set; }
/// <summary>
/// Drive partition format
/// </summary>
public string? DriveFormat { get; private set; } = null;
/// <summary>
/// Windows drive path
/// </summary>
public string? Name { get; private set; } = null;
/// <summary>
/// Represents if Windows has marked the drive as active
/// </summary>
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; private set; } = null;
#endregion
#region Derived Fields
/// <summary>
/// Media label as read by Windows, formatted to avoid odd outputs
/// If no volume label present, use PSX or PS2 serial if valid
/// Otherwise, use "track" as volume label
/// </summary>
public string? FormattedVolumeLabel
{
get
{
string? volumeLabel = Template.DiscNotDetected;
if (!this.MarkedActive)
return volumeLabel;
if (!string.IsNullOrEmpty(this.VolumeLabel))
{
volumeLabel = this.VolumeLabel;
}
else
{
// No Volume Label found, fallback to something sensible
switch (this.GetRedumpSystem(null))
{
case RedumpSystem.SonyPlayStation:
case RedumpSystem.SonyPlayStation2:
InfoTool.GetPlayStationExecutableInfo(this.Name, out string? serial, out _, out _);
volumeLabel = serial ?? "track";
break;
default:
volumeLabel = "track";
break;
}
}
foreach (char c in Path.GetInvalidFileNameChars())
volumeLabel = volumeLabel?.Replace(c, '_');
return volumeLabel;
}
}
/// <summary>
/// Read-only access to the drive letter
/// </summary>
/// <remarks>Should only be used in UI applications</remarks>
public char? Letter => this.Name?[0] ?? '\0';
#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>
public static Drive? Create(InternalDriveType? driveType, string devicePath)
{
// Create a new, empty drive object
var drive = new Drive()
{
InternalDriveType = driveType,
};
// If we have an invalid device path, return null
if (string.IsNullOrEmpty(devicePath))
return null;
// Sanitize a Windows-formatted long device path
if (devicePath.StartsWith("\\\\.\\"))
devicePath = devicePath.Substring("\\\\.\\".Length);
// 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>
private void PopulateFromDriveInfo(DriveInfo? driveInfo)
{
// 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">True to ignore fixed drives from population, false otherwise</param>
/// <returns>Active drives, matched to labels, if possible</returns>
public static List<Drive> CreateListOfDrives(bool ignoreFixedDrives)
{
var drives = GetDriveList(ignoreFixedDrives);
drives = [.. drives.OrderBy(i => i == null ? "\0" : i.Name)];
return drives;
}
/// <summary>
/// Get the current media type from drive letter
/// </summary>
/// <param name="system"></param>
/// <returns></returns>
public (MediaType?, string?) GetMediaType(RedumpSystem? system)
{
// Take care of the non-optical stuff first
switch (this.InternalDriveType)
{
case Data.InternalDriveType.Floppy:
return (MediaType.FloppyDisk, null);
case Data.InternalDriveType.HardDisk:
return (MediaType.HardDisk, null);
case Data.InternalDriveType.Removable:
return (MediaType.FlashDrive, null);
}
// 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>
/// Get the current system from drive
/// </summary>
/// <param name="defaultValue"></param>
/// <returns></returns>
public RedumpSystem? GetRedumpSystem(RedumpSystem? defaultValue)
{
// If we can't read the media in that drive, we can't do anything
if (string.IsNullOrEmpty(this.Name) || !Directory.Exists(this.Name))
return defaultValue;
// We're going to assume for floppies, HDDs, and removable drives
if (this.InternalDriveType != Data.InternalDriveType.Optical)
return RedumpSystem.IBMPCcompatible;
// Check volume labels first
RedumpSystem? systemFromLabel = GetRedumpSystemFromVolumeLabel(this.VolumeLabel);
if (systemFromLabel != null)
return systemFromLabel;
// Get a list of files for quicker checking
#region Arcade
// funworld Photo Play
if (File.Exists(Path.Combine(this.Name, "PP.INF"))
&& Directory.Exists(Path.Combine(this.Name, "PPINC")))
{
return RedumpSystem.funworldPhotoPlay;
}
// Konami Python 2
if (Directory.Exists(Path.Combine(this.Name, "PY2.D")))
{
return RedumpSystem.KonamiPython2;
}
#endregion
#region Consoles
// Bandai Playdia Quick Interactive System
try
{
#if NET20 || NET35
List<string> files = [.. Directory.GetFiles(this.Name, "*", SearchOption.TopDirectoryOnly)];
#else
List<string> files = Directory.EnumerateFiles(this.Name, "*", SearchOption.TopDirectoryOnly).ToList();
#endif
if (files.Any(f => f.EndsWith(".AJS", StringComparison.OrdinalIgnoreCase))
&& files.Any(f => f.EndsWith(".GLB", StringComparison.OrdinalIgnoreCase)))
{
return RedumpSystem.BandaiPlaydiaQuickInteractiveSystem;
}
}
catch { }
// Bandai Pippin
if (File.Exists(Path.Combine(this.Name, "PippinAuthenticationFile")))
{
return RedumpSystem.BandaiPippin;
}
// Commodore CDTV/CD32
#if NET20 || NET35
if (File.Exists(Path.Combine(Path.Combine(this.Name, "S"), "STARTUP-SEQUENCE")))
#else
if (File.Exists(Path.Combine(this.Name, "S", "STARTUP-SEQUENCE")))
#endif
{
if (File.Exists(Path.Combine(this.Name, "CDTV.TM")))
return RedumpSystem.CommodoreAmigaCDTV;
else
return RedumpSystem.CommodoreAmigaCD32;
}
// Mattel HyperScan -- TODO: May need case-insensitivity added
if (File.Exists(Path.Combine(this.Name, "hyper.exe")))
{
return RedumpSystem.MattelHyperScan;
}
// Mattel Fisher-Price iXL
#if NET20 || NET35
if (File.Exists(Path.Combine(Path.Combine(this.Name, "iXL"), "iXLUpdater.exe")))
#else
if (File.Exists(Path.Combine(this.Name, "iXL", "iXLUpdater.exe")))
#endif
{
return RedumpSystem.MattelFisherPriceiXL;
}
// Microsoft Xbox 360
try
{
if (Directory.Exists(Path.Combine(this.Name, "$SystemUpdate"))
#if NET20 || NET35
&& Directory.GetFiles(Path.Combine(this.Name, "$SystemUpdate")).Any()
#else
&& Directory.EnumerateFiles(Path.Combine(this.Name, "$SystemUpdate")).Any()
#endif
&& this.TotalSize <= 500_000_000)
{
return RedumpSystem.MicrosoftXbox360;
}
}
catch { }
// Microsoft Xbox One and Series X
try
{
if (Directory.Exists(Path.Combine(this.Name, "MSXC")))
{
try
{
#if NET20 || NET35
string catalogjs = Path.Combine(this.Name, Path.Combine("MSXC", Path.Combine("Metadata", "catalog.js")));
#else
string catalogjs = Path.Combine(this.Name, "MSXC", "Metadata", "catalog.js");
#endif
if (!File.Exists(catalogjs))
return RedumpSystem.MicrosoftXboxOne;
SabreTools.Models.Xbox.Catalog? catalog = SabreTools.Serialization.Deserializers.Catalog.DeserializeFile(catalogjs);
if (catalog != null && catalog.Version != null && catalog.Packages != null)
{
if (!double.TryParse(catalog.Version, out double version))
return RedumpSystem.MicrosoftXboxOne;
if (version < 4)
return RedumpSystem.MicrosoftXboxOne;
foreach (var package in catalog.Packages)
{
if (package.Generation != "9")
return RedumpSystem.MicrosoftXboxOne;
}
return RedumpSystem.MicrosoftXboxSeriesXS;
}
}
catch
{
return RedumpSystem.MicrosoftXboxOne;
}
}
}
catch { }
// Sega Dreamcast
if (File.Exists(Path.Combine(this.Name, "IP.BIN")))
{
return RedumpSystem.SegaDreamcast;
}
// Sega Mega-CD / Sega-CD
#if NET20 || NET35
if (File.Exists(Path.Combine(Path.Combine(this.Name, "_BOOT"), "IP.BIN"))
|| File.Exists(Path.Combine(Path.Combine(this.Name, "_BOOT"), "SP.BIN"))
|| File.Exists(Path.Combine(Path.Combine(this.Name, "_BOOT"), "SP_AS.BIN"))
|| File.Exists(Path.Combine(this.Name, "FILESYSTEM.BIN")))
#else
if (File.Exists(Path.Combine(this.Name, "_BOOT", "IP.BIN"))
|| File.Exists(Path.Combine(this.Name, "_BOOT", "SP.BIN"))
|| File.Exists(Path.Combine(this.Name, "_BOOT", "SP_AS.BIN"))
|| File.Exists(Path.Combine(this.Name, "FILESYSTEM.BIN")))
#endif
{
return RedumpSystem.SegaMegaCDSegaCD;
}
// Sony PlayStation and Sony PlayStation 2
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
var systemCnf = new IniFile(systemCnfPath);
if (systemCnf.ContainsKey("BOOT"))
return RedumpSystem.SonyPlayStation;
else if (systemCnf.ContainsKey("BOOT2"))
return RedumpSystem.SonyPlayStation2;
}
else if (File.Exists(psxExePath))
{
return RedumpSystem.SonyPlayStation;
}
// Sony PlayStation 3
try
{
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
// 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 NET20 || NET35
if (File.Exists(Path.Combine(Path.Combine(Path.Combine(this.Name, "PS4"), "UPDATE"), "PS4UPDATE.PUP")))
#else
if (File.Exists(Path.Combine(this.Name, "PS4", "UPDATE", "PS4UPDATE.PUP")))
#endif
{
return RedumpSystem.SonyPlayStation4;
}
// V.Tech V.Flash / V.Smile Pro
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"))
#if NET20 || NET35
&& Directory.GetFiles(Path.Combine(this.Name, "AUDIO_TS")).Any())
#else
&& Directory.EnumerateFiles(Path.Combine(this.Name, "AUDIO_TS")).Any())
#endif
{
return RedumpSystem.DVDAudio;
}
else if (Directory.Exists(Path.Combine(this.Name, "VIDEO_TS"))
#if NET20 || NET35
&& Directory.GetFiles(Path.Combine(this.Name, "VIDEO_TS")).Any())
#else
&& Directory.EnumerateFiles(Path.Combine(this.Name, "VIDEO_TS")).Any())
#endif
{
return RedumpSystem.DVDVideo;
}
}
catch { }
// HD-DVD-Video
try
{
if (Directory.Exists(Path.Combine(this.Name, "HVDVD_TS"))
#if NET20 || NET35
&& Directory.GetFiles(Path.Combine(this.Name, "HVDVD_TS")).Any())
#else
&& Directory.EnumerateFiles(Path.Combine(this.Name, "HVDVD_TS")).Any())
#endif
{
return RedumpSystem.HDDVDVideo;
}
}
catch { }
// Photo CD
try
{
if (Directory.Exists(Path.Combine(this.Name, "PHOTO_CD"))
#if NET20 || NET35
&& Directory.GetFiles(Path.Combine(this.Name, "PHOTO_CD")).Any())
#else
&& Directory.EnumerateFiles(Path.Combine(this.Name, "PHOTO_CD")).Any())
#endif
{
return RedumpSystem.PhotoCD;
}
}
catch { }
// VCD
try
{
if (Directory.Exists(Path.Combine(this.Name, "VCD"))
#if NET20 || NET35
&& Directory.GetFiles(Path.Combine(this.Name, "VCD")).Any())
#else
&& Directory.EnumerateFiles(Path.Combine(this.Name, "VCD")).Any())
#endif
{
return RedumpSystem.VideoCD;
}
}
catch { }
#endregion
// Default return
return defaultValue;
}
/// <summary>
/// Get the current system from the drive volume label
/// </summary>
/// <returns>The system based on volume label, null if none detected</returns>
public static RedumpSystem? GetRedumpSystemFromVolumeLabel(string? volumeLabel)
{
// If the volume label is empty, we can't do anything
if (string.IsNullOrEmpty(volumeLabel))
return null;
// Audio CD
if (volumeLabel!.Equals("Audio CD", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.AudioCD;
// Microsoft Xbox
if (volumeLabel.Equals("SEP13011042", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.MicrosoftXbox;
else if (volumeLabel.Equals("SEP13011042072", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.MicrosoftXbox;
// Microsoft Xbox 360
if (volumeLabel.Equals("XBOX360", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.MicrosoftXbox360;
else if (volumeLabel.Equals("XGD2DVD_NTSC", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.MicrosoftXbox360;
// Microsoft Xbox 360 - Too overly broad even if a lot of discs use this
//if (volumeLabel.Equals("CD_ROM", StringComparison.OrdinalIgnoreCase))
// return RedumpSystem.MicrosoftXbox360; // Also for Xbox One?
//if (volumeLabel.Equals("DVD_ROM", StringComparison.OrdinalIgnoreCase))
// return RedumpSystem.MicrosoftXbox360;
// Sega Mega-CD / Sega-CD
if (volumeLabel.Equals("Sega_CD", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.SegaMegaCDSegaCD;
// Sony PlayStation 3
if (volumeLabel.Equals("PS3VOLUME", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.SonyPlayStation3;
// Sony PlayStation 4
if (volumeLabel.Equals("PS4VOLUME", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.SonyPlayStation4;
// Sony PlayStation 5
if (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
{
drives = DriveInfo.GetDrives()
.Where(d => desiredDriveTypes.Contains(d.DriveType))
.Select(d => Create(EnumConverter.ToInternalDriveType(d.DriveType), d.Name) ?? new Drive())
.ToList();
}
catch
{
return drives;
}
// Find and update all floppy drives
#if NET462_OR_GREATER || NETCOREAPP
try
{
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
}
#endif
return drives;
}
#endregion
}
}

View File

@@ -1,294 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace MPF.Core.Data
{
public class IniFile : IDictionary<string, string>
{
private Dictionary<string, string> _keyValuePairs = [];
public string this[string key]
{
get
{
_keyValuePairs ??= [];
key = key.ToLowerInvariant();
if (_keyValuePairs.TryGetValue(key, out string? val))
return val;
return string.Empty;
}
set
{
_keyValuePairs ??= [];
key = key.ToLowerInvariant();
_keyValuePairs[key] = value;
}
}
/// <summary>
/// Create an empty INI file
/// </summary>
public IniFile()
{
}
/// <summary>
/// Populate an INI file from path
/// </summary>
public IniFile(string path)
{
this.Parse(path);
}
/// <summary>
/// Populate an INI file from stream
/// </summary>
public IniFile(Stream stream)
{
this.Parse(stream);
}
/// <summary>
/// Add or update a key and value to the INI file
/// </summary>
public void AddOrUpdate(string key, string value)
{
_keyValuePairs[key.ToLowerInvariant()] = value;
}
/// <summary>
/// Remove a key from the INI file
/// </summary>
public void Remove(string key)
{
_keyValuePairs.Remove(key.ToLowerInvariant());
}
/// <summary>
/// Read an INI file based on the path
/// </summary>
public bool Parse(string path)
{
// If we don't have a file, we can't read it
if (!File.Exists(path))
return false;
using var fileStream = File.OpenRead(path);
return Parse(fileStream);
}
/// <summary>
/// Read an INI file from a stream
/// </summary>
public bool Parse(Stream stream)
{
// If the stream is invalid or unreadable, we can't process it
if (stream == null || !stream.CanRead || stream.Position >= stream.Length - 1)
return false;
// Keys are case-insensitive by default
try
{
using var sr = new StreamReader(stream);
string section = string.Empty;
while (!sr.EndOfStream)
{
var line = sr.ReadLine()?.Trim();
// Empty lines are skipped
if (string.IsNullOrEmpty(line))
{
// No-op, we don't process empty lines
}
// Comments start with ';'
else if (line!.StartsWith(";"))
{
// No-op, we don't process comments
}
// Section titles are surrounded by square brackets
else if (line.StartsWith("["))
{
section = line.TrimStart('[').TrimEnd(']');
}
// Valid INI lines are in the format key=value
else if (line.Contains('='))
{
// Split the line by '=' for key-value pairs
string[] data = line.Split('=');
// If the value field contains an '=', we need to put them back in
string key = data[0].Trim();
string value = string.Join("=", data.Skip(1).ToArray()).Trim();
// Section names are prepended to the key with a '.' separating
if (!string.IsNullOrEmpty(section))
key = $"{section}.{key}";
// Set or overwrite keys in the returned dictionary
_keyValuePairs[key.ToLowerInvariant()] = value;
}
// All other lines are ignored
}
}
catch
{
// We don't care what the error was, just catch and return
return false;
}
return true;
}
/// <summary>
/// Write an INI file to a path
/// </summary>
public bool Write(string path)
{
// If we don't have a valid dictionary with values, we can't write out
if (_keyValuePairs == null || _keyValuePairs.Count == 0)
return false;
using var fileStream = File.OpenWrite(path);
return Write(fileStream);
}
/// <summary>
/// Write an INI file to a stream
/// </summary>
public bool Write(Stream stream)
{
// If we don't have a valid dictionary with values, we can't write out
if (_keyValuePairs == null || _keyValuePairs.Count == 0)
return false;
// If the stream is invalid or unwritable, we can't output to it
if (stream == null || !stream.CanWrite || stream.Position >= stream.Length - 1)
return false;
try
{
// Order the dictionary by keys to link sections together
using var sw = new StreamWriter(stream);
var orderedKeyValuePairs = _keyValuePairs.OrderBy(kvp => kvp.Key);
string section = string.Empty;
foreach (var keyValuePair in orderedKeyValuePairs)
{
// Extract the key and value
string key = keyValuePair.Key;
string value = keyValuePair.Value;
// We assume '.' is a section name separator
if (key.Contains('.'))
{
// Split the key by '.'
string[] data = keyValuePair.Key.Split('.');
// If the key contains an '.', we need to put them back in
string newSection = data[0].Trim();
key = string.Join(".", data.Skip(1).ToArray()).Trim();
// If we have a new section, write it out
if (!string.Equals(newSection, section, StringComparison.OrdinalIgnoreCase))
{
sw.WriteLine($"[{newSection}]");
section = newSection;
}
}
// Now write out the key and value in a standardized way
sw.WriteLine($"{key}={value}");
}
}
catch
{
// We don't care what the error was, just catch and return
return false;
}
return true;
}
#region IDictionary Impelementations
public ICollection<string> Keys => ((IDictionary<string, string>)_keyValuePairs).Keys;
public ICollection<string> Values => ((IDictionary<string, string>)_keyValuePairs).Values;
public int Count => ((ICollection<KeyValuePair<string, string>>)_keyValuePairs).Count;
public bool IsReadOnly => ((ICollection<KeyValuePair<string, string>>)_keyValuePairs).IsReadOnly;
public void Add(string key, string value)
{
((IDictionary<string, string>)_keyValuePairs).Add(key.ToLowerInvariant(), value);
}
bool IDictionary<string, string>.Remove(string key)
{
return ((IDictionary<string, string>)_keyValuePairs).Remove(key.ToLowerInvariant());
}
public bool TryGetValue(string key, out string 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)
{
var newItem = new KeyValuePair<string, string>(item.Key.ToLowerInvariant(), item.Value);
((ICollection<KeyValuePair<string, string>>)_keyValuePairs).Add(newItem);
}
public void Clear()
{
((ICollection<KeyValuePair<string, string>>)_keyValuePairs).Clear();
}
public bool Contains(KeyValuePair<string, string> item)
{
var newItem = new KeyValuePair<string, string>(item.Key.ToLowerInvariant(), item.Value);
return ((ICollection<KeyValuePair<string, string>>)_keyValuePairs).Contains(newItem);
}
public bool ContainsKey(string key)
{
return _keyValuePairs.ContainsKey(key.ToLowerInvariant());
}
public void CopyTo(KeyValuePair<string, string>[] array, int arrayIndex)
{
((ICollection<KeyValuePair<string, string>>)_keyValuePairs).CopyTo(array, arrayIndex);
}
public bool Remove(KeyValuePair<string, string> item)
{
var newItem = new KeyValuePair<string, string>(item.Key.ToLowerInvariant(), item.Value);
return ((ICollection<KeyValuePair<string, string>>)_keyValuePairs).Remove(newItem);
}
public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
{
return ((IEnumerable<KeyValuePair<string, string>>)_keyValuePairs).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)_keyValuePairs).GetEnumerator();
}
#endregion
}
}

View File

@@ -1,580 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using MPF.Core.Data;
using MPF.Core.Modules;
using MPF.Core.Utilities;
using SabreTools.RedumpLib;
using SabreTools.RedumpLib.Data;
namespace MPF.Core
{
/// <summary>
/// Represents the state of all settings to be used during dumping
/// </summary>
public class DumpEnvironment
{
#region Output paths
/// <summary>
/// Base output file path to write files to
/// </summary>
public string OutputPath { get; private set; }
#endregion
#region UI information
/// <summary>
/// Drive object representing the current drive
/// </summary>
public Drive? Drive { get; private set; }
/// <summary>
/// Currently selected system
/// </summary>
public RedumpSystem? System { get; private set; }
/// <summary>
/// Currently selected media type
/// </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 Data.Options Options { get; private set; }
/// <summary>
/// Parameters object representing what to send to the internal program
/// </summary>
public BaseParameters? Parameters { get; private set; }
#endregion
#region Event Handlers
/// <summary>
/// Generic way of reporting a message
/// </summary>
#if NET20 || NET35 || NET40
public EventHandler<BaseParameters.StringEventArgs>? ReportStatus;
#else
public EventHandler<string>? ReportStatus;
#endif
/// <summary>
/// Queue of items that need to be logged
/// </summary>
private ProcessingQueue<string>? outputQueue;
/// <summary>
/// Event handler for data returned from a process
/// </summary>
#if NET20 || NET35 || NET40
private void OutputToLog(object? proc, BaseParameters.StringEventArgs args) => outputQueue?.Enqueue(args.Value);
#else
private void OutputToLog(object? proc, string args) => outputQueue?.Enqueue(args);
#endif
/// <summary>
/// Process the outputs in the queue
/// </summary>
#if NET20 || NET35 || NET40
private void ProcessOutputs(string nextOutput) => ReportStatus?.Invoke(this, new BaseParameters.StringEventArgs { Value = nextOutput });
#else
private void ProcessOutputs(string nextOutput) => ReportStatus?.Invoke(this, nextOutput);
#endif
#endregion
/// <summary>
/// Constructor for a full DumpEnvironment object from user information
/// </summary>
/// <param name="options"></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(Data.Options options,
string outputPath,
Drive? drive,
RedumpSystem? system,
MediaType? type,
InternalProgram? internalProgram,
string? parameters)
{
// Set options object
Options = options;
// Output paths
OutputPath = InfoTool.NormalizeOutputPaths(outputPath, false);
// UI information
Drive = drive;
System = system ?? options.DefaultSystem;
Type = type ?? MediaType.NONE;
InternalProgram = internalProgram ?? options.InternalProgram;
// Dumping program
SetParameters(parameters);
}
#region Public Functionality
/// <summary>
/// Set the parameters object based on the internal program and parameters string
/// </summary>
/// <param name="parameters">String representation of the parameters</param>
public void SetParameters(string? parameters)
{
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.PS3CFW => new Modules.PS3CFW.Parameters(parameters) { ExecutablePath = null },
InternalProgram.UmdImageCreator => new Modules.UmdImageCreator.Parameters(parameters) { ExecutablePath = null },
// If no dumping program found, set to null
InternalProgram.NONE => null,
// This should never happen, but it needs a fallback
_ => new Modules.Redumper.Parameters(parameters) { ExecutablePath = Options.RedumperPath },
};
// Set system and type
if (Parameters != null)
{
Parameters.System = System;
Parameters.Type = Type;
}
}
/// <summary>
/// Get the full parameter string for either DiscImageCreator or Aaru
/// </summary>
/// <param name="driveSpeed">Nullable int representing the drive speed</param>
/// <returns>String representing the params, null on error</returns>
public string? GetFullParameters(int? driveSpeed)
{
// Populate with the correct params for inputs (if we're not on the default option)
if (System != null && Type != MediaType.NONE)
{
// If drive letter is invalid, skip this
if (Drive == null)
return null;
// Set the proper parameters
Parameters = InternalProgram switch
{
InternalProgram.Aaru => new Modules.Aaru.Parameters(System, Type, Drive.Name, OutputPath, driveSpeed, Options),
InternalProgram.DiscImageCreator => new Modules.DiscImageCreator.Parameters(System, Type, Drive.Name, OutputPath, driveSpeed, Options),
InternalProgram.Redumper => new Modules.Redumper.Parameters(System, Type, Drive.Name, OutputPath, driveSpeed, Options),
// If no dumping program found, set to null
InternalProgram.NONE => null,
// This should never happen, but it needs a fallback
_ => new Modules.Redumper.Parameters(System, Type, Drive.Name, OutputPath, driveSpeed, Options),
};
// Generate and return the param string
return Parameters?.GenerateParameters();
}
return null;
}
#endregion
#region Dumping
/// <summary>
/// Cancel an in-progress dumping process
/// </summary>
public void CancelDumping() => Parameters?.KillInternalProgram();
/// <summary>
/// Eject the disc using DiscImageCreator
/// </summary>
public async Task<string?> EjectDisc() =>
await RunStandaloneDiscImageCreatorCommand(Modules.DiscImageCreator.CommandStrings.Eject);
/// <summary>
/// Reset the current drive using DiscImageCreator
/// </summary>
public async Task<string?> ResetDrive() =>
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 NET40
public 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)
return result;
// Invoke output processing, if needed
if (!Options.ToolsInSeparateWindow)
{
outputQueue = new ProcessingQueue<string>(ProcessOutputs);
if (Parameters.ReportStatus != null)
Parameters.ReportStatus += OutputToLog;
}
// Execute internal tool
progress?.Report(Result.Success($"Executing {InternalProgram}... {(Options.ToolsInSeparateWindow ? "please wait!" : "see log for output!")}"));
var directoryName = Path.GetDirectoryName(OutputPath);
if (!string.IsNullOrEmpty(directoryName))
Directory.CreateDirectory(directoryName);
#if NET40
var executeTask = Task.Factory.StartNew(() => Parameters.ExecuteInternalProgram(Options.ToolsInSeparateWindow));
executeTask.Wait();
#else
await Task.Run(() => Parameters.ExecuteInternalProgram(Options.ToolsInSeparateWindow));
#endif
progress?.Report(Result.Success($"{InternalProgram} has finished!"));
// Remove event handler if needed
if (!Options.ToolsInSeparateWindow)
{
outputQueue?.Dispose();
Parameters.ReportStatus -= OutputToLog;
}
return result;
}
/// <summary>
/// Verify that the current environment has a complete dump and create submission info is possible
/// </summary>
/// <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(
IProgress<Result>? resultProgress = null,
IProgress<BinaryObjectScanner.ProtectionProgress>? protectionProgress = null,
Func<SubmissionInfo?, (bool?, SubmissionInfo?)>? processUserInfo = null,
SubmissionInfo? seedInfo = null)
{
if (Parameters == null)
return Result.Failure("Error! Current configuration is not supported!");
resultProgress?.Report(Result.Success("Gathering submission information... please wait!"));
// Get the output directory and filename separately
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) = Parameters.FoundAllFiles(outputDirectory, outputFilename, false);
if (!foundFiles)
{
resultProgress?.Report(Result.Failure($"There were files missing from the output:\n{string.Join("\n", [.. missingFiles])}"));
return Result.Failure("Error! Please check output directory as dump may be incomplete!");
}
// Extract the information from the output files
resultProgress?.Report(Result.Success("Extracting output information from output files..."));
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..."));
Builder.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?.Name}"));
await EjectDisc();
}
// Reset the drive automatically if configured to
if (InternalProgram == InternalProgram.DiscImageCreator && Options.DICResetDriveAfterDump)
{
resultProgress?.Report(Result.Success($"Resetting drive {Drive?.Name}"));
await ResetDrive();
}
// Get user-modifiable information if confugured to
if (Options.PromptForDiscInformation && processUserInfo != null)
{
resultProgress?.Report(Result.Success("Waiting for additional disc information..."));
bool? filledInfo;
(filledInfo, submissionInfo) = processUserInfo(submissionInfo);
if (filledInfo == true)
resultProgress?.Report(Result.Success("Additional disc information added!"));
else
resultProgress?.Report(Result.Success("Disc information skipped!"));
}
// Process special fields for site codes
resultProgress?.Report(Result.Success("Processing site codes..."));
Formatter.ProcessSpecialFields(submissionInfo);
resultProgress?.Report(Result.Success("Processing complete!"));
// Format the information for the text output
resultProgress?.Report(Result.Success("Formatting information..."));
(var formattedValues, var formatResult) = Formatter.FormatOutputData(submissionInfo, Options.EnableRedumpCompatibility);
if (formattedValues == null)
resultProgress?.Report(Result.Failure(formatResult));
else
resultProgress?.Report(Result.Success(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 txtSuccess, string txtResult) = InfoTool.WriteOutputData(outputDirectory, filenameSuffix, formattedValues);
if (txtSuccess)
resultProgress?.Report(Result.Success(txtResult));
else
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, Options.HideDriveLetters);
if (scanSuccess)
resultProgress?.Report(Result.Success("Writing complete!"));
else
resultProgress?.Report(Result.Failure("Writing could not complete!"));
}
}
// Write the JSON output, if required
if (Options.OutputSubmissionJSON)
{
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!"));
}
// Compress the logs, if required
if (Options.CompressLogFiles)
{
resultProgress?.Report(Result.Success("Compressing log files..."));
(bool compressSuccess, string compressResult) = InfoTool.CompressLogFiles(outputDirectory, filenameSuffix, outputFilename, Parameters);
if (compressSuccess)
resultProgress?.Report(Result.Success(compressResult));
else
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));
}
// Create PS3 IRD, if required
if (Options.CreateIRDAfterDumping && System == RedumpSystem.SonyPlayStation3 && Type == MediaType.BluRay)
{
resultProgress?.Report(Result.Success("Creating IRD... please wait!"));
(bool deleteSuccess, string deleteResult) = await InfoTool.WriteIRD(OutputPath, submissionInfo?.Extras?.DiscKey, submissionInfo?.Extras?.DiscID, submissionInfo?.Extras?.PIC, submissionInfo?.SizeAndChecksums?.Layerbreak, submissionInfo?.SizeAndChecksums?.CRC32);
if (deleteSuccess)
resultProgress?.Report(Result.Success(deleteResult));
else
resultProgress?.Report(Result.Failure(deleteResult));
}
resultProgress?.Report(Result.Success("Submission information process complete!"));
return Result.Success();
}
/// <summary>
/// Checks if the parameters are valid
/// </summary>
/// <returns>True if the configuration is valid, false otherwise</returns>
internal bool ParametersValid()
{
// 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
bool removableDiskValid = !((Drive.InternalDriveType == InternalDriveType.Removable || Drive.InternalDriveType == InternalDriveType.HardDisk)
^ (Type == MediaType.CompactFlash || Type == MediaType.SDCard || Type == MediaType.FlashDrive || Type == MediaType.HardDisk));
return parametersValid && floppyValid && removableDiskValid;
}
/// <summary>
/// Run internal program async with an input set of parameters
/// </summary>
/// <param name="parameters"></param>
/// <returns>Standard output from commandline window</returns>
private static async Task<string> ExecuteInternalProgram(BaseParameters parameters)
{
Process childProcess;
#if NET40
string output = await Task.Factory.StartNew(() =>
#else
string output = await Task.Run(() =>
#endif
{
childProcess = new Process()
{
StartInfo = new ProcessStartInfo()
{
FileName = parameters.ExecutablePath!,
Arguments = parameters.GenerateParameters()!,
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
},
};
childProcess.Start();
childProcess.WaitForExit(1000);
// Just in case, we want to push a button 5 times to clear any errors
for (int i = 0; i < 5; i++)
childProcess.StandardInput.WriteLine("Y");
string stdout = childProcess.StandardOutput.ReadToEnd();
childProcess.Dispose();
return stdout;
});
return output;
}
/// <summary>
/// Validate the current environment is ready for a dump
/// </summary>
/// <returns>Result instance with the outcome</returns>
private Result IsValidForDump()
{
// Validate that everything is good
if (Parameters == null || !ParametersValid())
return Result.Failure("Error! Current configuration is not supported!");
// Fix the output paths, just in case
OutputPath = InfoTool.NormalizeOutputPaths(OutputPath, false);
// Validate that the output path isn't on the dumping drive
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))
return Result.Failure($"Error! {Parameters.ExecutablePath} does not exist!");
// Validate that the dumping drive doesn't contain the executable
string fullExecutablePath = Path.GetFullPath(Parameters.ExecutablePath!);
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);
}
/// <summary>
/// Validate that DIscImageCreator is able to be found
/// </summary>
/// <returns>True if DiscImageCreator is found properly, false otherwise</returns>
private bool RequiredProgramsExist()
{
// Validate that the path is configured
if (string.IsNullOrEmpty(Options.DiscImageCreatorPath))
return false;
// Validate that the required program exists
if (!File.Exists(Options.DiscImageCreatorPath))
return false;
return true;
}
/// <summary>
/// Run a standalone DiscImageCreator command
/// </summary>
/// <param name="command">Command string to run</param>
/// <returns>The output of the command on success, null on error</returns>
private async Task<string?> RunStandaloneDiscImageCreatorCommand(string command)
{
// Validate that DiscImageCreator is all set
if (!RequiredProgramsExist())
return null;
// Validate we're not trying to eject a non-optical
if (Drive == null || Drive.InternalDriveType != InternalDriveType.Optical)
return null;
CancelDumping();
var parameters = new Modules.DiscImageCreator.Parameters(string.Empty)
{
BaseCommand = command,
DrivePath = Drive.Name,
ExecutablePath = Options.DiscImageCreatorPath,
};
return await ExecuteInternalProgram(parameters);
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,491 +0,0 @@
namespace MPF.Core.Modules.Aaru
{
/// <summary>
/// Top-level commands for Aaru
/// </summary>
public static class CommandStrings
{
public const string NONE = "";
// Archive Family
public const string ArchivePrefixShort = "arc";
public const string ArchivePrefixLong = "archive";
public const string ArchiveInfo = "info";
// Database Family
public const string DatabasePrefixShort = "db";
public const string DatabasePrefixLong = "database";
public const string DatabaseStats = "stats";
public const string DatabaseUpdate = "update";
// Device Family
public const string DevicePrefixShort = "dev";
public const string DevicePrefixLong = "device";
public const string DeviceInfo = "info";
public const string DeviceList = "list";
public const string DeviceReport = "report";
// Filesystem Family
public const string FilesystemPrefixShort = "fi";
public const string FilesystemPrefixShortAlt = "fs";
public const string FilesystemPrefixLong = "filesystem";
public const string FilesystemExtract = "extract";
public const string FilesystemInfo = "info";
public const string FilesystemListShort = "ls";
public const string FilesystemListLong = "list";
public const string FilesystemOptions = "options";
// Image Family
public const string ImagePrefixShort = "i";
public const string ImagePrefixLong = "image";
public const string ImageChecksumShort = "chk";
public const string ImageChecksumLong = "checksum";
public const string ImageCompareShort = "cmp";
public const string ImageCompareLong = "compare";
public const string ImageConvert = "convert";
public const string ImageCreateSidecar = "create-sidecar";
public const string ImageDecode = "decode";
public const string ImageEntropy = "entropy";
public const string ImageInfo = "info";
public const string ImageOptions = "options";
public const string ImagePrint = "print";
public const string ImageVerify = "verify";
// Media Family
public const string MediaPrefixShort = "m";
public const string MediaPrefixLong = "media";
public const string MediaDump = "dump";
public const string MediaInfo = "info";
public const string MediaScan = "scan";
// Standalone Commands
public const string Configure = "configure";
public const string Formats = "formats";
public const string ListEncodings = "list-encodings";
public const string ListNamespaces = "list-namespaces";
public const string Remote = "remote";
}
/// <summary>
/// Supported encodings for Aaru
/// </summary>
/// TODO: Use to verify encoding settings
public static class EncodingStrings
{
public const string ArabicMac = "x-mac-arabic";
public const string AtariASCII = "atascii";
public const string CentralEuropeanMac = "x-mac-ce";
public const string CommodorePETSCII = "petscii";
public const string CroatianMac = "x-mac-croatian";
public const string CyrillicMac = "x-mac-cryillic";
public const string FarsiMac = "x-mac-farsi";
public const string GreekMac = "x-mac-greek";
public const string HebrewMac = "x-mac-hebrew";
public const string RomanianMac = "x-mac-romanian";
public const string SinclairZXSpectrum = "spectrum";
public const string SinclairZX80 = "zx80";
public const string SinclairZX81 = "zx81";
public const string TurkishMac = "x-mac-turkish";
public const string UkrainianMac = "x-mac-ukrainian";
public const string Unicode = "utf-16";
public const string UnicodeBigEndian = "utf-16BE";
public const string UnicodeUTF32BigEndian = "utf-32BE";
public const string UnicodeUTF32 = "utf-32";
public const string UnicodeUTF7 = "utf-7";
public const string UnicodeUTF8 = "utf-8";
public const string USASCII = "us-ascii";
public const string WesternEuropeanAppleII = "apple2";
public const string WesternEuropeanAppleIIc = "apple2c";
public const string WesternEuropeanAppleIIe = "apple2e";
public const string WesternEuropeanAppleIIgs = "apple2gs";
public const string WesternEuropeanAppleLisa = "lisa";
public const string WesternEuropeanAtariST = "atarist";
public const string WesternEuropeanGEM = "gem";
public const string WesternEuropeanGEOS = "geos";
public const string WesternEuropeanISO = "iso-8859-1";
public const string WesternEuropeanMac = "macintosh";
public const string WesternEuropeanRadix50 = "radix50";
}
/// <summary>
/// Dumping flags for Aaru
/// </summary>
public static class FlagStrings
{
// Boolean flags
public const string Adler32Short = "-a";
public const string Adler32Long = "--adler32";
public const string ClearLong = "--clear";
public const string ClearAllLong = "--clear-all";
public const string CRC16Long = "--crc16";
public const string CRC32Short = "-c";
public const string CRC32Long = "--crc32";
public const string CRC64Long = "--crc64";
public const string DebugShort = "-d";
public const string DebugLong = "--debug";
public const string DiskTagsShort = "-f";
public const string DiskTagsLong = "--disk-tags";
public const string DuplicatedSectorsShort = "-p";
public const string DuplicatedSectorsLong = "--duplicated-sectors";
public const string EjectLong = "--eject";
public const string ExtendedAttributesShort = "-x";
public const string ExtendedAttributesLong = "--xattrs";
public const string FilesystemsShort = "-f";
public const string FilesystemsLong = "--filesystems";
public const string FirstPregapLong = "--first-pregap";
public const string FixOffsetLong = "--fix-offset";
public const string FixSubchannelLong = "--fix-subchannel";
public const string FixSubchannelCrcLong = "--fix-subchannel-crc";
public const string FixSubchannelPositionLong = "--fix-subchannel-position";
public const string Fletcher16Long = "--fletcher16";
public const string Fletcher32Long = "--fletcher32";
public const string ForceShort = "-f";
public const string ForceLong = "--force";
public const string GenerateSubchannelsLong = "--generate-subchannels";
public const string HelpShort = "-h";
public const string HelpShortAlt = "-?";
public const string HelpLong = "--help";
public const string LongFormatShort = "-l";
public const string LongFormatLong = "--long-format";
public const string LongSectorsShort = "-r";
public const string LongSectorsLong = "--long-sectors";
public const string MD5Short = "-m";
public const string MD5Long = "--md5";
public const string MetadataLong = "--metadata";
public const string PartitionsShort = "-p";
public const string PartitionsLong = "--partitions";
public const string PauseLong = "--pause";
public const string PersistentLong = "--persistent";
public const string PrivateLong = "--private";
public const string ResumeShort = "-r";
public const string ResumeLong = "--resume";
public const string RetrySubchannelLong = "--retry-subchannel";
public const string SectorTagsShort = "-p";
public const string SectorTagsLong = "--sector-tags";
public const string SeparatedTracksShort = "-t";
public const string SeparatedTracksLong = "--separated-tracks";
public const string SHA1Short = "-s";
public const string SHA1Long = "--sha1";
public const string SHA256Long = "--sha256";
public const string SHA384Long = "--sha384";
public const string SHA512Long = "--sha512";
public const string SkipCdiReadyHoleLong = "--skip-cdiready-hole";
public const string SpamSumShort = "-f";
public const string SpamSumLong = "--spamsum";
public const string StopOnErrorShort = "-s";
public const string StopOnErrorLong = "--stop-on-error";
public const string StoreEncryptedLong = "--store-encrypted";
public const string TapeShort = "-t";
public const string TapeLong = "--tape";
public const string TitleKeysLong = "--title-keys";
public const string TrapDiscShort = "-t";
public const string TrapDiscLong = "--trap-disc";
public const string TrimLong = "--trim";
public const string UseBufferedReadsLong = "--use-buffered-reads";
public const string VerboseShort = "-v";
public const string VerboseLong = "--verbose";
public const string VerifyDiscShort = "-w";
public const string VerifyDiscLong = "--verify-disc";
public const string VerifySectorsShort = "-s";
public const string VerifySectorsLong = "--verify-sectors";
public const string VersionLong = "--version";
public const string WholeDiscShort = "-w";
public const string WholeDiscLong = "--whole-disc";
// Int8 flags
public const string SpeedLong = "--speed";
// Int16 flags
public const string RetryPassesShort = "-p";
public const string RetryPassesLong = "--retry-passes";
public const string WidthShort = "-w";
public const string WidthLong = "--width";
// Int32 flags
public const string BlockSizeShort = "-b";
public const string BlockSizeLong = "--block-size";
public const string CountShort = "-c";
public const string CountLong = "--count";
public const string MaxBlocksLong = "--max-blocks";
public const string MediaLastSequenceLong = "--media-lastsequence";
public const string MediaSequenceLong = "--media-sequence";
public const string SkipShort = "-k";
public const string SkipLong = "--skip";
// Int64 flags
public const string LengthShort = "-l"; // or "all"
public const string LengthLong = "--length"; // or "all"
public const string StartShort = "-s";
public const string StartLong = "--start";
// String flags
public const string CommentsLong = "--comments";
public const string CreatorLong = "--creator";
public const string DriveManufacturerLong = "--drive-manufacturer";
public const string DriveModelLong = "--drive-model";
public const string DriveRevisionLong = "--drive-revision";
public const string DriveSerialLong = "--drive-serial";
public const string EncodingShort = "-e";
public const string EncodingLong = "--encoding";
public const string FormatConvertShort = "-p";
public const string FormatConvertLong = "--format";
public const string FormatDumpShort = "-t";
public const string FormatDumpLong = "--format";
public const string GeometryShort = "-g";
public const string GeometryLong = "--geometry";
public const string ImgBurnLogShort = "-b";
public const string ImgBurnLogLong = "--ibg-log";
public const string MediaBarcodeLong = "--media-barcode";
public const string MediaManufacturerLong = "--media-manufacturer";
public const string MediaModelLong = "--media-model";
public const string MediaPartNumberLong = "--media-partnumber";
public const string MediaSerialLong = "--media-serial";
public const string MediaTitleLong = "--media-title";
public const string MHDDLogShort = "-m";
public const string MHDDLogLong = "--mhdd-log";
public const string NamespaceShort = "-n";
public const string NamespaceLong = "--namespace";
public const string OptionsShort = "-O";
public const string OptionsLong = "--options";
public const string OutputPrefixShort = "-w";
public const string OutputPrefixLong = "--output-prefix";
public const string ResumeFileShort = "-r";
public const string ResumeFileLong = "--resume-file";
public const string SubchannelLong = "--subchannel";
public const string XMLSidecarShort = "-x";
public const string XMLSidecarLong = "--cicm-xml";
}
/// <summary>
/// Supported formats for Aaru
/// </summary>
/// TODO: Use to verify format settings
public static class FormatStrings
{
// Supported filters
public const string AppleDouble = "AppleDouble";
public const string AppleSingle = "AppleSingle";
public const string BZip2 = "BZip2";
public const string GZip = "GZip";
public const string LZip = "LZip";
public const string MacBinary = "MacBinary";
public const string NoFilter = "No filter";
public const string PCExchange = "PCExchange";
public const string XZ = "XZ";
// Read-only media image formats
public const string AppleDiskArchivalRetrievalTool = "Apple Disk Archival/Retrieval Tool";
public const string AppleNewDiskImageFormat = "Apple New Disk Image Format";
public const string AppleNIB = "Apple NIB";
public const string BlindWrite4 = "BlindWrite 4";
public const string BlindWrite5 = "BlindWrite 5";
public const string CPCEMUDiskFileAndExtendedCPCDiskFile = "CPCEMU Disk-File and Extended CPC Disk-File";
public const string D2FDiskImage = "d2f disk image";
public const string D88DiskImage = "D88 Disk Image";
public const string DIMDiskImage = "DIM Disk Image";
public const string DiscFerret = "DiscFerret";
public const string DiscJuggler = "DiscJuggler";
public const string DreamcastGDIImage = "Dreamcast GDI image";
public const string DunfieldsIMD = "Dunfield's IMD";
public const string HDCopyDiskImage = "HD-Copy disk image";
public const string KryoFluxSTREAM = "KryoFlux STREAM";
public const string MAMECompressedHunksOfData = "MAME Compressed Hunks of Data";
public const string MicrosoftVHDX = "Microsoft VHDX";
public const string NeroBurningROMImage = "Nero Burning ROM image";
public const string PartCloneDiskImage = "PartClone disk image";
public const string PartimageDiskImage = "Partimage disk image";
public const string SpectrumFloppyDiskImage = "Spectrum Floppy Disk Image";
public const string SuperCardPro = "SuperCardPro";
public const string SydexCopyQM = "Sydex CopyQM";
public const string SydexTeleDisk = "Sydex TeleDisk";
// Read/write media image formats
public const string AaruFormat = "Aaru Format";
public const string ACTApricotDiskImage = "ACT Apricot Disk Image";
public const string Alcohol120MediaDescriptorStructure = "Alcohol 120% Media Descriptor Structure";
public const string Anex86DiskImage = "Anex86 Disk Image";
public const string Apple2InterleavedDiskImage = "Apple ][Interleaved Disk Image";
public const string Apple2IMG = "Apple 2IMG";
public const string AppleDiskCopy42 = "Apple DiskCopy 4.2";
public const string AppleUniversalDiskImageFormat = "Apple Universal Disk Image Format";
public const string BasicLisaUtility = "Basic Lisa Utility";
public const string CDRDAOTocfile = "CDRDAO tocfile";
public const string CDRWinCuesheet = "CDRWin cuesheet";
public const string CisCopyDiskImageDCFile = "CisCopy Disk Image(DC-File)";
public const string CloneCD = "CloneCD";
public const string CopyTape = "CopyTape";
public const string DigitalResearchDiskCopy = "Digital Research DiskCopy";
public const string IBMSaveDskF = "IBM SaveDskF";
public const string MAXIDiskImage = "MAXI Disk image";
public const string ParallelsDiskImage = "Parallels disk image";
public const string QEMUCopyOnWriteDiskImage = "QEMU Copy-On-Write disk image";
public const string QEMUCopyOnWriteDiskImageV2 = "QEMU Copy-On-Write disk image v2";
public const string QEMUEnhancedDiskImage = "QEMU Enhanced Disk image";
public const string RawDiskImage = "Raw Disk Image";
public const string RayAracheliansDiskIMage = "Ray Arachelian's Disk IMage";
public const string RSIDEHardDiskImage = "RS-IDE Hard Disk Image";
public const string T98HardDiskImage = "T98 Hard Disk Image";
public const string T98NextNHDr0DiskImage = "T98-Next NHD r0 Disk Image";
public const string Virtual98DiskImage = "Virtual98 Disk Image";
public const string VirtualBoxDiskImage = "VirtualBox Disk Image";
public const string VirtualPC = "VirtualPC";
public const string VMwareDiskImage = "VMware disk image";
// Supported filesystems for identification and information only
public const string AcornAdvancedDiscFilingSystem = "Acorn Advanced Disc Filing System";
public const string AlexanderOsipovDOSFileSystem = "Alexander Osipov DOS file system";
public const string AmigaDOSFilesystem = "Amiga DOS filesystem";
public const string AppleFileSystem = "Apple File System";
public const string AppleHFSPlusFilesystem = "Apple HFS+ filesystem";
public const string AppleHierarchicalFileSystem = "Apple Hierarchical File System";
public const string AppleProDOSFilesystem = "Apple ProDOS filesystem";
public const string AtheOSFilesystem = "AtheOS Filesystem";
public const string BeFilesystem = "Be Filesystem";
public const string BSDFastFileSystem = "BSD Fast File System(aka UNIX File System, UFS)";
public const string BTreeFileSystem = "B-tree file system";
public const string CommodoreFileSystem = "Commodore file system";
public const string CramFilesystem = "Cram filesystem";
public const string DumpEightPlugin = "dump(8) Plugin";
public const string ECMA67 = "ECMA-67";
public const string ExtentFileSystemPlugin = "Extent File System Plugin";
public const string F2FSPlugin = "F2FS Plugin";
public const string Files11OnDiskStructure = "Files-11 On-Disk Structure";
public const string FossilFilesystemPlugin = "Fossil Filesystem Plugin";
public const string HAMMERFilesystem = "HAMMER Filesystem";
public const string HighPerformanceOpticalFileSystem = "High Performance Optical File System";
public const string HPLogicalInterchangeFormatPlugin = "HP Logical Interchange Format Plugin";
public const string JFSPlugin = "JFS Plugin";
public const string LinuxExtendedFilesystem = "Linux extended Filesystem";
public const string LinuxExtendedFilesystem234 = "Linux extended Filesystem 2, 3 and 4";
public const string LocusFilesystemPlugin = "Locus Filesystem Plugin";
public const string MicroDOSFileSystem = "MicroDOS file system";
public const string MicrosoftExtendedFileAllocationTable = "Microsoft Extended File Allocation Table";
public const string MinixFilesystem = "Minix Filesystem";
public const string NewTechnologyFileSystem = "New Technology File System(NTFS)";
public const string NILFS2Plugin = "NILFS2 Plugin";
public const string NintendoOpticalFilesystems = "Nintendo optical filesystems";
public const string OS2HighPerformanceFileSystem = "OS/2 High Performance File System";
public const string OS9RandomBlockFilePlugin = "OS-9 Random Block File Plugin";
public const string PCEngineCDPlugin = "PC Engine CD Plugin";
public const string PCFXPlugin = "PC-FX Plugin";
public const string ProfessionalFileSystem = "Professional File System";
public const string QNX4Plugin = "QNX4 Plugin";
public const string QNX6Plugin = "QNX6 Plugin";
public const string ReiserFilesystemPlugin = "Reiser Filesystem Plugin";
public const string Reiser4FilesystemPlugin = "Reiser4 Filesystem Plugin";
public const string ResilientFileSystemPlugin = "Resilient File System plugin";
public const string RT11FileSystem = "RT-11 file system";
public const string SmartFileSystem = "SmartFileSystem";
public const string SolarOSFilesystem = "Solar_OS filesystem";
public const string SquashFilesystem = "Squash filesystem";
public const string UNICOSFilesystemPlugin = "UNICOS Filesystem Plugin";
public const string UniversalDiskFormat = "Universal Disk Format";
public const string UNIXBootFilesystem = "UNIX Boot filesystem";
public const string UNIXSystemVFilesystem = "UNIX System V filesystem";
public const string VeritasFilesystem = "Veritas filesystem";
public const string VMwareFilesystem = "VMware filesystem";
public const string XFSFilesystemPlugin = "XFS Filesystem Plugin";
public const string XiaFilesystem = "Xia filesystem";
public const string ZFSFilesystemPlugin = "ZFS Filesystem Plugin";
// Supported filesystems that can read their contents
public const string AppleDOSFileSystem = "Apple DOS File System";
public const string AppleLisaFileSystem = "Apple Lisa File System";
public const string AppleMacintoshFileSystem = "Apple Macintosh File System";
public const string CPMFileSystem = "CP/M File System";
public const string FATXFilesystemPlugin = "FATX Filesystem Plugin";
public const string ISO9660Filesystem = "ISO9660 Filesystem";
public const string MicrosoftFileAllocationTable = "Microsoft File Allocation Table";
public const string OperaFilesystemPlugin = "Opera Filesystem Plugin";
public const string UCSDPascalFilesystem = "U.C.S.D.Pascal filesystem";
// Supported partitioning schemes
public const string AcornFileCorePartitions = "Acorn FileCore partitions";
public const string ACTApricotPartitions = "ACT Apricot partitions";
public const string AmigaRigidDiskBlock = "Amiga Rigid Disk Block";
public const string ApplePartitionMap = "Apple Partition Map";
public const string AtariPartitions = "Atari partitions";
public const string BSDDisklabel = "BSD disklabel";
public const string DECDisklabel = "DEC disklabel";
public const string DragonFlyBSD64bitDisklabel = "DragonFly BSD 64-bit disklabel";
public const string GUIDPartitionTable = "GUID Partition Table";
public const string Human68kPartitions = "Human 68k partitions";
public const string MasterBootRecord = "Master Boot Record";
public const string NECPC9800PartitionTable = "NEC PC-9800 partition table";
public const string NeXTDisklabel = "NeXT Disklabel";
public const string Plan9PartitionTable = "Plan9 partition table";
public const string RioKarmaPartitioning = "Rio Karma partitioning";
public const string SGIDiskVolumeHeader = "SGI Disk Volume Header";
public const string SunDisklabel = "Sun Disklabel";
public const string UNIXHardwired = "UNIX hardwired";
public const string UNIXVTOC = "UNIX VTOC";
public const string XboxPartitioning = "Xbox partitioning";
public const string XENIX = "XENIX";
}
/// <summary>
/// Supported namespaces for Aaru
/// </summary>
/// TODO: Use to verify namespace settings
public static class NamespaceStrings
{
// Namespaces for Apple Lisa File System
public const string LisaOfficeSystem = "office";
public const string LisaPascalWorkshop = "workshop"; // Default
// Namespaces for ISO9660 Filesystem
public const string JolietVolumeDescriptor = "joliet"; // Default
public const string PrimaryVolumeDescriptor = "normal";
public const string PrimaryVolumeDescriptorwithEncoding = "romeo";
public const string RockRidge = "rrip";
public const string PrimaryVolumeDescriptorVersionSuffix = "vms";
// Namespaces for Microsoft File Allocation Table
public const string DOS83UpperCase = "dos";
public const string LFNWhenAvailableWithFallback = "ecs"; // Default
public const string LongFileNames = "lfn";
public const string WindowsNT83MixedCase = "nt";
public const string OS2Extended = "os2";
}
/// <summary>
/// Supported options for Aaru
/// </summary>
/// TODO: Use to verify option settings
public static class OptionStrings
{
// Aaru format
public const string AaruCompress = "compress"; // boolean, default true;
public const string AaruDeduplicate = "deduplicate"; // boolean, default true
public const string AaruDictionary = "dictionary"; // number, default 33554432
public const string AaruMaxDDTSize = "max_ddt_size"; // number, default 256
public const string AaruMD5 = "md5"; // boolean, default false
public const string AaruSectorsPerBlock = "sectors_per_block"; // number, default 4096 [power of 2]
public const string AaruSHA1 = "sha1"; // boolean, default false
public const string AaruSHA256 = "sha256"; // boolean, default false
public const string AaruSpamSum = "spamsum"; // boolean, default false
// ACT Apricot Disk Image
public const string ACTApricotDiskImageCompress = "compress"; // boolean, default false
// Apple DiskCopy 4.2
public const string AppleDiskCopyMacOSX = "macosx"; // boolean, default false
// CDRDAO tocfile
public const string CDRDAOTocfileSeparate = "separate"; // boolean, default false
// CDRWin cuesheet
public const string CDRWinCuesheetSeparate = "separate"; // boolean, default false
// ISO9660 Filesystem
public const string ISO9660FSUseEvd = "use_evd"; // boolean, default false
public const string ISO9660FSUsePathTable = "use_path_table"; // boolean, default false
public const string ISO9660FSUseTransTbl = "use_trans_tbl"; // boolean, default false
// VMware disk image
public const string VMwareDiskImageAdapterType = "adapter_type"; // string, default ide [ide, lsilogic, buslogic, legacyESX]
public const string VMwareDiskImageHWVersion = "hwversion"; // number, default 4
public const string VMwareDiskImageSparse = "sparse"; // boolean, default false
public const string VMwareDiskImageSplit = "split"; // boolean, default false
}
}

View File

@@ -1,73 +0,0 @@
using System.Xml.Serialization;
namespace MPF.Core.Modules
{
[XmlRoot("datafile")]
public class Datafile
{
[XmlElement("header")]
public Header? Header;
[XmlElement("game")]
public Game[]? Games;
}
public class Header
{
[XmlElement("name")]
public string? Name;
[XmlElement("description")]
public string? Description;
[XmlElement("version")]
public string? Version;
[XmlElement("date")]
public string? Date;
[XmlElement("author")]
public string? Author;
[XmlElement("homepage")]
public string? Homepage;
[XmlElement("url")]
public string? Url;
}
public class Game
{
[XmlAttribute("name")]
public string? Name;
[XmlElement("category")]
public string? Category;
[XmlElement("description")]
public string? Description;
[XmlElement("rom")]
public Rom[]? Roms;
}
public class Rom
{
[XmlAttribute("name")]
public string? Name;
[XmlAttribute("size")]
public string? Size;
[XmlAttribute("crc")]
public string? Crc;
[XmlAttribute("md5")]
public string? Md5;
[XmlAttribute("sha1")]
public string? Sha1;
// TODO: Add extended hashes here
}
}

View File

@@ -1,293 +0,0 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using MPF.Core.Converters;
using MPF.Core.Data;
using SabreTools.Hashing;
using SabreTools.RedumpLib;
using SabreTools.RedumpLib.Data;
namespace MPF.Core.Modules.PS3CFW
{
/// <summary>
/// Represents a generic set of PlayStation 3 Custom Firmware parameters
/// </summary>
public class Parameters : BaseParameters
{
#region Metadata
/// <inheritdoc/>
public override InternalProgram InternalProgram => InternalProgram.PS3CFW;
#endregion
/// <inheritdoc/>
public Parameters(string? parameters) : base(parameters) { }
/// <inheritdoc/>
public Parameters(RedumpSystem? system, MediaType? type, string? drivePath, string filename, int? driveSpeed, Options options)
: base(system, type, drivePath, filename, driveSpeed, options)
{
}
#region BaseParameters Implementations
/// <inheritdoc/>
public override (bool, List<string>) CheckAllOutputFilesExist(string basePath, bool preCheck)
{
var missingFiles = new List<string>();
if (this.Type != MediaType.BluRay || this.System != RedumpSystem.SonyPlayStation3)
{
missingFiles.Add("Media and system combination not supported for PS3 CFW");
}
else
{
string? getKeyBasePath = GetCFWBasePath(basePath);
if (!File.Exists($"{getKeyBasePath}.getkey.log"))
missingFiles.Add($"{getKeyBasePath}.getkey.log");
if (!File.Exists($"{getKeyBasePath}.disc.pic"))
missingFiles.Add($"{getKeyBasePath}.disc.pic");
}
return (missingFiles.Count == 0, missingFiles);
}
/// <inheritdoc/>
public override void GenerateSubmissionInfo(SubmissionInfo info, Options options, string basePath, Drive? drive, bool includeArtifacts)
{
// Ensure that required sections exist
info = Builder.EnsureAllSections(info);
info.DumpingInfo!.DumpingProgram = EnumConverter.LongName(this.InternalProgram);
// Get the Datafile information
Datafile? datafile = GeneratePS3CFWDatafile(basePath + ".iso");
// Fill in the hash data
info.TracksAndWriteOffsets!.ClrMameProData = InfoTool.GenerateDatfile(datafile);
// Get the individual hash data, as per internal
if (InfoTool.GetISOHashValues(datafile, out long size, out var crc32, out var md5, out var sha1))
{
info.SizeAndChecksums!.Size = size;
info.SizeAndChecksums.CRC32 = crc32;
info.SizeAndChecksums.MD5 = md5;
info.SizeAndChecksums.SHA1 = sha1;
}
// Get the PVD from the ISO
if (GetPVD(basePath + ".iso", out string? pvd))
info.Extras!.PVD = pvd;
// Try get the serial, version, and firmware version if a drive is provided
if (drive != null)
{
info.VersionAndEditions!.Version = InfoTool.GetPlayStation3Version(drive?.Name) ?? string.Empty;
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = InfoTool.GetPlayStation3Serial(drive?.Name) ?? string.Empty;
string? firmwareVersion = InfoTool.GetPlayStation3FirmwareVersion(drive?.Name);
if (firmwareVersion != null)
info.CommonDiscInfo!.ContentsSpecialFields![SiteCode.Patches] = $"PS3 Firmware {firmwareVersion}";
}
// Try to determine the name of the GetKey file(s)
string? getKeyBasePath = GetCFWBasePath(basePath);
// If GenerateSubmissionInfo is run, .getkey.log existence should already be checked
if (!File.Exists(getKeyBasePath + ".getkey.log"))
return;
// Get dumping date from GetKey log date
info.DumpingInfo.DumpingDate = InfoTool.GetFileModifiedDate(getKeyBasePath + ".getkey.log")?.ToString("yyyy-MM-dd HH:mm:ss");
// TODO: Put info about abnormal PIC info beyond 132 bytes in comments?
if (File.Exists(getKeyBasePath + ".disc.pic"))
info.Extras!.PIC = GetPIC(getKeyBasePath + ".disc.pic", 264);
// Parse Disc Key, Disc ID, and PIC from the .getkey.log file
if (Utilities.Tools.ParseGetKeyLog(getKeyBasePath + ".getkey.log", out string? key, out string? id, out string? pic))
{
if (key != null)
info.Extras!.DiscKey = key.ToUpperInvariant();
if (id != null)
info.Extras!.DiscID = id.ToUpperInvariant().Substring(0, 24) + "XXXXXXXX";
if (string.IsNullOrEmpty(info.Extras!.PIC) && !string.IsNullOrEmpty(pic))
{
pic = Regex.Replace(pic, ".{32}", "$0\n");
info.Extras.PIC = pic;
}
}
// Fill in any artifacts that exist, Base64-encoded, if we need to
if (includeArtifacts)
{
info.Artifacts ??= [];
if (File.Exists(getKeyBasePath + ".disc.pic"))
info.Artifacts["discpic"] = GetBase64(GetFullFile(getKeyBasePath + ".disc.pic", binary: true)) ?? string.Empty;
if (File.Exists(getKeyBasePath + ".getkey.log"))
info.Artifacts["getkeylog"] = GetBase64(GetFullFile(getKeyBasePath + ".getkey.log")) ?? string.Empty;
}
}
/// <inheritdoc/>
public override List<string> GetLogFilePaths(string basePath)
{
var logFiles = new List<string>();
string? getKeyBasePath = GetCFWBasePath(basePath);
if (this.System != RedumpSystem.SonyPlayStation3)
return logFiles;
switch (this.Type)
{
case MediaType.BluRay:
if (File.Exists($"{getKeyBasePath}.getkey.log"))
logFiles.Add($"{getKeyBasePath}.getkey.log");
if (File.Exists($"{getKeyBasePath}.disc.pic"))
logFiles.Add($"{getKeyBasePath}.disc.pic");
break;
}
return logFiles;
}
#endregion
#region Information Extraction Methods
/// <summary>
/// Get a formatted datfile from the PS3 CFW output, if possible
/// </summary>
/// <param name="iso">Path to ISO file</param>
/// <returns></returns>
private static Datafile? GeneratePS3CFWDatafile(string iso)
{
// If the ISO file doesn't exist, we can't get info from it
if (!File.Exists(iso))
return null;
try
{
if (HashTool.GetStandardHashes(iso, out long size, out string? crc, out string? md5, out string? sha1))
{
return new Datafile
{
Games = [new Game { Roms = [new Rom { Name = Path.GetFileName(iso), Size = size.ToString(), Crc = crc, Md5 = md5, Sha1 = sha1, }] }]
};
}
return null;
}
catch
{
// We don't care what the exception is right now
return null;
}
}
// TODO: Don't hardcode 0x8320 and move this function to BaseParameters
/// <summary>
/// Get a isobuster-formatted PVD from a PS3 ISO, if possible
/// </summary>
/// <param name="isoPath">Path to ISO file</param>
/// <param name="pvd">Formatted PVD string, otherwise null</param>
/// <returns>True if PVD was successfully parsed, otherwise false</returns>
private static bool GetPVD(string isoPath, out string? pvd)
{
pvd = null;
try
{
// Get PVD bytes from ISO file
var buf = new byte[96];
using (FileStream iso = File.OpenRead(isoPath))
{
iso.Seek(0x8320, SeekOrigin.Begin);
int offset = 0;
while (offset < 96)
{
int read = iso.Read(buf, offset, buf.Length - offset);
if (read == 0)
throw new EndOfStreamException();
offset += read;
}
}
// Format PVD to isobuster standard
char[] pvdCharArray = new char[96];
for (int i = 0; i < 96; i++)
{
if (buf[i] >= 0x20 && buf[i] <= 0x7E)
pvdCharArray[i] = (char)buf[i];
else
pvdCharArray[i] = '.';
}
string pvdASCII = new string(pvdCharArray, 0, 96);
pvd = string.Empty;
for (int i = 0; i < 96; i += 16)
{
pvd += $"{(0x0320+i):X4} : {buf[i]:X2} {buf[i+1]:X2} {buf[i+2]:X2} {buf[i+3]:X2} {buf[i+4]:X2} {buf[i+5]:X2} {buf[i+6]:X2} {buf[i+7]:X2} " +
$"{buf[i+8]:X2} {buf[i+9]:X2} {buf[i+10]:X2} {buf[i+11]:X2} {buf[i+12]:X2} {buf[i+13]:X2} {buf[i+14]:X2} {buf[i+15]:X2} {pvdASCII.Substring(i, 16)}\n";
}
return true;
}
catch
{
// We don't care what the error is
return false;
}
}
/// <summary>
/// Get a formatted datfile from the PS3 CFW output, if possible
/// </summary>
/// <param name="iso">Path to ISO file</param>
/// <returns>Formatted datfile, null if not valid</returns>
private static string? GetPS3CFWDatfile(string iso)
{
// If the files don't exist, we can't get info from it
if (!File.Exists(iso))
return null;
try
{
if (HashTool.GetStandardHashes(iso, out long size, out string? crc, out string? md5, out string? sha1))
return $"<rom name=\"{Path.GetFileName(iso)}\" size=\"{size}\" crc=\"{crc}\" md5=\"{md5}\" sha1=\"{sha1}\" />";
return null;
}
catch
{
// We don't care what the exception is right now
return null;
}
}
#endregion
#region Helper Functions
/// <summary>
/// Estimate the base filename of the .getkey.log file associated with the dump
/// </summary>
/// <param name="iso">Path to ISO file</param>
/// <returns>Base filename, null if not found</returns>
private string? GetCFWBasePath(string iso)
{
string? dir = Path.GetDirectoryName(iso);
dir ??= ".";
string[] files = Directory.GetFiles(dir, "*.getkey.log");
if (files.Length != 1)
return null;
return files[0].Substring(0, files[0].Length - 11);
}
#endregion
}
}

View File

@@ -1,743 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using MPF.Core.Data;
using MPF.Core.Modules;
using SabreTools.RedumpLib;
using SabreTools.RedumpLib.Data;
using SabreTools.RedumpLib.Web;
namespace MPF.Core
{
/// <summary>
/// Class to hold all SubmissionInfo-related methods
/// </summary>
internal static class SubmissionInfoTool
{
#region Extraction and Filling
/// <summary>
/// Extract all of the possible information from a given input combination
/// </summary>
/// <param name="outputPath">Output path to write to</param>
/// <param name="drive">Drive object representing the current drive</param>
/// <param name="system">Currently selected system</param>
/// <param name="mediaType">Currently selected media type</param>
/// <param name="options">Options object representing user-defined options</param>
/// <param name="parameters">Parameters object representing what to send to the internal program</param>
/// <param name="resultProgress">Optional result progress callback</param>
/// <param name="protectionProgress">Optional protection progress callback</param>
/// <returns>SubmissionInfo populated based on outputs, null on error</returns>
public static async Task<SubmissionInfo?> ExtractOutputInformation(
string outputPath,
Drive? drive,
RedumpSystem? system,
MediaType? mediaType,
Options options,
BaseParameters? parameters,
IProgress<Result>? resultProgress = null,
IProgress<BinaryObjectScanner.ProtectionProgress>? protectionProgress = null)
{
// Ensure the current disc combination should exist
if (!system.MediaTypes().Contains(mediaType))
return null;
// Split the output path for easier use
var outputDirectory = Path.GetDirectoryName(outputPath);
string outputFilename = Path.GetFileName(outputPath);
// Check that all of the relevant files are there
(bool foundFiles, List<string> missingFiles) = parameters.FoundAllFiles(outputDirectory, outputFilename, false);
if (!foundFiles)
{
resultProgress?.Report(Result.Failure($"There were files missing from the output:\n{string.Join("\n", [.. missingFiles])}"));
resultProgress?.Report(Result.Failure($"This may indicate an issue with the hardware or media, including unsupported devices.\nPlease see dumping program documentation for more details."));
return null;
}
// Sanitize the output filename to strip off any potential extension
outputFilename = Path.GetFileNameWithoutExtension(outputFilename);
// Create the SubmissionInfo object with all user-inputted values by default
string combinedBase;
if (string.IsNullOrEmpty(outputDirectory))
combinedBase = outputFilename;
else
combinedBase = Path.Combine(outputDirectory, outputFilename);
var info = new SubmissionInfo()
{
CommonDiscInfo = new CommonDiscInfoSection()
{
System = system,
Media = mediaType.ToDiscType(),
Title = options.AddPlaceholders ? Template.RequiredValue : string.Empty,
ForeignTitleNonLatin = options.AddPlaceholders ? Template.OptionalValue : string.Empty,
DiscNumberLetter = options.AddPlaceholders ? Template.OptionalValue : string.Empty,
DiscTitle = options.AddPlaceholders ? Template.OptionalValue : string.Empty,
Category = null,
Region = null,
Languages = null,
Serial = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty,
Barcode = options.AddPlaceholders ? Template.OptionalValue : string.Empty,
Contents = string.Empty,
},
VersionAndEditions = new VersionAndEditionsSection()
{
Version = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty,
OtherEditions = options.AddPlaceholders ? "(VERIFY THIS) Original" : string.Empty,
},
DumpingInfo = new DumpingInfoSection()
{
FrontendVersion = Utilities.Tools.GetCurrentVersion(),
},
};
// Ensure that required sections exist
info = Builder.EnsureAllSections(info);
// Get specific tool output handling
parameters?.GenerateSubmissionInfo(info, options, combinedBase, drive, options.IncludeArtifacts);
// Get a list of matching IDs for each line in the DAT
if (!string.IsNullOrEmpty(info.TracksAndWriteOffsets!.ClrMameProData) && options.HasRedumpLogin)
#if NET40
_ = FillFromRedump(options, info, resultProgress);
#else
_ = await FillFromRedump(options, info, resultProgress);
#endif
// If we have both ClrMamePro and Size and Checksums data, remove the ClrMamePro
if (!string.IsNullOrEmpty(info.SizeAndChecksums?.CRC32))
info.TracksAndWriteOffsets.ClrMameProData = null;
// Add the volume label to comments, if possible or necessary
string? volLabels = FormatVolumeLabels(drive?.VolumeLabel, parameters?.VolumeLabels);
if (volLabels != null)
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.VolumeLabel] = volLabels;
// Extract info based generically on MediaType
switch (mediaType)
{
case MediaType.CDROM:
case MediaType.GDROM:
info.CommonDiscInfo!.Layer0MasteringRing = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MasteringSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0ToolstampMasteringCode = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MouldSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MouldSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0AdditionalMould = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
break;
case MediaType.DVD:
case MediaType.HDDVD:
case MediaType.BluRay:
// If we have a single-layer disc
if (info.SizeAndChecksums!.Layerbreak == default)
{
info.CommonDiscInfo!.Layer0MasteringRing = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MasteringSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0ToolstampMasteringCode = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MouldSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MouldSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0AdditionalMould = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
}
// If we have a dual-layer disc
else if (info.SizeAndChecksums!.Layerbreak2 == default)
{
info.CommonDiscInfo!.Layer0MasteringRing = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MasteringSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0ToolstampMasteringCode = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MouldSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0AdditionalMould = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MasteringRing = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MasteringSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1ToolstampMasteringCode = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MouldSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
}
// If we have a triple-layer disc
else if (info.SizeAndChecksums!.Layerbreak3 == default)
{
info.CommonDiscInfo!.Layer0MasteringRing = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MasteringSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0ToolstampMasteringCode = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MouldSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0AdditionalMould = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MasteringRing = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MasteringSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1ToolstampMasteringCode = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MouldSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer2MasteringRing = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer2MasteringSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer2ToolstampMasteringCode = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
}
// If we have a quad-layer disc
else
{
info.CommonDiscInfo!.Layer0MasteringRing = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MasteringSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0ToolstampMasteringCode = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MouldSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0AdditionalMould = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MasteringRing = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MasteringSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1ToolstampMasteringCode = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MouldSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer2MasteringRing = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer2MasteringSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer2ToolstampMasteringCode = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer3MasteringRing = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer3MasteringSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer3ToolstampMasteringCode = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
}
break;
case MediaType.NintendoGameCubeGameDisc:
info.CommonDiscInfo!.Layer0MasteringRing = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MasteringSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0ToolstampMasteringCode = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MouldSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MouldSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0AdditionalMould = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.Extras!.BCA ??= (options.AddPlaceholders ? Template.RequiredValue : string.Empty);
break;
case MediaType.NintendoWiiOpticalDisc:
// If we have a single-layer disc
if (info.SizeAndChecksums!.Layerbreak == default)
{
info.CommonDiscInfo!.Layer0MasteringRing = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MasteringSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0ToolstampMasteringCode = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MouldSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MouldSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0AdditionalMould = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
}
// If we have a dual-layer disc
else
{
info.CommonDiscInfo!.Layer0MasteringRing = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MasteringSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0ToolstampMasteringCode = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MouldSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0AdditionalMould = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MasteringRing = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MasteringSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1ToolstampMasteringCode = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MouldSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
}
info.Extras!.DiscKey = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
info.Extras.BCA = info.Extras.BCA ?? (options.AddPlaceholders ? Template.RequiredValue : string.Empty);
break;
case MediaType.UMD:
// Both single- and dual-layer discs have two "layers" for the ring
info.CommonDiscInfo!.Layer0MasteringRing = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MasteringSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0ToolstampMasteringCode = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MouldSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MasteringRing = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MasteringSID = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1ToolstampMasteringCode = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.SizeAndChecksums!.CRC32 ??= (options.AddPlaceholders ? Template.RequiredValue + " [Not automatically generated for UMD]" : string.Empty);
info.SizeAndChecksums.MD5 ??= (options.AddPlaceholders ? Template.RequiredValue + " [Not automatically generated for UMD]" : string.Empty);
info.SizeAndChecksums.SHA1 ??= (options.AddPlaceholders ? Template.RequiredValue + " [Not automatically generated for UMD]" : string.Empty);
info.TracksAndWriteOffsets.ClrMameProData = null;
break;
}
// Extract info based specifically on RedumpSystem
switch (system)
{
case RedumpSystem.AcornArchimedes:
info.CommonDiscInfo!.Region ??= Region.UnitedKingdom;
break;
case RedumpSystem.AppleMacintosh:
case RedumpSystem.EnhancedCD:
case RedumpSystem.IBMPCcompatible:
case RedumpSystem.PalmOS:
case RedumpSystem.PocketPC:
case RedumpSystem.RainbowDisc:
case RedumpSystem.SonyElectronicBook:
resultProgress?.Report(Result.Success("Running copy protection scan... this might take a while!"));
var (protectionString, fullProtections) = await InfoTool.GetCopyProtection(drive, options, protectionProgress);
info.CopyProtection!.Protection += protectionString;
info.CopyProtection.FullProtections = fullProtections as Dictionary<string, List<string>?> ?? [];
resultProgress?.Report(Result.Success("Copy protection scan complete!"));
break;
case RedumpSystem.AudioCD:
case RedumpSystem.DVDAudio:
case RedumpSystem.SuperAudioCD:
info.CommonDiscInfo!.Category ??= DiscCategory.Audio;
break;
case RedumpSystem.BandaiPlaydiaQuickInteractiveSystem:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? Region.Japan;
break;
case RedumpSystem.BDVideo:
info.CommonDiscInfo!.Category ??= DiscCategory.BonusDiscs;
info.CopyProtection!.Protection = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
break;
case RedumpSystem.CommodoreAmigaCD:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
break;
case RedumpSystem.CommodoreAmigaCD32:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
info.CommonDiscInfo.Region ??= Region.Europe;
break;
case RedumpSystem.CommodoreAmigaCDTV:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
info.CommonDiscInfo.Region ??= Region.Europe;
break;
case RedumpSystem.DVDVideo:
info.CommonDiscInfo!.Category ??= DiscCategory.BonusDiscs;
break;
case RedumpSystem.FujitsuFMTownsseries:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? Region.Japan;
break;
case RedumpSystem.FujitsuFMTownsMarty:
info.CommonDiscInfo!.Region ??= Region.Japan;
break;
case RedumpSystem.IncredibleTechnologiesEagle:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
break;
case RedumpSystem.KonamieAmusement:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
break;
case RedumpSystem.KonamiFireBeat:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
break;
case RedumpSystem.KonamiSystemGV:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
break;
case RedumpSystem.KonamiSystem573:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
break;
case RedumpSystem.KonamiTwinkle:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
break;
case RedumpSystem.MattelHyperScan:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
break;
case RedumpSystem.MicrosoftXboxOne:
if (drive?.Name != null)
{
string xboxOneMsxcPath = Path.Combine(drive.Name, "MSXC");
if (drive != null && Directory.Exists(xboxOneMsxcPath))
{
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.Filename] = string.Join("\n",
Directory.GetFiles(xboxOneMsxcPath, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName).ToArray());
}
}
break;
case RedumpSystem.MicrosoftXboxSeriesXS:
if (drive?.Name != null)
{
string xboxSeriesXMsxcPath = Path.Combine(drive.Name, "MSXC");
if (drive != null && Directory.Exists(xboxSeriesXMsxcPath))
{
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.Filename] = string.Join("\n",
Directory.GetFiles(xboxSeriesXMsxcPath, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName).ToArray());
}
}
break;
case RedumpSystem.NamcoSegaNintendoTriforce:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
break;
case RedumpSystem.NavisoftNaviken21:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
info.CommonDiscInfo.Region ??= Region.Japan;
break;
case RedumpSystem.NECPC88series:
info.CommonDiscInfo!.Region ??= Region.Japan;
break;
case RedumpSystem.NECPC98series:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
info.CommonDiscInfo!.Region ??= Region.Japan;
break;
case RedumpSystem.NECPCFXPCFXGA:
info.CommonDiscInfo!.Region ??= Region.Japan;
break;
case RedumpSystem.SegaChihiro:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
break;
case RedumpSystem.SegaDreamcast:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
break;
case RedumpSystem.SegaNaomi:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
break;
case RedumpSystem.SegaNaomi2:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
break;
case RedumpSystem.SegaTitanVideo:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
break;
case RedumpSystem.SharpX68000:
info.CommonDiscInfo!.Region ??= Region.Japan;
break;
case RedumpSystem.SNKNeoGeoCD:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
break;
case RedumpSystem.SonyPlayStation:
// Only check the disc if the dumping program couldn't detect
if (drive != null && info.CopyProtection!.AntiModchip == YesNo.NULL)
{
resultProgress?.Report(Result.Success("Checking for anti-modchip strings... this might take a while!"));
info.CopyProtection.AntiModchip = await InfoTool.GetAntiModchipDetected(drive) ? YesNo.Yes : YesNo.No;
resultProgress?.Report(Result.Success("Anti-modchip string scan complete!"));
}
// Special case for DIC only
if (parameters?.InternalProgram == InternalProgram.DiscImageCreator)
{
resultProgress?.Report(Result.Success("Checking for LibCrypt status... this might take a while!"));
InfoTool.GetLibCryptDetected(info, combinedBase);
resultProgress?.Report(Result.Success("LibCrypt status checking complete!"));
}
break;
case RedumpSystem.SonyPlayStation2:
info.CommonDiscInfo!.LanguageSelection = [];
break;
case RedumpSystem.SonyPlayStation3:
info.Extras!.DiscKey ??= options.AddPlaceholders ? Template.RequiredValue : string.Empty;
info.Extras.DiscID ??= options.AddPlaceholders ? Template.RequiredValue : string.Empty;
break;
case RedumpSystem.TomyKissSite:
info.CommonDiscInfo!.Region ??= Region.Japan;
break;
case RedumpSystem.ZAPiTGamesGameWaveFamilyEntertainmentSystem:
info.CopyProtection!.Protection = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
break;
}
// Set the category if it's not overriden
info.CommonDiscInfo!.Category ??= DiscCategory.Games;
// Comments and contents have odd handling
if (string.IsNullOrEmpty(info.CommonDiscInfo.Comments))
info.CommonDiscInfo.Comments = options.AddPlaceholders ? Template.OptionalValue : string.Empty;
if (string.IsNullOrEmpty(info.CommonDiscInfo.Contents))
info.CommonDiscInfo.Contents = options.AddPlaceholders ? Template.OptionalValue : string.Empty;
// Normalize the disc type with all current information
Validator.NormalizeDiscType(info);
return info;
}
/// <summary>
/// Fill in a SubmissionInfo object from Redump, if possible
/// </summary>
/// <param name="options">Options object representing user-defined options</param>
/// <param name="info">Existing SubmissionInfo object to fill</param>
/// <param name="resultProgress">Optional result progress callback</param>
#if NET40
public static bool FillFromRedump(Options options, SubmissionInfo info, IProgress<Result>? resultProgress = null)
#else
public async static Task<bool> FillFromRedump(Options options, SubmissionInfo info, IProgress<Result>? resultProgress = null)
#endif
{
// If no username is provided
if (string.IsNullOrEmpty(options.RedumpUsername) || string.IsNullOrEmpty(options.RedumpPassword))
return false;
// Set the current dumper based on username
info.DumpersAndStatus ??= new DumpersAndStatusSection();
info.DumpersAndStatus.Dumpers = [options.RedumpUsername!];
info.PartiallyMatchedIDs = [];
// Login to Redump
#if NETFRAMEWORK
using var wc = new RedumpWebClient();
bool? loggedIn = wc.Login(options.RedumpUsername!, options.RedumpPassword!);
#else
using var wc = new RedumpHttpClient();
bool? loggedIn = await wc.Login(options.RedumpUsername, options.RedumpPassword);
#endif
if (loggedIn == null)
{
resultProgress?.Report(Result.Failure("There was an unknown error connecting to Redump"));
return false;
}
else if (loggedIn == false)
{
// Don't log the as a failure or error
return false;
}
// Setup the full-track checks
bool allFound = true;
List<int>? fullyMatchedIDs = null;
// Loop through all of the hashdata to find matching IDs
resultProgress?.Report(Result.Success("Finding disc matches on Redump..."));
var splitData = info.TracksAndWriteOffsets?.ClrMameProData?.TrimEnd('\n')?.Split('\n');
int trackCount = splitData?.Length ?? 0;
foreach (string hashData in splitData ?? [])
{
// Catch any errant blank lines
if (string.IsNullOrEmpty(hashData))
{
trackCount--;
resultProgress?.Report(Result.Success("Blank line found, skipping!"));
continue;
}
// If the line ends in a known extra track names, skip them for checking
if (hashData.Contains("(Track 0).bin")
|| hashData.Contains("(Track 0.2).bin")
|| hashData.Contains("(Track 00).bin")
|| hashData.Contains("(Track 00.2).bin")
|| hashData.Contains("(Track A).bin")
|| hashData.Contains("(Track A.2).bin")
|| hashData.Contains("(Track AA).bin")
|| hashData.Contains("(Track AA.2).bin"))
{
trackCount--;
resultProgress?.Report(Result.Success("Extra track found, skipping!"));
continue;
}
// Get the SHA-1 hash
if (!InfoTool.GetISOHashValues(hashData, out _, out _, out _, out string? sha1))
{
resultProgress?.Report(Result.Failure($"Line could not be parsed: {hashData}"));
continue;
}
#if NET40
var validateTask = Validator.ValidateSingleTrack(wc, info, sha1);
validateTask.Wait();
(bool singleFound, var foundIds, string? result) = validateTask.Result;
#else
(bool singleFound, var foundIds, string? result) = await Validator.ValidateSingleTrack(wc, info, sha1);
#endif
if (singleFound)
resultProgress?.Report(Result.Success(result));
else
resultProgress?.Report(Result.Failure(result));
// Ensure that all tracks are found
allFound &= singleFound;
// If we found a track, only keep track of distinct found tracks
if (singleFound && foundIds != null)
{
if (fullyMatchedIDs == null)
fullyMatchedIDs = foundIds;
else
fullyMatchedIDs = fullyMatchedIDs.Intersect(foundIds).ToList();
}
// If no tracks were found, remove all fully matched IDs found so far
else
{
fullyMatchedIDs = [];
}
}
// If we don't have any matches but we have a universal hash
if (!info.PartiallyMatchedIDs.Any() && info.CommonDiscInfo?.CommentsSpecialFields?.ContainsKey(SiteCode.UniversalHash) == true)
{
#if NET40
var validateTask = Validator.ValidateUniversalHash(wc, info);
validateTask.Wait();
(bool singleFound, var foundIds, string? result) = validateTask.Result;
#else
(bool singleFound, var foundIds, string? result) = await Validator.ValidateUniversalHash(wc, info);
#endif
if (singleFound)
resultProgress?.Report(Result.Success(result));
else
resultProgress?.Report(Result.Failure(result));
// Ensure that the hash is found
allFound = singleFound;
// If we found a track, only keep track of distinct found tracks
if (singleFound && foundIds != null)
{
fullyMatchedIDs = foundIds;
}
// If no tracks were found, remove all fully matched IDs found so far
else
{
fullyMatchedIDs = [];
}
}
// Make sure we only have unique IDs
info.PartiallyMatchedIDs = [.. info.PartiallyMatchedIDs.Distinct().OrderBy(id => id)];
resultProgress?.Report(Result.Success("Match finding complete! " + (fullyMatchedIDs != null && fullyMatchedIDs.Count > 0
? "Fully Matched IDs: " + string.Join(",", fullyMatchedIDs.Select(i => i.ToString()).ToArray())
: "No matches found")));
// Exit early if one failed or there are no matched IDs
if (!allFound || fullyMatchedIDs == null || fullyMatchedIDs.Count == 0)
return false;
// Find the first matched ID where the track count matches, we can grab a bunch of info from it
int totalMatchedIDsCount = fullyMatchedIDs.Count;
for (int i = 0; i < totalMatchedIDsCount; i++)
{
// Skip if the track count doesn't match
#if NET40
var validateTask = Validator.ValidateTrackCount(wc, fullyMatchedIDs[i], trackCount);
validateTask.Wait();
if (!validateTask.Result)
#else
if (!await Validator.ValidateTrackCount(wc, fullyMatchedIDs[i], trackCount))
#endif
continue;
// Fill in the fields from the existing ID
resultProgress?.Report(Result.Success($"Filling fields from existing ID {fullyMatchedIDs[i]}..."));
#if NET40
var fillTask = Task.Factory.StartNew(() => Builder.FillFromId(wc, info, fullyMatchedIDs[i], options.PullAllInformation));
fillTask.Wait();
_ = fillTask.Result;
#else
_ = await Builder.FillFromId(wc, info, fullyMatchedIDs[i], options.PullAllInformation);
#endif
resultProgress?.Report(Result.Success("Information filling complete!"));
// Set the fully matched ID to the current
info.FullyMatchedID = fullyMatchedIDs[i];
break;
}
// Clear out fully matched IDs from the partial list
if (info.FullyMatchedID.HasValue)
{
if (info.PartiallyMatchedIDs.Count == 1)
info.PartiallyMatchedIDs = null;
else
info.PartiallyMatchedIDs.Remove(info.FullyMatchedID.Value);
}
return true;
}
#endregion
#region Helper Functions
/// <summary>
/// Formats a list of volume labels and their corresponding filesystems
/// </summary>
/// <param name="labels">Dictionary of volume labels and their filesystems</param>
/// <returns>Formatted string of volume labels and their filesystems</returns>
private static string? FormatVolumeLabels(string? driveLabel, Dictionary<string, List<string>>? labels)
{
// Must have at least one label to format
if (driveLabel == null && (labels == null || labels.Count == 0))
return null;
// If no labels given, use drive label
if (labels == null || labels.Count == 0)
{
// Ignore common volume labels
if (Drive.GetRedumpSystemFromVolumeLabel(driveLabel) != null)
return null;
return driveLabel;
}
// If only one label, don't mention fs
string firstLabel = labels.First().Key;
if (labels.Count == 1 && (firstLabel == driveLabel || driveLabel == null))
{
// Ignore common volume labels
if (Drive.GetRedumpSystemFromVolumeLabel(firstLabel) != null)
return null;
return firstLabel;
}
// Otherwise, state filesystem for each label
List<string> volLabels = [];
// Begin formatted output with the label from Windows, if it is unique and not a common volume label
if (driveLabel != null && !labels.TryGetValue(driveLabel, out List<string>? value) && Drive.GetRedumpSystemFromVolumeLabel(driveLabel) == null)
volLabels.Add(driveLabel);
// Add remaining labels with their corresponding filesystems
foreach (KeyValuePair<string, List<string>> label in labels)
{
// Ignore common volume labels
if (Drive.GetRedumpSystemFromVolumeLabel(label.Key) == null)
volLabels.Add($"{label.Key} ({string.Join(", ", [.. label.Value])})");
}
// Ensure that no labels are empty
volLabels = volLabels.Where(l => !string.IsNullOrEmpty(l?.Trim())).ToList();
// Print each label separated by a comma and a space
if (volLabels.Count == 0)
return null;
return string.Join(", ", [.. volLabels]);
}
#endregion
}
}

View File

@@ -1,174 +0,0 @@
using System;
using System.IO;
namespace MPF.Core.Utilities
{
/// <summary>
/// Big endian reading overloads for BinaryReader
/// </summary>
public static class BinaryReaderExtensions
{
/// <summary>
/// Reads the specified number of bytes from the stream, starting from a specified point in the byte array.
/// </summary>
/// <param name="buffer">The buffer to read data into.</param>
/// <param name="index">The starting point in the buffer at which to begin reading into the buffer.</param>
/// <param name="count">The number of bytes to read.</param>
/// <returns>The number of bytes read into buffer. This might be less than the number of bytes requested if that many bytes are not available, or it might be zero if the end of the stream is reached.</returns>
public static int ReadBigEndian(this BinaryReader reader, byte[] buffer, int index, int count)
{
int retval = reader.Read(buffer, index, count);
Array.Reverse(buffer);
return retval;
}
/// <summary>
/// Reads the specified number of characters from the stream, starting from a specified point in the character array.
/// </summary>
/// <param name="buffer">The buffer to read data into.</param>
/// <param name="index">The starting point in the buffer at which to begin reading into the buffer.</param>
/// <param name="count">The number of characters to read.</param>
/// <returns>The total number of characters read into the buffer. This might be less than the number of characters requested if that many characters are not currently available, or it might be zero if the end of the stream is reached.</returns>
public static int ReadBigEndian(this BinaryReader reader, char[] buffer, int index, int count)
{
int retval = reader.Read(buffer, index, count);
Array.Reverse(buffer);
return retval;
}
/// <summary>
/// Reads the specified number of bytes from the current stream into a byte array and advances the current position by that number of bytes.
/// </summary>
/// <param name="count">The number of bytes to read. This value must be 0 or a non-negative number or an exception will occur.</param>
/// <returns>A byte array containing data read from the underlying stream. This might be less than the number of bytes requested if the end of the stream is reached.</returns>
public static byte[] ReadBytesBigEndian(this BinaryReader reader, int count)
{
byte[] retval = reader.ReadBytes(count);
Array.Reverse(retval);
return retval;
}
/// <summary>
/// Reads the specified number of characters from the current stream, returns the data in a character array, and advances the current position in accordance with the Encoding used and the specific character being read from the stream.
/// </summary>
/// <param name="count">The number of characters to read. This value must be 0 or a non-negative number or an exception will occur.</param>
/// <returns>A character array containing data read from the underlying stream. This might be less than the number of bytes requested if the end of the stream is reached.</returns>
public static char[] ReadCharsBigEndian(this BinaryReader reader, int count)
{
char[] retval = reader.ReadChars(count);
Array.Reverse(retval);
return retval;
}
/// <summary>
/// Reads a decimal value from the current stream and advances the current position of the stream by sixteen bytes.
/// </summary>
/// <returns>A decimal value read from the current stream.</returns>
public static decimal ReadDecimalBigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(16);
Array.Reverse(retval);
int i1 = BitConverter.ToInt32(retval, 0);
int i2 = BitConverter.ToInt32(retval, 4);
int i3 = BitConverter.ToInt32(retval, 8);
int i4 = BitConverter.ToInt32(retval, 12);
return new decimal(new int[] { i1, i2, i3, i4 });
}
/// <summary>
/// eads an 8-byte floating point value from the current stream and advances the current position of the stream by eight bytes.
/// </summary>
/// <returns>An 8-byte floating point value read from the current stream.</returns>
public static double ReadDoubleBigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(8);
Array.Reverse(retval);
return BitConverter.ToDouble(retval, 0);
}
/// <summary>
/// Reads a 2-byte signed integer from the current stream and advances the current position of the stream by two bytes.
/// </summary>
/// <returns>A 2-byte signed integer read from the current stream.</returns>
public static short ReadInt16BigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(2);
Array.Reverse(retval);
return BitConverter.ToInt16(retval, 0);
}
/// <summary>
/// Reads a 4-byte signed integer from the current stream and advances the current position of the stream by four bytes.
/// </summary>
/// <returns>A 4-byte signed integer read from the current stream.</returns>
public static int ReadInt32BigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(4);
Array.Reverse(retval);
return BitConverter.ToInt32(retval, 0);
}
/// <summary>
/// Reads an 8-byte signed integer from the current stream and advances the current position of the stream by eight bytes.
/// </summary>
/// <returns>An 8-byte signed integer read from the current stream.</returns>
public static long ReadInt64BigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(8);
Array.Reverse(retval);
return BitConverter.ToInt64(retval, 0);
}
/// <summary>
/// Reads a 4-byte floating point value from the current stream and advances the current position of the stream by four bytes.
/// </summary>
/// <returns>A 4-byte floating point value read from the current stream.</returns>
public static float ReadSingleBigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(4);
Array.Reverse(retval);
return BitConverter.ToSingle(retval, 0);
}
/// <summary>
/// Reads a 2-byte unsigned integer from the current stream using little-endian encoding and advances the position of the stream by two bytes.
///
/// This API is not CLS-compliant.
/// </summary>
/// <returns>A 2-byte unsigned integer read from this stream.</returns>
public static ushort ReadUInt16BigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(2);
Array.Reverse(retval);
return BitConverter.ToUInt16(retval, 0);
}
/// <summary>
/// Reads a 4-byte unsigned integer from the current stream and advances the position of the stream by four bytes.
///
/// This API is not CLS-compliant.
/// </summary>
/// <returns>A 4-byte unsigned integer read from this stream.</returns>
public static uint ReadUInt32BigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(4);
Array.Reverse(retval);
return BitConverter.ToUInt32(retval, 0);
}
/// <summary>
/// Reads an 8-byte unsigned integer from the current stream and advances the position of the stream by eight bytes.
///
/// This API is not CLS-compliant.
/// </summary>
/// <returns>An 8-byte unsigned integer read from this stream.</returns>
public static ulong ReadUInt64BigEndian(this BinaryReader reader)
{
byte[] retval = reader.ReadBytes(8);
Array.Reverse(retval);
return BitConverter.ToUInt64(retval, 0);
}
}
}

View File

@@ -1,400 +0,0 @@
#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,49 +0,0 @@
using System;
using System.Collections.Generic;
using MPF.Core.Converters;
using MPF.Core.Data;
using SabreTools.RedumpLib.Data;
namespace MPF.Core.Utilities
{
public static class EnumExtensions
{
/// <summary>
/// Determine if the media supports drive speeds
/// </summary>
/// <param name="type">MediaType value to check</param>
/// <returns>True if the media has variable dumping speeds, false otherwise</returns>
public static bool DoesSupportDriveSpeed(this MediaType? type)
{
return type switch
{
MediaType.CDROM
or MediaType.DVD
or MediaType.GDROM
or MediaType.HDDVD
or MediaType.BluRay
or MediaType.NintendoGameCubeGameDisc
or MediaType.NintendoWiiOpticalDisc => true,
_ => 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

@@ -1,177 +0,0 @@
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace MPF.Core.Utilities
{
public static class Logging
{
/// <summary>
/// Process a chunk of text and send it to a handler
/// </summary>
/// <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 NET20 || NET35
public static async Task OutputToLog(TextReader reader, object baseClass, EventHandler<Modules.BaseParameters.StringEventArgs>? handler)
#elif NET40
public static void OutputToLog(TextReader reader, object baseClass, EventHandler<Modules.BaseParameters.StringEventArgs>? handler)
#else
public static async Task OutputToLog(TextReader reader, object baseClass, EventHandler<string>? handler)
#endif
{
// Initialize the required variables
char[] buffer = new char[256];
int read = 0;
var sb = new StringBuilder();
try
{
while (true)
{
// Try to read the next chunk of characters
#if NET20 || NET35
read = await Task.Run(() => reader.Read(buffer, 0, buffer.Length));
#elif NET40
var readTask = Task.Factory.StartNew(() => reader.Read(buffer, 0, buffer.Length));
readTask.Wait();
read = readTask.Result;
#else
read = await reader.ReadAsync(buffer, 0, buffer.Length);
#endif
if (read == 0)
{
Thread.Sleep(10);
continue;
}
// Convert the characters into a string
string line = new(buffer, 0, read);
// If we have no newline characters, store in the string builder
#if NETFRAMEWORK
if (!line.Contains("\r") && !line.Contains("\n"))
#else
if (!line.Contains('\r') && !line.Contains('\n'))
#endif
sb.Append(line);
// If we have a newline, append and log
#if NETFRAMEWORK
else if (line.Contains("\n") || line.Contains("\r\n"))
#else
else if (line.Contains('\n') || line.Contains("\r\n"))
#endif
ProcessNewLines(sb, line, baseClass, handler);
// If we have a carriage return only, append and log first and last instances
#if NETFRAMEWORK
else if (line.Contains("\r"))
#else
else if (line.Contains('\r'))
#endif
ProcessCarriageReturns(sb, line, baseClass, handler);
}
}
catch { }
finally
{
#if NET20 || NET35 || NET40
handler?.Invoke(baseClass, new Modules.BaseParameters.StringEventArgs { Value = sb.ToString() });
#else
handler?.Invoke(baseClass, sb.ToString());
#endif
}
}
/// <summary>
/// Process a chunk that contains newlines
/// </summary>
/// <param name="sb">StringBuilder to write from and append to</param>
/// <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 NET20 || NET35 || NET40
private static void ProcessNewLines(StringBuilder sb, string line, object baseClass, EventHandler<Modules.BaseParameters.StringEventArgs>? handler)
#else
private static void ProcessNewLines(StringBuilder sb, string line, object baseClass, EventHandler<string>? handler)
#endif
{
line = line.Replace("\r\n", "\n");
var split = line.Split('\n');
for (int i = 0; i < split.Length; i++)
{
// If the chunk contains a carriage return, handle it like a separate line
#if NETFRAMEWORK
if (split[i].Contains("\r"))
#else
if (split[i].Contains('\r'))
#endif
{
ProcessCarriageReturns(sb, split[i], baseClass, handler);
continue;
}
// For the first item, append to anything existing and then write out
if (i == 0)
{
sb.Append(split[i]);
#if NET20 || NET35 || NET40
handler?.Invoke(baseClass, new Modules.BaseParameters.StringEventArgs { Value = sb.ToString() });
sb = new();
#else
handler?.Invoke(baseClass, sb.ToString());
sb.Clear();
#endif
}
// For the last item, just append so it's dealt with the next time
else if (i == split.Length - 1)
{
sb.Append(split[i]);
}
// For everything else, directly write out
else
{
#if NET20 || NET35 || NET40
handler?.Invoke(baseClass, new Modules.BaseParameters.StringEventArgs { Value = split[i] });
#else
handler?.Invoke(baseClass, split[i]);
#endif
}
}
}
/// <summary>
/// Process a chunk that contains carriage returns
/// </summary>
/// <param name="sb">StringBuilder to write from and append to</param>
/// <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 NET20 || NET35 || NET40
private static void ProcessCarriageReturns(StringBuilder sb, string line, object baseClass, EventHandler<Modules.BaseParameters.StringEventArgs>? handler)
#else
private static void ProcessCarriageReturns(StringBuilder sb, string line, object baseClass, EventHandler<string>? handler)
#endif
{
var split = line.Split('\r');
// Append and log the first
sb.Append(split[0]);
#if NET20 || NET35 || NET40
handler?.Invoke(baseClass, new Modules.BaseParameters.StringEventArgs { Value = sb.ToString() });
sb = new();
#else
handler?.Invoke(baseClass, sb.ToString());
sb.Clear();
#endif
// Append the last
sb.Append($"\r{split[split.Length - 1]}");
}
}
}

View File

@@ -1,294 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using MPF.Core.Converters;
using MPF.Core.Data;
using Newtonsoft.Json;
using SabreTools.RedumpLib;
using SabreTools.RedumpLib.Data;
namespace MPF.Core.Utilities
{
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>
public static (bool, MediaType, RedumpSystem?, string?) ProcessCommonArguments(string[] args)
{
// 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>
public static (Options, SubmissionInfo?, string?, int) LoadFromArguments(string[] args, int startIndex = 0)
{
// 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
SubmissionInfo? info = null;
string? parsedPath = null;
// These values require multiple parts to be active
bool scan = false, protectFile = false, hideDriveLetters = 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;
}
// Hide drive letters from scan output (requires --protect-file)
else if (args[startIndex].Equals("-g") || args[startIndex].Equals("--hide-drive-letters"))
{
hideDriveLetters = true;
}
// Include seed info file
else if (args[startIndex].StartsWith("-l=") || args[startIndex].StartsWith("--load-seed="))
{
string seedInfo = args[startIndex].Split('=')[1];
info = Builder.CreateFromFile(seedInfo);
}
else if (args[startIndex] == "-l" || args[startIndex] == "--load-seed")
{
string seedInfo = args[startIndex + 1];
info = Builder.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.IsNullOrEmpty(parsedPath);
options.OutputSeparateProtectionFile = scan && protectFile && !string.IsNullOrEmpty(parsedPath);
options.HideDriveLetters = hideDriveLetters && scan && protectFile && !string.IsNullOrEmpty(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)",
"-g, --hide-drive-letters Hide drive letters from scan output (requires --protect-file)",
"-l, --load-seed <path> Load a seed submission JSON for user information",
"-x, --suffix Enable adding filename suffix",
"-j, --json Enable submission JSON output",
"-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 stream = File.Open(ConfigurationPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
var reader = new StreamReader(stream);
var settings = serializer.Deserialize(reader, typeof(Dictionary<string, string?>)) as Dictionary<string, string?>;
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,646 +0,0 @@
using System;
using System.IO;
using System.Net;
using System.Reflection;
using MPF.Core.Data;
using Newtonsoft.Json.Linq;
using SabreTools.RedumpLib.Data;
namespace MPF.Core.Utilities
{
public static class Tools
{
#region Byte Arrays
/// <summary>
/// Search for a byte array in another array
/// </summary>
public static bool Contains(this byte[] stack, byte[] needle, out int position, int start = 0, int end = -1)
{
// Initialize the found position to -1
position = -1;
// If either array is null or empty, we can't do anything
if (stack == null || stack.Length == 0 || needle == null || needle.Length == 0)
return false;
// If the needle array is larger than the stack array, it can't be contained within
if (needle.Length > stack.Length)
return false;
// If start or end are not set properly, set them to defaults
if (start < 0)
start = 0;
if (end < 0)
end = stack.Length - needle.Length;
for (int i = start; i < end; i++)
{
if (stack.EqualAt(needle, i))
{
position = i;
return true;
}
}
return false;
}
/// <summary>
/// See if a byte array starts with another
/// </summary>
public static bool StartsWith(this byte[] stack, byte[] needle)
{
return stack.Contains(needle, out int _, start: 0, end: 1);
}
/// <summary>
/// Get if a stack at a certain index is equal to a needle
/// </summary>
private static bool EqualAt(this byte[] stack, byte[] needle, int index)
{
// If we're too close to the end of the stack, return false
if (needle.Length >= stack.Length - index)
return false;
for (int i = 0; i < needle.Length; i++)
{
if (stack[i + index] != needle[i])
return false;
}
return true;
}
/// <summary>
/// Converts a hex string into a byte array
/// </summary>
/// <param name="hex">Hex string</param>
/// <returns>Converted byte array, or null if invalid hex string</returns>
public static byte[]? HexStringToByteArray(string? hexString)
{
// Valid hex string must be an even number of characters
if (string.IsNullOrEmpty(hexString) || hexString!.Length % 2 == 1)
return null;
// Convert ASCII to byte via lookup table
int[] hexLookup = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F];
byte[] byteArray = new byte[hexString.Length / 2];
for (int i = 0; i < hexString.Length; i += 2)
{
// Convert next two chars to ASCII value relative to '0'
int a = Char.ToUpper(hexString[i]) - '0';
int b = Char.ToUpper(hexString[i + 1]) - '0';
// Ensure hex string only has '0' through '9' and 'A' through 'F' (case insensitive)
if ((a < 0 || b < 0 || a > 22 || b > 22) || (a > 10 && a < 17) || (b > 10 && b < 17))
return null;
byteArray[i / 2] = (byte)(hexLookup[a] << 4 | hexLookup[b]);
}
return byteArray;
}
#endregion
#region Support
/// <summary>
/// Verify that, given a system and a media type, they are correct
/// </summary>
public static Result GetSupportStatus(RedumpSystem? system, MediaType? type)
{
// No system chosen, update status
if (system == null)
return Result.Failure("Please select a valid system");
// If we're on an unsupported type, update the status accordingly
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"),
};
}
/// <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;
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,
};
}
#endregion
#region Versioning
/// <summary>
/// Check for a new MPF version
/// </summary>
/// <returns>
/// Bool representing if the values are different.
/// String representing the message to display the the user.
/// String representing the new release URL.
/// </returns>
public static (bool different, string message, string? url) CheckForNewVersion()
{
try
{
// Get current assembly version
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
var (tag, url) = GetRemoteVersionAndUrl();
bool different = version != tag && tag != null;
string message = $"Local version: {version}"
+ $"{Environment.NewLine}Remote version: {tag}"
+ (different
? $"{Environment.NewLine}The update URL has been added copied to your clipboard"
: $"{Environment.NewLine}You have the newest version!");
return (different, message, url);
}
catch (Exception ex)
{
return (false, ex.ToString(), null);
}
}
/// <summary>
/// Get the current informational version formatted as a string
/// </summary>
public static string? GetCurrentVersion()
{
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>
private static (string? tag, string? url) GetRemoteVersionAndUrl()
{
#if NET20 || NET35 || NET40
// Not supported in .NET Frameworks 2.0, 3.5, or 4.0
return (null, null);
#else
using var hc = new System.Net.Http.HttpClient();
#if NET452
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
#endif
// TODO: Figure out a better way than having this hardcoded...
string url = "https://api.github.com/repos/SabreTools/MPF/releases/latest";
var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, url);
message.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:64.0) Gecko/20100101 Firefox/64.0");
var latestReleaseJsonString = hc.SendAsync(message)?.ConfigureAwait(false).GetAwaiter().GetResult()
.Content?.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult();
if (latestReleaseJsonString == null)
return (null, null);
var latestReleaseJson = JObject.Parse(latestReleaseJsonString);
if (latestReleaseJson == null)
return (null, null);
var latestTag = latestReleaseJson["tag_name"]?.ToString();
var releaseUrl = latestReleaseJson["html_url"]?.ToString();
return (latestTag, releaseUrl);
#endif
}
#endregion
#region PlayStation 3 specific tools
/// <summary>
/// Validates a getkey log to check for presence of valid PS3 key
/// </summary>
/// <param name="logPath">Path to getkey log file</param>
/// <param name="key">Output key string, null if not valid</param>
/// <param name="id">Output disc ID string, null if not valid</param>
/// <param name="pic">Output PIC string, null if not valid</param>
/// <returns>True if path to log file contains valid key, false otherwise</returns>
public static bool ParseGetKeyLog(string? logPath, out string? key, out string? id, out string? pic)
{
key = id = pic = null;
if (string.IsNullOrEmpty(logPath))
return false;
try
{
if (!File.Exists(logPath))
return false;
// Protect from attempting to read from really long files
FileInfo logFile = new(logPath);
if (logFile.Length > 65536)
return false;
// Read from .getkey.log file
using StreamReader sr = File.OpenText(logPath);
// Determine whether GetKey was successful
string? line;
while ((line = sr.ReadLine()) != null && line.Trim().StartsWith("get_dec_key succeeded!") == false) ;
if (line == null)
return false;
// Look for Disc Key in log
while ((line = sr.ReadLine()) != null && line.Trim().StartsWith("disc_key = ") == false) ;
// If end of file reached, no key found
if (line == null)
return false;
// Get Disc Key from log
string discKeyStr = line.Substring("disc_key = ".Length);
// Validate Disc Key from log
if (discKeyStr.Length != 32)
return false;
// Convert Disc Key to byte array
key = discKeyStr;
if (key == null)
return false;
// Read Disc ID
while ((line = sr.ReadLine()) != null && line.Trim().StartsWith("disc_id = ") == false) ;
// If end of file reached, no ID found
if (line == null)
return false;
// Get Disc ID from log
string discIDStr = line.Substring("disc_id = ".Length);
// Validate Disc ID from log
if (discIDStr.Length != 32)
return false;
// Replace X's in Disc ID with 00000001
discIDStr = discIDStr.Substring(0, 24) + "00000001";
// Convert Disc ID to byte array
id = discIDStr;
if (id == null)
return false;
// Look for PIC in log
while ((line = sr.ReadLine()) != null && line.Trim().StartsWith("PIC:") == false) ;
// If end of file reached, no PIC found
if (line == null)
return false;
// Get PIC from log
string discPICStr = "";
for (int i = 0; i < 9; i++)
discPICStr += sr.ReadLine();
if (discPICStr == null)
return false;
// Validate PIC from log
if (discPICStr.Length != 264)
return false;
// Convert PIC to byte array
pic = discPICStr;
if (pic == null)
return false;
// Double check for warnings in .getkey.log
while ((line = sr.ReadLine()) != null)
{
string t = line.Trim();
if (t.StartsWith("WARNING"))
return false;
else if (t.StartsWith("SUCCESS"))
return true;
}
}
catch
{
// We are not concerned with the error
return false;
}
return true;
}
/// <summary>
/// Validates a getkey log to check for presence of valid PS3 key
/// </summary>
/// <param name="logPath">Path to getkey log file</param>
/// <param name="key">Output 16 byte disc key, null if not valid</param>
/// <param name="id">Output 16 byte disc ID, null if not valid</param>
/// <param name="pic">Output 230 byte PIC, null if not valid</param>
/// <returns>True if path to log file contains valid key, false otherwise</returns>
public static bool ParseGetKeyLog(string? logPath, out byte[]? key, out byte[]? id, out byte[]? pic)
{
key = id = pic = null;
if (ParseGetKeyLog(logPath, out string? keyString, out string? idString, out string? picString))
{
if (string.IsNullOrEmpty(keyString) || string.IsNullOrEmpty(idString) || string.IsNullOrEmpty(picString) || picString!.Length < 230)
return false;
key = Tools.HexStringToByteArray(keyString);
id = Tools.HexStringToByteArray(idString);
pic = Tools.HexStringToByteArray(picString.Substring(0, 230));
return true;
}
else
{
return false;
}
}
/// <summary>
/// Validates a hexadecimal disc ID
/// </summary>
/// <param name="discID">String representing hexadecimal disc ID</param>
/// <returns>True if string is a valid disc ID, false otherwise</returns>
public static byte[]? ParseDiscID(string? discID)
{
if (string.IsNullOrEmpty(discID))
return null;
string cleandiscID = discID!.Trim().Replace("\n", string.Empty);
if (discID!.Length != 32)
return null;
// Censor last 4 bytes by replacing with 0x00000001
cleandiscID = cleandiscID.Substring(0, 24) + "00000001";
// Convert to byte array, null if invalid hex string
byte[]? id = Tools.HexStringToByteArray(cleandiscID);
return id;
}
/// <summary>
/// Validates a key file to check for presence of valid PS3 key
/// </summary>
/// <param name="keyPath">Path to key file</param>
/// <returns>Output 16 byte key, null if not valid</returns>
public static byte[]? ParseKeyFile(string? keyPath)
{
if (string.IsNullOrEmpty(keyPath))
return null;
// Try read from key file
try
{
if (!File.Exists(keyPath))
return null;
// Key file must be exactly 16 bytes long
FileInfo keyFile = new(keyPath);
if (keyFile.Length != 16)
return null;
byte[] key = new byte[16];
// Read 16 bytes from Key file
using FileStream fs = new(keyPath, FileMode.Open, FileAccess.Read);
using BinaryReader reader = new(fs);
int numBytes = reader.Read(key, 0, 16);
if (numBytes != 16)
return null;
return key;
}
catch
{
// Not concerned with error
return null;
}
}
/// <summary>
/// Validates a hexadecimal key
/// </summary>
/// <param name="hexKey">String representing hexadecimal key</param>
/// <returns>Output 16 byte key, null if not valid</returns>
public static byte[]? ParseHexKey(string? hexKey)
{
if (string.IsNullOrEmpty(hexKey))
return null;
string cleanHexKey = hexKey!.Trim().Replace("\n", string.Empty);
if (cleanHexKey.Length != 32)
return null;
// Convert to byte array, null if invalid hex string
byte[]? key = Tools.HexStringToByteArray(cleanHexKey);
return key;
}
/// <summary>
/// Validates a PIC file path
/// </summary>
/// <param name="picPath">Path to PIC file</param>
/// <returns>Output PIC byte array, null if not valid</returns>
public static byte[]? ParsePICFile(string? picPath)
{
if (string.IsNullOrEmpty(picPath))
return null;
// Try read from PIC file
try
{
if (!File.Exists(picPath))
return null;
// PIC file must be at least 115 bytes long
FileInfo picFile = new(picPath);
if (picFile.Length < 115)
return null;
byte[] pic = new byte[115];
// Read 115 bytes from PIC file
using FileStream fs = new(picPath, FileMode.Open, FileAccess.Read);
using BinaryReader reader = new(fs);
int numBytes = reader.Read(pic, 0, 115);
if (numBytes != 115)
return null;
// Validate that a PIC was read by checking first 6 bytes
if (pic[0] != 0x10 ||
pic[1] != 0x02 ||
pic[2] != 0x00 ||
pic[3] != 0x00 ||
pic[4] != 0x44 ||
pic[5] != 0x49)
return null;
return pic;
}
catch
{
// Not concerned with error
return null;
}
}
/// <summary>
/// Validates a PIC
/// </summary>
/// <param name="inputPIC">String representing PIC</param>
/// <returns>Output PIC byte array, null if not valid</returns>
public static byte[]? ParsePIC(string? inputPIC)
{
if (string.IsNullOrEmpty(inputPIC))
return null;
string cleanPIC = inputPIC!.Trim().Replace("\n", string.Empty);
if (cleanPIC.Length < 230)
return null;
// Convert to byte array, null if invalid hex string
byte[]? pic = Tools.HexStringToByteArray(cleanPIC.Substring(0, 230));
return pic;
}
/// <summary>
/// Validates a string representing a layerbreak value (in sectors)
/// </summary>
/// <param name="inputLayerbreak">String representing layerbreak value</param>
/// <param name="layerbreak">Output layerbreak value, null if not valid</param>
/// <returns>True if layerbreak is valid, false otherwise</returns>
public static long? ParseLayerbreak(string? inputLayerbreak)
{
if (string.IsNullOrEmpty(inputLayerbreak))
return null;
if (!long.TryParse(inputLayerbreak, out long layerbreak))
return null;
return ParseLayerbreak(layerbreak);
}
/// <summary>
/// Validates a layerbreak value (in sectors)
/// </summary>
/// <param name="inputLayerbreak">Number representing layerbreak value</param>
/// <param name="layerbreak">Output layerbreak value, null if not valid</param>
/// <returns>True if layerbreak is valid, false otherwise</returns>
public static long? ParseLayerbreak(long? layerbreak)
{
// Check that layerbreak is positive number and smaller than largest disc size (in sectors)
if (layerbreak <= 0 || layerbreak > 24438784)
return null;
return layerbreak;
}
/// <summary>
/// Converts a CRC32 hash hex string into uint32 representation
/// </summary>
/// <param name="inputLayerbreak">Hex string representing CRC32 hash</param>
/// <param name="layerbreak">Output CRC32 value, null if not valid</param>
/// <returns>True if CRC32 hash string is valid, false otherwise</returns>
public static uint? ParseCRC32(string? inputCRC32)
{
if (string.IsNullOrEmpty(inputCRC32))
return null;
byte[]? crc32 = Tools.HexStringToByteArray(inputCRC32);
if (crc32 == null || crc32.Length != 4)
return null;
return (uint)(0x01000000 * crc32[0] + 0x00010000 * crc32[1] + 0x00000100 * crc32[2] + 0x00000001 * crc32[3]);
}
#endregion
}
}

View File

@@ -0,0 +1,68 @@
namespace MPF.ExecutionContexts.Aaru
{
/// <summary>
/// Top-level commands for Aaru
/// </summary>
public static class CommandStrings
{
public const string NONE = "";
// Archive Family
public const string ArchivePrefixShort = "arc";
public const string ArchivePrefixLong = "archive";
public const string ArchiveInfo = "info";
// Database Family
public const string DatabasePrefixShort = "db";
public const string DatabasePrefixLong = "database";
public const string DatabaseStats = "stats";
public const string DatabaseUpdate = "update";
// Device Family
public const string DevicePrefixShort = "dev";
public const string DevicePrefixLong = "device";
public const string DeviceInfo = "info";
public const string DeviceList = "list";
public const string DeviceReport = "report";
// Filesystem Family
public const string FilesystemPrefixShort = "fi";
public const string FilesystemPrefixShortAlt = "fs";
public const string FilesystemPrefixLong = "filesystem";
public const string FilesystemExtract = "extract";
public const string FilesystemInfo = "info";
public const string FilesystemListShort = "ls";
public const string FilesystemListLong = "list";
public const string FilesystemOptions = "options";
// Image Family
public const string ImagePrefixShort = "i";
public const string ImagePrefixLong = "image";
public const string ImageChecksumShort = "chk";
public const string ImageChecksumLong = "checksum";
public const string ImageCompareShort = "cmp";
public const string ImageCompareLong = "compare";
public const string ImageConvert = "convert";
public const string ImageCreateSidecar = "create-sidecar";
public const string ImageDecode = "decode";
public const string ImageEntropy = "entropy";
public const string ImageInfo = "info";
public const string ImageOptions = "options";
public const string ImagePrint = "print";
public const string ImageVerify = "verify";
// Media Family
public const string MediaPrefixShort = "m";
public const string MediaPrefixLong = "media";
public const string MediaDump = "dump";
public const string MediaInfo = "info";
public const string MediaScan = "scan";
// Standalone Commands
public const string Configure = "configure";
public const string Formats = "formats";
public const string ListEncodings = "list-encodings";
public const string ListNamespaces = "list-namespaces";
public const string Remote = "remote";
}
}

View File

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

View File

@@ -0,0 +1,43 @@
namespace MPF.ExecutionContexts.Aaru
{
/// <summary>
/// Supported encodings for Aaru
/// </summary>
/// TODO: Use to verify encoding settings
public static class EncodingStrings
{
public const string ArabicMac = "x-mac-arabic";
public const string AtariASCII = "atascii";
public const string CentralEuropeanMac = "x-mac-ce";
public const string CommodorePETSCII = "petscii";
public const string CroatianMac = "x-mac-croatian";
public const string CyrillicMac = "x-mac-cryillic";
public const string FarsiMac = "x-mac-farsi";
public const string GreekMac = "x-mac-greek";
public const string HebrewMac = "x-mac-hebrew";
public const string RomanianMac = "x-mac-romanian";
public const string SinclairZXSpectrum = "spectrum";
public const string SinclairZX80 = "zx80";
public const string SinclairZX81 = "zx81";
public const string TurkishMac = "x-mac-turkish";
public const string UkrainianMac = "x-mac-ukrainian";
public const string Unicode = "utf-16";
public const string UnicodeBigEndian = "utf-16BE";
public const string UnicodeUTF32BigEndian = "utf-32BE";
public const string UnicodeUTF32 = "utf-32";
public const string UnicodeUTF7 = "utf-7";
public const string UnicodeUTF8 = "utf-8";
public const string USASCII = "us-ascii";
public const string WesternEuropeanAppleII = "apple2";
public const string WesternEuropeanAppleIIc = "apple2c";
public const string WesternEuropeanAppleIIe = "apple2e";
public const string WesternEuropeanAppleIIgs = "apple2gs";
public const string WesternEuropeanAppleLisa = "lisa";
public const string WesternEuropeanAtariST = "atarist";
public const string WesternEuropeanGEM = "gem";
public const string WesternEuropeanGEOS = "geos";
public const string WesternEuropeanISO = "iso-8859-1";
public const string WesternEuropeanMac = "macintosh";
public const string WesternEuropeanRadix50 = "radix50";
}
}

View File

@@ -0,0 +1,151 @@
namespace MPF.ExecutionContexts.Aaru
{
/// <summary>
/// Dumping flags for Aaru
/// </summary>
public static class FlagStrings
{
// Boolean flags
public const string Adler32Short = "-a";
public const string Adler32Long = "--adler32";
public const string ClearLong = "--clear";
public const string ClearAllLong = "--clear-all";
public const string CRC16Long = "--crc16";
public const string CRC32Short = "-c";
public const string CRC32Long = "--crc32";
public const string CRC64Long = "--crc64";
public const string DebugShort = "-d";
public const string DebugLong = "--debug";
public const string DiskTagsShort = "-f";
public const string DiskTagsLong = "--disk-tags";
public const string DuplicatedSectorsShort = "-p";
public const string DuplicatedSectorsLong = "--duplicated-sectors";
public const string EjectLong = "--eject";
public const string ExtendedAttributesShort = "-x";
public const string ExtendedAttributesLong = "--xattrs";
public const string FilesystemsShort = "-f";
public const string FilesystemsLong = "--filesystems";
public const string FirstPregapLong = "--first-pregap";
public const string FixOffsetLong = "--fix-offset";
public const string FixSubchannelLong = "--fix-subchannel";
public const string FixSubchannelCrcLong = "--fix-subchannel-crc";
public const string FixSubchannelPositionLong = "--fix-subchannel-position";
public const string Fletcher16Long = "--fletcher16";
public const string Fletcher32Long = "--fletcher32";
public const string ForceShort = "-f";
public const string ForceLong = "--force";
public const string GenerateSubchannelsLong = "--generate-subchannels";
public const string HelpShort = "-h";
public const string HelpShortAlt = "-?";
public const string HelpLong = "--help";
public const string LongFormatShort = "-l";
public const string LongFormatLong = "--long-format";
public const string LongSectorsShort = "-r";
public const string LongSectorsLong = "--long-sectors";
public const string MD5Short = "-m";
public const string MD5Long = "--md5";
public const string MetadataLong = "--metadata";
public const string PartitionsShort = "-p";
public const string PartitionsLong = "--partitions";
public const string PauseLong = "--pause";
public const string PersistentLong = "--persistent";
public const string PrivateLong = "--private";
public const string ResumeShort = "-r";
public const string ResumeLong = "--resume";
public const string RetrySubchannelLong = "--retry-subchannel";
public const string SectorTagsShort = "-p";
public const string SectorTagsLong = "--sector-tags";
public const string SeparatedTracksShort = "-t";
public const string SeparatedTracksLong = "--separated-tracks";
public const string SHA1Short = "-s";
public const string SHA1Long = "--sha1";
public const string SHA256Long = "--sha256";
public const string SHA384Long = "--sha384";
public const string SHA512Long = "--sha512";
public const string SkipCdiReadyHoleLong = "--skip-cdiready-hole";
public const string SpamSumShort = "-f";
public const string SpamSumLong = "--spamsum";
public const string StopOnErrorShort = "-s";
public const string StopOnErrorLong = "--stop-on-error";
public const string StoreEncryptedLong = "--store-encrypted";
public const string TapeShort = "-t";
public const string TapeLong = "--tape";
public const string TitleKeysLong = "--title-keys";
public const string TrapDiscShort = "-t";
public const string TrapDiscLong = "--trap-disc";
public const string TrimLong = "--trim";
public const string UseBufferedReadsLong = "--use-buffered-reads";
public const string VerboseShort = "-v";
public const string VerboseLong = "--verbose";
public const string VerifyDiscShort = "-w";
public const string VerifyDiscLong = "--verify-disc";
public const string VerifySectorsShort = "-s";
public const string VerifySectorsLong = "--verify-sectors";
public const string VersionLong = "--version";
public const string WholeDiscShort = "-w";
public const string WholeDiscLong = "--whole-disc";
// Int8 flags
public const string SpeedLong = "--speed";
// Int16 flags
public const string RetryPassesShort = "-p";
public const string RetryPassesLong = "--retry-passes";
public const string WidthShort = "-w";
public const string WidthLong = "--width";
// Int32 flags
public const string BlockSizeShort = "-b";
public const string BlockSizeLong = "--block-size";
public const string CountShort = "-c";
public const string CountLong = "--count";
public const string MaxBlocksLong = "--max-blocks";
public const string MediaLastSequenceLong = "--media-lastsequence";
public const string MediaSequenceLong = "--media-sequence";
public const string SkipShort = "-k";
public const string SkipLong = "--skip";
// Int64 flags
public const string LengthShort = "-l"; // or "all"
public const string LengthLong = "--length"; // or "all"
public const string StartShort = "-s";
public const string StartLong = "--start";
// String flags
public const string CommentsLong = "--comments";
public const string CreatorLong = "--creator";
public const string DriveManufacturerLong = "--drive-manufacturer";
public const string DriveModelLong = "--drive-model";
public const string DriveRevisionLong = "--drive-revision";
public const string DriveSerialLong = "--drive-serial";
public const string EncodingShort = "-e";
public const string EncodingLong = "--encoding";
public const string FormatConvertShort = "-p";
public const string FormatConvertLong = "--format";
public const string FormatDumpShort = "-t";
public const string FormatDumpLong = "--format";
public const string GeometryShort = "-g";
public const string GeometryLong = "--geometry";
public const string ImgBurnLogShort = "-b";
public const string ImgBurnLogLong = "--ibg-log";
public const string MediaBarcodeLong = "--media-barcode";
public const string MediaManufacturerLong = "--media-manufacturer";
public const string MediaModelLong = "--media-model";
public const string MediaPartNumberLong = "--media-partnumber";
public const string MediaSerialLong = "--media-serial";
public const string MediaTitleLong = "--media-title";
public const string MHDDLogShort = "-m";
public const string MHDDLogLong = "--mhdd-log";
public const string NamespaceShort = "-n";
public const string NamespaceLong = "--namespace";
public const string OptionsShort = "-O";
public const string OptionsLong = "--options";
public const string OutputPrefixShort = "-w";
public const string OutputPrefixLong = "--output-prefix";
public const string ResumeFileShort = "-r";
public const string ResumeFileLong = "--resume-file";
public const string SubchannelLong = "--subchannel";
public const string XMLSidecarShort = "-x";
public const string XMLSidecarLong = "--cicm-xml";
}
}

View File

@@ -0,0 +1,169 @@
namespace MPF.ExecutionContexts.Aaru
{
/// <summary>
/// Supported formats for Aaru
/// </summary>
/// TODO: Use to verify format settings
public static class FormatStrings
{
// Supported filters
public const string AppleDouble = "AppleDouble";
public const string AppleSingle = "AppleSingle";
public const string BZip2 = "BZip2";
public const string GZip = "GZip";
public const string LZip = "LZip";
public const string MacBinary = "MacBinary";
public const string NoFilter = "No filter";
public const string PCExchange = "PCExchange";
public const string XZ = "XZ";
// Read-only media image formats
public const string AppleDiskArchivalRetrievalTool = "Apple Disk Archival/Retrieval Tool";
public const string AppleNewDiskImageFormat = "Apple New Disk Image Format";
public const string AppleNIB = "Apple NIB";
public const string BlindWrite4 = "BlindWrite 4";
public const string BlindWrite5 = "BlindWrite 5";
public const string CPCEMUDiskFileAndExtendedCPCDiskFile = "CPCEMU Disk-File and Extended CPC Disk-File";
public const string D2FDiskImage = "d2f disk image";
public const string D88DiskImage = "D88 Disk Image";
public const string DIMDiskImage = "DIM Disk Image";
public const string DiscFerret = "DiscFerret";
public const string DiscJuggler = "DiscJuggler";
public const string DreamcastGDIImage = "Dreamcast GDI image";
public const string DunfieldsIMD = "Dunfield's IMD";
public const string HDCopyDiskImage = "HD-Copy disk image";
public const string KryoFluxSTREAM = "KryoFlux STREAM";
public const string MAMECompressedHunksOfData = "MAME Compressed Hunks of Data";
public const string MicrosoftVHDX = "Microsoft VHDX";
public const string NeroBurningROMImage = "Nero Burning ROM image";
public const string PartCloneDiskImage = "PartClone disk image";
public const string PartimageDiskImage = "Partimage disk image";
public const string SpectrumFloppyDiskImage = "Spectrum Floppy Disk Image";
public const string SuperCardPro = "SuperCardPro";
public const string SydexCopyQM = "Sydex CopyQM";
public const string SydexTeleDisk = "Sydex TeleDisk";
// Read/write media image formats
public const string AaruFormat = "Aaru Format";
public const string ACTApricotDiskImage = "ACT Apricot Disk Image";
public const string Alcohol120MediaDescriptorStructure = "Alcohol 120% Media Descriptor Structure";
public const string Anex86DiskImage = "Anex86 Disk Image";
public const string Apple2InterleavedDiskImage = "Apple ][Interleaved Disk Image";
public const string Apple2IMG = "Apple 2IMG";
public const string AppleDiskCopy42 = "Apple DiskCopy 4.2";
public const string AppleUniversalDiskImageFormat = "Apple Universal Disk Image Format";
public const string BasicLisaUtility = "Basic Lisa Utility";
public const string CDRDAOTocfile = "CDRDAO tocfile";
public const string CDRWinCuesheet = "CDRWin cuesheet";
public const string CisCopyDiskImageDCFile = "CisCopy Disk Image(DC-File)";
public const string CloneCD = "CloneCD";
public const string CopyTape = "CopyTape";
public const string DigitalResearchDiskCopy = "Digital Research DiskCopy";
public const string IBMSaveDskF = "IBM SaveDskF";
public const string MAXIDiskImage = "MAXI Disk image";
public const string ParallelsDiskImage = "Parallels disk image";
public const string QEMUCopyOnWriteDiskImage = "QEMU Copy-On-Write disk image";
public const string QEMUCopyOnWriteDiskImageV2 = "QEMU Copy-On-Write disk image v2";
public const string QEMUEnhancedDiskImage = "QEMU Enhanced Disk image";
public const string RawDiskImage = "Raw Disk Image";
public const string RayAracheliansDiskIMage = "Ray Arachelian's Disk IMage";
public const string RSIDEHardDiskImage = "RS-IDE Hard Disk Image";
public const string T98HardDiskImage = "T98 Hard Disk Image";
public const string T98NextNHDr0DiskImage = "T98-Next NHD r0 Disk Image";
public const string Virtual98DiskImage = "Virtual98 Disk Image";
public const string VirtualBoxDiskImage = "VirtualBox Disk Image";
public const string VirtualPC = "VirtualPC";
public const string VMwareDiskImage = "VMware disk image";
// Supported filesystems for identification and information only
public const string AcornAdvancedDiscFilingSystem = "Acorn Advanced Disc Filing System";
public const string AlexanderOsipovDOSFileSystem = "Alexander Osipov DOS file system";
public const string AmigaDOSFilesystem = "Amiga DOS filesystem";
public const string AppleFileSystem = "Apple File System";
public const string AppleHFSPlusFilesystem = "Apple HFS+ filesystem";
public const string AppleHierarchicalFileSystem = "Apple Hierarchical File System";
public const string AppleProDOSFilesystem = "Apple ProDOS filesystem";
public const string AtheOSFilesystem = "AtheOS Filesystem";
public const string BeFilesystem = "Be Filesystem";
public const string BSDFastFileSystem = "BSD Fast File System(aka UNIX File System, UFS)";
public const string BTreeFileSystem = "B-tree file system";
public const string CommodoreFileSystem = "Commodore file system";
public const string CramFilesystem = "Cram filesystem";
public const string DumpEightPlugin = "dump(8) Plugin";
public const string ECMA67 = "ECMA-67";
public const string ExtentFileSystemPlugin = "Extent File System Plugin";
public const string F2FSPlugin = "F2FS Plugin";
public const string Files11OnDiskStructure = "Files-11 On-Disk Structure";
public const string FossilFilesystemPlugin = "Fossil Filesystem Plugin";
public const string HAMMERFilesystem = "HAMMER Filesystem";
public const string HighPerformanceOpticalFileSystem = "High Performance Optical File System";
public const string HPLogicalInterchangeFormatPlugin = "HP Logical Interchange Format Plugin";
public const string JFSPlugin = "JFS Plugin";
public const string LinuxExtendedFilesystem = "Linux extended Filesystem";
public const string LinuxExtendedFilesystem234 = "Linux extended Filesystem 2, 3 and 4";
public const string LocusFilesystemPlugin = "Locus Filesystem Plugin";
public const string MicroDOSFileSystem = "MicroDOS file system";
public const string MicrosoftExtendedFileAllocationTable = "Microsoft Extended File Allocation Table";
public const string MinixFilesystem = "Minix Filesystem";
public const string NewTechnologyFileSystem = "New Technology File System(NTFS)";
public const string NILFS2Plugin = "NILFS2 Plugin";
public const string NintendoOpticalFilesystems = "Nintendo optical filesystems";
public const string OS2HighPerformanceFileSystem = "OS/2 High Performance File System";
public const string OS9RandomBlockFilePlugin = "OS-9 Random Block File Plugin";
public const string PCEngineCDPlugin = "PC Engine CD Plugin";
public const string PCFXPlugin = "PC-FX Plugin";
public const string ProfessionalFileSystem = "Professional File System";
public const string QNX4Plugin = "QNX4 Plugin";
public const string QNX6Plugin = "QNX6 Plugin";
public const string ReiserFilesystemPlugin = "Reiser Filesystem Plugin";
public const string Reiser4FilesystemPlugin = "Reiser4 Filesystem Plugin";
public const string ResilientFileSystemPlugin = "Resilient File System plugin";
public const string RT11FileSystem = "RT-11 file system";
public const string SmartFileSystem = "SmartFileSystem";
public const string SolarOSFilesystem = "Solar_OS filesystem";
public const string SquashFilesystem = "Squash filesystem";
public const string UNICOSFilesystemPlugin = "UNICOS Filesystem Plugin";
public const string UniversalDiskFormat = "Universal Disk Format";
public const string UNIXBootFilesystem = "UNIX Boot filesystem";
public const string UNIXSystemVFilesystem = "UNIX System V filesystem";
public const string VeritasFilesystem = "Veritas filesystem";
public const string VMwareFilesystem = "VMware filesystem";
public const string XFSFilesystemPlugin = "XFS Filesystem Plugin";
public const string XiaFilesystem = "Xia filesystem";
public const string ZFSFilesystemPlugin = "ZFS Filesystem Plugin";
// Supported filesystems that can read their contents
public const string AppleDOSFileSystem = "Apple DOS File System";
public const string AppleLisaFileSystem = "Apple Lisa File System";
public const string AppleMacintoshFileSystem = "Apple Macintosh File System";
public const string CPMFileSystem = "CP/M File System";
public const string FATXFilesystemPlugin = "FATX Filesystem Plugin";
public const string ISO9660Filesystem = "ISO9660 Filesystem";
public const string MicrosoftFileAllocationTable = "Microsoft File Allocation Table";
public const string OperaFilesystemPlugin = "Opera Filesystem Plugin";
public const string UCSDPascalFilesystem = "U.C.S.D.Pascal filesystem";
// Supported partitioning schemes
public const string AcornFileCorePartitions = "Acorn FileCore partitions";
public const string ACTApricotPartitions = "ACT Apricot partitions";
public const string AmigaRigidDiskBlock = "Amiga Rigid Disk Block";
public const string ApplePartitionMap = "Apple Partition Map";
public const string AtariPartitions = "Atari partitions";
public const string BSDDisklabel = "BSD disklabel";
public const string DECDisklabel = "DEC disklabel";
public const string DragonFlyBSD64bitDisklabel = "DragonFly BSD 64-bit disklabel";
public const string GUIDPartitionTable = "GUID Partition Table";
public const string Human68kPartitions = "Human 68k partitions";
public const string MasterBootRecord = "Master Boot Record";
public const string NECPC9800PartitionTable = "NEC PC-9800 partition table";
public const string NeXTDisklabel = "NeXT Disklabel";
public const string Plan9PartitionTable = "Plan9 partition table";
public const string RioKarmaPartitioning = "Rio Karma partitioning";
public const string SGIDiskVolumeHeader = "SGI Disk Volume Header";
public const string SunDisklabel = "Sun Disklabel";
public const string UNIXHardwired = "UNIX hardwired";
public const string UNIXVTOC = "UNIX VTOC";
public const string XboxPartitioning = "Xbox partitioning";
public const string XENIX = "XENIX";
}
}

View File

@@ -0,0 +1,27 @@
namespace MPF.ExecutionContexts.Aaru
{
/// <summary>
/// Supported namespaces for Aaru
/// </summary>
/// TODO: Use to verify namespace settings
public static class NamespaceStrings
{
// Namespaces for Apple Lisa File System
public const string LisaOfficeSystem = "office";
public const string LisaPascalWorkshop = "workshop"; // Default
// Namespaces for ISO9660 Filesystem
public const string JolietVolumeDescriptor = "joliet"; // Default
public const string PrimaryVolumeDescriptor = "normal";
public const string PrimaryVolumeDescriptorwithEncoding = "romeo";
public const string RockRidge = "rrip";
public const string PrimaryVolumeDescriptorVersionSuffix = "vms";
// Namespaces for Microsoft File Allocation Table
public const string DOS83UpperCase = "dos";
public const string LFNWhenAvailableWithFallback = "ecs"; // Default
public const string LongFileNames = "lfn";
public const string WindowsNT83MixedCase = "nt";
public const string OS2Extended = "os2";
}
}

View File

@@ -0,0 +1,43 @@
namespace MPF.ExecutionContexts.Aaru
{
/// <summary>
/// Supported options for Aaru
/// </summary>
/// TODO: Use to verify option settings
public static class OptionStrings
{
// Aaru format
public const string AaruCompress = "compress"; // boolean, default true;
public const string AaruDeduplicate = "deduplicate"; // boolean, default true
public const string AaruDictionary = "dictionary"; // number, default 33554432
public const string AaruMaxDDTSize = "max_ddt_size"; // number, default 256
public const string AaruMD5 = "md5"; // boolean, default false
public const string AaruSectorsPerBlock = "sectors_per_block"; // number, default 4096 [power of 2]
public const string AaruSHA1 = "sha1"; // boolean, default false
public const string AaruSHA256 = "sha256"; // boolean, default false
public const string AaruSpamSum = "spamsum"; // boolean, default false
// ACT Apricot Disk Image
public const string ACTApricotDiskImageCompress = "compress"; // boolean, default false
// Apple DiskCopy 4.2
public const string AppleDiskCopyMacOSX = "macosx"; // boolean, default false
// CDRDAO tocfile
public const string CDRDAOTocfileSeparate = "separate"; // boolean, default false
// CDRWin cuesheet
public const string CDRWinCuesheetSeparate = "separate"; // boolean, default false
// ISO9660 Filesystem
public const string ISO9660FSUseEvd = "use_evd"; // boolean, default false
public const string ISO9660FSUsePathTable = "use_path_table"; // boolean, default false
public const string ISO9660FSUseTransTbl = "use_trans_tbl"; // boolean, default false
// VMware disk image
public const string VMwareDiskImageAdapterType = "adapter_type"; // string, default ide [ide, lsilogic, buslogic, legacyESX]
public const string VMwareDiskImageHWVersion = "hwversion"; // number, default 4
public const string VMwareDiskImageSparse = "sparse"; // boolean, default false
public const string VMwareDiskImageSplit = "split"; // boolean, default false
}
}

View File

@@ -0,0 +1,20 @@
namespace MPF.ExecutionContexts.Aaru
{
public static class SettingConstants
{
public const string EnableDebug = "AaruEnableDebug";
public const bool EnableDebugDefault = false;
public const string EnableVerbose = "AaruEnableVerbose";
public const bool EnableVerboseDefault = true;
public const string ForceDumping = "AaruForceDumping";
public const bool ForceDumpingDefault = true;
public const string RereadCount = "AaruRereadCount";
public const int RereadCountDefault = 5;
public const string StripPersonalData = "AaruStripPersonalData";
public const bool StripPersonalDataDefault = false;
}
}

View File

@@ -2,43 +2,13 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using MPF.Core.Data;
using MPF.Core.Utilities;
using SabreTools.RedumpLib.Data;
namespace MPF.Core.Modules
namespace MPF.ExecutionContexts
{
public abstract class BaseParameters
public abstract class BaseExecutionContext
{
#region Event Handlers
#if NET20 || NET35 || NET40
/// <summary>
/// Wrapper event args class for old .NET
/// </summary>
public class StringEventArgs : EventArgs
{
public string? Value { get; set; }
}
/// <summary>
/// Geneeic way of reporting a message
/// </summary>
/// <param name="message">String value to report</param>
public EventHandler<StringEventArgs>? ReportStatus;
#else
/// <summary>
/// Geneeic way of reporting a message
/// </summary>
/// <param name="message">String value to report</param>
public EventHandler<string>? ReportStatus;
#endif
#endregion
#region Generic Dumping Information
/// <summary>
@@ -75,11 +45,6 @@ namespace MPF.Core.Modules
/// </summary>
private Process? process;
/// <summary>
/// All found volume labels and their corresponding file systems
/// </summary>
public Dictionary<string, List<string>>? VolumeLabels;
#endregion
#region Virtual Dumping Information
@@ -114,11 +79,6 @@ namespace MPF.Core.Modules
/// </summary>
public string? ExecutablePath { get; set; }
/// <summary>
/// Program that this set of parameters represents
/// </summary>
public virtual InternalProgram InternalProgram { get; }
/// <summary>
/// Currently represented system
/// </summary>
@@ -135,7 +95,7 @@ namespace MPF.Core.Modules
/// Populate a Parameters object from a param string
/// </summary>
/// <param name="parameters">String possibly representing a set of parameters</param>
public BaseParameters(string? parameters)
public BaseExecutionContext(string? parameters)
{
// If any parameters are not valid, wipe out everything
if (!ValidateAndSetParameters(parameters))
@@ -150,8 +110,8 @@ namespace MPF.Core.Modules
/// <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, string? drivePath, string filename, int? driveSpeed, Options options)
/// <param name="options">Dictionary object containing all settings that may be used for setting parameters</param>
public BaseExecutionContext(RedumpSystem? system, MediaType? type, string? drivePath, string filename, int? driveSpeed, Dictionary<string, string?> options)
{
this.System = system;
this.Type = type;
@@ -160,88 +120,36 @@ namespace MPF.Core.Modules
#region Abstract Methods
/// <summary>
/// Validate if all required output files exist
/// </summary>
/// <param name="basePath">Base filename and path to use for checking</param>
/// <param name="preCheck">True if this is a check done before a dump, false if done after</param>
/// <returns>Tuple of true if all required files exist, false otherwise and a list representing missing files</returns>
public abstract (bool, List<string>) CheckAllOutputFilesExist(string basePath, bool preCheck);
/// <summary>
/// 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, Options options, string basePath, Drive? drive, bool includeArtifacts);
#endregion
#region Virtual Methods
/// <summary>
/// Get all commands mapped to the supported flags
/// </summary>
/// <returns>Mappings from command to supported flags</returns>
public virtual Dictionary<string, List<string>>? GetCommandSupport() => null;
public abstract Dictionary<string, List<string>>? GetCommandSupport();
/// <summary>
/// Blindly generate a parameter string based on the inputs
/// </summary>
/// <returns>Parameter string for invocation, null on error</returns>
public virtual string? GenerateParameters() => null;
public abstract string? GenerateParameters();
/// <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>
public virtual string? GetDefaultExtension(MediaType? mediaType) => null;
/// <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>
public virtual List<string> GetDeleteableFilePaths(string basePath) => new();
/// <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>
public virtual List<string> GetLogFilePaths(string basePath) => new();
public abstract string? GetDefaultExtension(MediaType? mediaType);
/// <summary>
/// Get the MediaType from the current set of parameters
/// </summary>
/// <returns>MediaType value if successful, null on error</returns>
public virtual MediaType? GetMediaType() => null;
public abstract MediaType? GetMediaType();
/// <summary>
/// Gets if the current command is considered a dumping command or not
/// </summary>
/// <returns>True if it's a dumping command, false otherwise</returns>
public virtual bool IsDumpingCommand() => true;
/// <summary>
/// Gets if the flag is supported by the current command
/// </summary>
/// <param name="flag">Flag value to check</param>
/// <returns>True if the flag value is supported, false otherwise</returns>
public virtual bool IsFlagSupported(string flag)
{
if (CommandSupport == null)
return false;
if (this.BaseCommand == null)
return false;
if (!CommandSupport.TryGetValue(this.BaseCommand, out var supported))
return false;
return supported.Contains(flag);
}
public abstract bool IsDumpingCommand();
/// <summary>
/// Returns if the current Parameter object is valid
@@ -252,7 +160,7 @@ namespace MPF.Core.Modules
/// <summary>
/// Reset all special variables to have default values
/// </summary>
protected virtual void ResetValues() { }
protected abstract void ResetValues();
/// <summary>
/// Set default parameters for a given system and media type
@@ -260,15 +168,15 @@ namespace MPF.Core.Modules
/// <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(string? drivePath, string filename, int? driveSpeed, Options options) { }
/// <param name="options">Dictionary containing all settings that may be used for setting parameters</param>
protected abstract void SetDefaultParameters(string? drivePath, string filename, int? driveSpeed, Dictionary<string, string?> options);
/// <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) => !string.IsNullOrEmpty(parameters);
protected abstract bool ValidateAndSetParameters(string? parameters);
#endregion
@@ -277,18 +185,17 @@ namespace MPF.Core.Modules
/// <summary>
/// Run internal program
/// </summary>
/// <param name="separateWindow">True to show in separate window, false otherwise</param>
public void ExecuteInternalProgram(bool separateWindow)
public void ExecuteInternalProgram()
{
// Create the start info
var startInfo = new ProcessStartInfo()
{
FileName = ExecutablePath!,
Arguments = GenerateParameters() ?? "",
CreateNoWindow = !separateWindow,
UseShellExecute = separateWindow,
RedirectStandardOutput = !separateWindow,
RedirectStandardError = !separateWindow,
CreateNoWindow = false,
UseShellExecute = true,
RedirectStandardOutput = false,
RedirectStandardError = false,
};
// Create the new process
@@ -296,19 +203,6 @@ namespace MPF.Core.Modules
// Start the process
process.Start();
// Start processing tasks, if necessary
if (!separateWindow)
{
#if NET40
Logging.OutputToLog(process.StandardOutput, this, ReportStatus);
Logging.OutputToLog(process.StandardError, this, ReportStatus);
#else
_ = Logging.OutputToLog(process.StandardOutput, this, ReportStatus);
_ = Logging.OutputToLog(process.StandardError, this, ReportStatus);
#endif
}
process.WaitForExit();
process.Close();
}
@@ -329,7 +223,70 @@ namespace MPF.Core.Modules
{ }
}
#endregion
#endregion
#region Option Processing
/// <summary>
/// Get a Boolean setting from a settings, dictionary
/// </summary>
/// <param name="settings">Dictionary representing the settings</param>
/// <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>
protected static bool GetBooleanSetting(Dictionary<string, string?> settings, string key, bool defaultValue)
{
if (settings.ContainsKey(key))
{
if (bool.TryParse(settings[key], out bool value))
return value;
else
return defaultValue;
}
else
{
return defaultValue;
}
}
/// <summary>
/// Get an Int32 setting from a settings, dictionary
/// </summary>
/// <param name="settings">Dictionary representing the settings</param>
/// <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>
protected static int GetInt32Setting(Dictionary<string, string?> settings, string key, int defaultValue)
{
if (settings.ContainsKey(key))
{
if (int.TryParse(settings[key], out int value))
return value;
else
return defaultValue;
}
else
{
return defaultValue;
}
}
/// <summary>
/// Get a String setting from a settings, dictionary
/// </summary>
/// <param name="settings">Dictionary representing the settings</param>
/// <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>
protected static string? GetStringSetting(Dictionary<string, string?> settings, string key, string? defaultValue)
{
if (settings.ContainsKey(key))
return settings[key];
else
return defaultValue;
}
#endregion
#region Parameter Parsing
@@ -343,49 +300,21 @@ namespace MPF.Core.Modules
=> index < parameters.Count;
/// <summary>
/// Get the Base64 representation of a string
/// Gets if the flag is supported by the current command
/// </summary>
/// <param name="content">String content to encode</param>
/// <returns>Base64-encoded contents, if possible</returns>
protected static string? GetBase64(string? content)
/// <param name="flag">Flag value to check</param>
/// <returns>True if the flag value is supported, false otherwise</returns>
protected bool IsFlagSupported(string flag)
{
if (string.IsNullOrEmpty(content))
return null;
byte[] temp = Encoding.UTF8.GetBytes(content);
return Convert.ToBase64String(temp);
if (CommandSupport == null)
return false;
if (this.BaseCommand == null)
return false;
if (!CommandSupport.TryGetValue(this.BaseCommand, out var supported))
return false;
return supported.Contains(flag);
}
/// <summary>
/// Get the full lines from the input file, if possible
/// </summary>
/// <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>
protected static string? GetFullFile(string filename, bool binary = false)
{
// If the file doesn't exist, we can't get info from it
if (!File.Exists(filename))
return null;
// If we're reading as binary
if (binary)
{
byte[] bytes = File.ReadAllBytes(filename);
return BitConverter.ToString(bytes).Replace("-", string.Empty);
}
return File.ReadAllText(filename);
}
/// <summary>
/// Returns whether a string is a valid drive letter
/// </summary>
/// <param name="parameter">String value to check</param>
/// <returns>True if it's a valid drive letter, false otherwise</W>
protected static bool IsValidDriveLetter(string parameter)
=> Regex.IsMatch(parameter, @"^[A-Z]:?\\?$");
/// <summary>
/// Returns whether a string is a valid bool
/// </summary>
@@ -994,11 +923,9 @@ namespace MPF.Core.Modules
if (!IsFlagSupported(longFlagString))
return null;
string[] commandParts = parts[i].Split('=');
if (commandParts.Length != 2)
return null;
int loc = parts[i].IndexOf('=');
string valuePart = commandParts[1];
string valuePart = parts[i].Substring(loc + 1);
this[longFlagString] = true;
return valuePart.Trim('"');
@@ -1175,40 +1102,5 @@ namespace MPF.Core.Modules
}
#endregion
#region Methods to Move
/// <summary>
/// Get the hex contents of the PIC file
/// </summary>
/// <param name="picPath">Path to the PIC.bin file associated with the dump</param>
/// <param name="trimLength">Number of characters to trim the PIC to, if -1, ignored</param>
/// <returns>PIC data as a hex string if possible, null on error</returns>
/// <remarks>https://stackoverflow.com/questions/9932096/add-separator-to-string-at-every-n-characters</remarks>
protected static string? GetPIC(string picPath, int trimLength = -1)
{
// If the file doesn't exist, we can't get the info
if (!File.Exists(picPath))
return null;
try
{
var hex = GetFullFile(picPath, true);
if (hex == null)
return null;
if (trimLength > -1)
hex = hex.Substring(0, trimLength);
return Regex.Replace(hex, ".{32}", "$0\n", RegexOptions.Compiled);
}
catch
{
// We don't care what the error was right now
return null;
}
}
#endregion
}
}

View File

@@ -0,0 +1,35 @@
namespace MPF.ExecutionContexts.DiscImageCreator
{
/// <summary>
/// Top-level commands for DiscImageCreator
/// </summary>
public static class CommandStrings
{
public const string NONE = "";
public const string Audio = "audio";
public const string BluRay = "bd";
public const string Close = "close";
public const string CompactDisc = "cd";
public const string Data = "data";
public const string DigitalVideoDisc = "dvd";
public const string Disk = "disk";
public const string DriveSpeed = "ls";
public const string Eject = "eject";
public const string Floppy = "fd";
public const string GDROM = "gd";
public const string MDS = "mds";
public const string Merge = "merge";
public const string Reset = "reset";
public const string SACD = "sacd";
public const string Start = "start";
public const string Stop = "stop";
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";
public const string XGD3Swap = "xgd3swap";
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,38 +1,5 @@
namespace MPF.Core.Modules.DiscImageCreator
namespace MPF.ExecutionContexts.DiscImageCreator
{
/// <summary>
/// Top-level commands for DiscImageCreator
/// </summary>
public static class CommandStrings
{
public const string NONE = "";
public const string Audio = "audio";
public const string BluRay = "bd";
public const string Close = "close";
public const string CompactDisc = "cd";
public const string Data = "data";
public const string DigitalVideoDisc = "dvd";
public const string Disk = "disk";
public const string DriveSpeed = "ls";
public const string Eject = "eject";
public const string Floppy = "fd";
public const string GDROM = "gd";
public const string MDS = "mds";
public const string Merge = "merge";
public const string Reset = "reset";
public const string SACD = "sacd";
public const string Start = "start";
public const string Stop = "stop";
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";
public const string XGD3Swap = "xgd3swap";
}
/// <summary>
/// Dumping flags for DiscImageCreator
/// </summary>
@@ -69,6 +36,7 @@ namespace MPF.Core.Modules.DiscImageCreator
public const string SeventyFour = "/74";
public const string SkipSector = "/sk";
public const string SubchannelReadLevel = "/s";
public const string Tages = "/t";
public const string UseAnchorVolumeDescriptorPointer = "/avdp";
public const string VideoNow = "/vn";
public const string VideoNowColor = "/vnc";

View File

@@ -0,0 +1,26 @@
namespace MPF.ExecutionContexts.DiscImageCreator
{
public static class SettingConstants
{
public const string DVDRereadCount = "DICDVDRereadCount";
public const int DVDRereadCountDefault = 10;
public const string MultiSectorRead = "DICMultiSectorRead";
public const bool MultiSectorReadDefault = false;
public const string MultiSectorReadValue = "DICMultiSectorReadValue";
public const int MultiSectorReadValueDefault = 0;
public const string ParanoidMode = "DICParanoidMode";
public const bool ParanoidModeDefault = false;
public const string QuietMode = "DICQuietMode";
public const bool QuietModeDefault = false;
public const string RereadCount = "DICRereadCount";
public const int RereadCountDefault = 20;
public const string UseCMIFlag = "DICUseCMIFlag";
public const bool UseCMIFlagDefault = false;
}
}

View File

@@ -0,0 +1,59 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Assembly Properties -->
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<VersionPrefix>3.2.1</VersionPrefix>
<WarningsNotAsErrors>NU5104</WarningsNotAsErrors>
<!-- Package Properties -->
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
<Description>Common code for all MPF execution contexts</Description>
<Copyright>Copyright (c) Matt Nadareski 2019-2024</Copyright>
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
<RepositoryUrl>https://github.com/SabreTools/MPF</RepositoryUrl>
<RepositoryType>git</RepositoryType>
</PropertyGroup>
<ItemGroup>
<InternalsVisibleTo Include="MPF.Test" />
</ItemGroup>
<!-- Support All Frameworks -->
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net4`))">
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`))">
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`))">
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(RuntimeIdentifier.StartsWith(`osx-arm`))">
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
</PropertyGroup>
<!-- Support for old .NET versions -->
<ItemGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net40`))">
<PackageReference Include="MinAsyncBridge" Version="0.12.4" />
<PackageReference Include="MinTasksExtensionsBridge" Version="0.3.4" />
<PackageReference Include="MinThreadingBridge" Version="0.11.4" />
</ItemGroup>
<ItemGroup Condition="$(TargetFramework.StartsWith(`net4`)) AND !$(TargetFramework.StartsWith(`net40`))">
<PackageReference Include="IndexRange" Version="1.0.3" />
</ItemGroup>
<ItemGroup Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`)) AND !$(TargetFramework.StartsWith(`net452`))">
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SabreTools.RedumpLib" Version="1.4.1" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,28 @@
namespace MPF.ExecutionContexts.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 New = "new"; // Synonym for CD; Temporary command, to be removed later
public const string Rings = "rings";
public const string Dump = "dump";
public const string DumpNew = "dumpnew"; // Temporary command, to be removed later
public const string Refine = "refine";
public const string RefineNew = "refinenew"; // Temporary command, to be removed later
public const string Verify = "verify";
public const string DVDKey = "dvdkey";
public const string DVDIsoKey = "dvdisokey";
public const string Protection = "protection";
public const string Split = "split";
public const string Hash = "hash";
public const string Info = "info";
public const string Skeleton = "skeleton";
}
}

View File

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

View File

@@ -0,0 +1,27 @@
namespace MPF.ExecutionContexts.Redumper
{
/// <summary>
/// Drive read method option
/// </summary>
public enum ReadMethod
{
NONE = 0,
BE,
D8,
BE_CDDA,
}
/// <summary>
/// Drive sector order option
/// </summary>
public enum SectorOrder
{
NONE = 0,
DATA_C2_SUB,
DATA_SUB_C2,
DATA_SUB,
DATA_C2,
}
}

View File

@@ -0,0 +1,901 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using SabreTools.RedumpLib.Data;
namespace MPF.ExecutionContexts.Redumper
{
/// <summary>
/// Represents a generic set of Redumper parameters
/// </summary>
public sealed class ExecutionContext : BaseExecutionContext
{
#region Generic Dumping Information
/// <inheritdoc/>
public override string? InputPath => DriveValue;
/// <inheritdoc/>
public override string? OutputPath => Path.Combine(ImagePathValue?.Trim('"') ?? string.Empty, ImageNameValue?.Trim('"') ?? string.Empty) + GetDefaultExtension(this.Type);
/// <inheritdoc/>
public override int? Speed => SpeedValue;
#endregion
#region Flag Values
/// <summary>
/// List of all modes being run
/// </summary>
public List<string>? ModeValues { get; set; }
#region General
/// <summary>
/// Drive to use, first available drive with disc, if not provided
/// </summary>
public string? DriveValue { get; set; }
/// <summary>
/// Drive read speed, optimal drive speed will be used if not provided
/// </summary>
public int? SpeedValue { get; set; }
/// <summary>
/// Number of sector retries in case of SCSI/C2 error (default: 0)
/// </summary>
public int? RetriesValue { get; set; }
/// <summary>
/// Dump files base directory
/// </summary>
public string? ImagePathValue { get; set; }
/// <summary>
/// Dump files prefix, autogenerated in dump mode, if not provided
/// </summary>
public string? ImageNameValue { get; set; }
#endregion
#region Drive Configuration
/// <summary>
/// Override drive type, possible values: GENERIC, PLEXTOR, LG_ASUS
/// </summary>
public string? DriveTypeValue { get; set; }
/// <summary>
/// Override drive read offset
/// </summary>
public int? DriveReadOffsetValue { get; set; }
/// <summary>
/// Override drive C2 shift
/// </summary>
public int? DriveC2ShiftValue { get; set; }
/// <summary>
/// Override drive pre-gap start LBA
/// </summary>
public int? DrivePregapStartValue { get; set; }
/// <summary>
/// Override drive read method, possible values: BE, D8, BE_CDDA
/// </summary>
public string? DriveReadMethodValue { get; set; }
/// <summary>
/// Override drive sector order, possible values: DATA_C2_SUB, DATA_SUB_C2
/// </summary>
public string? DriveSectorOrderValue { get; set; }
#endregion
#region Offset
/// <summary>
/// Override offset autodetection and use supplied value
/// </summary>
public int? ForceOffsetValue { get; set; }
/// <summary>
/// Maximum absolute sample value to treat it as silence (default: 32)
/// </summary>
public int? AudioSilenceThresholdValue { get; set; }
#endregion
#region Split
/// <summary>
/// Fill byte value for skipped sectors (default: 0x55)
/// </summary>
public byte? SkipFillValue { get; set; }
#endregion
#region Miscellaneous
/// <summary>
/// LBA to start dumping from
/// </summary>
public int? LBAStartValue { get; set; }
/// <summary>
/// LBA to stop dumping at (everything before the value), useful for discs with fake TOC
/// </summary>
public int? LBAEndValue { get; set; }
/// <summary>
/// LBA ranges of sectors to skip
/// </summary>
public string? SkipValue { get; set; }
/// <summary>
/// Write offset for dumps when reading as data
/// </summary>
public int? DumpWriteOffsetValue { get; set; }
/// <summary>
/// Number of sectors to read at once on initial dump, DVD only (Default 32)
/// </summary>
public int? DumpReadSizeValue { get; set; }
/// <summary>
/// Maximum number of lead-in retries per session (Default 4)
/// </summary>
public int? PlextorLeadinRetriesValue { get; set; }
#endregion
#endregion
/// <inheritdoc/>
public ExecutionContext(string? parameters) : base(parameters) { }
/// <inheritdoc/>
public ExecutionContext(RedumpSystem? system, MediaType? type, string? drivePath, string filename, int? driveSpeed, Dictionary<string, string?> options)
: base(system, type, drivePath, filename, driveSpeed, options)
{
}
#region BaseExecutionContext Implementations
/// <inheritdoc/>
/// <remarks>Command support is irrelevant for redumper</remarks>
public override Dictionary<string, List<string>> GetCommandSupport()
{
return new Dictionary<string, List<string>>()
{
[CommandStrings.NONE] =
[
// General
FlagStrings.HelpLong,
FlagStrings.HelpShort,
FlagStrings.Version,
FlagStrings.Verbose,
FlagStrings.Debug,
FlagStrings.Drive,
FlagStrings.Speed,
FlagStrings.Retries,
FlagStrings.ImagePath,
FlagStrings.ImageName,
FlagStrings.Overwrite,
// Drive Configuration
FlagStrings.DriveType,
FlagStrings.DriveReadOffset,
FlagStrings.DriveC2Shift,
FlagStrings.DrivePregapStart,
FlagStrings.DriveReadMethod,
FlagStrings.DriveSectorOrder,
// Drive Specific
FlagStrings.PlextorSkipLeadin,
FlagStrings.PlextorLeadinRetries,
FlagStrings.AsusSkipLeadout,
// Offset
FlagStrings.ForceOffset,
FlagStrings.AudioSilenceThreshold,
FlagStrings.CorrectOffsetShift,
FlagStrings.OffsetShiftRelocate,
// Split
FlagStrings.ForceSplit,
FlagStrings.LeaveUnchanged,
FlagStrings.ForceQTOC,
FlagStrings.SkipFill,
FlagStrings.ISO9660Trim,
// Miscellaneous
FlagStrings.LBAStart,
FlagStrings.LBAEnd,
FlagStrings.RefineSubchannel,
FlagStrings.Skip,
FlagStrings.DumpWriteOffset,
FlagStrings.DumpReadSize,
FlagStrings.OverreadLeadout,
FlagStrings.ForceUnscrambled,
FlagStrings.LegacySubs,
FlagStrings.DisableCDText,
],
};
}
/// <inheritdoc/>
/// <remarks>
/// Redumper is unique in that the base command can be multiple
/// modes all listed together. It is also unique in that "all
/// flags are supported for everything" and it filters out internally
/// </remarks>
public override string GenerateParameters()
{
var parameters = new List<string>();
ModeValues ??= [CommandStrings.NONE];
// Modes
parameters.AddRange(ModeValues);
#region General
// Help
if (this[FlagStrings.HelpLong] == true)
parameters.Add(FlagStrings.HelpLong);
// Version
if (this[FlagStrings.Version] == true)
parameters.Add(FlagStrings.Version);
// Verbose
if (this[FlagStrings.Verbose] == true)
parameters.Add(FlagStrings.Verbose);
// Debug
if (this[FlagStrings.Debug] == true)
parameters.Add(FlagStrings.Debug);
// Drive
if (this[FlagStrings.Drive] == true)
{
if (DriveValue != null)
parameters.Add($"{FlagStrings.Drive}={DriveValue}");
}
// Speed
if (this[FlagStrings.Speed] == true)
{
if (SpeedValue != null)
parameters.Add($"{FlagStrings.Speed}={SpeedValue}");
}
// Retries
if (this[FlagStrings.Retries] == true)
{
if (RetriesValue != null)
parameters.Add($"{FlagStrings.Retries}={RetriesValue}");
}
// Image Path
if (this[FlagStrings.ImagePath] == true)
{
if (ImagePathValue != null)
parameters.Add($"{FlagStrings.ImagePath}={ImagePathValue}");
}
// Image Name
if (this[FlagStrings.ImageName] == true)
{
if (ImageNameValue != null)
parameters.Add($"{FlagStrings.ImageName}={ImageNameValue}");
}
// Overwrite
if (this[FlagStrings.Overwrite] == true)
parameters.Add(FlagStrings.Overwrite);
#endregion
#region Drive Configuration
// Drive Type
if (this[FlagStrings.DriveType] == true)
{
if (DriveTypeValue != null)
parameters.Add($"{FlagStrings.DriveType}={DriveTypeValue}");
}
// Drive Read Offset
if (this[FlagStrings.DriveReadOffset] == true)
{
if (DriveReadOffsetValue != null)
parameters.Add($"{FlagStrings.DriveReadOffset}={DriveReadOffsetValue}");
}
// Drive C2 Shift
if (this[FlagStrings.DriveC2Shift] == true)
{
if (DriveC2ShiftValue != null)
parameters.Add($"{FlagStrings.DriveC2Shift}={DriveC2ShiftValue}");
}
// Drive Pregap Start
if (this[FlagStrings.DrivePregapStart] == true)
{
if (DrivePregapStartValue != null)
parameters.Add($"{FlagStrings.DrivePregapStart}={DrivePregapStartValue}");
}
// Drive Read Method
if (this[FlagStrings.DriveReadMethod] == true)
{
if (DriveReadMethodValue != null)
parameters.Add($"{FlagStrings.DriveReadMethod}={DriveReadMethodValue}");
}
// Drive Sector Order
if (this[FlagStrings.DriveSectorOrder] == true)
{
if (DriveSectorOrderValue != null)
parameters.Add($"{FlagStrings.DriveSectorOrder}={DriveSectorOrderValue}");
}
#endregion
#region Drive Specific
// Plextor Leadin Skip
if (this[FlagStrings.PlextorSkipLeadin] == true)
parameters.Add(FlagStrings.PlextorSkipLeadin);
// Plextor Leadin Retries
if (this[FlagStrings.PlextorLeadinRetries] == true)
{
if (PlextorLeadinRetriesValue != null)
parameters.Add($"{FlagStrings.PlextorLeadinRetries}={PlextorLeadinRetriesValue}");
}
// Asus Skip Leadout
if (this[FlagStrings.AsusSkipLeadout] == true)
parameters.Add(FlagStrings.AsusSkipLeadout);
#endregion
#region Offset
// Force Offset
if (this[FlagStrings.ForceOffset] == true)
{
if (ForceOffsetValue != null)
parameters.Add($"{FlagStrings.ForceOffset}={ForceOffsetValue}");
}
// Audio Silence Threshold
if (this[FlagStrings.AudioSilenceThreshold] == true)
{
if (AudioSilenceThresholdValue != null)
parameters.Add($"{FlagStrings.AudioSilenceThreshold}={AudioSilenceThresholdValue}");
}
// Correct Offset Shift
if (this[FlagStrings.CorrectOffsetShift] == true)
parameters.Add(FlagStrings.CorrectOffsetShift);
// Offset Shift Relocate
if (this[FlagStrings.OffsetShiftRelocate] == true)
parameters.Add(FlagStrings.OffsetShiftRelocate);
#endregion
#region Split
// Force Split
if (this[FlagStrings.ForceSplit] == true)
parameters.Add(FlagStrings.ForceSplit);
// Leave Unchanged
if (this[FlagStrings.LeaveUnchanged] == true)
parameters.Add(FlagStrings.LeaveUnchanged);
// Force QTOC
if (this[FlagStrings.ForceQTOC] == true)
parameters.Add(FlagStrings.ForceQTOC);
// Skip Fill
if (this[FlagStrings.SkipFill] == true)
{
if (SkipFillValue != null)
parameters.Add($"{FlagStrings.SkipFill}={SkipFillValue:x}");
}
// ISO9660 Trim
if (this[FlagStrings.ISO9660Trim] == true)
parameters.Add(FlagStrings.ISO9660Trim);
#endregion
#region Miscellaneous
// LBA Start
if (this[FlagStrings.LBAStart] == true)
{
if (LBAStartValue != null)
parameters.Add($"{FlagStrings.LBAStart}={LBAStartValue}");
}
// LBA End
if (this[FlagStrings.LBAEnd] == true)
{
if (LBAEndValue != null)
parameters.Add($"{FlagStrings.LBAEnd}={LBAEndValue}");
}
// Refine Subchannel
if (this[FlagStrings.RefineSubchannel] == true)
parameters.Add(FlagStrings.RefineSubchannel);
// Skip
if (this[FlagStrings.Skip] == true)
{
if (!string.IsNullOrEmpty(SkipValue))
parameters.Add($"{FlagStrings.Skip}={SkipValue}");
}
// Dump Write Offset
if (this[FlagStrings.DumpWriteOffset] == true)
{
if (DumpWriteOffsetValue != null)
parameters.Add($"{FlagStrings.DumpWriteOffset}={DumpWriteOffsetValue}");
}
// Dump Read Size
if (this[FlagStrings.DumpReadSize] == true)
{
if (DumpReadSizeValue != null && DumpReadSizeValue > 0)
parameters.Add($"{FlagStrings.DumpReadSize}={DumpReadSizeValue}");
}
// Overread Leadout
if (this[FlagStrings.OverreadLeadout] == true)
parameters.Add(FlagStrings.OverreadLeadout);
// Force Unscrambled
if (this[FlagStrings.ForceUnscrambled] == true)
parameters.Add(FlagStrings.ForceUnscrambled);
// Legacy Subs
if (this[FlagStrings.LegacySubs] == true)
parameters.Add(FlagStrings.LegacySubs);
// Disable CD Text
if (this[FlagStrings.DisableCDText] == true)
parameters.Add(FlagStrings.DisableCDText);
#endregion
return string.Join(" ", [.. parameters]);
}
/// <inheritdoc/>
public override string? GetDefaultExtension(MediaType? mediaType) => Converters.Extension(mediaType);
/// <inheritdoc/>
public override MediaType? GetMediaType() => null;
/// <inheritdoc/>
public override bool IsDumpingCommand()
{
return this.BaseCommand == CommandStrings.NONE
|| this.BaseCommand?.Contains(CommandStrings.CD) == true
|| this.BaseCommand?.Contains(CommandStrings.DVD) == true
|| this.BaseCommand?.Contains(CommandStrings.BluRay) == true
|| this.BaseCommand?.Contains(CommandStrings.SACD) == true
|| this.BaseCommand?.Contains(CommandStrings.New) == true
|| this.BaseCommand?.Contains(CommandStrings.Dump) == true
|| this.BaseCommand?.Contains(CommandStrings.DumpNew) == true;
}
/// <inheritdoc/>
protected override void ResetValues()
{
BaseCommand = CommandStrings.NONE;
flags = [];
// General
DriveValue = null;
SpeedValue = null;
RetriesValue = null;
ImagePathValue = null;
ImageNameValue = null;
// Drive Configuration
DriveTypeValue = null;
DriveReadOffsetValue = null;
DriveC2ShiftValue = null;
DrivePregapStartValue = null;
DriveReadMethodValue = null;
DriveSectorOrderValue = null;
// Offset
ForceOffsetValue = null;
AudioSilenceThresholdValue = null;
// Split
SkipFillValue = null;
// Miscellaneous
LBAStartValue = null;
LBAEndValue = null;
SkipValue = null;
DumpReadSizeValue = null;
}
/// <inheritdoc/>
protected override void SetDefaultParameters(string? drivePath, string filename, int? driveSpeed, Dictionary<string, string?> options)
{
// If we don't have a CD, DVD, HD-DVD, or BD, we can't dump using redumper
if (this.Type != MediaType.CDROM
&& this.Type != MediaType.DVD
&& this.Type != MediaType.HDDVD
&& this.Type != MediaType.BluRay)
{
return;
}
BaseCommand = CommandStrings.NONE;
switch (this.Type)
{
case MediaType.CDROM:
ModeValues = this.System switch
{
RedumpSystem.SuperAudioCD => [CommandStrings.SACD],
_ => [CommandStrings.CD],
};
break;
case MediaType.DVD:
ModeValues = [CommandStrings.DVD];
break;
case MediaType.HDDVD: // TODO: Keep in sync if another command string shows up
ModeValues = [CommandStrings.DVD];
break;
case MediaType.BluRay:
ModeValues = [CommandStrings.BluRay];
break;
default:
BaseCommand = null;
return;
}
this[FlagStrings.Drive] = true;
DriveValue = drivePath;
this[FlagStrings.Speed] = true;
SpeedValue = driveSpeed;
// Set user-defined options
if (GetBooleanSetting(options, SettingConstants.EnableVerbose, SettingConstants.EnableVerboseDefault))
this[FlagStrings.Verbose] = true;
if (GetBooleanSetting(options, SettingConstants.EnableDebug, SettingConstants.EnableDebugDefault))
this[FlagStrings.Debug] = true;
string? readMethod = GetStringSetting(options, SettingConstants.ReadMethod, SettingConstants.ReadMethodDefault);
if (!string.IsNullOrEmpty(readMethod) && readMethod != ReadMethod.NONE.ToString())
{
this[FlagStrings.DriveReadMethod] = true;
DriveReadMethodValue = readMethod;
}
string? sectorOrder = GetStringSetting(options, SettingConstants.SectorOrder, SettingConstants.SectorOrderDefault);
if (!string.IsNullOrEmpty(sectorOrder) && sectorOrder != SectorOrder.NONE.ToString())
{
this[FlagStrings.DriveSectorOrder] = true;
DriveSectorOrderValue = sectorOrder;
}
if (GetBooleanSetting(options, SettingConstants.UseGenericDriveType, SettingConstants.UseGenericDriveTypeDefault))
{
this[FlagStrings.DriveType] = true;
DriveTypeValue = "GENERIC";
}
// Set the output paths
if (!string.IsNullOrEmpty(filename))
{
var imagePath = Path.GetDirectoryName(filename);
if (!string.IsNullOrEmpty(imagePath))
{
this[FlagStrings.ImagePath] = true;
ImagePathValue = $"\"{imagePath}\"";
}
string imageName = Path.GetFileNameWithoutExtension(filename);
if (!string.IsNullOrEmpty(imageName))
{
this[FlagStrings.ImageName] = true;
ImageNameValue = $"\"{imageName}\"";
}
}
this[FlagStrings.Retries] = true;
RetriesValue = GetInt32Setting(options, SettingConstants.RereadCount, SettingConstants.RereadCountDefault);
if (GetBooleanSetting(options, SettingConstants.EnableLeadinRetry, SettingConstants.EnableLeadinRetryDefault))
{
this[FlagStrings.PlextorLeadinRetries] = true;
PlextorLeadinRetriesValue = GetInt32Setting(options, SettingConstants.LeadinRetryCount, SettingConstants.LeadinRetryCountDefault);
}
}
/// <inheritdoc/>
protected override bool ValidateAndSetParameters(string? parameters)
{
BaseCommand = CommandStrings.NONE;
// The string has to be valid by itself first
if (string.IsNullOrEmpty(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, @"([a-zA-Z\-]*=)?[\""].+?[\""]|[^ ]+", RegexOptions.Compiled)
.Cast<Match>()
.Select(m => m.Value)
.ToList();
// Setup the modes
ModeValues = [];
// All modes should be cached separately
int index = 0;
for (; index < parts.Count; index++)
{
// Flag to see if we have a flag
bool isFlag = false;
string part = parts[index];
switch (part)
{
case CommandStrings.CD:
case CommandStrings.DVD:
case CommandStrings.BluRay:
case CommandStrings.SACD:
case CommandStrings.New: // Temporary command, to be removed later
case CommandStrings.Rings:
case CommandStrings.Dump:
case CommandStrings.DumpNew: // Temporary command, to be removed later
case CommandStrings.Refine:
case CommandStrings.RefineNew: // Temporary command, to be removed later
case CommandStrings.Verify:
case CommandStrings.DVDKey:
case CommandStrings.DVDIsoKey:
case CommandStrings.Protection:
case CommandStrings.Split:
case CommandStrings.Hash:
case CommandStrings.Info:
case CommandStrings.Skeleton:
ModeValues.Add(part);
break;
// Default is either a flag or an invalid mode
default:
if (part.StartsWith("-"))
{
isFlag = true;
break;
}
else
{
return false;
}
}
// If we had a flag, break out
if (isFlag)
break;
}
// Loop through all auxiliary flags, if necessary
for (int i = index; i < parts.Count; i++)
{
// Flag read-out values
byte? byteValue = null;
int? intValue = null;
string? stringValue = null;
#region General
// Help
ProcessFlagParameter(parts, FlagStrings.HelpShort, FlagStrings.HelpLong, ref i);
// Version
ProcessFlagParameter(parts, FlagStrings.Version, ref i);
// Verbose
ProcessFlagParameter(parts, FlagStrings.Verbose, ref i);
// Debug
ProcessFlagParameter(parts, FlagStrings.Debug, ref i);
// Drive
stringValue = ProcessStringParameter(parts, FlagStrings.Drive, ref i);
if (!string.IsNullOrEmpty(stringValue))
DriveValue = stringValue;
// Speed
intValue = ProcessInt32Parameter(parts, FlagStrings.Speed, ref i);
if (intValue != null && intValue != Int32.MinValue)
SpeedValue = intValue;
// Retries
intValue = ProcessInt32Parameter(parts, FlagStrings.Retries, ref i);
if (intValue != null && intValue != Int32.MinValue)
RetriesValue = intValue;
// Image Path
stringValue = ProcessStringParameter(parts, FlagStrings.ImagePath, ref i);
if (!string.IsNullOrEmpty(stringValue))
ImagePathValue = $"\"{stringValue!.Trim('"')}\"";
// Image Name
stringValue = ProcessStringParameter(parts, FlagStrings.ImageName, ref i);
if (!string.IsNullOrEmpty(stringValue))
ImageNameValue = $"\"{stringValue!.Trim('"')}\"";
// Overwrite
ProcessFlagParameter(parts, FlagStrings.Overwrite, ref i);
#endregion
#region Drive Configuration
// Drive Type
stringValue = ProcessStringParameter(parts, FlagStrings.DriveType, ref i);
if (!string.IsNullOrEmpty(stringValue))
DriveTypeValue = stringValue;
// Drive Read Offset
intValue = ProcessInt32Parameter(parts, FlagStrings.DriveReadOffset, ref i);
if (intValue != null && intValue != Int32.MinValue)
DriveReadOffsetValue = intValue;
// Drive C2 Shift
intValue = ProcessInt32Parameter(parts, FlagStrings.DriveC2Shift, ref i);
if (intValue != null && intValue != Int32.MinValue)
DriveC2ShiftValue = intValue;
// Drive Pregap Start
intValue = ProcessInt32Parameter(parts, FlagStrings.DrivePregapStart, ref i);
if (intValue != null && intValue != Int32.MinValue)
DrivePregapStartValue = intValue;
// Drive Read Method
stringValue = ProcessStringParameter(parts, FlagStrings.DriveReadMethod, ref i);
if (!string.IsNullOrEmpty(stringValue))
DriveReadMethodValue = stringValue;
// Drive Sector Order
stringValue = ProcessStringParameter(parts, FlagStrings.DriveSectorOrder, ref i);
if (!string.IsNullOrEmpty(stringValue))
DriveSectorOrderValue = stringValue;
#endregion
#region Drive Specific
// Plextor Skip Leadin
ProcessFlagParameter(parts, FlagStrings.PlextorSkipLeadin, ref i);
// Plextor Leadin Retries
intValue = ProcessInt32Parameter(parts, FlagStrings.PlextorLeadinRetries, ref i);
if (intValue != null && intValue != Int32.MinValue)
PlextorLeadinRetriesValue = intValue;
// Asus Skip Leadout
ProcessFlagParameter(parts, FlagStrings.AsusSkipLeadout, ref i);
#endregion
#region Offset
// Force Offset
intValue = ProcessInt32Parameter(parts, FlagStrings.ForceOffset, ref i);
if (intValue != null && intValue != Int32.MinValue)
ForceOffsetValue = intValue;
// Audio Silence Threshold
intValue = ProcessInt32Parameter(parts, FlagStrings.AudioSilenceThreshold, ref i);
if (intValue != null && intValue != Int32.MinValue)
AudioSilenceThresholdValue = intValue;
// Correct Offset Shift
ProcessFlagParameter(parts, FlagStrings.CorrectOffsetShift, ref i);
// Correct Shift Relocate
ProcessFlagParameter(parts, FlagStrings.OffsetShiftRelocate, ref i);
#endregion
#region Split
// Force Split
ProcessFlagParameter(parts, FlagStrings.ForceSplit, ref i);
// Leave Unchanged
ProcessFlagParameter(parts, FlagStrings.LeaveUnchanged, ref i);
// Force QTOC
ProcessFlagParameter(parts, FlagStrings.ForceQTOC, ref i);
// Skip Fill
byteValue = ProcessUInt8Parameter(parts, FlagStrings.SkipFill, ref i);
if (byteValue != null && byteValue != Byte.MinValue)
SkipFillValue = byteValue;
// ISO9660 Trim
ProcessFlagParameter(parts, FlagStrings.ISO9660Trim, ref i);
#endregion
#region Miscellaneous
// LBA Start
intValue = ProcessInt32Parameter(parts, FlagStrings.LBAStart, ref i);
if (intValue != null && intValue != Int32.MinValue)
LBAStartValue = intValue;
// LBA End
intValue = ProcessInt32Parameter(parts, FlagStrings.LBAEnd, ref i);
if (intValue != null && intValue != Int32.MinValue)
LBAEndValue = intValue;
// Refine Subchannel
ProcessFlagParameter(parts, FlagStrings.RefineSubchannel, ref i);
// Skip
stringValue = ProcessStringParameter(parts, FlagStrings.Skip, ref i);
if (!string.IsNullOrEmpty(stringValue))
SkipValue = stringValue;
// Dump Write Offset
intValue = ProcessInt32Parameter(parts, FlagStrings.DumpWriteOffset, ref i);
if (intValue != null && intValue != Int32.MinValue)
DumpWriteOffsetValue = intValue;
// Dump Read Size
intValue = ProcessInt32Parameter(parts, FlagStrings.DumpReadSize, ref i);
if (intValue != null && intValue != Int32.MinValue)
DumpReadSizeValue = intValue;
// Overread Leadout
ProcessFlagParameter(parts, FlagStrings.OverreadLeadout, ref i);
// Force Unscrambled
ProcessFlagParameter(parts, FlagStrings.ForceUnscrambled, ref i);
// Legacy Subs
ProcessFlagParameter(parts, FlagStrings.LegacySubs, ref i);
// Disable CD Text
ProcessFlagParameter(parts, FlagStrings.DisableCDText, ref i);
#endregion
}
// If the image name was not set, set it with a default value
if (string.IsNullOrEmpty(this.ImageNameValue))
this.ImageNameValue = "track";
return true;
}
#endregion
}
}

View File

@@ -1,30 +1,5 @@
namespace MPF.Core.Modules.Redumper
namespace MPF.ExecutionContexts.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 Rings = "rings";
public const string Dump = "dump";
public const string DumpNew = "dumpnew"; // Temporary command, to be removed later
public const string Refine = "refine";
public const string RefineNew = "refinenew"; // Temporary command, to be removed later
public const string Verify = "verify";
public const string DVDKey = "dvdkey";
public const string DVDIsoKey = "dvdisokey";
public const string Protection = "protection";
public const string Split = "split";
public const string Hash = "hash";
public const string Info = "info";
public const string Skeleton = "skeleton";
}
/// <summary>
/// Dumping flags for Redumper
/// </summary>
@@ -77,6 +52,7 @@ namespace MPF.Core.Modules.Redumper
public const string DumpWriteOffset = "--dump-write-offset";
public const string DumpReadSize = "--dump-read-size";
public const string OverreadLeadout = "--overread-leadout";
public const string ForceUnscrambled = "--force-unscrambled";
public const string LegacySubs = "--legacy-subs";
public const string DisableCDText = "--disable-cdtext";
}

View File

@@ -0,0 +1,29 @@
namespace MPF.ExecutionContexts.Redumper
{
public static class SettingConstants
{
public const string EnableDebug = "RedumperEnableDebug";
public const bool EnableDebugDefault = false;
public const string EnableLeadinRetry = "RedumperEnableLeadinRetry";
public const bool EnableLeadinRetryDefault = false;
public const string EnableVerbose = "RedumperEnableVerbose";
public const bool EnableVerboseDefault = true;
public const string LeadinRetryCount = "RedumperLeadinRetryCount";
public const int LeadinRetryCountDefault = 4;
public const string ReadMethod = "RedumperReadMethod";
public static readonly string ReadMethodDefault = Redumper.ReadMethod.NONE.ToString();
public const string RereadCount = "RedumperRereadCount";
public const int RereadCountDefault = 20;
public const string SectorOrder = "RedumperSectorOrder";
public static readonly string SectorOrderDefault = Redumper.SectorOrder.NONE.ToString();
public const string UseGenericDriveType = "RedumperUseGenericDriveType";
public const bool UseGenericDriveTypeDefault = false;
}
}

View File

@@ -1,9 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using MPF.Core.Converters;
namespace MPF.Core.UI.ComboBoxItems
namespace MPF.Frontend.ComboBoxItems
{
/// <summary>
/// A generic combo box element
@@ -22,7 +21,7 @@ namespace MPF.Core.UI.ComboBoxItems
public static implicit operator T? (Element<T> item) => item?.Data;
/// <inheritdoc/>
public string Name => EnumConverter.GetLongName(Data);
public string Name => EnumExtensions.GetLongName(Data);
public override string ToString() => Name;

View File

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

View File

@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using SabreTools.RedumpLib.Data;
namespace MPF.Core.UI.ComboBoxItems
namespace MPF.Frontend.ComboBoxItems
{
/// <summary>
/// Represents a single item in the System combo box

780
MPF.Frontend/Drive.cs Normal file
View File

@@ -0,0 +1,780 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
#if NET462_OR_GREATER || NETCOREAPP
using Microsoft.Management.Infrastructure;
using Microsoft.Management.Infrastructure.Generic;
#endif
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using MPF.Processors;
using SabreTools.IO;
using SabreTools.RedumpLib.Data;
namespace MPF.Frontend
{
/// <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>
public InternalDriveType? InternalDriveType { get; set; }
/// <summary>
/// Drive partition format
/// </summary>
public string? DriveFormat { get; private set; } = null;
/// <summary>
/// Windows drive path
/// </summary>
public string? Name { get; private set; } = null;
/// <summary>
/// Represents if Windows has marked the drive as active
/// </summary>
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; private set; } = null;
#endregion
#region Derived Fields
/// <summary>
/// Read-only access to the drive letter
/// </summary>
/// <remarks>Should only be used in UI applications</remarks>
public char? Letter => Name?[0] ?? '\0';
#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>
public static Drive? Create(InternalDriveType? driveType, string devicePath)
{
// Create a new, empty drive object
var drive = new Drive()
{
InternalDriveType = driveType,
};
// If we have an invalid device path, return null
if (string.IsNullOrEmpty(devicePath))
return null;
// Sanitize a Windows-formatted long device path
if (devicePath.StartsWith("\\\\.\\"))
devicePath = devicePath.Substring("\\\\.\\".Length);
// 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>
private void PopulateFromDriveInfo(DriveInfo? driveInfo)
{
// If we have an invalid DriveInfo, just return
if (driveInfo == null || driveInfo == default)
return;
// Populate the data fields
Name = driveInfo.Name;
MarkedActive = driveInfo.IsReady;
if (MarkedActive)
{
DriveFormat = driveInfo.DriveFormat;
TotalSize = driveInfo.TotalSize;
VolumeLabel = driveInfo.VolumeLabel;
}
else
{
DriveFormat = string.Empty;
TotalSize = default;
VolumeLabel = string.Empty;
}
}
#region Public Functionality
/// <summary>
/// Create a list of active drives matched to their volume labels
/// </summary>
/// <param name="ignoreFixedDrives">True to ignore fixed drives from population, false otherwise</param>
/// <returns>Active drives, matched to labels, if possible</returns>
public static List<Drive> CreateListOfDrives(bool ignoreFixedDrives)
{
var drives = GetDriveList(ignoreFixedDrives);
drives = [.. drives.OrderBy(i => i == null ? "\0" : i.Name)];
return drives;
}
/// <summary>
/// Get the current media type from drive letter
/// </summary>
/// <param name="system"></param>
/// <returns></returns>
public (MediaType?, string?) GetMediaType(RedumpSystem? system)
{
// Take care of the non-optical stuff first
switch (InternalDriveType)
{
case Frontend.InternalDriveType.Floppy:
return (MediaType.FloppyDisk, null);
case Frontend.InternalDriveType.HardDisk:
return (MediaType.HardDisk, null);
case Frontend.InternalDriveType.Removable:
return (MediaType.FlashDrive, null);
}
// 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 (TotalSize >= 0 && TotalSize <= 800_000_000 && (DriveFormat == "CDFS" || DriveFormat == "UDF"))
return (MediaType.CDROM, null);
else if (TotalSize > 800_000_000 && TotalSize <= 8_540_000_000 && (DriveFormat == "CDFS" || DriveFormat == "UDF"))
return (MediaType.DVD, null);
else if (TotalSize > 8_540_000_000)
return (MediaType.BluRay, null);
return (null, "Could not determine media type!");
}
/// <summary>
/// Refresh the current drive information based on path
/// </summary>
public void RefreshDrive()
{
var driveInfo = DriveInfo.GetDrives().FirstOrDefault(d => d?.Name == Name);
PopulateFromDriveInfo(driveInfo);
}
#endregion
#region Information Extraction
/// <summary>
/// Get the EXE name from a PlayStation disc, if possible
/// </summary>
/// <returns>Executable name on success, null otherwise</returns>
public string? GetPlayStationExecutableName()
{
// If there's no drive path, we can't get exe name
if (string.IsNullOrEmpty(Name))
return null;
// If the folder no longer exists, we can't get exe name
if (!Directory.Exists(Name))
return null;
// Get the two paths that we will need to check
string psxExePath = Path.Combine(Name, "PSX.EXE");
string systemCnfPath = Path.Combine(Name, "SYSTEM.CNF");
// 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.?:\\?(.*)", RegexOptions.Compiled);
if (match.Groups.Count > 1)
{
string? serial = match.Groups[1].Value;
// Some games may have the EXE in a subfolder
serial = Path.GetFileName(serial);
return serial;
}
}
// If the SYSTEM.CNF value can't be found, try PSX.EXE
if (File.Exists(psxExePath))
return "PSX.EXE";
// If neither can be found, we return null
return null;
}
/// <summary>
/// Get the EXE date from a PlayStation disc, if possible
/// </summary>
/// <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>True if information could be determined, false otherwise</returns>
public bool GetPlayStationExecutableInfo(out string? serial, out Region? region, out string? date)
{
serial = null; region = null; date = null;
// If there's no drive path, we can't do this part
if (string.IsNullOrEmpty(Name))
return false;
// If the folder no longer exists, we can't do this part
if (!Directory.Exists(Name))
return false;
// Get the executable name
string? exeName = GetPlayStationExecutableName();
// If no executable found, we can't do this part
if (exeName == null)
return false;
// EXE name may have a trailing `;` after
// EXE name should always be in all caps
exeName = exeName
.Split(';')[0]
.ToUpperInvariant();
// Serial is most of the EXE name normalized
serial = exeName
.Replace('_', '-')
.Replace(".", string.Empty);
// Get the region, if possible
region = ProcessingTool.GetPlayStationRegion(exeName);
// Now that we have the EXE name, try to get the fileinfo for it
string exePath = Path.Combine(Name, exeName);
if (!File.Exists(exePath))
return false;
// Fix the Y2K timestamp issue
var fi = new FileInfo(exePath);
var 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>
/// <returns>Game version if possible, null on error</returns>
public string? GetPlayStation2Version()
{
// If there's no drive path, we can't do this part
if (string.IsNullOrEmpty(Name))
return null;
// If the folder no longer exists, we can't do this part
if (!Directory.Exists(Name))
return null;
// Get the SYSTEM.CNF path to check
string systemCnfPath = Path.Combine(Name, "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 internal serial from a PlayStation 3 disc, if possible
/// </summary>
/// <returns>Internal disc serial if possible, null on error</returns>
public string? GetPlayStation3Serial()
{
// If there's no drive path, we can't do this part
if (string.IsNullOrEmpty(Name))
return null;
// If the folder no longer exists, we can't do this part
if (!Directory.Exists(Name))
return null;
// Attempt to use PS3_DISC.SFB
string sfbPath = Path.Combine(Name, "PS3_DISC.SFB");
if (File.Exists(sfbPath))
{
try
{
using var br = new BinaryReader(File.OpenRead(sfbPath));
br.BaseStream.Seek(0x220, SeekOrigin.Begin);
return new string(br.ReadChars(0x10)).TrimEnd('\0');
}
catch
{
// We don't care what the error was
return null;
}
}
// Attempt to use PARAM.SFO
#if NET20 || NET35
string sfoPath = Path.Combine(Path.Combine(Name, "PS3_GAME"), "PARAM.SFO");
#else
string sfoPath = Path.Combine(Name, "PS3_GAME", "PARAM.SFO");
#endif
if (File.Exists(sfoPath))
{
try
{
using var br = new BinaryReader(File.OpenRead(sfoPath));
br.BaseStream.Seek(-0x18, SeekOrigin.End);
return new string(br.ReadChars(9)).TrimEnd('\0').Insert(4, "-");
}
catch
{
// We don't care what the error was
return null;
}
}
return null;
}
/// <summary>
/// Get the version from a PlayStation 3 disc, if possible
/// </summary>
/// <returns>Game version if possible, null on error</returns>
public string? GetPlayStation3Version()
{
// If there's no drive path, we can't do this part
if (string.IsNullOrEmpty(Name))
return null;
// If the folder no longer exists, we can't do this part
if (!Directory.Exists(Name))
return null;
// Attempt to use PS3_DISC.SFB
string sfbPath = Path.Combine(Name, "PS3_DISC.SFB");
if (File.Exists(sfbPath))
{
try
{
using var br = new BinaryReader(File.OpenRead(sfbPath));
br.BaseStream.Seek(0x230, SeekOrigin.Begin);
var discVersion = new string(br.ReadChars(0x10)).TrimEnd('\0');
if (!string.IsNullOrEmpty(discVersion))
return discVersion;
}
catch
{
// We don't care what the error was
return null;
}
}
// Attempt to use PARAM.SFO
#if NET20 || NET35
string sfoPath = Path.Combine(Path.Combine(Name, "PS3_GAME"), "PARAM.SFO");
#else
string sfoPath = Path.Combine(Name, "PS3_GAME", "PARAM.SFO");
#endif
if (File.Exists(sfoPath))
{
try
{
using var br = new BinaryReader(File.OpenRead(sfoPath));
br.BaseStream.Seek(-0x08, SeekOrigin.End);
return new string(br.ReadChars(5)).TrimEnd('\0');
}
catch
{
// We don't care what the error was
return null;
}
}
return null;
}
/// <summary>
/// Get the firmware version from a PlayStation 3 disc, if possible
/// </summary>
/// <returns>Firmware version if possible, null on error</returns>
public string? GetPlayStation3FirmwareVersion()
{
// If there's no drive path, we can't do this part
if (string.IsNullOrEmpty(Name))
return null;
// If the folder no longer exists, we can't do this part
if (!Directory.Exists(Name))
return null;
// Attempt to read from /PS3_UPDATE/PS3UPDAT.PUP
#if NET20 || NET35
string pupPath = Path.Combine(Path.Combine(Name, "PS3_UPDATE"), "PS3UPDAT.PUP");
#else
string pupPath = Path.Combine(Name, "PS3_UPDATE", "PS3UPDAT.PUP");
#endif
if (!File.Exists(pupPath))
return null;
try
{
using var br = new BinaryReader(File.OpenRead(pupPath));
br.BaseStream.Seek(0x3E, SeekOrigin.Begin);
byte[] buf = new byte[2];
br.Read(buf, 0, 2);
Array.Reverse(buf);
short location = BitConverter.ToInt16(buf, 0);
br.BaseStream.Seek(location, SeekOrigin.Begin);
return new string(br.ReadChars(4));
}
catch
{
// We don't care what the error was
return null;
}
}
/// <summary>
/// Get the internal serial from a PlayStation 4 disc, if possible
/// </summary>
/// <returns>Internal disc serial if possible, null on error</returns>
public string? GetPlayStation4Serial()
{
// If there's no drive path, we can't do this part
if (string.IsNullOrEmpty(Name))
return null;
// If the folder no longer exists, we can't do this part
if (!Directory.Exists(Name))
return null;
// If we can't find param.sfo, we don't have a PlayStation 4 disc
#if NET20 || NET35
string paramSfoPath = Path.Combine(Path.Combine(Name, "bd"), "param.sfo");
#else
string paramSfoPath = Path.Combine(Name, "bd", "param.sfo");
#endif
if (!File.Exists(paramSfoPath))
return null;
// Let's try reading param.sfo to find the serial at the end of the file
try
{
using var br = new BinaryReader(File.OpenRead(paramSfoPath));
br.BaseStream.Seek(-0x14, SeekOrigin.End);
return new string(br.ReadChars(9)).Insert(4, "-");
}
catch
{
// We don't care what the error was
return null;
}
}
/// <summary>
/// Get the version from a PlayStation 4 disc, if possible
/// </summary>
/// <returns>Game version if possible, null on error</returns>
public string? GetPlayStation4Version()
{
// If there's no drive path, we can't do this part
if (string.IsNullOrEmpty(Name))
return null;
// If the folder no longer exists, we can't do this part
if (!Directory.Exists(Name))
return null;
// If we can't find param.sfo, we don't have a PlayStation 4 disc
#if NET20 || NET35
string paramSfoPath = Path.Combine(Path.Combine(Name, "bd"), "param.sfo");
#else
string paramSfoPath = Path.Combine(Name, "bd", "param.sfo");
#endif
if (!File.Exists(paramSfoPath))
return null;
// Let's try reading param.sfo to find the version at the end of the file
try
{
using var br = new BinaryReader(File.OpenRead(paramSfoPath));
br.BaseStream.Seek(-0x08, SeekOrigin.End);
return new string(br.ReadChars(5));
}
catch
{
// We don't care what the error was
return null;
}
}
/// <summary>
/// Get the internal serial from a PlayStation 5 disc, if possible
/// </summary>
/// <returns>Internal disc serial if possible, null on error</returns>
public string? GetPlayStation5Serial()
{
// Attempt to get the param.json file
var json = GetPlayStation5ParamsJsonFromDrive();
if (json == null)
return null;
try
{
return json["disc"]?[0]?["masterDataId"]?.Value<string>()?.Insert(4, "-");
}
catch
{
// We don't care what the error was
return null;
}
}
// <summary>
/// Get the version from a PlayStation 5 disc, if possible
/// </summary>
/// <returns>Game version if possible, null on error</returns>
public string? GetPlayStation5Version()
{
// Attempt to get the param.json file
var json = GetPlayStation5ParamsJsonFromDrive();
if (json == null)
return null;
try
{
return json["masterVersion"]?.Value<string>();
}
catch
{
// We don't care what the error was
return null;
}
}
/// <summary>
/// Get the params.json file from a drive path, if possible
/// </summary>
/// <returns>JObject representing the JSON on success, null on error</returns>
private JObject? GetPlayStation5ParamsJsonFromDrive()
{
// If there's no drive path, we can't do this part
if (string.IsNullOrEmpty(Name))
return null;
// If the folder no longer exists, we can't do this part
if (!Directory.Exists(Name))
return null;
// If we can't find param.json, we don't have a PlayStation 5 disc
#if NET20 || NET35
string paramJsonPath = Path.Combine(Path.Combine(Name, "bd"), "param.json");
#else
string paramJsonPath = Path.Combine(Name, "bd", "param.json");
#endif
return GetPlayStation5ParamsJsonFromFile(paramJsonPath);
}
/// <summary>
/// Get the params.json file from a filename, if possible
/// </summary>
/// <param name="filename">Filename to check</param>
/// <returns>JObject representing the JSON on success, null on error</returns>
private static JObject? GetPlayStation5ParamsJsonFromFile(string? filename)
{
// If the file doesn't exist
if (string.IsNullOrEmpty(filename) || !File.Exists(filename))
return null;
// Let's try reading param.json to find the version in the unencrypted JSON
try
{
using var br = new BinaryReader(File.OpenRead(filename));
br.BaseStream.Seek(0x800, SeekOrigin.Begin);
byte[] jsonBytes = br.ReadBytes((int)(br.BaseStream.Length - 0x800));
return JsonConvert.DeserializeObject(Encoding.ASCII.GetString(jsonBytes)) as JObject;
}
catch
{
// We don't care what the error was
return null;
}
}
#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
{
drives = DriveInfo.GetDrives()
.Where(d => desiredDriveTypes.Contains(d.DriveType))
.Select(d => Create(ToInternalDriveType(d.DriveType), d.Name) ?? new Drive())
.ToList();
}
catch
{
return drives;
}
// Find and update all floppy drives
#if NET462_OR_GREATER || NETCOREAPP
try
{
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 = Frontend.InternalDriveType.Floppy; } });
}
}
}
catch
{
// No-op
}
#endif
return drives;
}
/// <summary>
/// Convert drive type to internal version, if possible
/// </summary>
/// <param name="driveType">DriveType value to check</param>
/// <returns>InternalDriveType, if possible, null on error</returns>
internal static InternalDriveType? ToInternalDriveType(DriveType driveType)
{
return driveType switch
{
DriveType.CDRom => (InternalDriveType?)Frontend.InternalDriveType.Optical,
DriveType.Fixed => (InternalDriveType?)Frontend.InternalDriveType.HardDisk,
DriveType.Removable => (InternalDriveType?)Frontend.InternalDriveType.Removable,
_ => null,
};
}
#endregion
}
}

View File

@@ -0,0 +1,839 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
#if NET40
using System.Threading;
#endif
using System.Threading.Tasks;
using BinaryObjectScanner;
using MPF.ExecutionContexts;
using MPF.Frontend.Tools;
using MPF.Processors;
using Newtonsoft.Json;
using SabreTools.RedumpLib;
using SabreTools.RedumpLib.Data;
using Formatting = Newtonsoft.Json.Formatting;
namespace MPF.Frontend
{
/// <summary>
/// Represents the state of all settings to be used during dumping
/// </summary>
public class DumpEnvironment
{
#region Output paths
/// <summary>
/// Base output file path to write files to
/// </summary>
public string OutputPath { get; private set; }
#endregion
#region UI information
/// <summary>
/// Drive object representing the current drive
/// </summary>
private Drive? _drive;
/// <summary>
/// ExecutionContext object representing how to invoke the internal program
/// </summary>
private BaseExecutionContext? _executionContext;
/// <summary>
/// Currently selected dumping program
/// </summary>
private readonly InternalProgram _internalProgram;
/// <summary>
/// Options object representing user-defined options
/// </summary>
private readonly Frontend.Options _options;
/// <summary>
/// Processor object representing how to process the outputs
/// </summary>
private BaseProcessor? _processor;
/// <summary>
/// Currently selected system
/// </summary>
private readonly RedumpSystem? _system;
/// <summary>
/// Currently selected media type
/// </summary>
private readonly MediaType? _type;
#endregion
#region Passthrough Fields
/// <inheritdoc cref="BaseExecutionContext.InputPath"/>
public string? ContextInputPath => _executionContext?.InputPath;
/// <inheritdoc cref="BaseExecutionContext.OutputPath"/>
public string? ContextOutputPath => _executionContext?.OutputPath;
/// <inheritdoc cref="Drive.MarkedActive/>
public bool DriveMarkedActive => _drive?.MarkedActive ?? false;
/// <inheritdoc cref="Drive.Name/>
public string? DriveName => _drive?.Name;
/// <inheritdoc cref="BaseExecutionContext.Speed"/>
public int? Speed
{
get => _executionContext?.Speed;
set
{
if (_executionContext != null)
_executionContext.Speed = value;
}
}
/// <inheritdoc cref="Extensions.LongName(RedumpSystem?)/>
public string? SystemName => _system.LongName();
#endregion
#region Event Handlers
/// <summary>
/// Generic way of reporting a message
/// </summary>
public EventHandler<StringEventArgs>? ReportStatus;
#endregion
/// <summary>
/// Constructor for a full DumpEnvironment object from user information
/// </summary>
/// <param name="options"></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(Frontend.Options options,
string? outputPath,
Drive? drive,
RedumpSystem? system,
MediaType? type,
InternalProgram? internalProgram,
string? parameters)
{
// Set options object
_options = options;
// Output paths
OutputPath = FrontendTool.NormalizeOutputPaths(outputPath, false);
// UI information
_drive = drive;
_system = system ?? options.DefaultSystem;
_type = type ?? MediaType.NONE;
_internalProgram = internalProgram ?? options.InternalProgram;
// Dumping program
SetExecutionContext(parameters);
SetProcessor();
}
#region Internal Program Management
/// <summary>
/// Check output path for matching logs from all dumping programs
/// </summary>
public InternalProgram? CheckForMatchingProgram(string? outputDirectory, string outputFilename)
{
// If a complete dump exists from a different program
InternalProgram? programFound = null;
if (programFound == null && _internalProgram != InternalProgram.Aaru)
{
var processor = new Processors.Aaru(_system, _type);
(bool foundOtherFiles, _) = processor.FoundAllFiles(outputDirectory, outputFilename, true);
if (foundOtherFiles)
programFound = InternalProgram.Aaru;
}
if (programFound == null && _internalProgram != InternalProgram.DiscImageCreator)
{
var processor = new Processors.DiscImageCreator(_system, _type);
(bool foundOtherFiles, _) = processor.FoundAllFiles(outputDirectory, outputFilename, true);
if (foundOtherFiles)
programFound = InternalProgram.DiscImageCreator;
}
if (programFound == null && _internalProgram != InternalProgram.Redumper)
{
var processor = new Processors.Redumper(_system, _type);
(bool foundOtherFiles, _) = processor.FoundAllFiles(outputDirectory, outputFilename, true);
if (foundOtherFiles)
programFound = InternalProgram.Redumper;
}
return programFound;
}
/// <summary>
/// Set the parameters object based on the internal program and parameters string
/// </summary>
/// <param name="parameters">String representation of the parameters</param>
public bool SetExecutionContext(string? parameters)
{
_executionContext = _internalProgram switch
{
InternalProgram.Aaru => new ExecutionContexts.Aaru.ExecutionContext(parameters) { ExecutablePath = _options.AaruPath },
InternalProgram.DiscImageCreator => new ExecutionContexts.DiscImageCreator.ExecutionContext(parameters) { ExecutablePath = _options.DiscImageCreatorPath },
InternalProgram.Redumper => new ExecutionContexts.Redumper.ExecutionContext(parameters) { ExecutablePath = _options.RedumperPath },
// If no dumping program found, set to null
InternalProgram.NONE => null,
_ => null,
};
// Set system, type, and drive
if (_executionContext != null)
{
_executionContext.System = _system;
_executionContext.Type = _type;
// Set some parameters, if not already set
OutputPath ??= _executionContext.OutputPath!;
_drive ??= Drive.Create(InternalDriveType.Optical, _executionContext.InputPath!);
}
return _executionContext != null;
}
/// <summary>
/// Set the processor object based on the internal program
/// </summary>
public bool SetProcessor()
{
_processor = _internalProgram switch
{
InternalProgram.Aaru => new Processors.Aaru(_system, _type),
InternalProgram.CleanRip => new CleanRip(_system, _type),
InternalProgram.DiscImageCreator => new DiscImageCreator(_system, _type),
InternalProgram.PS3CFW => new PS3CFW(_system, _type),
InternalProgram.Redumper => new Redumper(_system, _type),
InternalProgram.UmdImageCreator => new UmdImageCreator(_system, _type),
InternalProgram.XboxBackupCreator => new XboxBackupCreator(_system, _type),
// If no dumping program found, set to null
InternalProgram.NONE => null,
_ => null,
};
return _processor != null;
}
/// <summary>
/// Get the full parameter string for either DiscImageCreator or Aaru
/// </summary>
/// <param name="driveSpeed">Nullable int representing the drive speed</param>
/// <returns>String representing the params, null on error</returns>
public string? GetFullParameters(int? driveSpeed)
{
// Populate with the correct params for inputs (if we're not on the default option)
if (_system != null && _type != MediaType.NONE)
{
// If drive letter is invalid, skip this
if (_drive == null)
return null;
// Set the proper parameters
_executionContext = _internalProgram switch
{
InternalProgram.Aaru => new ExecutionContexts.Aaru.ExecutionContext(_system, _type, _drive.Name, OutputPath, driveSpeed, _options.Settings),
InternalProgram.DiscImageCreator => new ExecutionContexts.DiscImageCreator.ExecutionContext(_system, _type, _drive.Name, OutputPath, driveSpeed, _options.Settings),
InternalProgram.Redumper => new ExecutionContexts.Redumper.ExecutionContext(_system, _type, _drive.Name, OutputPath, driveSpeed, _options.Settings),
// If no dumping program found, set to null
InternalProgram.NONE => null,
_ => null,
};
// Generate and return the param string
return _executionContext?.GenerateParameters();
}
return null;
}
#endregion
#region Passthrough Functionality
/// <inheritdoc cref="Extensions.DetectedByWindows(RedumpSystem?)"/>
public bool DetectedByWindows() => _system.DetectedByWindows();
/// <summary>
/// Determine if the media supports drive speeds
/// </summary>
/// <param name="type">MediaType value to check</param>
/// <returns>True if the media has variable dumping speeds, false otherwise</returns>
public bool DoesSupportDriveSpeed()
{
return _type switch
{
MediaType.CDROM
or MediaType.DVD
or MediaType.GDROM
or MediaType.HDDVD
or MediaType.BluRay
or MediaType.NintendoGameCubeGameDisc
or MediaType.NintendoWiiOpticalDisc => true,
_ => false,
};
}
/// <inheritdoc cref="BaseProcessor.FoundAllFiles(string?, string, bool)"/>
public bool FoundAllFiles(string? outputDirectory, string outputFilename, bool preCheck)
{
if (_processor == null)
return false;
return _processor.FoundAllFiles(outputDirectory, outputFilename, preCheck).Item1;
}
/// <inheritdoc cref="BaseExecutionContext.GetDefaultExtension(MediaType?)"/>
public string? GetDefaultExtension(MediaType? mediaType)
{
if (_executionContext == null)
return null;
return _executionContext.GetDefaultExtension(mediaType);
}
/// <inheritdoc cref="BaseExecutionContext.GetMediaType()"/>
public MediaType? GetMediaType()
{
if (_executionContext == null)
return null;
return _executionContext.GetMediaType();
}
/// <summary>
/// Verify that, given a system and a media type, they are correct
/// </summary>
public ResultEventArgs GetSupportStatus()
{
// No system chosen, update status
if (_system == null)
return ResultEventArgs.Failure("Please select a valid system");
// If we're on an unsupported type, update the status accordingly
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 => ResultEventArgs.Success($"{_type.LongName()} ready to dump"),
// Partially supported types
MediaType.GDROM
or MediaType.NintendoGameCubeGameDisc
or MediaType.NintendoWiiOpticalDisc => ResultEventArgs.Success($"{_type.LongName()} partially supported for dumping"),
// Special case for other supported tools
MediaType.UMD => ResultEventArgs.Failure($"{_type.LongName()} supported for submission info parsing"),
// Specifically unknown type
MediaType.NONE => ResultEventArgs.Failure($"Please select a valid media type"),
// Undumpable but recognized types
_ => ResultEventArgs.Failure($"{_type.LongName()} media are not supported for dumping"),
};
}
/// <inheritdoc cref="BaseExecutionContext.IsDumpingCommand()"/>
public bool IsDumpingCommand()
{
if (_executionContext == null)
return false;
return _executionContext.IsDumpingCommand();
}
/// <inheritdoc cref="Drive.RefreshDrive"/>
public void RefreshDrive() => _drive?.RefreshDrive();
#endregion
#region Dumping
/// <summary>
/// Cancel an in-progress dumping process
/// </summary>
public void CancelDumping() => _executionContext?.KillInternalProgram();
/// <summary>
/// Execute the initial invocation of the dumping programs
/// </summary>
/// <param name="progress">Optional result progress callback</param>
public async Task<ResultEventArgs> Run(IProgress<ResultEventArgs>? progress = null)
{
// If we don't have parameters
if (_executionContext == null)
return ResultEventArgs.Failure("Error! Current configuration is not supported!");
// Check that we have the basics for dumping
ResultEventArgs result = IsValidForDump();
if (!result)
return result;
// Execute internal tool
progress?.Report(ResultEventArgs.Success($"Executing {_internalProgram}... please wait!"));
var directoryName = Path.GetDirectoryName(OutputPath);
if (!string.IsNullOrEmpty(directoryName))
Directory.CreateDirectory(directoryName);
#if NET40
await Task.Factory.StartNew(() => { _executionContext.ExecuteInternalProgram(); return true; });
#else
await Task.Run(_executionContext.ExecuteInternalProgram);
#endif
progress?.Report(ResultEventArgs.Success($"{_internalProgram} has finished!"));
return result;
}
/// <summary>
/// Verify that the current environment has a complete dump and create submission info is possible
/// </summary>
/// <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<ResultEventArgs> VerifyAndSaveDumpOutput(
IProgress<ResultEventArgs>? resultProgress = null,
IProgress<ProtectionProgress>? protectionProgress = null,
Func<SubmissionInfo?, (bool?, SubmissionInfo?)>? processUserInfo = null,
SubmissionInfo? seedInfo = null)
{
if (_processor == null)
return ResultEventArgs.Failure("Error! Current configuration is not supported!");
resultProgress?.Report(ResultEventArgs.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) = _processor.FoundAllFiles(outputDirectory, outputFilename, false);
if (!foundFiles)
{
resultProgress?.Report(ResultEventArgs.Failure($"There were files missing from the output:\n{string.Join("\n", [.. missingFiles])}"));
return ResultEventArgs.Failure("Error! Please check output directory as dump may be incomplete!");
}
// Extract the information from the output files
resultProgress?.Report(ResultEventArgs.Success("Extracting output information from output files..."));
var submissionInfo = await SubmissionGenerator.ExtractOutputInformation(
OutputPath,
_drive,
_system,
_type,
_options,
_processor,
resultProgress,
protectionProgress);
resultProgress?.Report(ResultEventArgs.Success("Extracting information complete!"));
// Inject seed submission info data, if necessary
if (seedInfo != null)
{
resultProgress?.Report(ResultEventArgs.Success("Injecting user-supplied information..."));
Builder.InjectSubmissionInformation(submissionInfo, seedInfo);
resultProgress?.Report(ResultEventArgs.Success("Information injection complete!"));
}
// Get user-modifiable information if confugured to
if (_options.PromptForDiscInformation && processUserInfo != null)
{
resultProgress?.Report(ResultEventArgs.Success("Waiting for additional disc information..."));
bool? filledInfo;
(filledInfo, submissionInfo) = processUserInfo(submissionInfo);
if (filledInfo == true)
resultProgress?.Report(ResultEventArgs.Success("Additional disc information added!"));
else
resultProgress?.Report(ResultEventArgs.Success("Disc information skipped!"));
}
// Process special fields for site codes
resultProgress?.Report(ResultEventArgs.Success("Processing site codes..."));
Formatter.ProcessSpecialFields(submissionInfo);
resultProgress?.Report(ResultEventArgs.Success("Processing complete!"));
// Format the information for the text output
resultProgress?.Report(ResultEventArgs.Success("Formatting information..."));
(var formattedValues, var formatResult) = Formatter.FormatOutputData(submissionInfo, _options.EnableRedumpCompatibility);
if (formattedValues == null)
resultProgress?.Report(ResultEventArgs.Failure(formatResult));
else
resultProgress?.Report(ResultEventArgs.Success(formatResult));
// Get the filename suffix for auto-generated files
var filenameSuffix = _options.AddFilenameSuffix ? Path.GetFileNameWithoutExtension(outputFilename) : null;
// Write the text output
resultProgress?.Report(ResultEventArgs.Success("Writing submission information file..."));
(bool txtSuccess, string txtResult) = WriteOutputData(outputDirectory, filenameSuffix, formattedValues);
if (txtSuccess)
resultProgress?.Report(ResultEventArgs.Success(txtResult));
else
resultProgress?.Report(ResultEventArgs.Failure(txtResult));
// Write the copy protection output
if (submissionInfo?.CopyProtection?.FullProtections != null && submissionInfo.CopyProtection.FullProtections.Any())
{
if (_options.ScanForProtection)
{
resultProgress?.Report(ResultEventArgs.Success("Writing protection information file..."));
bool scanSuccess = WriteProtectionData(outputDirectory, filenameSuffix, submissionInfo, _options.HideDriveLetters);
if (scanSuccess)
resultProgress?.Report(ResultEventArgs.Success("Writing complete!"));
else
resultProgress?.Report(ResultEventArgs.Failure("Writing could not complete!"));
}
}
// Write the JSON output, if required
if (_options.OutputSubmissionJSON)
{
resultProgress?.Report(ResultEventArgs.Success($"Writing submission information JSON file{(_options.IncludeArtifacts ? " with artifacts" : string.Empty)}..."));
bool jsonSuccess = WriteOutputData(outputDirectory, filenameSuffix, submissionInfo, _options.IncludeArtifacts);
if (jsonSuccess)
resultProgress?.Report(ResultEventArgs.Success("Writing complete!"));
else
resultProgress?.Report(ResultEventArgs.Failure("Writing could not complete!"));
}
// Compress the logs, if required
if (_options.CompressLogFiles)
{
resultProgress?.Report(ResultEventArgs.Success("Compressing log files..."));
(bool compressSuccess, string compressResult) = _processor?.CompressLogFiles(outputDirectory, filenameSuffix, outputFilename) ?? (false, "No processor provided!");
if (compressSuccess)
resultProgress?.Report(ResultEventArgs.Success(compressResult));
else
resultProgress?.Report(ResultEventArgs.Failure(compressResult));
}
// Delete unnecessary files, if required
if (_options.DeleteUnnecessaryFiles)
{
resultProgress?.Report(ResultEventArgs.Success("Deleting unnecessary files..."));
(bool deleteSuccess, string deleteResult) = _processor?.DeleteUnnecessaryFiles(outputDirectory, outputFilename) ?? (false, "No processor provided!");
if (deleteSuccess)
resultProgress?.Report(ResultEventArgs.Success(deleteResult));
else
resultProgress?.Report(ResultEventArgs.Failure(deleteResult));
}
// Create PS3 IRD, if required
if (_options.CreateIRDAfterDumping && _system == RedumpSystem.SonyPlayStation3 && _type == MediaType.BluRay)
{
resultProgress?.Report(ResultEventArgs.Success("Creating IRD... please wait!"));
(bool deleteSuccess, string deleteResult) = await WriteIRD(OutputPath, submissionInfo?.Extras?.DiscKey, submissionInfo?.Extras?.DiscID, submissionInfo?.Extras?.PIC, submissionInfo?.SizeAndChecksums?.Layerbreak, submissionInfo?.SizeAndChecksums?.CRC32);
if (deleteSuccess)
resultProgress?.Report(ResultEventArgs.Success(deleteResult));
else
resultProgress?.Report(ResultEventArgs.Failure(deleteResult));
}
resultProgress?.Report(ResultEventArgs.Success("Submission information process complete!"));
return ResultEventArgs.Success();
}
/// <summary>
/// Checks if the parameters are valid
/// </summary>
/// <returns>True if the configuration is valid, false otherwise</returns>
internal bool ParametersValid()
{
// Missing drive means it can never be valid
if (_drive == null)
return false;
bool parametersValid = _executionContext?.IsValid() ?? false;
bool floppyValid = !(_drive.InternalDriveType == InternalDriveType.Floppy ^ _type == MediaType.FloppyDisk);
// TODO: HardDisk being in the Removable category is a hack, fix this later
bool removableDiskValid = !((_drive.InternalDriveType == InternalDriveType.Removable || _drive.InternalDriveType == InternalDriveType.HardDisk)
^ (_type == MediaType.CompactFlash || _type == MediaType.SDCard || _type == MediaType.FlashDrive || _type == MediaType.HardDisk));
return parametersValid && floppyValid && removableDiskValid;
}
/// <summary>
/// Validate the current environment is ready for a dump
/// </summary>
/// <returns>Result instance with the outcome</returns>
private ResultEventArgs IsValidForDump()
{
// Validate that everything is good
if (_executionContext == null || !ParametersValid())
return ResultEventArgs.Failure("Error! Current configuration is not supported!");
// Fix the output paths, just in case
OutputPath = FrontendTool.NormalizeOutputPaths(OutputPath, false);
// Validate that the output path isn't on the dumping drive
if (_drive?.Name != null && OutputPath.StartsWith(_drive.Name))
return ResultEventArgs.Failure("Error! Cannot output to same drive that is being dumped!");
// Validate that the required program exists
if (!File.Exists(_executionContext.ExecutablePath))
return ResultEventArgs.Failure($"Error! {_executionContext.ExecutablePath} does not exist!");
// Validate that the dumping drive doesn't contain the executable
string fullExecutablePath = Path.GetFullPath(_executionContext.ExecutablePath!);
if (_drive?.Name != null && fullExecutablePath.StartsWith(_drive.Name))
return ResultEventArgs.Failure("Error! Cannot dump same drive that executable resides on!");
// Validate that the current configuration is supported
return GetSupportStatus();
}
#endregion
#region Information Output
/// <summary>
/// Write the data to the output folder
/// </summary>
/// <param name="outputDirectory">Output folder to write to</param>
/// <param name="filenameSuffix">Optional suffix to append to the filename</param>
/// <param name="lines">Preformatted list of lines to write out to the file</param>
/// <returns>True on success, false on error</returns>
private static (bool, string) WriteOutputData(string? outputDirectory, string? filenameSuffix, List<string>? lines)
{
// Check to see if the inputs are valid
if (lines == null)
return (false, "No formatted data found to write!");
// Now write out to a generic file
try
{
// Get the file path
var path = string.Empty;
if (string.IsNullOrEmpty(outputDirectory) && string.IsNullOrEmpty(filenameSuffix))
path = "!submissionInfo.txt";
else if (string.IsNullOrEmpty(outputDirectory) && !string.IsNullOrEmpty(filenameSuffix))
path = $"!submissionInfo_{filenameSuffix}.txt";
else if (!string.IsNullOrEmpty(outputDirectory) && string.IsNullOrEmpty(filenameSuffix))
path = Path.Combine(outputDirectory, "!submissionInfo.txt");
else if (!string.IsNullOrEmpty(outputDirectory) && !string.IsNullOrEmpty(filenameSuffix))
path = Path.Combine(outputDirectory, $"!submissionInfo_{filenameSuffix}.txt");
using var sw = new StreamWriter(File.Open(path, FileMode.Create, FileAccess.Write), Encoding.UTF8);
foreach (string line in lines)
{
sw.WriteLine(line);
}
}
catch (Exception ex)
{
return (false, $"Writing could not complete: {ex}");
}
return (true, "Writing complete!");
}
// MOVE TO REDUMPLIB
/// <summary>
/// Write the data to the output folder
/// </summary>
/// <param name="outputDirectory">Output folder to write to</param>
/// <param name="filenameSuffix">Optional suffix to append to the filename</param>
/// <param name="info">SubmissionInfo object representing the JSON to write out to the file</param>
/// <param name="includedArtifacts">True if artifacts were included, false otherwise</param>
/// <returns>True on success, false on error</returns>
private static bool WriteOutputData(string? outputDirectory, string? filenameSuffix, SubmissionInfo? info, bool includedArtifacts)
{
// Check to see if the input is valid
if (info == null)
return false;
try
{
// Serialize the JSON and get it writable
string json = JsonConvert.SerializeObject(info, Formatting.Indented);
byte[] jsonBytes = Encoding.UTF8.GetBytes(json);
// If we included artifacts, write to a GZip-compressed file
if (includedArtifacts)
{
var path = string.Empty;
if (string.IsNullOrEmpty(outputDirectory) && string.IsNullOrEmpty(filenameSuffix))
path = "!submissionInfo.json.gz";
else if (string.IsNullOrEmpty(outputDirectory) && !string.IsNullOrEmpty(filenameSuffix))
path = $"!submissionInfo_{filenameSuffix}.json.gz";
else if (!string.IsNullOrEmpty(outputDirectory) && string.IsNullOrEmpty(filenameSuffix))
path = Path.Combine(outputDirectory, "!submissionInfo.json.gz");
else if (!string.IsNullOrEmpty(outputDirectory) && !string.IsNullOrEmpty(filenameSuffix))
path = Path.Combine(outputDirectory, $"!submissionInfo_{filenameSuffix}.json.gz");
using var fs = File.Create(path);
using var gs = new GZipStream(fs, CompressionMode.Compress);
gs.Write(jsonBytes, 0, jsonBytes.Length);
}
// Otherwise, write out to a normal JSON
else
{
var path = string.Empty;
if (string.IsNullOrEmpty(outputDirectory) && string.IsNullOrEmpty(filenameSuffix))
path = "!submissionInfo.json";
else if (string.IsNullOrEmpty(outputDirectory) && !string.IsNullOrEmpty(filenameSuffix))
path = $"!submissionInfo_{filenameSuffix}.json";
else if (!string.IsNullOrEmpty(outputDirectory) && string.IsNullOrEmpty(filenameSuffix))
path = Path.Combine(outputDirectory, "!submissionInfo.json");
else if (!string.IsNullOrEmpty(outputDirectory) && !string.IsNullOrEmpty(filenameSuffix))
path = Path.Combine(outputDirectory, $"!submissionInfo_{filenameSuffix}.json");
using var fs = File.Create(path);
fs.Write(jsonBytes, 0, jsonBytes.Length);
}
}
catch
{
// We don't care what the error is right now
return false;
}
return true;
}
// MOVE TO REDUMPLIB
/// <summary>
/// Write the protection data to the output folder
/// </summary>
/// <param name="outputDirectory">Output folder to write to</param>
/// <param name="filenameSuffix">Optional suffix to append to the filename</param>
/// <param name="info">SubmissionInfo object containing the protection information</param>
/// <param name="hideDriveLetters">True if drive letters are to be removed from output, false otherwise</param>
/// <returns>True on success, false on error</returns>
private static bool WriteProtectionData(string? outputDirectory, string? filenameSuffix, SubmissionInfo? info, bool hideDriveLetters)
{
// Check to see if the inputs are valid
if (info?.CopyProtection?.FullProtections == null || !info.CopyProtection.FullProtections.Any())
return true;
// Now write out to a generic file
try
{
var path = string.Empty;
if (string.IsNullOrEmpty(outputDirectory) && string.IsNullOrEmpty(filenameSuffix))
path = "!protectionInfo.txt";
else if (string.IsNullOrEmpty(outputDirectory) && !string.IsNullOrEmpty(filenameSuffix))
path = $"!protectionInfo{filenameSuffix}.txt";
else if (!string.IsNullOrEmpty(outputDirectory) && string.IsNullOrEmpty(filenameSuffix))
path = Path.Combine(outputDirectory, "!protectionInfo.txt");
else if (!string.IsNullOrEmpty(outputDirectory) && !string.IsNullOrEmpty(filenameSuffix))
path = Path.Combine(outputDirectory, $"!protectionInfo{filenameSuffix}.txt");
using var sw = new StreamWriter(File.Open(path, FileMode.Create, FileAccess.Write), Encoding.UTF8);
List<string> sortedKeys = [.. info.CopyProtection.FullProtections.Keys.OrderBy(k => k)];
foreach (string key in sortedKeys)
{
string scanPath = key;
if (hideDriveLetters)
scanPath = Path.DirectorySeparatorChar + key.Substring((Path.GetPathRoot(key) ?? String.Empty).Length);
List<string>? scanResult = info.CopyProtection.FullProtections[key];
if (scanResult == null)
sw.WriteLine($"{scanPath}: None");
else
sw.WriteLine($"{scanPath}: {string.Join(", ", [.. scanResult])}");
}
}
catch
{
// We don't care what the error is right now
return false;
}
return true;
}
/// <summary>
/// Create an IRD and write it to the specified output directory with optional filename suffix
/// </summary>
/// <param name="outputDirectory">Output folder to write to</param>
/// <param name="filenameSuffix">Optional suffix to append to the filename</param>
/// <param name="outputFilename">Output filename to use as the base path</param>
/// <returns>True on success, false on error</returns>
private static async Task<(bool, string)> WriteIRD(string isoPath, string? discKeyString, string? discIDString, string? picString, long? layerbreak, string? crc32)
{
try
{
// Output IRD file path
string irdPath = Path.ChangeExtension(isoPath, ".ird");
// Parse disc key from submission info (Required)
byte[]? discKey = ProcessingTool.ParseHexKey(discKeyString);
if (discKey == null)
return (false, "Failed to create IRD: No key provided");
// Parse Disc ID from submission info (Optional)
byte[]? discID = ProcessingTool.ParseDiscID(discIDString);
// Parse PIC from submission info (Optional)
byte[]? pic = ProcessingTool.ParsePIC(picString);
// Parse CRC32 strings into ISO hash for Unique ID field (Optional)
uint? uid = ProcessingTool.ParseCRC32(crc32);
// Ensure layerbreak value is valid (Optional)
layerbreak = ProcessingTool.ParseLayerbreak(layerbreak);
// Create Redump-style reproducible IRD
#if NET40
LibIRD.ReIRD ird = await Task.Factory.StartNew(() =>
#else
LibIRD.ReIRD ird = await Task.Run(() =>
#endif
new LibIRD.ReIRD(isoPath, discKey, layerbreak, uid));
if (pic != null)
ird.PIC = pic;
if (discID != null && ird.DiscID[15] != 0x00)
ird.DiscID = discID;
// Write IRD to file
ird.Write(irdPath);
return (true, "IRD created!");
}
catch (Exception)
{
// We don't care what the error is
return (false, "Failed to create IRD");
}
}
#endregion
}
}

View File

@@ -0,0 +1,187 @@
using System;
#if NET20 || NET35
using System.Collections.Generic;
#else
using System.Collections.Concurrent;
#endif
using System.Reflection;
using SabreTools.RedumpLib.Data;
using RedumperReadMethod = MPF.ExecutionContexts.Redumper.ReadMethod;
using RedumperSectorOrder = MPF.ExecutionContexts.Redumper.SectorOrder;
namespace MPF.Frontend
{
public static class EnumExtensions
{
#region Convert to Long Name
/// <summary>
/// Long name method cache
/// </summary>
#if NET20 || NET35
private static readonly Dictionary<Type, MethodInfo?> LongNameMethods = [];
#else
private static readonly ConcurrentDictionary<Type, MethodInfo?> LongNameMethods = [];
#endif
/// <summary>
/// Get the string representation of a generic enumerable value
/// </summary>
/// <param name="value">Enum value to convert</param>
/// <returns>String representation of that value if possible, empty string on error</returns>
public static string GetLongName(Enum value)
{
try
{
var sourceType = value.GetType();
sourceType = Nullable.GetUnderlyingType(sourceType) ?? sourceType;
if (!LongNameMethods.TryGetValue(sourceType, out var method))
{
method = typeof(Extensions).GetMethod("LongName", [typeof(Nullable<>).MakeGenericType(sourceType)]);
method ??= typeof(EnumExtensions).GetMethod("LongName", [typeof(Nullable<>).MakeGenericType(sourceType)]);
#if NET20 || NET35
LongNameMethods[sourceType] = method;
#else
LongNameMethods.TryAdd(sourceType, method);
#endif
}
if (method != null)
return method.Invoke(null, new[] { value }) as string ?? string.Empty;
else
return string.Empty;
}
catch
{
// Converter is not implemented for the given type
return string.Empty;
}
}
/// <summary>
/// Get the string representation of the InternalProgram enum values
/// </summary>
/// <param name="prog">InternalProgram value to convert</param>
/// <returns>String representing the value, if possible</returns>
public static string LongName(this InternalProgram? prog)
{
return (prog) switch
{
#region Dumping support
InternalProgram.Aaru => "Aaru",
InternalProgram.DiscImageCreator => "DiscImageCreator",
InternalProgram.Redumper => "Redumper",
#endregion
#region Verification support only
InternalProgram.CleanRip => "CleanRip",
InternalProgram.PS3CFW => "PS3 CFW",
InternalProgram.UmdImageCreator => "UmdImageCreator",
InternalProgram.XboxBackupCreator => "XboxBackupCreator",
#endregion
InternalProgram.NONE => "Unknown",
_ => "Unknown",
};
}
/// <summary>
/// Get the string representation of the RedumperReadMethod enum values
/// </summary>
/// <param name="method">RedumperReadMethod value to convert</param>
/// <returns>String representing the value, if possible</returns>
public static string LongName(this RedumperReadMethod? method)
{
return (method) switch
{
RedumperReadMethod.D8 => "D8",
RedumperReadMethod.BE => "BE",
RedumperReadMethod.BE_CDDA => "BE_CDDA",
RedumperReadMethod.NONE => "Default",
_ => "Unknown",
};
}
/// <summary>
/// Get the string representation of the RedumperSectorOrder enum values
/// </summary>
/// <param name="order">RedumperSectorOrder value to convert</param>
/// <returns>String representing the value, if possible</returns>
public static string LongName(this RedumperSectorOrder? order)
{
return (order) switch
{
RedumperSectorOrder.DATA_C2_SUB => "DATA_C2_SUB",
RedumperSectorOrder.DATA_SUB_C2 => "DATA_SUB_C2",
RedumperSectorOrder.DATA_SUB => "DATA_SUB",
RedumperSectorOrder.DATA_C2 => "DATA_C2",
RedumperSectorOrder.NONE => "Default",
_ => "Unknown",
};
}
#endregion
#region Convert from String
/// <summary>
/// Get the RedumperReadMethod enum value for a given string
/// </summary>
/// <param name="method">String value to convert</param>
/// <returns>RedumperReadMethod represented by the string, if possible</returns>
public static RedumperReadMethod ToRedumperReadMethod(this string? method)
{
return (method?.ToLowerInvariant()) switch
{
"d8" => RedumperReadMethod.D8,
"be" => RedumperReadMethod.BE,
"be_cdda"
or "be cdda"
or "be-cdda"
or "becdda" => RedumperReadMethod.BE_CDDA,
_ => RedumperReadMethod.NONE,
};
}
/// <summary>
/// Get the RedumperSectorOrder enum value for a given string
/// </summary>
/// <param name="order">String value to convert</param>
/// <returns>RedumperSectorOrder represented by the string, if possible</returns>
public static RedumperSectorOrder ToRedumperSectorOrder(this string? order)
{
return (order?.ToLowerInvariant()) switch
{
"data_c2_sub"
or "data c2 sub"
or "data-c2-sub"
or "datac2sub" => RedumperSectorOrder.DATA_C2_SUB,
"data_sub_c2"
or "data sub c2"
or "data-sub-c2"
or "datasubc2" => RedumperSectorOrder.DATA_SUB_C2,
"data_sub"
or "data sub"
or "data-sub"
or "datasub" => RedumperSectorOrder.DATA_SUB,
"data_c2"
or "data c2"
or "data-c2"
or "datac2" => RedumperSectorOrder.DATA_C2,
_ => RedumperSectorOrder.NONE,
};
}
#endregion
}
}

View File

@@ -1,25 +1,5 @@
namespace MPF.Core.Data
namespace MPF.Frontend
{
/// <summary>
/// Available hashing types
/// </summary>
public enum Hash
{
CRC32,
#if NET6_0_OR_GREATER
CRC64,
#endif
MD5,
SHA1,
SHA256,
SHA384,
SHA512,
#if NET6_0_OR_GREATER
XxHash32,
XxHash64,
#endif
}
/// <summary>
/// Drive type for dumping
/// </summary>
@@ -45,9 +25,9 @@
// Verification support only
CleanRip,
DCDumper,
PS3CFW,
UmdImageCreator,
XboxBackupCreator,
}
/// <summary>
@@ -60,4 +40,4 @@
ERROR,
SECRET,
}
}
}

View File

@@ -0,0 +1,62 @@
using System.Collections.Generic;
using System.Linq;
using SabreTools.RedumpLib.Data;
namespace MPF.Frontend
{
/// <summary>
/// Constant values for UI
/// </summary>
public static class InterfaceConstants
{
/// <summary>
/// Set of all accepted speed values
/// </summary>
private static readonly List<int> _speedValues = [1, 2, 3, 4, 6, 8, 12, 16, 20, 24, 32, 40, 44, 48, 52, 56, 72];
/// <summary>
/// Set of accepted speeds for CD and GD media
/// </summary>
public static IList<int> CD => _speedValues.Where(s => s <= 72).ToList();
/// <summary>
/// Set of accepted speeds for DVD media
/// </summary>
public static IList<int> DVD => _speedValues.Where(s => s <= 24).ToList();
/// <summary>
/// Set of accepted speeds for HD-DVD media
/// </summary>
public static IList<int> HDDVD => _speedValues.Where(s => s <= 24).ToList();
/// <summary>
/// Set of accepted speeds for BD media
/// </summary>
public static IList<int> BD => _speedValues.Where(s => s <= 16).ToList();
/// <summary>
/// Set of accepted speeds for all other media
/// </summary>
public static IList<int> Unknown => _speedValues.Where(s => s <= 1).ToList();
/// <summary>
/// Get list of all drive speeds for a given MediaType
/// </summary>
/// <param name="type">MediaType? that represents the current item</param>
/// <returns>Read-only list of drive speeds</returns>
public static IList<int> GetSpeedsForMediaType(MediaType? type)
{
return type switch
{
MediaType.CDROM
or MediaType.GDROM => CD,
MediaType.DVD
or MediaType.NintendoGameCubeGameDisc
or MediaType.NintendoWiiOpticalDisc => DVD,
MediaType.HDDVD => HDDVD,
MediaType.BluRay => BD,
_ => Unknown,
};
}
}
}

View File

@@ -0,0 +1,73 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Assembly Properties -->
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<VersionPrefix>3.2.1</VersionPrefix>
<!-- Package Properties -->
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
<Description>Common code for all MPF frontend implementations</Description>
<Copyright>Copyright (c) Matt Nadareski 2019-2024</Copyright>
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
<RepositoryUrl>https://github.com/SabreTools/MPF</RepositoryUrl>
<RepositoryType>git</RepositoryType>
</PropertyGroup>
<ItemGroup>
<InternalsVisibleTo Include="MPF.Test" />
</ItemGroup>
<!-- Support All Frameworks -->
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net4`))">
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`))">
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`))">
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(RuntimeIdentifier.StartsWith(`osx-arm`))">
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MPF.ExecutionContexts\MPF.ExecutionContexts.csproj" />
<ProjectReference Include="..\MPF.Processors\MPF.Processors.csproj" />
</ItemGroup>
<!-- Support for old .NET versions -->
<ItemGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net40`))">
<PackageReference Include="MinAsyncBridge" Version="0.12.4" />
<PackageReference Include="MinTasksExtensionsBridge" Version="0.3.4" />
<PackageReference Include="MinThreadingBridge" Version="0.11.4" />
</ItemGroup>
<ItemGroup Condition="$(TargetFramework.StartsWith(`net4`)) AND !$(TargetFramework.StartsWith(`net40`))">
<PackageReference Include="IndexRange" Version="1.0.3" />
</ItemGroup>
<ItemGroup Condition="$(TargetFramework.StartsWith(`net452`))">
<PackageReference Include="Microsoft.Net.Http" Version="2.2.29" />
</ItemGroup>
<ItemGroup Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`)) AND !$(TargetFramework.StartsWith(`net452`))">
<PackageReference Include="Microsoft.Management.Infrastructure" Version="3.0.0" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BinaryObjectScanner" PrivateAssets="build; analyzers" ExcludeAssets="contentFiles" Version="3.1.13" GeneratePathProperty="true">
<IncludeAssets>runtime; compile; build; native; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="LibIRD" Version="0.9.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="SabreTools.RedumpLib" Version="1.4.1" />
</ItemGroup>
</Project>

View File

@@ -1,8 +1,12 @@
using System.Collections.Generic;
using MPF.Core.Converters;
using SabreTools.RedumpLib.Data;
using AaruSettings = MPF.ExecutionContexts.Aaru.SettingConstants;
using DICSettings = MPF.ExecutionContexts.DiscImageCreator.SettingConstants;
using RedumperReadMethod = MPF.ExecutionContexts.Redumper.ReadMethod;
using RedumperSectorOrder = MPF.ExecutionContexts.Redumper.SectorOrder;
using RedumperSettings = MPF.ExecutionContexts.Redumper.SettingConstants;
namespace MPF.Core.Data
namespace MPF.Frontend
{
public class Options
{
@@ -57,7 +61,7 @@ namespace MPF.Core.Data
get
{
var valueString = GetStringSetting(Settings, "InternalProgram", InternalProgram.Redumper.ToString());
var valueEnum = EnumConverter.ToInternalProgram(valueString);
var valueEnum = ToInternalProgram(valueString);
return valueEnum == InternalProgram.NONE ? InternalProgram.Redumper : valueEnum;
}
set
@@ -79,6 +83,33 @@ namespace MPF.Core.Data
set { Settings["EnableDarkMode"] = value.ToString(); }
}
/// <summary>
/// Enable purple mode for UI elements
/// </summary>
public bool EnablePurpMode
{
get { return GetBooleanSetting(Settings, "EnablePurpMode", false); }
set { Settings["EnablePurpMode"] = value.ToString(); }
}
/// <summary>
/// Custom color setting
/// </summary>
public string? CustomBackgroundColor
{
get { return GetStringSetting(Settings, "CustomBackgroundColor", null); }
set { Settings["CustomBackgroundColor"] = value; }
}
/// <summary>
/// Custom color setting
/// </summary>
public string? CustomTextColor
{
get { return GetStringSetting(Settings, "CustomTextColor", null); }
set { Settings["CustomTextColor"] = value; }
}
/// <summary>
/// Check for updates on startup
/// </summary>
@@ -182,8 +213,8 @@ namespace MPF.Core.Data
/// </summary>
public bool AaruEnableDebug
{
get { return GetBooleanSetting(Settings, "AaruEnableDebug", false); }
set { Settings["AaruEnableDebug"] = value.ToString(); }
get { return GetBooleanSetting(Settings, AaruSettings.EnableDebug, AaruSettings.EnableDebugDefault); }
set { Settings[AaruSettings.EnableDebug] = value.ToString(); }
}
/// <summary>
@@ -191,8 +222,8 @@ namespace MPF.Core.Data
/// </summary>
public bool AaruEnableVerbose
{
get { return GetBooleanSetting(Settings, "AaruEnableVerbose", false); }
set { Settings["AaruEnableVerbose"] = value.ToString(); }
get { return GetBooleanSetting(Settings, AaruSettings.EnableVerbose, AaruSettings.EnableVerboseDefault); }
set { Settings[AaruSettings.EnableVerbose] = value.ToString(); }
}
/// <summary>
@@ -200,8 +231,8 @@ namespace MPF.Core.Data
/// </summary>
public bool AaruForceDumping
{
get { return GetBooleanSetting(Settings, "AaruForceDumping", true); }
set { Settings["AaruForceDumping"] = value.ToString(); }
get { return GetBooleanSetting(Settings, AaruSettings.ForceDumping, AaruSettings.ForceDumpingDefault); }
set { Settings[AaruSettings.ForceDumping] = value.ToString(); }
}
/// <summary>
@@ -209,8 +240,8 @@ namespace MPF.Core.Data
/// </summary>
public int AaruRereadCount
{
get { return GetInt32Setting(Settings, "AaruRereadCount", 5); }
set { Settings["AaruRereadCount"] = value.ToString(); }
get { return GetInt32Setting(Settings, AaruSettings.RereadCount, AaruSettings.RereadCountDefault); }
set { Settings[AaruSettings.RereadCount] = value.ToString(); }
}
/// <summary>
@@ -218,8 +249,8 @@ namespace MPF.Core.Data
/// </summary>
public bool AaruStripPersonalData
{
get { return GetBooleanSetting(Settings, "AaruStripPersonalData", false); }
set { Settings["AaruStripPersonalData"] = value.ToString(); }
get { return GetBooleanSetting(Settings, AaruSettings.StripPersonalData, AaruSettings.StripPersonalDataDefault); }
set { Settings[AaruSettings.StripPersonalData] = value.ToString(); }
}
#endregion
@@ -231,8 +262,8 @@ namespace MPF.Core.Data
/// </summary>
public bool DICMultiSectorRead
{
get { return GetBooleanSetting(Settings, "DICMultiSectorRead", false); }
set { Settings["DICMultiSectorRead"] = value.ToString(); }
get { return GetBooleanSetting(Settings, DICSettings.MultiSectorRead, DICSettings.MultiSectorReadDefault); }
set { Settings[DICSettings.MultiSectorRead] = value.ToString(); }
}
/// <summary>
@@ -240,8 +271,8 @@ namespace MPF.Core.Data
/// </summary>
public int DICMultiSectorReadValue
{
get { return GetInt32Setting(Settings, "DICMultiSectorReadValue", 0); }
set { Settings["DICMultiSectorReadValue"] = value.ToString(); }
get { return GetInt32Setting(Settings, DICSettings.MultiSectorReadValue, DICSettings.MultiSectorReadValueDefault); }
set { Settings[DICSettings.MultiSectorReadValue] = value.ToString(); }
}
/// <summary>
@@ -254,8 +285,8 @@ namespace MPF.Core.Data
/// </remarks>
public bool DICParanoidMode
{
get { return GetBooleanSetting(Settings, "DICParanoidMode", false); }
set { Settings["DICParanoidMode"] = value.ToString(); }
get { return GetBooleanSetting(Settings, DICSettings.ParanoidMode, DICSettings.ParanoidModeDefault); }
set { Settings[DICSettings.ParanoidMode] = value.ToString(); }
}
/// <summary>
@@ -263,8 +294,8 @@ namespace MPF.Core.Data
/// </summary>
public bool DICQuietMode
{
get { return GetBooleanSetting(Settings, "DICQuietMode", false); }
set { Settings["DICQuietMode"] = value.ToString(); }
get { return GetBooleanSetting(Settings, DICSettings.QuietMode, DICSettings.QuietModeDefault); }
set { Settings[DICSettings.QuietMode] = value.ToString(); }
}
/// <summary>
@@ -272,8 +303,8 @@ namespace MPF.Core.Data
/// </summary>
public int DICRereadCount
{
get { return GetInt32Setting(Settings, "DICRereadCount", 20); }
set { Settings["DICRereadCount"] = value.ToString(); }
get { return GetInt32Setting(Settings, DICSettings.RereadCount, DICSettings.RereadCountDefault); }
set { Settings[DICSettings.RereadCount] = value.ToString(); }
}
/// <summary>
@@ -281,17 +312,8 @@ namespace MPF.Core.Data
/// </summary>
public int DICDVDRereadCount
{
get { return GetInt32Setting(Settings, "DICDVDRereadCount", 10); }
set { Settings["DICDVDRereadCount"] = value.ToString(); }
}
/// <summary>
/// Reset drive after dumping (useful for older drives)
/// </summary>
public bool DICResetDriveAfterDump
{
get { return GetBooleanSetting(Settings, "DICResetDriveAfterDump", false); }
set { Settings["DICResetDriveAfterDump"] = value.ToString(); }
get { return GetInt32Setting(Settings, DICSettings.DVDRereadCount, DICSettings.DVDRereadCountDefault); }
set { Settings[DICSettings.DVDRereadCount] = value.ToString(); }
}
/// <summary>
@@ -299,8 +321,8 @@ namespace MPF.Core.Data
/// </summary>
public bool DICUseCMIFlag
{
get { return GetBooleanSetting(Settings, "DICUseCMIFlag", false); }
set { Settings["DICUseCMIFlag"] = value.ToString(); }
get { return GetBooleanSetting(Settings, DICSettings.UseCMIFlag, DICSettings.UseCMIFlagDefault); }
set { Settings[DICSettings.UseCMIFlag] = value.ToString(); }
}
#endregion
@@ -312,8 +334,17 @@ namespace MPF.Core.Data
/// </summary>
public bool RedumperEnableDebug
{
get { return GetBooleanSetting(Settings, "RedumperEnableDebug", false); }
set { Settings["RedumperEnableDebug"] = value.ToString(); }
get { return GetBooleanSetting(Settings, RedumperSettings.EnableDebug, RedumperSettings.EnableDebugDefault); }
set { Settings[RedumperSettings.EnableDebug] = value.ToString(); }
}
/// <summary>
/// Enable Redumper custom lead-in retries for Plextor drives
/// </summary>
public bool RedumperEnableLeadinRetry
{
get { return GetBooleanSetting(Settings, RedumperSettings.EnableLeadinRetry, RedumperSettings.EnableLeadinRetryDefault); }
set { Settings[RedumperSettings.EnableLeadinRetry] = value.ToString(); }
}
/// <summary>
@@ -321,17 +352,26 @@ namespace MPF.Core.Data
/// </summary>
public bool RedumperEnableVerbose
{
get { return GetBooleanSetting(Settings, "RedumperEnableVerbose", true); }
set { Settings["RedumperEnableVerbose"] = value.ToString(); }
get { return GetBooleanSetting(Settings, RedumperSettings.EnableVerbose, RedumperSettings.EnableVerboseDefault); }
set { Settings[RedumperSettings.EnableVerbose] = value.ToString(); }
}
/// <summary>
/// Enable BE reading by default with Redumper
/// Default number of redumper Plextor leadin retries
/// </summary>
public bool RedumperUseBEReading
public int RedumperLeadinRetryCount
{
get { return GetBooleanSetting(Settings, "RedumperUseBEReading", false); }
set { Settings["RedumperUseBEReading"] = value.ToString(); }
get { return GetInt32Setting(Settings, RedumperSettings.LeadinRetryCount, RedumperSettings.LeadinRetryCountDefault); }
set { Settings[RedumperSettings.LeadinRetryCount] = value.ToString(); }
}
/// <summary>
/// Enable options incompatible with redump submissions
/// </summary>
public bool RedumperNonRedumpMode
{
get { return GetBooleanSetting(Settings, "RedumperNonRedumpMode", false); }
set { Settings["RedumperNonRedumpMode"] = value.ToString(); }
}
/// <summary>
@@ -339,8 +379,40 @@ namespace MPF.Core.Data
/// </summary>
public bool RedumperUseGenericDriveType
{
get { return GetBooleanSetting(Settings, "RedumperUseGenericDriveType", false); }
set { Settings["RedumperUseGenericDriveType"] = value.ToString(); }
get { return GetBooleanSetting(Settings, RedumperSettings.UseGenericDriveType, RedumperSettings.UseGenericDriveTypeDefault); }
set { Settings[RedumperSettings.UseGenericDriveType] = value.ToString(); }
}
/// <summary>
/// Currently selected default redumper read method
/// </summary>
public RedumperReadMethod RedumperReadMethod
{
get
{
var valueString = GetStringSetting(Settings, RedumperSettings.ReadMethod, RedumperSettings.ReadMethodDefault);
return valueString.ToRedumperReadMethod();
}
set
{
Settings[RedumperSettings.ReadMethod] = value.ToString();
}
}
/// <summary>
/// Currently selected default redumper sector order
/// </summary>
public RedumperSectorOrder RedumperSectorOrder
{
get
{
var valueString = GetStringSetting(Settings, RedumperSettings.SectorOrder, RedumperSettings.SectorOrderDefault);
return valueString.ToRedumperSectorOrder();
}
set
{
Settings[RedumperSettings.SectorOrder] = value.ToString();
}
}
/// <summary>
@@ -348,8 +420,8 @@ namespace MPF.Core.Data
/// </summary>
public int RedumperRereadCount
{
get { return GetInt32Setting(Settings, "RedumperRereadCount", 20); }
set { Settings["RedumperRereadCount"] = value.ToString(); }
get { return GetInt32Setting(Settings, RedumperSettings.RereadCount, RedumperSettings.RereadCountDefault); }
set { Settings[RedumperSettings.RereadCount] = value.ToString(); }
}
#endregion
@@ -365,15 +437,6 @@ namespace MPF.Core.Data
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>
/// Add placeholder values in the submission info
/// </summary>
@@ -428,15 +491,6 @@ namespace MPF.Core.Data
set { Settings["ShowDiscEjectReminder"] = value.ToString(); }
}
/// <summary>
/// Eject the disc after dumping
/// </summary>
public bool EjectAfterDump
{
get { return GetBooleanSetting(Settings, "EjectAfterDump", false); }
set { Settings["EjectAfterDump"] = value.ToString(); }
}
/// <summary>
/// Ignore fixed drives when populating the list
/// </summary>
@@ -446,15 +500,6 @@ namespace MPF.Core.Data
set { Settings["IgnoreFixedDrives"] = value.ToString(); }
}
/// <summary>
/// Show dumping tools in their own window instead of in the log
/// </summary>
public bool ToolsInSeparateWindow
{
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>
@@ -649,6 +694,49 @@ namespace MPF.Core.Data
#region Helpers
/// <summary>
/// Get the InternalProgram enum value for a given string
/// </summary>
/// <param name="internalProgram">String value to convert</param>
/// <returns>InternalProgram represented by the string, if possible</returns>
public static InternalProgram ToInternalProgram(string? internalProgram)
{
return (internalProgram?.ToLowerInvariant()) switch
{
// Dumping support
"aaru"
or "chef"
or "dichef"
or "discimagechef" => InternalProgram.Aaru,
"creator"
or "dic"
or "dicreator"
or "discimagecreator" => InternalProgram.DiscImageCreator,
"rd"
or "redumper" => InternalProgram.Redumper,
// Verification support only
"cleanrip"
or "cr" => InternalProgram.CleanRip,
"ps3cfw"
or "ps3"
or "getkey"
or "managunz"
or "multiman" => InternalProgram.PS3CFW,
"uic"
or "umd"
or "umdcreator"
or "umdimagecreator" => InternalProgram.UmdImageCreator,
"xbc"
or "xbox"
or "xbox360"
or "xboxcreator"
or "xboxbackupcreator" => InternalProgram.XboxBackupCreator,
_ => InternalProgram.NONE,
};
}
/// <summary>
/// Get a Boolean setting from a settings, dictionary
/// </summary>

View File

@@ -7,7 +7,7 @@ using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
namespace MPF.Core.Data
namespace MPF.Frontend
{
public sealed class ProcessingQueue<T> : IDisposable
{
@@ -15,43 +15,43 @@ namespace MPF.Core.Data
/// Internal queue to hold data to process
/// </summary>
#if NET20 || NET35
private readonly Queue<T> InternalQueue;
private readonly Queue<T> _internalQueue;
#else
private readonly ConcurrentQueue<T> InternalQueue;
private readonly ConcurrentQueue<T> _internalQueue;
#endif
/// <summary>
/// Custom processing step for dequeued data
/// </summary>
private readonly Action<T> CustomProcessing;
private readonly Action<T> _customProcessing;
/// <summary>
/// Cancellation method for the processing task
/// </summary>
private readonly CancellationTokenSource TokenSource;
private readonly CancellationTokenSource _tokenSource;
public ProcessingQueue(Action<T> customProcessing)
{
#if NET20 || NET35
this.InternalQueue = new Queue<T>();
_internalQueue = new Queue<T>();
#else
this.InternalQueue = new ConcurrentQueue<T>();
_internalQueue = new ConcurrentQueue<T>();
#endif
this.CustomProcessing = customProcessing;
this.TokenSource = new CancellationTokenSource();
_customProcessing = customProcessing;
_tokenSource = new CancellationTokenSource();
#if NET20 || NET35
Task.Run(() => ProcessQueue());
#elif NET40
Task.Factory.StartNew(() => ProcessQueue());
#else
Task.Run(() => ProcessQueue(), this.TokenSource.Token);
Task.Run(() => ProcessQueue(), _tokenSource.Token);
#endif
}
/// <summary>
/// Dispose the current instance
/// </summary>
public void Dispose() => this.TokenSource.Cancel();
public void Dispose() => _tokenSource.Cancel();
/// <summary>
/// Enqueue a new item for processing
@@ -60,8 +60,8 @@ namespace MPF.Core.Data
public void Enqueue(T? item)
{
// Only accept new data when not cancelled
if (item != null && !this.TokenSource.IsCancellationRequested)
this.InternalQueue.Enqueue(item);
if (item != null && !_tokenSource.IsCancellationRequested)
_internalQueue.Enqueue(item);
}
/// <summary>
@@ -73,28 +73,28 @@ namespace MPF.Core.Data
{
// Nothing in the queue means we get to idle
#if NET20 || NET35
if (InternalQueue.Count == 0)
if (_internalQueue.Count == 0)
#else
if (InternalQueue.IsEmpty)
if (_internalQueue.IsEmpty)
#endif
{
if (this.TokenSource.IsCancellationRequested)
if (_tokenSource.IsCancellationRequested)
break;
Thread.Sleep(10);
Thread.Sleep(1);
continue;
}
#if NET20 || NET35
// Get the next item from the queue and invoke the lambda, if possible
this.CustomProcessing?.Invoke(this.InternalQueue.Dequeue());
_customProcessing?.Invoke(_internalQueue.Dequeue());
#else
// Get the next item from the queue
if (!this.InternalQueue.TryDequeue(out var nextItem))
if (!_internalQueue.TryDequeue(out var nextItem))
continue;
// Invoke the lambda, if possible
this.CustomProcessing?.Invoke(nextItem);
_customProcessing?.Invoke(nextItem);
#endif
}
}

View File

@@ -1,59 +1,59 @@
using System;
namespace MPF.Core.Data
namespace MPF.Frontend
{
/// <summary>
/// Generic success/failure result object, with optional message
/// </summary>
public class Result : System.EventArgs
public class ResultEventArgs : EventArgs
{
/// <summary>
/// Internal representation of success
/// </summary>
private readonly bool success;
private readonly bool _success;
/// <summary>
/// Optional message for the result
/// </summary>
public string Message { get; private set; }
public string Message { get; }
private Result(bool success, string message)
private ResultEventArgs(bool success, string message)
{
this.success = success;
this.Message = message;
_success = success;
Message = message;
}
/// <summary>
/// Create a default success result with no message
/// </summary>
public static Result Success() => new(true, "");
public static ResultEventArgs Success() => new(true, string.Empty);
/// <summary>
/// Create a success result with a custom message
/// </summary>
/// <param name="message">String to add as a message</param>
public static Result Success(string? message) => new(true, message ?? string.Empty);
public static ResultEventArgs Success(string? message) => new(true, message ?? string.Empty);
/// <summary>
/// Create a default failure result with no message
/// </summary>
/// <returns></returns>
public static Result Failure() => new(false, "");
public static ResultEventArgs Failure() => new(false, string.Empty);
/// <summary>
/// Create a failure result with a custom message
/// </summary>
/// <param name="message">String to add as a message</param>
public static Result Failure(string? message) => new(false, message ?? string.Empty);
internal static Result Success(object value)
{
throw new NotImplementedException();
}
public static ResultEventArgs Failure(string? message) => new(false, message ?? string.Empty);
/// <summary>
/// Results can be compared to boolean values based on the success value
/// </summary>
public static implicit operator bool(Result result) => result.success;
public static implicit operator bool(ResultEventArgs result) => result._success;
/// <summary>
/// Results can be compared to boolean values based on the success value
/// </summary>
public static implicit operator ResultEventArgs(bool bval) => new(bval, string.Empty);
}
}

View File

@@ -0,0 +1,52 @@
using System;
using System.Text;
namespace MPF.Frontend
{
/// <summary>
/// String wrapper for event arguments
/// </summary>
public class StringEventArgs : EventArgs
{
/// <summary>
/// String represented by the event arguments
/// </summary>
private readonly string _value;
/// <summary>
/// Constructor for string values
/// </summary>
public StringEventArgs(string? value)
{
_value = value ?? string.Empty;
}
/// <summary>
/// Constructor for StringBuilder values
/// </summary>
public StringEventArgs(StringBuilder? value)
{
_value = value?.ToString() ?? string.Empty;
}
/// <summary>
/// Event arguments are just the value of the string contained within
/// </summary>
public static implicit operator string(StringEventArgs args) => args._value;
/// <summary>
/// Event arguments are just the value of the string contained within
/// </summary>
public static implicit operator StringBuilder(StringEventArgs args) => new(args._value);
/// <summary>
/// Event arguments are just the value of the string contained within
/// </summary>
public static implicit operator StringEventArgs(string? str) => new(str);
/// <summary>
/// Event arguments are just the value of the string contained within
/// </summary>
public static implicit operator StringEventArgs(StringBuilder? sb) => new(sb);
}
}

View File

@@ -0,0 +1,649 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using SabreTools.RedumpLib.Data;
namespace MPF.Frontend.Tools
{
public static class FrontendTool
{
#region Information Extraction
/// <summary>
/// Get the default speed for a given media type from the supplied options
/// </summary>
public static int GetDefaultSpeedForMediaType(MediaType? mediaType, Options options)
{
return mediaType switch
{
// CD dump speed
MediaType.CDROM => options.PreferredDumpSpeedCD,
MediaType.GDROM => options.PreferredDumpSpeedCD,
// DVD dump speed
MediaType.DVD => options.PreferredDumpSpeedDVD,
MediaType.NintendoGameCubeGameDisc => options.PreferredDumpSpeedDVD,
MediaType.NintendoWiiOpticalDisc => options.PreferredDumpSpeedDVD,
// HD-DVD dump speed
MediaType.HDDVD => options.PreferredDumpSpeedHDDVD,
// BD dump speed
MediaType.BluRay => options.PreferredDumpSpeedBD,
// Default
_ => options.PreferredDumpSpeedCD,
};
}
/// <summary>
/// Get the current system from the drive volume label
/// </summary>
/// <returns>The system based on volume label, null if none detected</returns>
public static RedumpSystem? GetRedumpSystemFromVolumeLabel(string? volumeLabel)
{
// If the volume label is empty, we can't do anything
if (string.IsNullOrEmpty(volumeLabel))
return null;
// Audio CD
if (volumeLabel!.Equals("Audio CD", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.AudioCD;
// Microsoft Xbox
if (volumeLabel.Equals("SEP13011042", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.MicrosoftXbox;
else if (volumeLabel.Equals("SEP13011042072", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.MicrosoftXbox;
// Microsoft Xbox 360
if (volumeLabel.Equals("XBOX360", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.MicrosoftXbox360;
else if (volumeLabel.Equals("XGD2DVD_NTSC", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.MicrosoftXbox360;
// Microsoft Xbox 360 - Too overly broad even if a lot of discs use this
//if (volumeLabel.Equals("CD_ROM", StringComparison.OrdinalIgnoreCase))
// return RedumpSystem.MicrosoftXbox360; // Also for Xbox One?
//if (volumeLabel.Equals("DVD_ROM", StringComparison.OrdinalIgnoreCase))
// return RedumpSystem.MicrosoftXbox360;
// Sega Mega-CD / Sega-CD
if (volumeLabel.Equals("Sega_CD", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.SegaMegaCDSegaCD;
// Sony PlayStation 3
if (volumeLabel.Equals("PS3VOLUME", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.SonyPlayStation3;
// Sony PlayStation 4
if (volumeLabel.Equals("PS4VOLUME", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.SonyPlayStation4;
// Sony PlayStation 5
if (volumeLabel.Equals("PS5VOLUME", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.SonyPlayStation5;
return null;
}
#endregion
#region Normalization
/// <summary>
/// Adjust a disc title so that it will be processed correctly by Redump
/// </summary>
/// <param name="title">Existing title to potentially reformat</param>
/// <param name="languages">Array of languages to use for assuming articles</param>
/// <returns>The reformatted title</returns>
public static string? NormalizeDiscTitle(string? title, Language?[]? languages)
{
// If we have no set languages, then assume English
if (languages == null || languages.Length == 0)
languages = [Language.English];
// Loop through all of the given languages
foreach (var language in languages)
{
// If the new title is different, assume it was normalized and return it
string? newTitle = NormalizeDiscTitle(title, language);
if (newTitle != title)
return newTitle;
}
// If we didn't already try English, try it now
if (!languages.Contains(Language.English))
return NormalizeDiscTitle(title, Language.English);
// If all fails, then the title didn't need normalization
return title;
}
/// <summary>
/// Adjust a disc title so that it will be processed correctly by Redump
/// </summary>
/// <param name="title">Existing title to potentially reformat</param>
/// <param name="language">Language to use for assuming articles</param>
/// <returns>The reformatted title</returns>
/// <remarks>
/// If the language of the title is unknown or if it's multilingual,
/// pass in Language.English for standardized coverage.
/// </remarks>
public static string? NormalizeDiscTitle(string? title, Language? language)
{
// If we have an invalid title, just return it as-is
if (string.IsNullOrEmpty(title))
return title;
// If we have an invalid language, assume Language.English
if (language == null)
language = Language.English;
// Get the title split into parts
string[] splitTitle = title!.Split(' ').Where(s => !string.IsNullOrEmpty(s)).ToArray();
// If we only have one part, we can't do anything
if (splitTitle.Length <= 1)
return title;
// Determine if we have a definite or indefinite article as the first item
string firstItem = splitTitle[0];
switch (firstItem.ToLowerInvariant())
{
// Latin script articles
case "'n"
when language is Language.Manx:
case "a"
when language is Language.English
|| language is Language.Hungarian
|| language is Language.Portuguese
|| language is Language.Scots:
case "a'"
when language is Language.English
|| language is Language.Hungarian
|| language is Language.Irish
|| language is Language.Gaelic: // Scottish Gaelic
case "al"
when language is Language.Breton:
case "am"
when language is Language.Gaelic: // Scottish Gaelic
case "an"
when language is Language.Breton
|| language is Language.Cornish
|| language is Language.English
|| language is Language.Irish
|| language is Language.Gaelic: // Scottish Gaelic
case "anek"
when language is Language.Nepali:
case "ar"
when language is Language.Breton:
case "az"
when language is Language.Hungarian:
case "ān"
when language is Language.Persian:
case "as"
when language is Language.Portuguese:
case "d'"
when language is Language.Luxembourgish:
case "das"
when language is Language.German:
case "dat"
when language is Language.Luxembourgish:
case "de"
when language is Language.Dutch:
case "déi"
when language is Language.Luxembourgish:
case "dem"
when language is Language.German
|| language is Language.Luxembourgish:
case "den"
when language is Language.Dutch
|| language is Language.German
|| language is Language.Luxembourgish:
case "der"
when language is Language.Dutch
|| language is Language.German
|| language is Language.Luxembourgish:
case "des"
when language is Language.Dutch
|| language is Language.French
|| language is Language.German:
case "die"
when language is Language.Afrikaans
|| language is Language.German:
case "du"
when language is Language.French:
case "e"
when language is Language.Papiamento:
case "een"
when language is Language.Dutch:
case "egy"
when language is Language.Hungarian:
case "ei"
when language is Language.Norwegian:
case "ein"
when language is Language.German
|| language is Language.Norwegian:
case "eine"
when language is Language.German:
case "einem"
when language is Language.German:
case "einen"
when language is Language.German:
case "einer"
when language is Language.German:
case "eines"
when language is Language.German:
case "eit"
when language is Language.Norwegian:
case "ek"
when language is Language.Nepali:
case "el"
when language is Language.Arabic
|| language is Language.Catalan
|| language is Language.Spanish:
case "els"
when language is Language.Catalan:
case "en"
when language is Language.Danish
|| language is Language.Luxembourgish
|| language is Language.Norwegian
|| language is Language.Swedish:
case "eng"
when language is Language.Luxembourgish:
case "engem"
when language is Language.Luxembourgish:
case "enger"
when language is Language.Luxembourgish:
case "es"
when language is Language.Catalan:
case "et"
when language is Language.Danish
|| language is Language.Norwegian:
case "ett"
when language is Language.Swedish:
case "euta"
when language is Language.Nepali:
case "euti"
when language is Language.Nepali:
case "gli"
when language is Language.Italian:
case "he"
when language is Language.Hawaiian
|| language is Language.Maori:
case "het"
when language is Language.Dutch:
case "i"
when language is Language.Italian
|| language is Language.Khasi:
case "il"
when language is Language.Italian:
case "in"
when language is Language.Persian:
case "ka"
when language is Language.Hawaiian
|| language is Language.Khasi:
case "ke"
when language is Language.Hawaiian:
case "ki"
when language is Language.Khasi:
case "kunai"
when language is Language.Nepali:
case "l'"
when language is Language.Catalan
|| language is Language.French
|| language is Language.Italian:
case "la"
when language is Language.Catalan
|| language is Language.Esperanto
|| language is Language.French
|| language is Language.Italian
|| language is Language.Spanish:
case "las"
when language is Language.Spanish:
case "le"
when language is Language.French
|| language is Language.Interlingua
|| language is Language.Italian:
case "les"
when language is Language.Catalan
|| language is Language.French:
case "lo"
when language is Language.Catalan
|| language is Language.Italian
|| language is Language.Spanish:
case "los"
when language is Language.Catalan
|| language is Language.Spanish:
case "na"
when language is Language.Irish
|| language is Language.Gaelic: // Scottish Gaelic
case "nam"
when language is Language.Gaelic: // Scottish Gaelic
case "nan"
when language is Language.Gaelic: // Scottish Gaelic
case "nā"
when language is Language.Hawaiian:
case "ngā"
when language is Language.Maori:
case "niște"
when language is Language.Romanian:
case "ny"
when language is Language.Manx:
case "o"
when language is Language.Portuguese
|| language is Language.Romanian:
case "os"
when language is Language.Portuguese:
case "sa"
when language is Language.Catalan:
case "sang"
when language is Language.Malay:
case "se"
when language is Language.Finnish:
case "ses"
when language is Language.Catalan:
case "si"
when language is Language.Malay:
case "te"
when language is Language.Maori:
case "the"
when language is Language.English
|| language is Language.Scots:
case "u"
when language is Language.Khasi:
case "ul"
when language is Language.Breton:
case "um"
when language is Language.Portuguese:
case "uma"
when language is Language.Portuguese:
case "umas"
when language is Language.Portuguese:
case "un"
when language is Language.Breton
|| language is Language.Catalan
|| language is Language.French
|| language is Language.Interlingua
|| language is Language.Italian
|| language is Language.Papiamento
|| language is Language.Romanian
|| language is Language.Spanish:
case "un'"
when language is Language.Italian:
case "una"
when language is Language.Catalan
|| language is Language.Italian:
case "unas"
when language is Language.Spanish:
case "une"
when language is Language.French:
case "uno"
when language is Language.Italian:
case "unos"
when language is Language.Spanish:
case "uns"
when language is Language.Catalan
|| language is Language.Portuguese:
case "unei"
when language is Language.Romanian:
case "unes"
when language is Language.Catalan:
case "unor"
when language is Language.Romanian:
case "unui"
when language is Language.Romanian:
case "ur"
when language is Language.Breton:
case "y"
when language is Language.Manx
|| language is Language.Welsh:
case "ye"
when language is Language.Persian:
case "yek"
when language is Language.Persian:
case "yn"
when language is Language.Manx:
case "yr"
when language is Language.Welsh:
// Non-latin script articles
case "ο"
when language is Language.Greek:
case "η"
when language is Language.Greek:
case "το"
when language is Language.Greek:
case "οι"
when language is Language.Greek:
case "τα"
when language is Language.Greek:
case "ένας"
when language is Language.Greek:
case "μια"
when language is Language.Greek:
case "ένα"
when language is Language.Greek:
case "еден"
when language is Language.Macedonian:
case "една"
when language is Language.Macedonian:
case "едно"
when language is Language.Macedonian:
case "едни"
when language is Language.Macedonian:
case "एउटा"
when language is Language.Nepali:
case "एउटी"
when language is Language.Nepali:
case "एक"
when language is Language.Nepali:
case "अनेक"
when language is Language.Nepali:
case "कुनै"
when language is Language.Nepali:
case "דער"
when language is Language.Yiddish:
case "די"
when language is Language.Yiddish:
case "דאָס"
when language is Language.Yiddish:
case "דעם"
when language is Language.Yiddish:
case "אַ"
when language is Language.Yiddish:
case "אַן"
when language is Language.Yiddish:
break;
// Otherwise, just return it as-is
default:
return title;
}
// Insert the first item if we have a `:` or `-`
bool itemInserted = false;
var newTitleBuilder = new StringBuilder();
for (int i = 1; i < splitTitle.Length; i++)
{
string segment = splitTitle[i];
if (!itemInserted && segment == ":")
{
itemInserted = true;
newTitleBuilder.Append($", {firstItem} :");
}
else if (!itemInserted && segment == "-")
{
itemInserted = true;
newTitleBuilder.Append($", {firstItem} -");
}
else if (!itemInserted && segment.EndsWith(":"))
{
itemInserted = true;
newTitleBuilder.Append($" {segment.Substring(0, segment.Length - 1)}, {firstItem}:");
}
else if (!itemInserted && segment.EndsWith("-"))
{
itemInserted = true;
newTitleBuilder.Append($" {segment.Substring(0, segment.Length - 1)}, {firstItem}-");
}
else
{
newTitleBuilder.Append($" {segment}");
}
}
// If we didn't insert the item yet, add it to the end
string newTitle = newTitleBuilder.ToString().Trim();
if (!itemInserted)
newTitle = $"{newTitle}, {firstItem}";
return newTitle;
}
/// <summary>
/// Normalize a split set of paths
/// </summary>
/// <param name="path">Path value to normalize</param>
public static string NormalizeOutputPaths(string? path, bool getFullPath)
{
// The easy way
try
{
// If we have an invalid path
if (string.IsNullOrEmpty(path))
return string.Empty;
// Remove quotes and angle brackets from path
path = path!.Replace("\"", string.Empty);
path = path!.Replace("<", string.Empty);
path = path!.Replace(">", string.Empty);
// Try getting the combined path and returning that directly
string fullPath = getFullPath ? Path.GetFullPath(path) : path;
var fullDirectory = Path.GetDirectoryName(fullPath);
string fullFile = Path.GetFileName(fullPath);
// Remove invalid path characters
if (fullDirectory != null)
{
foreach (char c in Path.GetInvalidPathChars())
fullDirectory = fullDirectory.Replace(c, '_');
}
// Remove invalid filename characters
foreach (char c in Path.GetInvalidFileNameChars())
fullFile = fullFile.Replace(c, '_');
if (string.IsNullOrEmpty(fullDirectory))
return fullFile;
else
return Path.Combine(fullDirectory, fullFile);
}
catch { }
return path ?? string.Empty;
}
#endregion
#region Versioning
/// <summary>
/// Check for a new MPF version
/// </summary>
/// <returns>
/// Bool representing if the values are different.
/// String representing the message to display the the user.
/// String representing the new release URL.
/// </returns>
public static (bool different, string message, string? url) CheckForNewVersion()
{
try
{
// Get current assembly version
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
var (tag, url) = GetRemoteVersionAndUrl();
bool different = version != tag && tag != null;
string message = $"Local version: {version}"
+ $"{Environment.NewLine}Remote version: {tag}"
+ (different
? $"{Environment.NewLine}The update URL has been added copied to your clipboard"
: $"{Environment.NewLine}You have the newest version!");
return (different, message, url);
}
catch (Exception ex)
{
return (false, ex.ToString(), null);
}
}
/// <summary>
/// Get the current informational version formatted as a string
/// </summary>
public static string? GetCurrentVersion()
{
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>
private static (string? tag, string? url) GetRemoteVersionAndUrl()
{
#if NET20 || NET35 || NET40
// Not supported in .NET Frameworks 2.0, 3.5, or 4.0
return (null, null);
#else
using var hc = new System.Net.Http.HttpClient();
#if NET452
System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12;
#endif
// TODO: Figure out a better way than having this hardcoded...
string url = "https://api.github.com/repos/SabreTools/MPF/releases/latest";
var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, url);
message.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:64.0) Gecko/20100101 Firefox/64.0");
var latestReleaseJsonString = hc.SendAsync(message)?.ConfigureAwait(false).GetAwaiter().GetResult()
.Content?.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult();
if (latestReleaseJsonString == null)
return (null, null);
var latestReleaseJson = Newtonsoft.Json.Linq.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,159 +1,112 @@
using System;
#if NET20 || NET35
using System.Collections.Generic;
#else
using System.Collections.Concurrent;
#endif
using System.IO;
using System.Reflection;
using MPF.Core.Data;
using Newtonsoft.Json;
using SabreTools.RedumpLib.Data;
namespace MPF.Core.Converters
namespace MPF.Frontend.Tools
{
public static class EnumConverter
public static class OptionsLoader
{
#region Cross-enumeration conversions
private const string ConfigurationPath = "config.json";
#region Arguments
/// <summary>
/// Convert drive type to internal version, if possible
/// Process any standalone arguments for the program
/// </summary>
/// <param name="driveType">DriveType value to check</param>
/// <returns>InternalDriveType, if possible, null on error</returns>
public static InternalDriveType? ToInternalDriveType(this DriveType driveType)
/// <returns>True if one of the arguments was processed, false otherwise</returns>
public static bool? ProcessStandaloneArguments(string[] args)
{
return driveType switch
// Help options
if (args.Length == 0 || args[0] == "-h" || args[0] == "-?")
return false;
// List options
if (args[0] == "-lc" || args[0] == "--listcodes")
{
DriveType.CDRom => (InternalDriveType?)InternalDriveType.Optical,
DriveType.Fixed => (InternalDriveType?)InternalDriveType.HardDisk,
DriveType.Removable => (InternalDriveType?)InternalDriveType.Removable,
_ => null,
};
}
#endregion
#region Convert to Long Name
/// <summary>
/// Long name method cache
/// </summary>
#if NET20 || NET35
private static readonly Dictionary<Type, MethodInfo?> LongNameMethods = [];
#else
private static readonly ConcurrentDictionary<Type, MethodInfo?> LongNameMethods = [];
#endif
/// <summary>
/// Get the string representation of a generic enumerable value
/// </summary>
/// <param name="value">Enum value to convert</param>
/// <returns>String representation of that value if possible, empty string on error</returns>
public static string GetLongName(Enum value)
{
try
{
var sourceType = value.GetType();
sourceType = Nullable.GetUnderlyingType(sourceType) ?? sourceType;
if (!LongNameMethods.TryGetValue(sourceType, out var method))
Console.WriteLine("Supported Site Codes:");
foreach (string siteCode in Extensions.ListSiteCodes())
{
method = typeof(Extensions).GetMethod("LongName", [typeof(Nullable<>).MakeGenericType(sourceType)]);
method ??= typeof(EnumConverter).GetMethod("LongName", [typeof(Nullable<>).MakeGenericType(sourceType)]);
#if NET20 || NET35
LongNameMethods[sourceType] = method;
#else
LongNameMethods.TryAdd(sourceType, method);
#endif
Console.WriteLine(siteCode);
}
if (method != null)
return method.Invoke(null, new[] { value }) as string ?? string.Empty;
else
return string.Empty;
Console.ReadLine();
return true;
}
catch
else if (args[0] == "-lm" || args[0] == "--listmedia")
{
// Converter is not implemented for the given type
return string.Empty;
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 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>
/// Get the string representation of the InternalProgram enum values
/// Process common arguments for all functionality
/// </summary>
/// <param name="prog">InternalProgram value to convert</param>
/// <returns>String representing the value, if possible</returns>
public static string LongName(this InternalProgram? prog)
/// <returns>True if all arguments pass, false otherwise</returns>
public static (bool, MediaType, RedumpSystem?, string?) ProcessCommonArguments(string[] args)
{
return (prog) switch
{
#region Dumping support
// All other use requires at least 3 arguments
if (args.Length < 3)
return (false, MediaType.NONE, null, "Invalid number of arguments");
InternalProgram.Aaru => "Aaru",
InternalProgram.DiscImageCreator => "DiscImageCreator",
InternalProgram.Redumper => "Redumper",
// Check the MediaType
var mediaType = ToMediaType(args[0].Trim('"'));
if (mediaType == MediaType.NONE)
return (false, MediaType.NONE, null, $"{args[0]} is not a recognized media type");
#endregion
// 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");
#region Verification support only
InternalProgram.CleanRip => "CleanRip",
InternalProgram.DCDumper => "DCDumper",
InternalProgram.PS3CFW => "PS3 CFW",
InternalProgram.UmdImageCreator => "UmdImageCreator",
#endregion
InternalProgram.NONE => "Unknown",
_ => "Unknown",
};
return (true, mediaType, knownSystem, null);
}
#endregion
#region Convert From String
/// <summary>
/// Get the InternalProgram enum value for a given string
/// List all programs with their short usable names
/// </summary>
/// <param name="internalProgram">String value to convert</param>
/// <returns>InternalProgram represented by the string, if possible</returns>
public static InternalProgram ToInternalProgram(string? internalProgram)
private static List<string> ListPrograms()
{
return (internalProgram?.ToLowerInvariant()) switch
var programs = new List<string>();
foreach (var val in Enum.GetValues(typeof(InternalProgram)))
{
// Dumping support
"aaru"
or "chef"
or "dichef"
or "discimagechef" => InternalProgram.Aaru,
"creator"
or "dic"
or "dicreator"
or "discimagecreator" => InternalProgram.DiscImageCreator,
"rd"
or "redumper" => InternalProgram.Redumper,
if (((InternalProgram)val!) == InternalProgram.NONE)
continue;
// Verification support only
"cleanrip"
or "cr" => InternalProgram.CleanRip,
"dc"
or "dcd"
or "dcdumper" => InternalProgram.DCDumper,
"ps3cfw"
or "ps3"
or "getkey"
or "managunz"
or "multiman" => InternalProgram.PS3CFW,
"uic"
or "umd"
or "umdcreator"
or "umdimagecreator" => InternalProgram.UmdImageCreator,
programs.Add($"{((InternalProgram?)val).LongName()}");
}
_ => InternalProgram.NONE,
};
return programs;
}
/// <summary>
@@ -161,7 +114,7 @@ namespace MPF.Core.Converters
/// </summary>
/// <param name="type">String value to convert</param>
/// <returns>MediaType represented by the string, if possible</returns>
public static MediaType ToMediaType(string type)
private static MediaType ToMediaType(string type)
{
return (type.ToLowerInvariant()) switch
{
@@ -300,5 +253,62 @@ namespace MPF.Core.Converters
}
#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).Dispose();
return new Options();
}
var serializer = JsonSerializer.Create();
var stream = File.Open(ConfigurationPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
var reader = new StreamReader(stream);
var settings = serializer.Deserialize(reader, typeof(Dictionary<string, string?>)) as Dictionary<string, string?>;
reader.Dispose();
return new Options(settings);
}
/// <summary>
/// Save the current set of options to the application configuration
/// </summary>
public static void SaveToConfig(Options options, bool saveDefault = false)
{
// If default values should be saved as well
if (saveDefault)
{
PropertyInfo[] properties = typeof(Options).GetProperties();
foreach (var property in properties)
{
// Skip dictionary properties
if (property.Name == "Item")
continue;
// Skip non-option properties
if (property.Name == "Settings" || property.Name == "HasRedumpLogin")
continue;
var val = property.GetValue(options, null);
property.SetValue(options, val, null);
}
}
// Handle a very strange edge case
if (!File.Exists(ConfigurationPath))
File.Create(ConfigurationPath).Dispose();
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

@@ -4,14 +4,34 @@ using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using psxt001z;
using BinaryObjectScanner;
#pragma warning disable SYSLIB1045 // Convert to 'GeneratedRegexAttribute'.
namespace MPF.Core
namespace MPF.Frontend.Tools
{
public static class Protection
public static class ProtectionTool
{
/// <summary>
/// Get the current detected copy protection(s), if possible
/// </summary>
/// <param name="drive">Drive object representing the current drive</param>
/// <param name="options">Options object that determines what to scan</param>
/// <param name="progress">Optional progress callback</param>
/// <returns>Detected copy protection(s) if possible, null on error</returns>
public static async Task<(string?, Dictionary<string, List<string>>?)> GetCopyProtection(Drive? drive,
Frontend.Options options,
IProgress<ProtectionProgress>? progress = null)
{
if (options.ScanForProtection && drive?.Name != null)
{
(var protection, _) = await RunProtectionScanOnPath(drive.Name, options, progress);
return (FormatProtections(protection), protection);
}
return ("(CHECK WITH PROTECTIONID)", null);
}
/// <summary>
/// Run protection scan on a given path
/// </summary>
@@ -19,14 +39,16 @@ namespace MPF.Core
/// <param name="options">Options object that determines what to scan</param>
/// <param name="progress">Optional progress callback</param>
/// <returns>Set of all detected copy protections with an optional error string</returns>
public static async Task<(Dictionary<string, List<string>>?, string?)> RunProtectionScanOnPath(string path, Data.Options options, IProgress<BinaryObjectScanner.ProtectionProgress>? progress = null)
public static async Task<(Dictionary<string, List<string>>?, string?)> RunProtectionScanOnPath(string path,
Frontend.Options options,
IProgress<ProtectionProgress>? progress = null)
{
try
{
#if NET40
var found = await Task.Factory.StartNew(() =>
{
var scanner = new BinaryObjectScanner.Scanner(
var scanner = new Scanner(
options.ScanArchivesForProtection,
scanContents: true, // Hardcoded value to avoid issues
scanGameEngines: false, // Hardcoded value to avoid issues
@@ -40,7 +62,7 @@ namespace MPF.Core
#else
var found = await Task.Run(() =>
{
var scanner = new BinaryObjectScanner.Scanner(
var scanner = new Scanner(
options.ScanArchivesForProtection,
scanContents: true, // Hardcoded value to avoid issues
scanGameEngines: false, // Hardcoded value to avoid issues
@@ -168,20 +190,6 @@ namespace MPF.Core
#endif
}
/// <summary>
/// Get if LibCrypt data is detected in the subchannel file, if possible
/// </summary>
/// <param name="sub">.sub file location</param>
/// <returns>Status of the LibCrypt data, if possible</returns>
public static bool? GetLibCryptDetected(string sub)
{
// If the file doesn't exist, we can't get info from it
if (!File.Exists(sub))
return null;
return LibCrypt.DetectLibCrypt([sub]);
}
/// <summary>
/// Sanitize unnecessary protection duplication from output
/// </summary>
@@ -296,7 +304,7 @@ namespace MPF.Core
// SafeDisc
if (foundProtections.Any(p => p.StartsWith("SafeDisc")))
{
if (foundProtections.Any(p => Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}", RegexOptions.Compiled)))
if (foundProtections.Any(p => Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}", RegexOptions.Compiled) && !p.StartsWith("Macrovision Protection File")))
{
foundProtections = foundProtections.Where(p => !p.StartsWith("Macrovision Protected Application"))
.Where(p => !p.StartsWith("Macrovision Protection File"))
@@ -307,7 +315,7 @@ namespace MPF.Core
.Where(p => p != "SafeDisc 1/Lite")
.Where(p => p != "SafeDisc 2+");
}
else if (foundProtections.Any(p => Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}-[0-9]\.[0-9]{2}\.[0-9]{3}", RegexOptions.Compiled)))
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) && !p.StartsWith("Macrovision Protection File")))
{
foundProtections = foundProtections.Where(p => !p.StartsWith("Macrovision Protected Application"))
.Where(p => !p.StartsWith("Macrovision Protection File"))
@@ -317,7 +325,7 @@ namespace MPF.Core
.Where(p => p != "SafeDisc 1/Lite")
.Where(p => p != "SafeDisc 2+");
}
else if (foundProtections.Any(p => Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}/+", RegexOptions.Compiled)))
else if (foundProtections.Any(p => Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}/+", RegexOptions.Compiled) && !p.StartsWith("Macrovision Protection File")))
{
foundProtections = foundProtections.Where(p => !p.StartsWith("Macrovision Protected Application"))
.Where(p => !p.StartsWith("Macrovision Protection File"))

View File

@@ -0,0 +1,920 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using BinaryObjectScanner;
using MPF.Processors;
using SabreTools.RedumpLib;
using SabreTools.RedumpLib.Data;
using SabreTools.RedumpLib.Web;
namespace MPF.Frontend.Tools
{
/// <summary>
/// Generator for SubmissionInfo objects
/// </summary>
internal static class SubmissionGenerator
{
#region Constants
private const string RequiredValue = "(REQUIRED)";
private const string RequiredIfExistsValue = "(REQUIRED, IF EXISTS)";
private const string OptionalValue = "(OPTIONAL)";
#endregion
#region Extraction and Filling
/// <summary>
/// Extract all of the possible information from a given input combination
/// </summary>
/// <param name="outputPath">Output path to write to</param>
/// <param name="drive">Drive object representing the current drive</param>
/// <param name="system">Currently selected system</param>
/// <param name="mediaType">Currently selected media type</param>
/// <param name="options">Options object representing user-defined options</param>
/// <param name="processor">Processor object representing how to process the outputs</param>
/// <param name="resultProgress">Optional result progress callback</param>
/// <param name="protectionProgress">Optional protection progress callback</param>
/// <returns>SubmissionInfo populated based on outputs, null on error</returns>
public static async Task<SubmissionInfo?> ExtractOutputInformation(
string outputPath,
Drive? drive,
RedumpSystem? system,
MediaType? mediaType,
Frontend.Options options,
BaseProcessor processor,
IProgress<ResultEventArgs>? resultProgress = null,
IProgress<ProtectionProgress>? protectionProgress = null)
{
// Ensure the current disc combination should exist
if (!system.MediaTypes().Contains(mediaType))
return null;
// Split the output path for easier use
var outputDirectory = Path.GetDirectoryName(outputPath);
string outputFilename = Path.GetFileName(outputPath);
// Check that all of the relevant files are there
(bool foundFiles, List<string> missingFiles) = processor.FoundAllFiles(outputDirectory, outputFilename, false);
if (!foundFiles)
{
resultProgress?.Report(ResultEventArgs.Failure($"There were files missing from the output:\n{string.Join("\n", [.. missingFiles])}"));
resultProgress?.Report(ResultEventArgs.Failure($"This may indicate an issue with the hardware or media, including unsupported devices.\nPlease see dumping program documentation for more details."));
return null;
}
// Sanitize the output filename to strip off any potential extension
outputFilename = Path.GetFileNameWithoutExtension(outputFilename);
// Create the SubmissionInfo object with all user-inputted values by default
string combinedBase;
if (string.IsNullOrEmpty(outputDirectory))
combinedBase = outputFilename;
else
combinedBase = Path.Combine(outputDirectory, outputFilename);
// Create the default submission info
SubmissionInfo info = CreateDefaultSubmissionInfo(processor, system, mediaType, options.AddPlaceholders);
// Get specific tool output handling
processor?.GenerateSubmissionInfo(info, combinedBase, options.EnableRedumpCompatibility);
if (options.IncludeArtifacts)
processor?.GenerateArtifacts(info, combinedBase);
// Get a list of matching IDs for each line in the DAT
if (!string.IsNullOrEmpty(info.TracksAndWriteOffsets!.ClrMameProData) && options.HasRedumpLogin)
#if NET40
_ = FillFromRedump(options, info, resultProgress);
#else
_ = await FillFromRedump(options, info, resultProgress);
#endif
// If we have both ClrMamePro and Size and Checksums data, remove the ClrMamePro
if (!string.IsNullOrEmpty(info.SizeAndChecksums?.CRC32))
info.TracksAndWriteOffsets.ClrMameProData = null;
// Add the volume label to comments, if possible or necessary
string? volLabels = FormatVolumeLabels(drive?.VolumeLabel, processor?.VolumeLabels);
if (volLabels != null)
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.VolumeLabel] = volLabels;
// Extract info based generically on MediaType
ProcessMediaType(info, mediaType, options.AddPlaceholders);
// Extract info based specifically on RedumpSystem
ProcessSystem(info, system, drive, options.AddPlaceholders, processor is DiscImageCreator, combinedBase);
// Run anti-modchip check, if necessary
if (drive != null && SupportsAntiModchipScans(system) && info.CopyProtection!.AntiModchip == YesNo.NULL)
{
resultProgress?.Report(ResultEventArgs.Success("Checking for anti-modchip strings... this might take a while!"));
info.CopyProtection.AntiModchip = await ProtectionTool.GetPlayStationAntiModchipDetected(drive?.Name) ? YesNo.Yes : YesNo.No;
resultProgress?.Report(ResultEventArgs.Success("Anti-modchip string scan complete!"));
}
// Run copy protection, if possible or necessary
if (SupportsCopyProtectionScans(system))
{
resultProgress?.Report(ResultEventArgs.Success("Running copy protection scan... this might take a while!"));
var (protectionString, fullProtections) = await ProtectionTool.GetCopyProtection(drive, options, protectionProgress);
info.CopyProtection!.Protection += protectionString;
info.CopyProtection.FullProtections = fullProtections as Dictionary<string, List<string>?> ?? [];
resultProgress?.Report(ResultEventArgs.Success("Copy protection scan complete!"));
}
// Set fields that may have automatic filling otherwise
info.CommonDiscInfo!.Category ??= DiscCategory.Games;
info.VersionAndEditions!.Version ??= options.AddPlaceholders ? RequiredIfExistsValue : string.Empty;
// Comments and contents have odd handling
if (string.IsNullOrEmpty(info.CommonDiscInfo.Comments))
info.CommonDiscInfo.Comments = options.AddPlaceholders ? OptionalValue : string.Empty;
if (string.IsNullOrEmpty(info.CommonDiscInfo.Contents))
info.CommonDiscInfo.Contents = options.AddPlaceholders ? OptionalValue : string.Empty;
// Normalize the disc type with all current information
Validator.NormalizeDiscType(info);
return info;
}
/// <summary>
/// Fill in a SubmissionInfo object from Redump, if possible
/// </summary>
/// <param name="options">Options object representing user-defined options</param>
/// <param name="info">Existing SubmissionInfo object to fill</param>
/// <param name="resultProgress">Optional result progress callback</param>
public async static Task<bool> FillFromRedump(Frontend.Options options, SubmissionInfo info, IProgress<ResultEventArgs>? resultProgress = null)
{
// If no username is provided
if (string.IsNullOrEmpty(options.RedumpUsername) || string.IsNullOrEmpty(options.RedumpPassword))
return false;
// Set the current dumper based on username
info.DumpersAndStatus ??= new DumpersAndStatusSection();
info.DumpersAndStatus.Dumpers = [options.RedumpUsername!];
info.PartiallyMatchedIDs = [];
// Login to Redump
var wc = new RedumpClient();
bool? loggedIn = await wc.Login(options.RedumpUsername ?? string.Empty, options.RedumpPassword ?? string.Empty);
if (loggedIn == null)
{
resultProgress?.Report(ResultEventArgs.Failure("There was an unknown error connecting to Redump"));
return false;
}
else if (loggedIn == false)
{
// Don't log the as a failure or error
return false;
}
// Setup the checks
bool allFound = true;
List<int[]> foundIdSets = [];
// Loop through all of the hashdata to find matching IDs
resultProgress?.Report(ResultEventArgs.Success("Finding disc matches on Redump..."));
var splitData = info.TracksAndWriteOffsets?.ClrMameProData?.TrimEnd('\n')?.Split('\n');
int trackCount = splitData?.Length ?? 0;
foreach (string hashData in splitData ?? [])
{
// Catch any errant blank lines
if (string.IsNullOrEmpty(hashData))
{
trackCount--;
resultProgress?.Report(ResultEventArgs.Success("Blank line found, skipping!"));
continue;
}
// If the line ends in a known extra track names, skip them for checking
if (hashData.Contains("(Track 0).bin")
|| hashData.Contains("(Track 0.2).bin")
|| hashData.Contains("(Track 00).bin")
|| hashData.Contains("(Track 00.2).bin")
|| hashData.Contains("(Track A).bin")
|| hashData.Contains("(Track A.2).bin")
|| hashData.Contains("(Track AA).bin")
|| hashData.Contains("(Track AA.2).bin"))
{
trackCount--;
resultProgress?.Report(ResultEventArgs.Success("Extra track found, skipping!"));
continue;
}
// Get the SHA-1 hash
if (!ProcessingTool.GetISOHashValues(hashData, out _, out _, out _, out string? sha1))
{
resultProgress?.Report(ResultEventArgs.Failure($"Line could not be parsed: {hashData}"));
continue;
}
(bool singleFound, var foundIds, string? result) = await Validator.ValidateSingleTrack(wc, info, sha1);
if (singleFound)
resultProgress?.Report(ResultEventArgs.Success(result));
else
resultProgress?.Report(ResultEventArgs.Failure(result));
// Add the found IDs to the map
foundIdSets.Add(foundIds?.ToArray() ?? []);
// Ensure that all tracks are found
allFound &= singleFound;
}
// If all tracks were found, check if there are any fully-matched IDs
List<int>? fullyMatchedIDs = null;
if (allFound)
{
fullyMatchedIDs = null;
foreach (var set in foundIdSets)
{
// First track is always all IDs
if (fullyMatchedIDs == null)
{
fullyMatchedIDs = [.. set];
continue;
}
// Try to intersect with all known IDs
fullyMatchedIDs = fullyMatchedIDs.Intersect(set).ToList();
if (!fullyMatchedIDs.Any())
break;
}
}
// If we don't have any matches but we have a universal hash
if (!info.PartiallyMatchedIDs.Any() && info.CommonDiscInfo?.CommentsSpecialFields?.ContainsKey(SiteCode.UniversalHash) == true)
{
#if NET40
var validateTask = Validator.ValidateUniversalHash(wc, info);
validateTask.Wait();
(bool singleFound, var foundIds, string? result) = validateTask.Result;
#else
(bool singleFound, var foundIds, string? result) = await Validator.ValidateUniversalHash(wc, info);
#endif
if (singleFound)
resultProgress?.Report(ResultEventArgs.Success(result));
else
resultProgress?.Report(ResultEventArgs.Failure(result));
// Ensure that the hash is found
allFound = singleFound;
// If we found a match, then the disc is a match
if (singleFound && foundIds != null)
fullyMatchedIDs = foundIds;
else
fullyMatchedIDs = [];
}
// Make sure we only have unique IDs
info.PartiallyMatchedIDs = [.. info.PartiallyMatchedIDs.Distinct().OrderBy(id => id)];
resultProgress?.Report(ResultEventArgs.Success("Match finding complete! " + (fullyMatchedIDs != null && fullyMatchedIDs.Count > 0
? "Fully Matched IDs: " + string.Join(",", fullyMatchedIDs.Select(i => i.ToString()).ToArray())
: "No matches found")));
// Exit early if one failed or there are no matched IDs
if (!allFound || fullyMatchedIDs == null || fullyMatchedIDs.Count == 0)
return false;
// Find the first matched ID where the track count matches, we can grab a bunch of info from it
int totalMatchedIDsCount = fullyMatchedIDs.Count;
for (int i = 0; i < totalMatchedIDsCount; i++)
{
// Skip if the track count doesn't match
#if NET40
var validateTask = Validator.ValidateTrackCount(wc, fullyMatchedIDs[i], trackCount);
validateTask.Wait();
if (!validateTask.Result)
#else
if (!await Validator.ValidateTrackCount(wc, fullyMatchedIDs[i], trackCount))
#endif
continue;
// Fill in the fields from the existing ID
resultProgress?.Report(ResultEventArgs.Success($"Filling fields from existing ID {fullyMatchedIDs[i]}..."));
#if NET40
var fillTask = Task.Factory.StartNew(() => Builder.FillFromId(wc, info, fullyMatchedIDs[i], options.PullAllInformation));
fillTask.Wait();
_ = fillTask.Result;
#else
_ = await Builder.FillFromId(wc, info, fullyMatchedIDs[i], options.PullAllInformation);
#endif
resultProgress?.Report(ResultEventArgs.Success("Information filling complete!"));
// Set the fully matched ID to the current
info.FullyMatchedID = fullyMatchedIDs[i];
break;
}
// Clear out fully matched IDs from the partial list
if (info.FullyMatchedID.HasValue)
{
if (info.PartiallyMatchedIDs.Count == 1)
info.PartiallyMatchedIDs = null;
else
info.PartiallyMatchedIDs.Remove(info.FullyMatchedID.Value);
}
return true;
}
#endregion
#region Helper Functions
/// <summary>
/// Creates a default SubmissionInfo object based on the current system and media type
/// </summary>
private static SubmissionInfo CreateDefaultSubmissionInfo(BaseProcessor processor, RedumpSystem? system, MediaType? mediaType, bool addPlaceholders)
{
// Create the template object
var info = new SubmissionInfo()
{
CommonDiscInfo = new CommonDiscInfoSection()
{
System = system,
Media = mediaType.ToDiscType(),
Title = addPlaceholders ? RequiredValue : string.Empty,
ForeignTitleNonLatin = addPlaceholders ? OptionalValue : string.Empty,
DiscNumberLetter = addPlaceholders ? OptionalValue : string.Empty,
DiscTitle = addPlaceholders ? OptionalValue : string.Empty,
Category = null,
Region = null,
Languages = null,
Serial = addPlaceholders ? RequiredIfExistsValue : string.Empty,
Barcode = addPlaceholders ? OptionalValue : string.Empty,
Contents = string.Empty,
},
VersionAndEditions = new VersionAndEditionsSection()
{
OtherEditions = addPlaceholders ? "(VERIFY THIS) Original" : string.Empty,
},
DumpingInfo = new DumpingInfoSection()
{
FrontendVersion = FrontendTool.GetCurrentVersion(),
DumpingProgram = GetDumpingProgramFromProcessor(processor),
},
};
// Ensure that required sections exist
info = Builder.EnsureAllSections(info);
return info;
}
/// <summary>
/// Get the dumping program name from the processor
/// </summary>
/// <param name="processor"></param>
/// <returns></returns>
private static string? GetDumpingProgramFromProcessor(BaseProcessor processor)
{
// Map to the internal program
InternalProgram? internalProgram = processor switch
{
Processors.Aaru => InternalProgram.Aaru,
CleanRip => InternalProgram.CleanRip,
DiscImageCreator => InternalProgram.DiscImageCreator,
PS3CFW => InternalProgram.PS3CFW,
Redumper => InternalProgram.Redumper,
UmdImageCreator => InternalProgram.UmdImageCreator,
XboxBackupCreator => InternalProgram.XboxBackupCreator,
_ => null,
};
// Use the internal program to map to the name
return internalProgram.LongName();
}
/// <summary>
/// Formats a list of volume labels and their corresponding filesystems
/// </summary>
/// <param name="labels">Dictionary of volume labels and their filesystems</param>
/// <returns>Formatted string of volume labels and their filesystems</returns>
private static string? FormatVolumeLabels(string? driveLabel, Dictionary<string, List<string>>? labels)
{
// Must have at least one label to format
if (driveLabel == null && (labels == null || labels.Count == 0))
return null;
// If no labels given, use drive label
if (labels == null || labels.Count == 0)
{
// Ignore common volume labels
if (FrontendTool.GetRedumpSystemFromVolumeLabel(driveLabel) != null)
return null;
return driveLabel;
}
// If only one label, don't mention fs
string firstLabel = labels.First().Key;
if (labels.Count == 1 && (firstLabel == driveLabel || driveLabel == null))
{
// Ignore common volume labels
if (FrontendTool.GetRedumpSystemFromVolumeLabel(firstLabel) != null)
return null;
return firstLabel;
}
// Otherwise, state filesystem for each label
List<string> volLabels = [];
// Begin formatted output with the label from Windows, if it is unique and not a common volume label
if (driveLabel != null && !labels.TryGetValue(driveLabel, out List<string>? value) && FrontendTool.GetRedumpSystemFromVolumeLabel(driveLabel) == null)
volLabels.Add(driveLabel);
// Add remaining labels with their corresponding filesystems
foreach (KeyValuePair<string, List<string>> label in labels)
{
// Ignore common volume labels
if (FrontendTool.GetRedumpSystemFromVolumeLabel(label.Key) == null)
volLabels.Add($"{label.Key} ({string.Join(", ", [.. label.Value])})");
}
// Ensure that no labels are empty
volLabels = volLabels.Where(l => !string.IsNullOrEmpty(l?.Trim())).ToList();
// Print each label separated by a comma and a space
if (volLabels.Count == 0)
return null;
return string.Join(", ", [.. volLabels]);
}
/// <summary>
/// Processes default data based on media type
/// </summary>
private static bool ProcessMediaType(SubmissionInfo info, MediaType? mediaType, bool addPlaceholders)
{
switch (mediaType)
{
case MediaType.CDROM:
case MediaType.GDROM:
info.CommonDiscInfo!.Layer0MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0AdditionalMould = addPlaceholders ? RequiredIfExistsValue : string.Empty;
break;
case MediaType.DVD:
case MediaType.HDDVD:
case MediaType.BluRay:
// If we have a single-layer disc
if (info.SizeAndChecksums!.Layerbreak == default)
{
info.CommonDiscInfo!.Layer0MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0AdditionalMould = addPlaceholders ? RequiredIfExistsValue : string.Empty;
}
// If we have a dual-layer disc
else if (info.SizeAndChecksums!.Layerbreak2 == default)
{
info.CommonDiscInfo!.Layer0MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0AdditionalMould = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
}
// If we have a triple-layer disc
else if (info.SizeAndChecksums!.Layerbreak3 == default)
{
info.CommonDiscInfo!.Layer0MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0AdditionalMould = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer2MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer2MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer2ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
}
// If we have a quad-layer disc
else
{
info.CommonDiscInfo!.Layer0MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0AdditionalMould = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer2MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer2MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer2ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer3MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer3MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer3ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
}
break;
case MediaType.NintendoGameCubeGameDisc:
info.CommonDiscInfo!.Layer0MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0AdditionalMould = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.Extras!.BCA ??= (addPlaceholders ? RequiredValue : string.Empty);
break;
case MediaType.NintendoWiiOpticalDisc:
// If we have a single-layer disc
if (info.SizeAndChecksums!.Layerbreak == default)
{
info.CommonDiscInfo!.Layer0MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0AdditionalMould = addPlaceholders ? RequiredIfExistsValue : string.Empty;
}
// If we have a dual-layer disc
else
{
info.CommonDiscInfo!.Layer0MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0AdditionalMould = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
}
info.Extras!.DiscKey = addPlaceholders ? RequiredValue : string.Empty;
info.Extras.BCA = info.Extras.BCA ?? (addPlaceholders ? RequiredValue : string.Empty);
break;
case MediaType.UMD:
// Both single- and dual-layer discs have two "layers" for the ring
info.CommonDiscInfo!.Layer0MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer0MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.CommonDiscInfo.Layer1ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
info.SizeAndChecksums!.CRC32 ??= (addPlaceholders ? RequiredValue + " [Not automatically generated for UMD]" : string.Empty);
info.SizeAndChecksums.MD5 ??= (addPlaceholders ? RequiredValue + " [Not automatically generated for UMD]" : string.Empty);
info.SizeAndChecksums.SHA1 ??= (addPlaceholders ? RequiredValue + " [Not automatically generated for UMD]" : string.Empty);
info.TracksAndWriteOffsets!.ClrMameProData = null;
break;
}
return true;
}
/// <summary>
/// Processes default data based on system type
/// </summary>
private static bool ProcessSystem(SubmissionInfo info, RedumpSystem? system, Drive? drive, bool addPlaceholders, bool isDiscImageCreator, string basePath)
{
// Extract info based specifically on RedumpSystem
switch (system)
{
case RedumpSystem.AcornArchimedes:
info.CommonDiscInfo!.Region ??= Region.UnitedKingdom;
break;
case RedumpSystem.AudioCD:
case RedumpSystem.DVDAudio:
case RedumpSystem.EnhancedCD:
case RedumpSystem.SuperAudioCD:
info.CommonDiscInfo!.Category ??= DiscCategory.Audio;
break;
case RedumpSystem.BandaiPlaydiaQuickInteractiveSystem:
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
info.CommonDiscInfo.Region ??= info.CommonDiscInfo.Region ?? Region.Japan;
break;
case RedumpSystem.BDVideo:
case RedumpSystem.DVDVideo:
case RedumpSystem.HDDVDVideo:
info.CommonDiscInfo!.Category ??= DiscCategory.Video;
info.CopyProtection!.Protection ??= addPlaceholders ? RequiredIfExistsValue : string.Empty;
break;
case RedumpSystem.CommodoreAmigaCD:
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
break;
case RedumpSystem.CommodoreAmigaCD32:
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
info.CommonDiscInfo.Region ??= Region.Europe;
break;
case RedumpSystem.CommodoreAmigaCDTV:
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
info.CommonDiscInfo.Region ??= Region.Europe;
break;
case RedumpSystem.FujitsuFMTownsseries:
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
info.CommonDiscInfo.Region ??= Region.Japan;
break;
case RedumpSystem.FujitsuFMTownsMarty:
info.CommonDiscInfo!.Region ??= Region.Japan;
break;
case RedumpSystem.HasbroVideoNow:
case RedumpSystem.HasbroVideoNowColor:
case RedumpSystem.HasbroVideoNowJr:
case RedumpSystem.VideoCD:
info.CommonDiscInfo!.Category ??= DiscCategory.Video;
break;
case RedumpSystem.HasbroVideoNowXP:
case RedumpSystem.PhotoCD:
case RedumpSystem.SonyElectronicBook:
info.CommonDiscInfo!.Category ??= DiscCategory.Multimedia;
break;
case RedumpSystem.IncredibleTechnologiesEagle:
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
break;
case RedumpSystem.KonamieAmusement:
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
break;
case RedumpSystem.KonamiFireBeat:
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
break;
case RedumpSystem.KonamiPython2:
// TODO: Remove this hack when DIC supports build date output
if (isDiscImageCreator)
info.CommonDiscInfo!.EXEDateBuildDate = DiscImageCreator.GetPlayStationEXEDate($"{basePath}_volDesc.txt", drive?.GetPlayStationExecutableName());
if (info.CommonDiscInfo!.CommentsSpecialFields!.TryGetValue(SiteCode.InternalSerialName, out string? kp2Exe) && !string.IsNullOrEmpty(kp2Exe))
info.CommonDiscInfo.Region = ProcessingTool.GetPlayStationRegion(kp2Exe);
if (drive?.GetPlayStationExecutableInfo(out var kp2Serial, out Region? kp2Region, out var kp2Date) == true)
{
if (!info.CommonDiscInfo!.CommentsSpecialFields!.TryGetValue(SiteCode.InternalSerialName, out string? value) || string.IsNullOrEmpty(value))
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = kp2Serial ?? string.Empty;
info.CommonDiscInfo.Region ??= kp2Region;
info.CommonDiscInfo.EXEDateBuildDate ??= kp2Date;
}
if (string.IsNullOrEmpty(info.VersionAndEditions!.Version))
info.VersionAndEditions!.Version = drive?.GetPlayStation2Version() ?? string.Empty;
break;
case RedumpSystem.KonamiSystemGV:
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
break;
case RedumpSystem.KonamiSystem573:
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
break;
case RedumpSystem.KonamiTwinkle:
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
break;
case RedumpSystem.MattelHyperScan:
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
break;
case RedumpSystem.MicrosoftXboxOne:
if (drive?.Name != null)
{
string xboxOneMsxcPath = Path.Combine(drive.Name, "MSXC");
if (drive != null && Directory.Exists(xboxOneMsxcPath))
{
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.Filename] = string.Join("\n",
Directory.GetFiles(xboxOneMsxcPath, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName).ToArray());
}
}
break;
case RedumpSystem.MicrosoftXboxSeriesXS:
if (drive?.Name != null)
{
string xboxSeriesXMsxcPath = Path.Combine(drive.Name, "MSXC");
if (drive != null && Directory.Exists(xboxSeriesXMsxcPath))
{
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.Filename] = string.Join("\n",
Directory.GetFiles(xboxSeriesXMsxcPath, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName).ToArray());
}
}
break;
case RedumpSystem.NamcoSegaNintendoTriforce:
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
break;
case RedumpSystem.NavisoftNaviken21:
info.CommonDiscInfo!.EXEDateBuildDate = addPlaceholders ? RequiredValue : string.Empty;
info.CommonDiscInfo.Region ??= Region.Japan;
break;
case RedumpSystem.NECPC88series:
info.CommonDiscInfo!.Region ??= Region.Japan;
break;
case RedumpSystem.NECPC98series:
info.CommonDiscInfo!.EXEDateBuildDate = addPlaceholders ? RequiredValue : string.Empty;
info.CommonDiscInfo!.Region ??= Region.Japan;
break;
case RedumpSystem.NECPCFXPCFXGA:
info.CommonDiscInfo!.Region ??= Region.Japan;
break;
case RedumpSystem.SegaChihiro:
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
break;
case RedumpSystem.SegaDreamcast:
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
break;
case RedumpSystem.SegaNaomi:
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
break;
case RedumpSystem.SegaNaomi2:
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
break;
case RedumpSystem.SegaTitanVideo:
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
break;
case RedumpSystem.SharpX68000:
info.CommonDiscInfo!.Region ??= Region.Japan;
break;
case RedumpSystem.SNKNeoGeoCD:
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
break;
case RedumpSystem.SonyPlayStation:
// TODO: Remove this hack when DIC supports build date output
if (isDiscImageCreator)
info.CommonDiscInfo!.EXEDateBuildDate = DiscImageCreator.GetPlayStationEXEDate($"{basePath}_volDesc.txt", drive?.GetPlayStationExecutableName(), psx: true);
if (info.CommonDiscInfo!.CommentsSpecialFields!.TryGetValue(SiteCode.InternalSerialName, out string? psxExe) && !string.IsNullOrEmpty(psxExe))
info.CommonDiscInfo.Region = ProcessingTool.GetPlayStationRegion(psxExe);
if (drive?.GetPlayStationExecutableInfo(out var psxSerial, out Region? psxRegion, out var psxDate) == true)
{
if (!info.CommonDiscInfo!.CommentsSpecialFields!.TryGetValue(SiteCode.InternalSerialName, out string? value) || string.IsNullOrEmpty(value))
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = psxSerial ?? string.Empty;
info.CommonDiscInfo.Region ??= psxRegion;
info.CommonDiscInfo.EXEDateBuildDate ??= psxDate;
}
break;
case RedumpSystem.SonyPlayStation2:
info.CommonDiscInfo!.LanguageSelection ??= [];
// TODO: Remove this hack when DIC supports build date output
if (isDiscImageCreator)
info.CommonDiscInfo!.EXEDateBuildDate = DiscImageCreator.GetPlayStationEXEDate($"{basePath}_volDesc.txt", drive?.GetPlayStationExecutableName());
if (info.CommonDiscInfo!.CommentsSpecialFields!.TryGetValue(SiteCode.InternalSerialName, out string? ps2Exe) && !string.IsNullOrEmpty(ps2Exe))
info.CommonDiscInfo.Region = ProcessingTool.GetPlayStationRegion(ps2Exe);
if (drive?.GetPlayStationExecutableInfo(out var ps2Serial, out Region? ps2Region, out var ps2Date) == true)
{
if (!info.CommonDiscInfo!.CommentsSpecialFields!.TryGetValue(SiteCode.InternalSerialName, out string? value) || string.IsNullOrEmpty(value))
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = ps2Serial ?? string.Empty;
info.CommonDiscInfo.Region ??= ps2Region;
info.CommonDiscInfo.EXEDateBuildDate ??= ps2Date;
}
if (string.IsNullOrEmpty(info.VersionAndEditions!.Version))
info.VersionAndEditions!.Version = drive?.GetPlayStation2Version() ?? string.Empty;
break;
case RedumpSystem.SonyPlayStation3:
info.Extras!.DiscKey ??= addPlaceholders ? RequiredValue : string.Empty;
info.Extras.DiscID ??= addPlaceholders ? RequiredValue : string.Empty;
if (!info.CommonDiscInfo!.CommentsSpecialFields!.TryGetValue(SiteCode.InternalSerialName, out string? ps3Serial) || string.IsNullOrEmpty(ps3Serial))
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = drive?.GetPlayStation3Serial() ?? string.Empty;
if (string.IsNullOrEmpty(info.VersionAndEditions!.Version))
info.VersionAndEditions!.Version = drive?.GetPlayStation3Version() ?? string.Empty;
if (!info.CommonDiscInfo!.CommentsSpecialFields!.TryGetValue(SiteCode.Patches, out string? ps3Firmware) || string.IsNullOrEmpty(ps3Firmware))
{
string? firmwareVersion = drive?.GetPlayStation3FirmwareVersion();
if (firmwareVersion != null)
info.CommonDiscInfo!.ContentsSpecialFields![SiteCode.Patches] = $"PS3 Firmware {firmwareVersion}";
}
break;
case RedumpSystem.SonyPlayStation4:
if (!info.CommonDiscInfo!.CommentsSpecialFields!.TryGetValue(SiteCode.InternalSerialName, out string? ps4Serial) || string.IsNullOrEmpty(ps4Serial))
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = drive?.GetPlayStation4Serial() ?? string.Empty;
if (string.IsNullOrEmpty(info.VersionAndEditions!.Version))
info.VersionAndEditions!.Version = drive?.GetPlayStation4Version() ?? string.Empty;
break;
case RedumpSystem.SonyPlayStation5:
if (!info.CommonDiscInfo!.CommentsSpecialFields!.TryGetValue(SiteCode.InternalSerialName, out string? ps5Serial) || string.IsNullOrEmpty(ps5Serial))
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = drive?.GetPlayStation5Serial() ?? string.Empty;
if (string.IsNullOrEmpty(info.VersionAndEditions!.Version))
info.VersionAndEditions!.Version = drive?.GetPlayStation5Version() ?? string.Empty;
break;
case RedumpSystem.TomyKissSite:
info.CommonDiscInfo!.Category ??= DiscCategory.Video;
info.CommonDiscInfo!.Region ??= Region.Japan;
break;
case RedumpSystem.ZAPiTGamesGameWaveFamilyEntertainmentSystem:
info.CopyProtection!.Protection ??= addPlaceholders ? RequiredIfExistsValue : string.Empty;
break;
}
return true;
}
/// <summary>
/// Helper to determine if a system requires an anti-modchip scan
/// </summary>
private static bool SupportsAntiModchipScans(RedumpSystem? system)
{
return system switch
{
RedumpSystem.SonyPlayStation => true,
_ => false,
};
}
/// <summary>
/// Helper to determine if a system requires a copy protection scan
/// </summary>
private static bool SupportsCopyProtectionScans(RedumpSystem? system)
{
return system switch
{
RedumpSystem.AppleMacintosh => true,
RedumpSystem.EnhancedCD => true,
RedumpSystem.IBMPCcompatible => true,
RedumpSystem.PalmOS => true,
RedumpSystem.PocketPC => true,
RedumpSystem.RainbowDisc => true,
RedumpSystem.SonyElectronicBook => true,
_ => false,
};
}
#endregion
}
}

View File

@@ -5,12 +5,11 @@ using System.IO;
using System.Linq;
using System.Threading.Tasks;
using BinaryObjectScanner;
using MPF.Core.Data;
using MPF.Core.UI.ComboBoxItems;
using MPF.Core.Utilities;
using MPF.Frontend.ComboBoxItems;
using MPF.Frontend.Tools;
using SabreTools.RedumpLib.Data;
namespace MPF.Core.UI.ViewModels
namespace MPF.Frontend.ViewModels
{
/// <summary>
/// Constructor
@@ -22,11 +21,11 @@ namespace MPF.Core.UI.ViewModels
/// <summary>
/// Access to the current options
/// </summary>
public Data.Options Options
public Frontend.Options Options
{
get => _options;
}
private readonly Data.Options _options;
private readonly Frontend.Options _options;
/// <summary>
/// Indicates if SelectionChanged events can be executed
@@ -402,7 +401,7 @@ namespace MPF.Core.UI.ViewModels
InternalProgram internalProgram = this.Options.InternalProgram;
// Create a static list of supported Check programs, not everything
var internalPrograms = new List<InternalProgram> { InternalProgram.Redumper, InternalProgram.Aaru, InternalProgram.DiscImageCreator, InternalProgram.CleanRip, InternalProgram.PS3CFW, InternalProgram.UmdImageCreator };
var internalPrograms = new List<InternalProgram> { InternalProgram.Redumper, InternalProgram.Aaru, InternalProgram.DiscImageCreator, InternalProgram.CleanRip, InternalProgram.PS3CFW, InternalProgram.UmdImageCreator, InternalProgram.XboxBackupCreator };
InternalPrograms = internalPrograms.Select(ip => new Element<InternalProgram>(ip)).ToList();
// Select the current default dumping program
@@ -463,10 +462,8 @@ namespace MPF.Core.UI.ViewModels
var env = new DumpEnvironment(Options, Path.GetFullPath(this.InputPath.Trim('"')), null, this.CurrentSystem, this.CurrentMediaType, this.CurrentProgram, parameters: null);
// Make new Progress objects
var resultProgress = new Progress<Result>();
resultProgress.ProgressChanged += ConsoleLogger.ProgressUpdated;
var resultProgress = new Progress<ResultEventArgs>();
var protectionProgress = new Progress<ProtectionProgress>();
protectionProgress.ProgressChanged += ConsoleLogger.ProgressUpdated;
// Finally, attempt to do the output dance
var result = await env.VerifyAndSaveDumpOutput(resultProgress, protectionProgress, processUserInfo);

View File

@@ -1,9 +1,10 @@
using System;
using System.ComponentModel;
using System.IO;
using MPF.Core.Utilities;
using MPF.Frontend.Tools;
using MPF.Processors;
namespace MPF.Core.UI.ViewModels
namespace MPF.Frontend.ViewModels
{
/// <summary>
/// Constructor
@@ -15,11 +16,11 @@ namespace MPF.Core.UI.ViewModels
/// <summary>
/// Access to the current options
/// </summary>
public Data.Options Options
public Options Options
{
get => _options;
}
private readonly Data.Options _options;
private readonly Options _options;
/// <summary>
/// Indicates if SelectionChanged events can be executed
@@ -625,7 +626,7 @@ namespace MPF.Core.UI.ViewModels
PICTextBoxEnabled = false;
LayerbreakTextBoxEnabled = false;
if (Tools.ParseGetKeyLog(LogPath, out byte[]? key, out byte[]? id, out byte[]? pic))
if (ProcessingTool.ParseGetKeyLog(LogPath, out byte[]? key, out byte[]? id, out byte[]? pic))
{
Key = key;
DiscID = id;
@@ -675,7 +676,7 @@ namespace MPF.Core.UI.ViewModels
LogPathTextBoxEnabled = false;
LogPathBrowseButtonEnabled = false;
byte[]? id = Tools.ParseDiscID(DiscIDString);
byte[]? id = ProcessingTool.ParseDiscID(DiscIDString);
if (id != null)
{
DiscID = id;
@@ -714,7 +715,7 @@ namespace MPF.Core.UI.ViewModels
LogPathBrowseButtonEnabled = false;
HexKeyTextBoxEnabled = false;
byte[]? key = Tools.ParseKeyFile(KeyPath);
byte[]? key = ProcessingTool.ParseKeyFile(KeyPath);
if (key != null)
{
Key = key;
@@ -758,7 +759,7 @@ namespace MPF.Core.UI.ViewModels
KeyPathTextBoxEnabled = false;
KeyPathBrowseButtonEnabled = false;
byte[]? key = Tools.ParseHexKey(HexKey);
byte[]? key = ProcessingTool.ParseHexKey(HexKey);
if (key != null)
{
Key = key;
@@ -798,7 +799,7 @@ namespace MPF.Core.UI.ViewModels
PICTextBoxEnabled = false;
LayerbreakTextBoxEnabled = false;
PIC = Tools.ParsePICFile(PICPath);
PIC = ProcessingTool.ParsePICFile(PICPath);
if (PIC != null)
{
PICStatus = $"Using PIC from file: {Path.GetFileName(PICPath)}";
@@ -841,7 +842,7 @@ namespace MPF.Core.UI.ViewModels
PICPathBrowseButtonEnabled = false;
LayerbreakTextBoxEnabled = false;
PIC = Tools.ParsePIC(PICString);
PIC = ProcessingTool.ParsePIC(PICString);
if (PIC != null)
{
PICStatus = "Using provided PIC";
@@ -881,7 +882,7 @@ namespace MPF.Core.UI.ViewModels
PICPathBrowseButtonEnabled = false;
PICTextBoxEnabled = false;
Layerbreak = Tools.ParseLayerbreak(LayerbreakString);
Layerbreak = ProcessingTool.ParseLayerbreak(LayerbreakString);
if (Layerbreak != null)
{
PICStatus = $"Will generate a PIC using a Layerbreak of {Layerbreak}";

View File

@@ -1,11 +1,10 @@
using System.Collections.Generic;
using System.Linq;
using MPF.Core.Data;
using MPF.Core.UI.ComboBoxItems;
using MPF.Core.Utilities;
using MPF.Frontend.ComboBoxItems;
using MPF.Frontend.Tools;
using SabreTools.RedumpLib.Data;
namespace MPF.Core.UI.ViewModels
namespace MPF.Frontend.ViewModels
{
public class DiscInformationViewModel
{
@@ -223,7 +222,7 @@ namespace MPF.Core.UI.ViewModels
if (!SubmissionInfo.CommonDiscInfo.Languages.Any())
SubmissionInfo.CommonDiscInfo.Languages = [null];
SubmissionInfo.CommonDiscInfo.LanguageSelection = LanguageSelections.Where(ls => ls.IsChecked).Select(ls => ls?.Value).ToArray();
SubmissionInfo.CommonDiscInfo.Title = InfoTool.NormalizeDiscTitle(SubmissionInfo.CommonDiscInfo.Title, SubmissionInfo.CommonDiscInfo.Languages);
SubmissionInfo.CommonDiscInfo.Title = FrontendTool.NormalizeDiscTitle(SubmissionInfo.CommonDiscInfo.Title, SubmissionInfo.CommonDiscInfo.Languages);
}
/// <summary>

View File

@@ -5,14 +5,12 @@ using System.IO;
using System.Linq;
using System.Threading.Tasks;
using BinaryObjectScanner;
using MPF.Core.Converters;
using MPF.Core.Data;
using MPF.Core.Modules;
using MPF.Core.UI.ComboBoxItems;
using MPF.Core.Utilities;
using MPF.Frontend.ComboBoxItems;
using MPF.Frontend.Tools;
using SabreTools.IO;
using SabreTools.RedumpLib.Data;
namespace MPF.Core.UI.ViewModels
namespace MPF.Frontend.ViewModels
{
public class MainViewModel : INotifyPropertyChanged
{
@@ -21,7 +19,7 @@ namespace MPF.Core.UI.ViewModels
/// <summary>
/// Access to the current options
/// </summary>
public Data.Options Options
public Frontend.Options Options
{
get => _options;
set
@@ -30,7 +28,7 @@ namespace MPF.Core.UI.ViewModels
OptionsLoader.SaveToConfig(_options);
}
}
private Data.Options _options;
private Frontend.Options _options;
/// <summary>
/// Indicates if SelectionChanged events can be executed
@@ -517,6 +515,14 @@ namespace MPF.Core.UI.ViewModels
#endregion
#region Constants
private const string DiscNotDetectedValue = "Disc Not Detected";
private const string StartDumpingValue = "Start Dumping";
private const string StopDumpingValue = "Stop Dumping";
#endregion
/// <summary>
/// Generic constructor
/// </summary>
@@ -544,7 +550,7 @@ namespace MPF.Core.UI.ViewModels
DriveLetterComboBoxEnabled = true;
DumpingProgramComboBoxEnabled = true;
StartStopButtonEnabled = true;
StartStopButtonText = Interface.StartDumping;
StartStopButtonText = StartDumpingValue;
MediaScanButtonEnabled = true;
EnableParametersCheckBoxEnabled = true;
LogPanelExpanded = _options.OpenLogWindowAtStartup;
@@ -714,7 +720,7 @@ namespace MPF.Core.UI.ViewModels
this.CurrentProgram = InternalProgram.NONE;
}
else
{
{
int currentIndex = InternalPrograms.FindIndex(m => m == internalProgram);
this.CurrentProgram = (currentIndex > -1 ? InternalPrograms[currentIndex].Value : InternalPrograms[0].Value);
}
@@ -734,7 +740,10 @@ namespace MPF.Core.UI.ViewModels
{
VerboseLogLn($"Changed dumping program to: {((InternalProgram?)this.CurrentProgram).LongName()}");
EnsureDiscInformation();
// New output name depends on new environment
GetOutputNames(false);
// New environment depends on new output name
EnsureDiscInformation();
}
/// <summary>
@@ -766,7 +775,7 @@ namespace MPF.Core.UI.ViewModels
/// </summary>
public (bool, string, string?) CheckForUpdates()
{
(bool different, string message, var url) = Tools.CheckForNewVersion();
(bool different, string message, var url) = FrontendTool.CheckForNewVersion();
SecretLogLn(message);
if (url == null)
@@ -789,7 +798,7 @@ namespace MPF.Core.UI.ViewModels
+ $"{Environment.NewLine}"
+ $"{Environment.NewLine}Thanks to everyone who has supported this project!"
+ $"{Environment.NewLine}"
+ $"{Environment.NewLine}Version {Tools.GetCurrentVersion()}";
+ $"{Environment.NewLine}Version {FrontendTool.GetCurrentVersion()}";
SecretLogLn(aboutText);
return aboutText;
}
@@ -937,30 +946,18 @@ namespace MPF.Core.UI.ViewModels
/// <summary>
/// Toggle the Start/Stop button
/// </summary>
public async void ToggleStartStop()
public void ToggleStartStop()
{
// Dump or stop the dump
if (this.StartStopButtonText as string == Interface.StartDumping)
if (this.StartStopButtonText as string == StartDumpingValue)
{
StartDumping();
}
else if (this.StartStopButtonText as string == Interface.StopDumping)
else if (this.StartStopButtonText as string == StopDumpingValue)
{
VerboseLogLn("Canceling dumping process...");
_environment?.CancelDumping();
this.CopyProtectScanButtonEnabled = true;
if (_environment != null && _environment.Options.EjectAfterDump)
{
VerboseLogLn($"Ejecting disc in drive {_environment.Drive?.Name}");
await _environment.EjectDisc();
}
if (_environment != null && this.Options.DICResetDriveAfterDump)
{
VerboseLogLn($"Resetting drive {_environment.Drive?.Name}");
await _environment.ResetDrive();
}
}
}
@@ -969,13 +966,13 @@ namespace MPF.Core.UI.ViewModels
/// </summary>
/// <param name="savedSettings">Indicates if the settings were saved or not</param>
/// <param name="newOptions">Options representing the new, saved values</param>
public void UpdateOptions(bool savedSettings, Data.Options? newOptions)
public void UpdateOptions(bool savedSettings, Frontend.Options? newOptions)
{
// Get which options to save
var optionsToSave = savedSettings ? newOptions : Options;
// Ensure the first run flag is unset
var continuingOptions = new Data.Options(optionsToSave) { FirstRun = false };
var continuingOptions = new Frontend.Options(optionsToSave) { FirstRun = false };
this.Options = continuingOptions;
// If settings were changed, reinitialize the UI
@@ -1252,7 +1249,7 @@ namespace MPF.Core.UI.ViewModels
else if (!this.Options.SkipSystemDetection)
{
VerboseLog($"Trying to detect system for drive {this.CurrentDrive.Name}.. ");
var currentSystem = this.CurrentDrive?.GetRedumpSystem(this.Options.DefaultSystem) ?? this.Options.DefaultSystem;
var currentSystem = GetRedumpSystem(CurrentDrive, Options.DefaultSystem) ?? Options.DefaultSystem;
VerboseLogLn(currentSystem == null ? "unable to detect." : ($"detected {currentSystem.LongName()}."));
if (currentSystem != null)
@@ -1286,7 +1283,7 @@ namespace MPF.Core.UI.ViewModels
DriveSpeedComboBoxEnabled = false;
DumpingProgramComboBoxEnabled = false;
EnableParametersCheckBoxEnabled = false;
StartStopButtonText = Interface.StopDumping;
StartStopButtonText = StopDumpingValue;
MediaScanButtonEnabled = false;
UpdateVolumeLabelEnabled = false;
CopyProtectScanButtonEnabled = false;
@@ -1308,7 +1305,7 @@ namespace MPF.Core.UI.ViewModels
DriveSpeedComboBoxEnabled = true;
DumpingProgramComboBoxEnabled = true;
EnableParametersCheckBoxEnabled = true;
StartStopButtonText = Interface.StartDumping;
StartStopButtonText = StartDumpingValue;
MediaScanButtonEnabled = true;
UpdateVolumeLabelEnabled = true;
CopyProtectScanButtonEnabled = true;
@@ -1323,8 +1320,8 @@ namespace MPF.Core.UI.ViewModels
_environment = DetermineEnvironment();
// Get the status to write out
Result result = Tools.GetSupportStatus(_environment.System, _environment.Type);
if (this.CurrentProgram == InternalProgram.NONE || _environment.Parameters == null)
ResultEventArgs result = _environment.GetSupportStatus();
if (this.CurrentProgram == InternalProgram.NONE)
this.Status = "No dumping program found";
else
this.Status = result.Message;
@@ -1333,7 +1330,7 @@ namespace MPF.Core.UI.ViewModels
this.StartStopButtonEnabled = result && ShouldEnableDumpingButton();
// If we're in a type that doesn't support drive speeds
this.DriveSpeedComboBoxEnabled = _environment.Type.DoesSupportDriveSpeed();
this.DriveSpeedComboBoxEnabled = _environment.DoesSupportDriveSpeed();
// If input params are not enabled, generate the full parameters from the environment
if (!this.ParametersCheckBoxEnabled)
@@ -1366,7 +1363,7 @@ namespace MPF.Core.UI.ViewModels
string programShort = program == "DiscImageCreator" ? "DIC" : program;
if (string.IsNullOrEmpty(programShort))
programShort = "Unknown Program";
string label = this._currentDrive?.FormattedVolumeLabel ?? "track";
string label = GetFormattedVolumeLabel(_currentDrive) ?? "track";
if (string.IsNullOrEmpty(label))
label = "track";
string date = DateTime.Today.ToString("yyyyMMdd");
@@ -1400,12 +1397,12 @@ namespace MPF.Core.UI.ViewModels
}
// Get the extension for the file for the next two statements
var extension = _environment?.Parameters?.GetDefaultExtension(this.CurrentMediaType);
var extension = _environment?.GetDefaultExtension(this.CurrentMediaType);
// Set the output filename, if it's not already
if (string.IsNullOrEmpty(this.OutputPath))
{
var label = this.CurrentDrive?.FormattedVolumeLabel ?? this.CurrentSystem.LongName();
var label = GetFormattedVolumeLabel(CurrentDrive) ?? this.CurrentSystem.LongName();
var directory = this.Options.DefaultOutputPath;
string filename = $"{label}{extension ?? ".bin"}";
@@ -1426,8 +1423,8 @@ namespace MPF.Core.UI.ViewModels
// Set the output filename, if we changed drives
else if (driveChanged)
{
var label = this.CurrentDrive?.FormattedVolumeLabel ?? this.CurrentSystem.LongName();
string oldPath = InfoTool.NormalizeOutputPaths(this.OutputPath, false);
var label = GetFormattedVolumeLabel(CurrentDrive) ?? this.CurrentSystem.LongName();
string oldPath = FrontendTool.NormalizeOutputPaths(this.OutputPath, false);
string oldFilename = Path.GetFileNameWithoutExtension(oldPath);
var directory = Path.GetDirectoryName(oldPath);
string filename = $"{label}{extension ?? ".bin"}";
@@ -1453,7 +1450,7 @@ namespace MPF.Core.UI.ViewModels
// Otherwise, reset the extension of the currently set path
else
{
string oldPath = InfoTool.NormalizeOutputPaths(this.OutputPath, false);
string oldPath = FrontendTool.NormalizeOutputPaths(this.OutputPath, false);
string filename = Path.GetFileNameWithoutExtension(oldPath);
var directory = Path.GetDirectoryName(oldPath);
filename = $"{filename}{extension ?? ".bin"}";
@@ -1465,39 +1462,372 @@ namespace MPF.Core.UI.ViewModels
}
}
/// <summary>
/// Get the current system from drive
/// </summary>
/// <param name="defaultValue"></param>
/// <returns></returns>
private static RedumpSystem? GetRedumpSystem(Drive? drive, RedumpSystem? defaultValue)
{
// If the drive does not exist, we can't do anything
if (drive == null)
return defaultValue;
// If we can't read the media in that drive, we can't do anything
if (string.IsNullOrEmpty(drive.Name) || !Directory.Exists(drive.Name))
return defaultValue;
// We're going to assume for floppies, HDDs, and removable drives
if (drive.InternalDriveType != InternalDriveType.Optical)
return RedumpSystem.IBMPCcompatible;
// Check volume labels first
RedumpSystem? systemFromLabel = FrontendTool.GetRedumpSystemFromVolumeLabel(drive.VolumeLabel);
if (systemFromLabel != null)
return systemFromLabel;
// Get a list of files for quicker checking
#region Arcade
// funworld Photo Play
if (File.Exists(Path.Combine(drive.Name, "PP.INF"))
&& Directory.Exists(Path.Combine(drive.Name, "PPINC")))
{
return RedumpSystem.funworldPhotoPlay;
}
// Konami Python 2
if (Directory.Exists(Path.Combine(drive.Name, "PY2.D")))
{
return RedumpSystem.KonamiPython2;
}
#endregion
#region Consoles
// Bandai Playdia Quick Interactive System
try
{
#if NET20 || NET35
List<string> files = [.. Directory.GetFiles(drive.Name, "*", SearchOption.TopDirectoryOnly)];
#else
List<string> files = Directory.EnumerateFiles(drive.Name, "*", SearchOption.TopDirectoryOnly).ToList();
#endif
if (files.Any(f => f.EndsWith(".AJS", StringComparison.OrdinalIgnoreCase))
&& files.Any(f => f.EndsWith(".GLB", StringComparison.OrdinalIgnoreCase)))
{
return RedumpSystem.BandaiPlaydiaQuickInteractiveSystem;
}
}
catch { }
// Bandai Pippin
if (File.Exists(Path.Combine(drive.Name, "PippinAuthenticationFile")))
{
return RedumpSystem.BandaiPippin;
}
// Commodore CDTV/CD32
#if NET20 || NET35
if (File.Exists(Path.Combine(Path.Combine(drive.Name, "S"), "STARTUP-SEQUENCE")))
#else
if (File.Exists(Path.Combine(drive.Name, "S", "STARTUP-SEQUENCE")))
#endif
{
if (File.Exists(Path.Combine(drive.Name, "CDTV.TM")))
return RedumpSystem.CommodoreAmigaCDTV;
else
return RedumpSystem.CommodoreAmigaCD32;
}
// Mattel HyperScan -- TODO: May need case-insensitivity added
if (File.Exists(Path.Combine(drive.Name, "hyper.exe")))
{
return RedumpSystem.MattelHyperScan;
}
// Mattel Fisher-Price iXL
#if NET20 || NET35
if (File.Exists(Path.Combine(Path.Combine(drive.Name, "iXL"), "iXLUpdater.exe")))
#else
if (File.Exists(Path.Combine(drive.Name, "iXL", "iXLUpdater.exe")))
#endif
{
return RedumpSystem.MattelFisherPriceiXL;
}
// Microsoft Xbox 360
try
{
if (Directory.Exists(Path.Combine(drive.Name, "$SystemUpdate"))
#if NET20 || NET35
&& Directory.GetFiles(Path.Combine(drive.Name, "$SystemUpdate")).Any()
#else
&& Directory.EnumerateFiles(Path.Combine(drive.Name, "$SystemUpdate")).Any()
#endif
&& drive.TotalSize <= 500_000_000)
{
return RedumpSystem.MicrosoftXbox360;
}
}
catch { }
// Microsoft Xbox One and Series X
try
{
if (Directory.Exists(Path.Combine(drive.Name, "MSXC")))
{
try
{
#if NET20 || NET35
string catalogjs = Path.Combine(drive.Name, Path.Combine("MSXC", Path.Combine("Metadata", "catalog.js")));
#else
string catalogjs = Path.Combine(drive.Name, "MSXC", "Metadata", "catalog.js");
#endif
if (!File.Exists(catalogjs))
return RedumpSystem.MicrosoftXboxOne;
SabreTools.Models.Xbox.Catalog? catalog = SabreTools.Serialization.Deserializers.Catalog.DeserializeFile(catalogjs);
if (catalog != null && catalog.Version != null && catalog.Packages != null)
{
if (!double.TryParse(catalog.Version, out double version))
return RedumpSystem.MicrosoftXboxOne;
if (version < 4)
return RedumpSystem.MicrosoftXboxOne;
foreach (var package in catalog.Packages)
{
if (package.Generation != "9")
return RedumpSystem.MicrosoftXboxOne;
}
return RedumpSystem.MicrosoftXboxSeriesXS;
}
}
catch
{
return RedumpSystem.MicrosoftXboxOne;
}
}
}
catch { }
// Sega Dreamcast
if (File.Exists(Path.Combine(drive.Name, "IP.BIN")))
{
return RedumpSystem.SegaDreamcast;
}
// Sega Mega-CD / Sega-CD
#if NET20 || NET35
if (File.Exists(Path.Combine(Path.Combine(drive.Name, "_BOOT"), "IP.BIN"))
|| File.Exists(Path.Combine(Path.Combine(drive.Name, "_BOOT"), "SP.BIN"))
|| File.Exists(Path.Combine(Path.Combine(drive.Name, "_BOOT"), "SP_AS.BIN"))
|| File.Exists(Path.Combine(drive.Name, "FILESYSTEM.BIN")))
#else
if (File.Exists(Path.Combine(drive.Name, "_BOOT", "IP.BIN"))
|| File.Exists(Path.Combine(drive.Name, "_BOOT", "SP.BIN"))
|| File.Exists(Path.Combine(drive.Name, "_BOOT", "SP_AS.BIN"))
|| File.Exists(Path.Combine(drive.Name, "FILESYSTEM.BIN")))
#endif
{
return RedumpSystem.SegaMegaCDSegaCD;
}
// Sony PlayStation and Sony PlayStation 2
string psxExePath = Path.Combine(drive.Name, "PSX.EXE");
string systemCnfPath = Path.Combine(drive.Name, "SYSTEM.CNF");
if (File.Exists(systemCnfPath))
{
// Check for either BOOT or BOOT2
var systemCnf = new IniFile(systemCnfPath);
if (systemCnf.ContainsKey("BOOT"))
return RedumpSystem.SonyPlayStation;
else if (systemCnf.ContainsKey("BOOT2"))
return RedumpSystem.SonyPlayStation2;
}
else if (File.Exists(psxExePath))
{
return RedumpSystem.SonyPlayStation;
}
// Sony PlayStation 3
try
{
if (Directory.Exists(Path.Combine(drive.Name, "PS3_GAME"))
|| Directory.Exists(Path.Combine(drive.Name, "PS3_UPDATE"))
|| File.Exists(Path.Combine(drive.Name, "PS3_DISC.SFB")))
{
return RedumpSystem.SonyPlayStation3;
}
}
catch { }
// Sony PlayStation 4
// 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 NET20 || NET35
if (File.Exists(Path.Combine(Path.Combine(Path.Combine(drive.Name, "PS4"), "UPDATE"), "PS4UPDATE.PUP")))
#else
if (File.Exists(Path.Combine(drive.Name, "PS4", "UPDATE", "PS4UPDATE.PUP")))
#endif
{
return RedumpSystem.SonyPlayStation4;
}
// V.Tech V.Flash / V.Smile Pro
if (File.Exists(Path.Combine(drive.Name, "0SYSTEM")))
{
return RedumpSystem.VTechVFlashVSmilePro;
}
#endregion
#region Computers
// Sharp X68000
if (File.Exists(Path.Combine(drive.Name, "COMMAND.X")))
{
return RedumpSystem.SharpX68000;
}
#endregion
#region Video Formats
// BD-Video
if (Directory.Exists(Path.Combine(drive.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(drive.Name, "AUDIO_TS"))
#if NET20 || NET35
&& Directory.GetFiles(Path.Combine(drive.Name, "AUDIO_TS")).Any())
#else
&& Directory.EnumerateFiles(Path.Combine(drive.Name, "AUDIO_TS")).Any())
#endif
{
return RedumpSystem.DVDAudio;
}
else if (Directory.Exists(Path.Combine(drive.Name, "VIDEO_TS"))
#if NET20 || NET35
&& Directory.GetFiles(Path.Combine(drive.Name, "VIDEO_TS")).Any())
#else
&& Directory.EnumerateFiles(Path.Combine(drive.Name, "VIDEO_TS")).Any())
#endif
{
return RedumpSystem.DVDVideo;
}
}
catch { }
// HD-DVD-Video
try
{
if (Directory.Exists(Path.Combine(drive.Name, "HVDVD_TS"))
#if NET20 || NET35
&& Directory.GetFiles(Path.Combine(drive.Name, "HVDVD_TS")).Any())
#else
&& Directory.EnumerateFiles(Path.Combine(drive.Name, "HVDVD_TS")).Any())
#endif
{
return RedumpSystem.HDDVDVideo;
}
}
catch { }
// Photo CD
try
{
if (Directory.Exists(Path.Combine(drive.Name, "PHOTO_CD"))
#if NET20 || NET35
&& Directory.GetFiles(Path.Combine(drive.Name, "PHOTO_CD")).Any())
#else
&& Directory.EnumerateFiles(Path.Combine(drive.Name, "PHOTO_CD")).Any())
#endif
{
return RedumpSystem.PhotoCD;
}
}
catch { }
// VCD
try
{
if (Directory.Exists(Path.Combine(drive.Name, "VCD"))
#if NET20 || NET35
&& Directory.GetFiles(Path.Combine(drive.Name, "drive.VCD")).Any())
#else
&& Directory.EnumerateFiles(Path.Combine(drive.Name, "VCD")).Any())
#endif
{
return RedumpSystem.VideoCD;
}
}
catch { }
#endregion
// Default return
return defaultValue;
}
/// <summary>
/// Process the current custom parameters back into UI values
/// </summary>
public void ProcessCustomParameters()
{
_environment?.SetParameters(this.Parameters);
if (_environment?.Parameters == null)
// Set the execution context and processor
if (_environment?.SetExecutionContext(this.Parameters) != true)
return;
if (_environment?.SetProcessor() != true)
return;
// Catch this in case there's an input path issue
try
{
int driveIndex = Drives.Select(d => d.Name?[0] ?? '\0').ToList().IndexOf(_environment.Parameters?.InputPath?[0] ?? default);
int driveIndex = Drives.Select(d => d.Name?[0] ?? '\0').ToList().IndexOf(_environment.ContextInputPath?[0] ?? default);
this.CurrentDrive = (driveIndex != -1 ? Drives[driveIndex] : Drives[0]);
}
catch { }
int driveSpeed = _environment.Parameters?.Speed ?? -1;
int driveSpeed = _environment.Speed ?? -1;
if (driveSpeed > 0)
this.DriveSpeed = driveSpeed;
else if (_environment.Parameters != null)
_environment.Parameters.Speed = this.DriveSpeed;
else
_environment.Speed = this.DriveSpeed;
// Disable change handling
DisableEventHandlers();
this.OutputPath = InfoTool.NormalizeOutputPaths(_environment.Parameters?.OutputPath, false);
this.OutputPath = FrontendTool.NormalizeOutputPaths(_environment.ContextOutputPath, false);
if (MediaTypes != null)
{
MediaType? mediaType = _environment.Parameters?.GetMediaType();
int mediaTypeIndex = MediaTypes.FindIndex(m => m == mediaType);
this.CurrentMediaType = (mediaTypeIndex > -1 ? MediaTypes[mediaTypeIndex] : MediaTypes[0]);
MediaType? mediaType = _environment.GetMediaType();
if (mediaType != null)
{
int mediaTypeIndex = MediaTypes.FindIndex(m => m == mediaType);
this.CurrentMediaType = (mediaTypeIndex > -1 ? MediaTypes[mediaTypeIndex] : MediaTypes[0]);
}
}
// Reenable change handling
@@ -1532,20 +1862,20 @@ namespace MPF.Core.UI.ViewModels
var progress = new Progress<ProtectionProgress>();
progress.ProgressChanged += ProgressUpdated;
#if NET40
var protectionTask = Protection.RunProtectionScanOnPath(this.CurrentDrive.Name, this.Options, progress);
var protectionTask = ProtectionTool.RunProtectionScanOnPath(this.CurrentDrive.Name, this.Options, progress);
protectionTask.Wait();
var (protections, error) = protectionTask.Result;
#else
var (protections, error) = await Protection.RunProtectionScanOnPath(this.CurrentDrive.Name, this.Options, progress);
var (protections, error) = await ProtectionTool.RunProtectionScanOnPath(this.CurrentDrive.Name, this.Options, progress);
#endif
var output = Protection.FormatProtections(protections);
var output = ProtectionTool.FormatProtections(protections);
// If SmartE is detected on the current disc, remove `/sf` from the flags for DIC only -- Disabled until further notice
//if (Env.InternalProgram == InternalProgram.DiscImageCreator && output.Contains("SmartE"))
//{
// ((Modules.DiscImageCreator.Parameters)Env.Parameters)[Modules.DiscImageCreator.FlagStrings.ScanFileProtect] = false;
// ((ExecutionContexts.DiscImageCreator.ExecutionContext)Env.ExecutionContext)[ExecutionContexts.DiscImageCreator.FlagStrings.ScanFileProtect] = false;
// if (this.Options.VerboseLogging)
// this.Logger.VerboseLogLn($"SmartE detected, removing {Modules.DiscImageCreator.FlagStrings.ScanFileProtect} from parameters");
// this.Logger.VerboseLogLn($"SmartE detected, removing {ExecutionContexts.DiscImageCreator.FlagStrings.ScanFileProtect} from parameters");
//}
if (string.IsNullOrEmpty(error))
@@ -1562,6 +1892,47 @@ namespace MPF.Core.UI.ViewModels
return (output, error);
}
/// <summary>
/// Media label as read by Windows, formatted to avoid odd outputs
/// If no volume label present, use PSX or PS2 serial if valid
/// Otherwise, use "track" as volume label
/// </summary>
private static string? GetFormattedVolumeLabel(Drive? drive)
{
if (drive == null)
return null;
string? volumeLabel = DiscNotDetectedValue;
if (!drive.MarkedActive)
return volumeLabel;
if (!string.IsNullOrEmpty(drive.VolumeLabel))
{
volumeLabel = drive.VolumeLabel;
}
else
{
// No Volume Label found, fallback to something sensible
switch (GetRedumpSystem(drive, null))
{
case RedumpSystem.SonyPlayStation:
case RedumpSystem.SonyPlayStation2:
drive.GetPlayStationExecutableInfo(out string? serial, out _, out _);
volumeLabel = serial ?? "track";
break;
default:
volumeLabel = "track";
break;
}
}
foreach (char c in Path.GetInvalidFileNameChars())
volumeLabel = volumeLabel?.Replace(c, '_');
return volumeLabel;
}
/// <summary>
/// Set the current disc type in the combo box
/// </summary>
@@ -1600,30 +1971,11 @@ namespace MPF.Core.UI.ViewModels
public void SetSupportedDriveSpeed()
{
// Set the drive speed list that's appropriate
this.DriveSpeeds = (List<int>)Interface.GetSpeedsForMediaType(CurrentMediaType);
this.DriveSpeeds = (List<int>)InterfaceConstants.GetSpeedsForMediaType(CurrentMediaType);
VerboseLogLn($"Supported media speeds: {string.Join(", ", this.DriveSpeeds.Select(ds => ds.ToString()).ToArray())}");
// Set the selected speed
int speed = (CurrentMediaType) switch
{
// CD dump speed
MediaType.CDROM => Options.PreferredDumpSpeedCD,
MediaType.GDROM => Options.PreferredDumpSpeedCD,
// DVD dump speed
MediaType.DVD => Options.PreferredDumpSpeedDVD,
MediaType.NintendoGameCubeGameDisc => Options.PreferredDumpSpeedDVD,
MediaType.NintendoWiiOpticalDisc => Options.PreferredDumpSpeedDVD,
// HD-DVD dump speed
MediaType.HDDVD => Options.PreferredDumpSpeedHDDVD,
// BD dump speed
MediaType.BluRay => Options.PreferredDumpSpeedBD,
// Default
_ => Options.PreferredDumpSpeedCD,
};
int speed = FrontendTool.GetDefaultSpeedForMediaType(CurrentMediaType, Options);
VerboseLogLn($"Setting drive speed to: {speed}");
this.DriveSpeed = speed;
@@ -1638,7 +1990,58 @@ namespace MPF.Core.UI.ViewModels
&& Drives.Count > 0
&& this.CurrentSystem != null
&& this.CurrentMediaType != null
&& Tools.ProgramSupportsMedia(this.CurrentProgram, this.CurrentMediaType);
&& ProgramSupportsMedia();
}
/// <summary>
/// Returns false if a given InternalProgram does not support a given MediaType
/// </summary>
private bool ProgramSupportsMedia()
{
// If the media type is not set, return false
if (CurrentMediaType == null || CurrentMediaType == MediaType.NONE)
return false;
return (CurrentProgram) switch
{
// Aaru
InternalProgram.Aaru when CurrentMediaType == MediaType.BluRay => true,
InternalProgram.Aaru when CurrentMediaType == MediaType.CDROM => true,
InternalProgram.Aaru when CurrentMediaType == MediaType.CompactFlash => true,
InternalProgram.Aaru when CurrentMediaType == MediaType.DVD => true,
InternalProgram.Aaru when CurrentMediaType == MediaType.GDROM => true,
InternalProgram.Aaru when CurrentMediaType == MediaType.FlashDrive => true,
InternalProgram.Aaru when CurrentMediaType == MediaType.FloppyDisk => true,
InternalProgram.Aaru when CurrentMediaType == MediaType.HardDisk => true,
InternalProgram.Aaru when CurrentMediaType == MediaType.HDDVD => true,
InternalProgram.Aaru when CurrentMediaType == MediaType.NintendoGameCubeGameDisc => true,
InternalProgram.Aaru when CurrentMediaType == MediaType.NintendoWiiOpticalDisc => true,
InternalProgram.Aaru when CurrentMediaType == MediaType.SDCard => true,
// DiscImageCreator
InternalProgram.DiscImageCreator when CurrentMediaType == MediaType.BluRay => true,
InternalProgram.DiscImageCreator when CurrentMediaType == MediaType.CDROM => true,
InternalProgram.DiscImageCreator when CurrentMediaType == MediaType.CompactFlash => true,
InternalProgram.DiscImageCreator when CurrentMediaType == MediaType.DVD => true,
InternalProgram.DiscImageCreator when CurrentMediaType == MediaType.GDROM => true,
InternalProgram.DiscImageCreator when CurrentMediaType == MediaType.FlashDrive => true,
InternalProgram.DiscImageCreator when CurrentMediaType == MediaType.FloppyDisk => true,
InternalProgram.DiscImageCreator when CurrentMediaType == MediaType.HardDisk => true,
InternalProgram.DiscImageCreator when CurrentMediaType == MediaType.HDDVD => true,
InternalProgram.DiscImageCreator when CurrentMediaType == MediaType.NintendoGameCubeGameDisc => true,
InternalProgram.DiscImageCreator when CurrentMediaType == MediaType.NintendoWiiOpticalDisc => true,
InternalProgram.DiscImageCreator when CurrentMediaType == MediaType.SDCard => true,
// Redumper
InternalProgram.Redumper when CurrentMediaType == MediaType.BluRay => true,
InternalProgram.Redumper when CurrentMediaType == MediaType.CDROM => true,
InternalProgram.Redumper when CurrentMediaType == MediaType.DVD => true,
InternalProgram.Redumper when CurrentMediaType == MediaType.GDROM => true,
InternalProgram.Redumper when CurrentMediaType == MediaType.HDDVD => true,
// Default
_ => false,
};
}
/// <summary>
@@ -1650,7 +2053,7 @@ namespace MPF.Core.UI.ViewModels
_environment = DetermineEnvironment();
// Force an internal drive refresh in case the user entered things manually
_environment.Drive?.RefreshDrive();
_environment.RefreshDrive();
// If still in custom parameter mode, check that users meant to continue or not
if (this.ParametersCheckBoxEnabled == true && _displayUserMessage != null)
@@ -1681,32 +2084,25 @@ namespace MPF.Core.UI.ViewModels
DisableAllUIElements();
// Refresh the drive, if it wasn't null
_environment.Drive?.RefreshDrive();
_environment.RefreshDrive();
// Output to the label and log
this.Status = "Starting dumping process... please wait!";
LogLn("Starting dumping process... please wait!");
if (this.Options.ToolsInSeparateWindow)
LogLn("Look for the separate command window for more details");
else
LogLn("Program outputs may be slow to populate in the log window");
LogLn("Look for the separate command window for more details");
// Get progress indicators
var resultProgress = new Progress<Result>();
var resultProgress = new Progress<ResultEventArgs>();
resultProgress.ProgressChanged += ProgressUpdated;
var protectionProgress = new Progress<ProtectionProgress>();
protectionProgress.ProgressChanged += ProgressUpdated;
_environment.ReportStatus += ProgressUpdated;
// Run the program with the parameters
#if NET40
Result result = _environment.Run(resultProgress);
#else
Result result = await _environment.Run(resultProgress);
#endif
ResultEventArgs result = await _environment.Run(resultProgress);
// If we didn't execute a dumping command we cannot get submission output
if (_environment.Parameters?.IsDumpingCommand() != true)
if (!_environment.IsDumpingCommand())
{
LogLn("No dumping command was run, submission information will not be gathered.");
this.Status = "Execution complete!";
@@ -1778,11 +2174,11 @@ namespace MPF.Core.UI.ViewModels
/// <returns>True if dumping should start, false otherwise</returns>
private bool ValidateBeforeDumping()
{
if (Parameters == null)
if (Parameters == null || _environment == null)
return false;
// Validate that we have an output path of any sort
if (string.IsNullOrEmpty(_environment?.OutputPath))
if (string.IsNullOrEmpty(_environment.OutputPath))
{
if (_displayUserMessage != null)
_ = _displayUserMessage("Missing Path", "No output path was provided so dumping cannot continue.", 1, false);
@@ -1791,10 +2187,10 @@ namespace MPF.Core.UI.ViewModels
}
// Validate that the user explicitly wants an inactive drive to be considered for dumping
if (_environment?.Drive?.MarkedActive != true && _displayUserMessage != null)
if (!_environment.DriveMarkedActive && _displayUserMessage != null)
{
string message = "The currently selected drive does not appear to contain a disc! "
+ (!_environment!.System.DetectedByWindows() ? $"This is normal for {_environment.System.LongName()} as the discs may not be readable on Windows. " : string.Empty)
+ (!_environment!.DetectedByWindows() ? $"This is normal for {_environment.SystemName} as the discs may not be readable on Windows. " : string.Empty)
+ "Do you want to continue?";
bool? mbresult = _displayUserMessage("No Disc Detected", message, 2, false);
@@ -1810,7 +2206,7 @@ namespace MPF.Core.UI.ViewModels
string outputFilename = Path.GetFileName(_environment.OutputPath);
// If a complete dump already exists
(bool foundFiles, List<string> _) = _environment.Parameters.FoundAllFiles(outputDirectory, outputFilename, true);
bool foundFiles = _environment.FoundAllFiles(outputDirectory, outputFilename, true);
if (foundFiles && _displayUserMessage != null)
{
bool? mbresult = _displayUserMessage("Overwrite?", "A complete dump already exists! Are you sure you want to overwrite?", 2, true);
@@ -1823,40 +2219,7 @@ namespace MPF.Core.UI.ViewModels
else
{
// If a complete dump exists from a different program
InternalProgram? programFound = null;
if (programFound == null && _environment.InternalProgram != InternalProgram.Aaru)
{
Modules.Aaru.Parameters parameters = new("")
{
Type = _environment.Type,
System = _environment.System
};
(bool foundOtherFiles, _) = parameters.FoundAllFiles(outputDirectory, outputFilename, true);
if (foundOtherFiles)
programFound = InternalProgram.Aaru;
}
if (programFound == null && _environment.InternalProgram != InternalProgram.DiscImageCreator)
{
Modules.DiscImageCreator.Parameters parameters = new("")
{
Type = _environment.Type,
System = _environment.System
};
(bool foundOtherFiles, _) = parameters.FoundAllFiles(outputDirectory, outputFilename, true);
if (foundOtherFiles)
programFound = InternalProgram.DiscImageCreator;
}
if (programFound == null && _environment.InternalProgram != InternalProgram.Redumper)
{
Modules.Redumper.Parameters parameters = new("")
{
Type = _environment.Type,
System = _environment.System
};
(bool foundOtherFiles, _) = parameters.FoundAllFiles(outputDirectory, outputFilename, true);
if (foundOtherFiles)
programFound = InternalProgram.Redumper;
}
InternalProgram? programFound = _environment.CheckForMatchingProgram(outputDirectory, outputFilename);
if (programFound != null && _displayUserMessage != null)
{
bool? mbresult = _displayUserMessage("Overwrite?", $"A complete dump from {programFound} already exists! Dumping here may cause issues. Are you sure you want to overwrite?", 2, true);
@@ -1921,21 +2284,11 @@ namespace MPF.Core.UI.ViewModels
/// <summary>
/// Handler for Result ProgressChanged event
/// </summary>
#if NET20 || NET35 || NET40
private void ProgressUpdated(object? sender, BaseParameters.StringEventArgs value)
#else
private void ProgressUpdated(object? sender, string value)
#endif
private void ProgressUpdated(object? sender, StringEventArgs value)
{
try
{
#if NET20 || NET35 || NET40
value.Value ??= string.Empty;
LogLn(value.Value);
#else
value ??= string.Empty;
LogLn(value);
#endif
}
catch { }
}
@@ -1943,7 +2296,7 @@ namespace MPF.Core.UI.ViewModels
/// <summary>
/// Handler for Result ProgressChanged event
/// </summary>
private void ProgressUpdated(object? sender, Result value)
private void ProgressUpdated(object? sender, ResultEventArgs value)
{
var message = value?.Message;

View File

@@ -2,11 +2,12 @@
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using MPF.Core.Data;
using MPF.Core.UI.ComboBoxItems;
using MPF.Frontend.ComboBoxItems;
using SabreTools.RedumpLib.Web;
using RedumperReadMethod = MPF.ExecutionContexts.Redumper.ReadMethod;
using RedumperSectorOrder = MPF.ExecutionContexts.Redumper.SectorOrder;
namespace MPF.Core.UI.ViewModels
namespace MPF.Frontend.ViewModels
{
/// <summary>
/// Constructor
@@ -51,6 +52,16 @@ namespace MPF.Core.UI.ViewModels
/// </summary>
public static List<Element<InternalProgram>> InternalPrograms => PopulateInternalPrograms();
/// <summary>
/// Current list of supported Redumper read methods
/// </summary>
public static List<Element<RedumperReadMethod>> RedumperReadMethods => PopulateRedumperReadMethods();
/// <summary>
/// Current list of supported Redumper sector orders
/// </summary>
public static List<Element<RedumperSectorOrder>> RedumperSectorOrders => PopulateRedumperSectorOrders();
/// <summary>
/// Current list of supported system profiles
/// </summary>
@@ -77,7 +88,7 @@ namespace MPF.Core.UI.ViewModels
#region Population
/// <summary>
/// Get a complete list of supported internal programs
/// Get a complete list of supported internal programs
/// </summary>
private static List<Element<InternalProgram>> PopulateInternalPrograms()
{
@@ -85,6 +96,24 @@ namespace MPF.Core.UI.ViewModels
return internalPrograms.Select(ip => new Element<InternalProgram>(ip)).ToList();
}
/// <summary>
/// Get a complete list of supported redumper drive read methods
/// </summary>
private static List<Element<RedumperReadMethod>> PopulateRedumperReadMethods()
{
var readMethods = new List<RedumperReadMethod> { RedumperReadMethod.NONE, RedumperReadMethod.D8, RedumperReadMethod.BE, RedumperReadMethod.BE_CDDA };
return readMethods.Select(rm => new Element<RedumperReadMethod>(rm)).ToList();
}
/// <summary>
/// Get a complete list of supported redumper drive sector orders
/// </summary>
private static List<Element<RedumperSectorOrder>> PopulateRedumperSectorOrders()
{
var sectorOrders = new List<RedumperSectorOrder> { RedumperSectorOrder.NONE, RedumperSectorOrder.DATA_C2_SUB, RedumperSectorOrder.DATA_SUB_C2, RedumperSectorOrder.DATA_SUB, RedumperSectorOrder.DATA_C2 };
return sectorOrders.Select(so => new Element<RedumperSectorOrder>(so)).ToList();
}
#endregion
#region UI Commands
@@ -92,19 +121,20 @@ namespace MPF.Core.UI.ViewModels
/// <summary>
/// Test Redump login credentials
/// </summary>
#if NET40
public static Task<(bool?, string?)> TestRedumpLogin(string username, string password)
#else
public static async Task<(bool?, string?)> TestRedumpLogin(string username, string password)
#endif
{
#if NET40
return Task.Factory.StartNew(() => RedumpWebClient.ValidateCredentials(username, password));
#elif NETFRAMEWORK
return await Task.Run(() => RedumpWebClient.ValidateCredentials(username, password));
#else
return await RedumpHttpClient.ValidateCredentials(username, password);
#endif
return await RedumpClient.ValidateCredentials(username, password);
}
/// <summary>
/// Reset Redumper non-redump options (Read Method, Sector Order, Drive Type)
/// </summary>
public void NonRedumpModeUnChecked()
{
Options.RedumperReadMethod = RedumperReadMethod.NONE;
Options.RedumperSectorOrder = RedumperSectorOrder.NONE;
Options.RedumperUseGenericDriveType = false;
TriggerPropertyChanged(nameof(Options));
}
#endregion

1311
MPF.Processors/Aaru.cs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,397 @@
using System;
using System.Collections.Generic;
using System.IO;
#if NET452_OR_GREATER || NETCOREAPP
using System.IO.Compression;
#endif
using System.Linq;
using System.Text.RegularExpressions;
using SabreTools.RedumpLib.Data;
namespace MPF.Processors
{
public abstract class BaseProcessor
{
/// <summary>
/// All found volume labels and their corresponding file systems
/// </summary>
public Dictionary<string, List<string>>? VolumeLabels;
#region Metadata
/// <summary>
/// Currently represented system
/// </summary>
public RedumpSystem? System { get; private set; }
/// <summary>
/// Currently represented media type
/// </summary>
public MediaType? Type { get; private set; }
#endregion
/// <summary>
/// Generate processor for a system and media type combination
/// </summary>
/// <param name="system">RedumpSystem value to use</param>
/// <param name="type">MediaType value to use</param>
public BaseProcessor(RedumpSystem? system, MediaType? type)
{
System = system;
Type = type;
}
#region Abstract Methods
/// <summary>
/// Validate if all required output files exist
/// </summary>
/// <param name="basePath">Base filename and path to use for checking</param>
/// <param name="preCheck">True if this is a check done before a dump, false if done after</param>
/// <returns>Tuple of true if all required files exist, false otherwise and a list representing missing files</returns>
public abstract (bool, List<string>) CheckAllOutputFilesExist(string basePath, bool preCheck);
/// <summary>
/// Generate artifacts and add to the SubmissionInfo
/// </summary>
/// <param name="submissionInfo">Base submission info to fill in specifics for</param>
/// <param name="basePath">Base filename and path to use for checking</param>
public abstract void GenerateArtifacts(SubmissionInfo submissionInfo, string basePath);
/// <summary>
/// Generate a SubmissionInfo for the output files
/// </summary>
/// <param name="submissionInfo">Base submission info to fill in specifics for</param>
/// <param name="basePath">Base filename and path to use for checking</param>
/// <param name="redumpCompat">Determines if outputs are processed according to Redump specifications</param>
public abstract void GenerateSubmissionInfo(SubmissionInfo submissionInfo, string basePath, bool redumpCompat);
/// <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>
public abstract List<string> GetLogFilePaths(string basePath);
#endregion
#region Virtual Methods
/// <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>
public virtual List<string> GetDeleteableFilePaths(string basePath) => [];
#endregion
#region Shared Methods
/// <summary>
/// Compress log files to save space
/// </summary>
/// <param name="outputDirectory">Output folder to write to</param>
/// <param name="filenameSuffix">Output filename to use as the base path</param>
/// <param name="outputFilename">Output filename to use as the base path</param>
/// <param name="processor">Processor object representing how to process the outputs</param>
/// <returns>True if the process succeeded, false otherwise</returns>
public (bool, string) CompressLogFiles(string? outputDirectory, string? filenameSuffix, string outputFilename)
{
#if NET20 || NET35 || NET40
return (false, "Log compression is not available for this framework version");
#else
// Prepare the necessary paths
outputFilename = Path.GetFileNameWithoutExtension(outputFilename);
string combinedBase;
if (string.IsNullOrEmpty(outputDirectory))
combinedBase = outputFilename;
else
combinedBase = Path.Combine(outputDirectory, outputFilename);
string archiveName = combinedBase + "_logs.zip";
// Get the list of log files from the parameters object
var files = GetLogFilePaths(combinedBase);
// Add on generated log files if they exist
var mpfFiles = GetGeneratedFilePaths(outputDirectory, filenameSuffix);
files.AddRange(mpfFiles);
if (!files.Any())
return (true, "No files to compress!");
// If the file already exists, we want to delete the old one
try
{
if (File.Exists(archiveName))
File.Delete(archiveName);
}
catch
{
return (false, "Could not delete old archive!");
}
// Add the log files to the archive and delete the uncompressed file after
ZipArchive? zf = null;
try
{
zf = ZipFile.Open(archiveName, ZipArchiveMode.Create);
foreach (string file in files)
{
if (string.IsNullOrEmpty(outputDirectory))
{
zf.CreateEntryFromFile(file, file, CompressionLevel.Optimal);
}
else
{
string entryName = file[outputDirectory!.Length..].TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
#if NETFRAMEWORK || NETCOREAPP3_1 || NET5_0
zf.CreateEntryFromFile(file, entryName, CompressionLevel.Optimal);
#else
zf.CreateEntryFromFile(file, entryName, CompressionLevel.SmallestSize);
#endif
}
// If the file is MPF-specific, don't delete
if (mpfFiles.Contains(file))
continue;
try
{
File.Delete(file);
}
catch { }
}
return (true, "Compression complete!");
}
catch (Exception ex)
{
return (false, $"Compression could not complete: {ex}");
}
finally
{
zf?.Dispose();
}
#endif
}
/// <summary>
/// Compress log files to save space
/// </summary>
/// <param name="outputDirectory">Output folder to write to</param>
/// <param name="outputFilename">Output filename to use as the base path</param>
/// <param name="processor">Processor object representing how to process the outputs</param>
/// <returns>True if the process succeeded, false otherwise</returns>
public (bool, string) DeleteUnnecessaryFiles(string? outputDirectory, string outputFilename)
{
// Prepare the necessary paths
outputFilename = Path.GetFileNameWithoutExtension(outputFilename);
string combinedBase;
if (string.IsNullOrEmpty(outputDirectory))
combinedBase = outputFilename;
else
combinedBase = Path.Combine(outputDirectory, outputFilename);
// Get the list of deleteable files from the parameters object
var files = GetDeleteableFilePaths(combinedBase);
if (!files.Any())
return (true, "No files to delete!");
// Attempt to delete all of the files
try
{
foreach (string file in files)
{
try
{
File.Delete(file);
}
catch { }
}
return (true, "Deletion complete!");
}
catch (Exception ex)
{
return (false, $"Deletion could not complete: {ex}");
}
}
/// <summary>
/// Ensures that all required output files have been created
/// </summary>
/// <param name="outputDirectory">Output folder to write to</param>
/// <param name="outputFilename">Output filename to use as the base path</param>
/// <param name="processor">Processor object representing how to process the outputs</param>
/// <param name="preCheck">True if this is a check done before a dump, false if done after</param>
/// <returns>Tuple of true if all required files exist, false otherwise and a list representing missing files</returns>
public (bool, List<string>) FoundAllFiles(string? outputDirectory, string outputFilename, bool preCheck)
{
// First, sanitized the output filename to strip off any potential extension
outputFilename = Path.GetFileNameWithoutExtension(outputFilename);
// Then get the base path for all checking
string basePath;
if (string.IsNullOrEmpty(outputDirectory))
basePath = outputFilename;
else
basePath = Path.Combine(outputDirectory, outputFilename);
// Finally, let the parameters say if all files exist
return CheckAllOutputFilesExist(basePath, preCheck);
}
/// <summary>
/// Get the hex contents of the PIC file
/// </summary>
/// <param name="picPath">Path to the PIC.bin file associated with the dump</param>
/// <param name="trimLength">Number of characters to trim the PIC to, if -1, ignored</param>
/// <returns>PIC data as a hex string if possible, null on error</returns>
/// <remarks>https://stackoverflow.com/questions/9932096/add-separator-to-string-at-every-n-characters</remarks>
protected static string? GetPIC(string picPath, int trimLength = -1)
{
// If the file doesn't exist, we can't get the info
if (!File.Exists(picPath))
return null;
try
{
var hex = ProcessingTool.GetFullFile(picPath, true);
if (hex == null)
return null;
if (trimLength > -1)
hex = hex.Substring(0, trimLength);
// TODO: Check for non-zero values in discarded PIC
return Regex.Replace(hex, ".{32}", "$0\n", RegexOptions.Compiled);
}
catch
{
// We don't care what the error was right now
return null;
}
}
/// <summary>
/// Get a isobuster-formatted PVD from a 2048 byte-per-sector image, if possible
/// </summary>
/// <param name="isoPath">Path to ISO file</param>
/// <param name="pvd">Formatted PVD string, otherwise null</param>
/// <returns>True if PVD was successfully parsed, otherwise false</returns>
protected static bool GetPVD(string isoPath, out string? pvd)
{
pvd = null;
try
{
// Get PVD bytes from ISO file
var buf = new byte[96];
using (FileStream iso = File.OpenRead(isoPath))
{
// TODO: Don't hardcode 0x8320
iso.Seek(0x8320, SeekOrigin.Begin);
int offset = 0;
while (offset < 96)
{
int read = iso.Read(buf, offset, buf.Length - offset);
if (read == 0)
throw new EndOfStreamException();
offset += read;
}
}
// Format PVD to isobuster standard
char[] pvdCharArray = new char[96];
for (int i = 0; i < 96; i++)
{
if (buf[i] >= 0x20 && buf[i] <= 0x7E)
pvdCharArray[i] = (char)buf[i];
else
pvdCharArray[i] = '.';
}
string pvdASCII = new string(pvdCharArray, 0, 96);
pvd = string.Empty;
for (int i = 0; i < 96; i += 16)
{
pvd += $"{(0x0320 + i):X4} : {buf[i]:X2} {buf[i + 1]:X2} {buf[i + 2]:X2} {buf[i + 3]:X2} {buf[i + 4]:X2} {buf[i + 5]:X2} {buf[i + 6]:X2} {buf[i + 7]:X2} " +
$"{buf[i + 8]:X2} {buf[i + 9]:X2} {buf[i + 10]:X2} {buf[i + 11]:X2} {buf[i + 12]:X2} {buf[i + 13]:X2} {buf[i + 14]:X2} {buf[i + 15]:X2} {pvdASCII.Substring(i, 16)}\n";
}
return true;
}
catch
{
// We don't care what the error is
return false;
}
}
/// <summary>
/// Generate a list of all MPF-specific log files generated
/// </summary>
/// <param name="outputDirectory">Output folder to write to</param>
/// <param name="filenameSuffix">Optional suffix to append to the filename</param>
/// <returns>List of all log file paths, empty otherwise</returns>
private static List<string> GetGeneratedFilePaths(string? outputDirectory, string? filenameSuffix)
{
var files = new List<string>();
if (string.IsNullOrEmpty(outputDirectory) && string.IsNullOrEmpty(filenameSuffix))
{
if (File.Exists("!submissionInfo.txt"))
files.Add("!submissionInfo.txt");
if (File.Exists("!submissionInfo.json"))
files.Add("!submissionInfo.json");
if (File.Exists("!submissionInfo.json.gz"))
files.Add("!submissionInfo.json.gz");
if (File.Exists("!protectionInfo.txt"))
files.Add("!protectionInfo.txt");
}
else if (string.IsNullOrEmpty(outputDirectory) && !string.IsNullOrEmpty(filenameSuffix))
{
if (File.Exists($"!submissionInfo_{filenameSuffix}.txt"))
files.Add($"!submissionInfo_{filenameSuffix}.txt");
if (File.Exists($"!submissionInfo_{filenameSuffix}.json"))
files.Add($"!submissionInfo_{filenameSuffix}.json");
if (File.Exists($"!submissionInfo_{filenameSuffix}.json.gz"))
files.Add($"!submissionInfo_{filenameSuffix}.json.gz");
if (File.Exists($"!protectionInfo_{filenameSuffix}.txt"))
files.Add($"!protectionInfo_{filenameSuffix}.txt");
}
else if (!string.IsNullOrEmpty(outputDirectory) && string.IsNullOrEmpty(filenameSuffix))
{
if (File.Exists(Path.Combine(outputDirectory, "!submissionInfo.txt")))
files.Add(Path.Combine(outputDirectory, "!submissionInfo.txt"));
if (File.Exists(Path.Combine(outputDirectory, "!submissionInfo.json")))
files.Add(Path.Combine(outputDirectory, "!submissionInfo.json"));
if (File.Exists(Path.Combine(outputDirectory, "!submissionInfo.json.gz")))
files.Add(Path.Combine(outputDirectory, "!submissionInfo.json.gz"));
if (File.Exists(Path.Combine(outputDirectory, "!protectionInfo.txt")))
files.Add(Path.Combine(outputDirectory, "!protectionInfo.txt"));
}
else if (!string.IsNullOrEmpty(outputDirectory) && !string.IsNullOrEmpty(filenameSuffix))
{
if (File.Exists(Path.Combine(outputDirectory, $"!submissionInfo_{filenameSuffix}.txt")))
files.Add(Path.Combine(outputDirectory, $"!submissionInfo_{filenameSuffix}.txt"));
if (File.Exists(Path.Combine(outputDirectory, $"!submissionInfo_{filenameSuffix}.json")))
files.Add(Path.Combine(outputDirectory, $"!submissionInfo_{filenameSuffix}.json"));
if (File.Exists(Path.Combine(outputDirectory, $"!submissionInfo_{filenameSuffix}.json.gz")))
files.Add(Path.Combine(outputDirectory, $"!submissionInfo_{filenameSuffix}.json.gz"));
if (File.Exists(Path.Combine(outputDirectory, $"!protectionInfo_{filenameSuffix}.txt")))
files.Add(Path.Combine(outputDirectory, $"!protectionInfo_{filenameSuffix}.txt"));
}
return files;
}
#endregion
}
}

View File

@@ -2,42 +2,28 @@
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using MPF.Core.Converters;
using MPF.Core.Data;
using SabreTools.Hashing;
using SabreTools.Models.Logiqx;
using SabreTools.RedumpLib;
using SabreTools.RedumpLib.Data;
namespace MPF.Core.Modules.CleanRip
namespace MPF.Processors
{
/// <summary>
/// Represents a generic set of CleanRip parameters
/// Represents processing CleanRip outputs
/// </summary>
public class Parameters : BaseParameters
public sealed class CleanRip : BaseProcessor
{
#region Metadata
/// <inheritdoc/>
public override InternalProgram InternalProgram => InternalProgram.CleanRip;
public CleanRip(RedumpSystem? system, MediaType? type) : base(system, type) { }
#endregion
/// <inheritdoc/>
public Parameters(string? parameters) : base(parameters) { }
/// <inheritdoc/>
public Parameters(RedumpSystem? system, MediaType? type, string? drivePath, string filename, int? driveSpeed, Options options)
: base(system, type, drivePath, filename, driveSpeed, options)
{
}
#region BaseParameters Implementations
#region BaseProcessor Implementations
/// <inheritdoc/>
public override (bool, List<string>) CheckAllOutputFilesExist(string basePath, bool preCheck)
{
var missingFiles = new List<string>();
switch (this.Type)
switch (Type)
{
case MediaType.DVD: // Only added here to help users; not strictly correct
case MediaType.NintendoGameCubeGameDisc:
@@ -61,24 +47,31 @@ namespace MPF.Core.Modules.CleanRip
}
/// <inheritdoc/>
public override void GenerateSubmissionInfo(SubmissionInfo info, Options options, string basePath, Drive? drive, bool includeArtifacts)
public override void GenerateArtifacts(SubmissionInfo info, string basePath)
{
info.Artifacts ??= [];
if (File.Exists(basePath + ".bca"))
info.Artifacts["bca"] = ProcessingTool.GetBase64(ProcessingTool.GetFullFile(basePath + ".bca", binary: true)) ?? string.Empty;
if (File.Exists(basePath + "-dumpinfo.txt"))
info.Artifacts["dumpinfo"] = ProcessingTool.GetBase64(ProcessingTool.GetFullFile(basePath + "-dumpinfo.txt")) ?? string.Empty;
}
/// <inheritdoc/>
public override void GenerateSubmissionInfo(SubmissionInfo info, string basePath, bool redumpCompat)
{
// Ensure that required sections exist
info = Builder.EnsureAllSections(info);
// TODO: Determine if there's a CleanRip version anywhere
info.DumpingInfo!.DumpingProgram = EnumConverter.LongName(this.InternalProgram);
info.DumpingInfo.DumpingDate = InfoTool.GetFileModifiedDate(basePath + "-dumpinfo.txt")?.ToString("yyyy-MM-dd HH:mm:ss");
info.DumpingInfo!.DumpingDate = ProcessingTool.GetFileModifiedDate(basePath + "-dumpinfo.txt")?.ToString("yyyy-MM-dd HH:mm:ss");
// Get the Datafile information
var datafile = GenerateCleanripDatafile(basePath + ".iso", basePath + "-dumpinfo.txt");
// ClrMameProData format is only for CDs
if (this.Type == MediaType.CDROM)
info.TracksAndWriteOffsets!.ClrMameProData = InfoTool.GenerateDatfile(datafile);
info.TracksAndWriteOffsets!.ClrMameProData = ProcessingTool.GenerateDatfile(datafile);
// Get the individual hash data, as per internal
if (InfoTool.GetISOHashValues(datafile, out long size, out var crc32, out var md5, out var sha1))
if (ProcessingTool.GetISOHashValues(datafile, out long size, out var crc32, out var md5, out var sha1))
{
info.SizeAndChecksums!.Size = size;
info.SizeAndChecksums.CRC32 = crc32;
@@ -91,7 +84,7 @@ namespace MPF.Core.Modules.CleanRip
}
// Extract info based generically on MediaType
switch (this.Type)
switch (Type)
{
case MediaType.DVD: // Only added here to help users; not strictly correct
case MediaType.NintendoGameCubeGameDisc:
@@ -109,24 +102,13 @@ namespace MPF.Core.Modules.CleanRip
break;
}
// Fill in any artifacts that exist, Base64-encoded, if we need to
if (includeArtifacts)
{
info.Artifacts ??= [];
if (File.Exists(basePath + ".bca"))
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")) ?? string.Empty;
}
}
/// <inheritdoc/>
public override List<string> GetLogFilePaths(string basePath)
{
var logFiles = new List<string>();
switch (this.Type)
switch (Type)
{
case MediaType.DVD: // Only added here to help users; not strictly correct
case MediaType.NintendoGameCubeGameDisc:
@@ -197,16 +179,7 @@ namespace MPF.Core.Modules.CleanRip
return new Datafile
{
Games =
[
new()
{
Roms =
[
new Rom { Name = Path.GetFileName(iso), Size = size.ToString(), Crc = crc, Md5 = md5, Sha1 = sha1 },
]
}
]
Game = [new Game() { Rom = [new Rom { Name = Path.GetFileName(iso), Size = size.ToString(), CRC = crc, MD5 = md5, SHA1 = sha1 }] }]
};
}
catch
@@ -230,7 +203,7 @@ namespace MPF.Core.Modules.CleanRip
try
{
var hex = GetFullFile(bcaPath, true);
var hex = ProcessingTool.GetFullFile(bcaPath, true);
if (hex == null)
return null;
@@ -243,53 +216,6 @@ namespace MPF.Core.Modules.CleanRip
}
}
/// <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>
private static string? GetCleanripDatfile(string iso, string dumpinfo)
{
// If the files don't exist, we can't get info from it
if (!File.Exists(iso) || !File.Exists(dumpinfo))
return null;
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
using var sr = File.OpenText(dumpinfo);
if (sr.ReadLine()?.Contains("--File Generated by CleanRip") != true)
return null;
// Read all lines and gather dat information
while (!sr.EndOfStream)
{
var line = sr.ReadLine()?.Trim();
if (string.IsNullOrEmpty(line))
continue;
else if (line!.StartsWith("CRC32"))
crc = line.Substring(7).ToLowerInvariant();
else if (line.StartsWith("MD5"))
md5 = line.Substring(5);
else if (line.StartsWith("SHA-1"))
sha1 = line.Substring(7);
}
return $"<rom name=\"{Path.GetFileName(iso)}\" size=\"{size}\" crc=\"{crc}\" md5=\"{md5}\" sha1=\"{sha1}\" />";
}
catch
{
// We don't care what the exception is right now
return null;
}
}
/// <summary>
/// Get the extracted GC and Wii version
/// </summary>

View File

@@ -0,0 +1,67 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Assembly Properties -->
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<VersionPrefix>3.2.1</VersionPrefix>
<WarningsNotAsErrors>NU5104</WarningsNotAsErrors>
<!-- Package Properties -->
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
<Description>Common code for all MPF implementations</Description>
<Copyright>Copyright (c) Matt Nadareski 2019-2024</Copyright>
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
<RepositoryUrl>https://github.com/SabreTools/MPF</RepositoryUrl>
<RepositoryType>git</RepositoryType>
</PropertyGroup>
<ItemGroup>
<InternalsVisibleTo Include="MPF.Test" />
</ItemGroup>
<!-- Support All Frameworks -->
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net4`))">
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`))">
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`))">
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(RuntimeIdentifier.StartsWith(`osx-arm`))">
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
</PropertyGroup>
<!-- Support for old .NET versions -->
<ItemGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net40`))">
<PackageReference Include="MinAsyncBridge" Version="0.12.4" />
<PackageReference Include="MinTasksExtensionsBridge" Version="0.3.4" />
<PackageReference Include="MinThreadingBridge" Version="0.11.4" />
</ItemGroup>
<ItemGroup Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`))">
<PackageReference Include="System.IO.Compression" Version="4.3.0" />
</ItemGroup>
<ItemGroup Condition="$(TargetFramework.StartsWith(`net4`)) AND !$(TargetFramework.StartsWith(`net40`))">
<PackageReference Include="IndexRange" Version="1.0.3" />
</ItemGroup>
<ItemGroup Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`)) AND !$(TargetFramework.StartsWith(`net452`))">
<PackageReference Include="System.IO.Compression.ZipFile" Version="4.3.0" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="psxt001z.Library" Version="0.21.0-rc1" />
<PackageReference Include="SabreTools.Hashing" Version="1.2.0" />
<PackageReference Include="SabreTools.Models" Version="1.4.8" />
<PackageReference Include="SabreTools.RedumpLib" Version="1.4.1" />
<PackageReference Include="SabreTools.Serialization" Version="1.6.5" />
</ItemGroup>
</Project>

192
MPF.Processors/PS3CFW.cs Normal file
View File

@@ -0,0 +1,192 @@
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using SabreTools.Hashing;
using SabreTools.Models.Logiqx;
using SabreTools.RedumpLib;
using SabreTools.RedumpLib.Data;
namespace MPF.Processors
{
/// <summary>
/// Represents processing PlayStation 3 Custom Firmware outputs
/// </summary>
public sealed class PS3CFW : BaseProcessor
{
/// <inheritdoc/>
public PS3CFW(RedumpSystem? system, MediaType? type) : base(system, type) { }
#region BaseProcessor Implementations
/// <inheritdoc/>
public override (bool, List<string>) CheckAllOutputFilesExist(string basePath, bool preCheck)
{
var missingFiles = new List<string>();
if (Type != MediaType.BluRay || System != RedumpSystem.SonyPlayStation3)
{
missingFiles.Add("Media and system combination not supported for PS3 CFW");
}
else
{
string? getKeyBasePath = GetCFWBasePath(basePath);
if (!File.Exists($"{getKeyBasePath}.getkey.log"))
missingFiles.Add($"{getKeyBasePath}.getkey.log");
if (!File.Exists($"{getKeyBasePath}.disc.pic"))
missingFiles.Add($"{getKeyBasePath}.disc.pic");
}
return (missingFiles.Count == 0, missingFiles);
}
/// <inheritdoc/>
public override void GenerateArtifacts(SubmissionInfo info, string basePath)
{
info.Artifacts ??= [];
string? getKeyBasePath = GetCFWBasePath(basePath);
if (File.Exists(getKeyBasePath + ".disc.pic"))
info.Artifacts["discpic"] = ProcessingTool.GetBase64(ProcessingTool.GetFullFile(getKeyBasePath + ".disc.pic", binary: true)) ?? string.Empty;
if (File.Exists(getKeyBasePath + ".getkey.log"))
info.Artifacts["getkeylog"] = ProcessingTool.GetBase64(ProcessingTool.GetFullFile(getKeyBasePath + ".getkey.log")) ?? string.Empty;
}
/// <inheritdoc/>
public override void GenerateSubmissionInfo(SubmissionInfo info, string basePath, bool redumpCompat)
{
// Ensure that required sections exist
info = Builder.EnsureAllSections(info);
// TODO: Determine if there's a CFW version anywhere
info.DumpingInfo!.DumpingDate = ProcessingTool.GetFileModifiedDate(basePath + ".iso")?.ToString("yyyy-MM-dd HH:mm:ss");
// Get the Datafile information
Datafile? datafile = GeneratePS3CFWDatafile(basePath + ".iso");
// Fill in the hash data
info.TracksAndWriteOffsets!.ClrMameProData = ProcessingTool.GenerateDatfile(datafile);
// Get the individual hash data, as per internal
if (ProcessingTool.GetISOHashValues(datafile, out long size, out var crc32, out var md5, out var sha1))
{
info.SizeAndChecksums!.Size = size;
info.SizeAndChecksums.CRC32 = crc32;
info.SizeAndChecksums.MD5 = md5;
info.SizeAndChecksums.SHA1 = sha1;
}
// Get the PVD from the ISO
if (GetPVD(basePath + ".iso", out string? pvd))
info.Extras!.PVD = pvd;
// Try to determine the name of the GetKey file(s)
string? getKeyBasePath = GetCFWBasePath(basePath);
// If GenerateSubmissionInfo is run, .getkey.log existence should already be checked
if (!File.Exists(getKeyBasePath + ".getkey.log"))
return;
// Get dumping date from GetKey log date
info.DumpingInfo.DumpingDate = ProcessingTool.GetFileModifiedDate(getKeyBasePath + ".getkey.log")?.ToString("yyyy-MM-dd HH:mm:ss");
// TODO: Put info about abnormal PIC info beyond 132 bytes in comments?
if (File.Exists(getKeyBasePath + ".disc.pic"))
info.Extras!.PIC = GetPIC(getKeyBasePath + ".disc.pic", 264);
// Parse Disc Key, Disc ID, and PIC from the .getkey.log file
if (ProcessingTool.ParseGetKeyLog(getKeyBasePath + ".getkey.log", out string? key, out string? id, out string? pic))
{
if (key != null)
info.Extras!.DiscKey = key.ToUpperInvariant();
if (id != null)
info.Extras!.DiscID = id.ToUpperInvariant().Substring(0, 24) + "XXXXXXXX";
if (string.IsNullOrEmpty(info.Extras!.PIC) && !string.IsNullOrEmpty(pic))
{
pic = Regex.Replace(pic, ".{32}", "$0\n");
info.Extras.PIC = pic;
}
}
}
/// <inheritdoc/>
public override List<string> GetLogFilePaths(string basePath)
{
var logFiles = new List<string>();
string? getKeyBasePath = GetCFWBasePath(basePath);
if (System != RedumpSystem.SonyPlayStation3)
return logFiles;
switch (Type)
{
case MediaType.BluRay:
if (File.Exists($"{getKeyBasePath}.getkey.log"))
logFiles.Add($"{getKeyBasePath}.getkey.log");
if (File.Exists($"{getKeyBasePath}.disc.pic"))
logFiles.Add($"{getKeyBasePath}.disc.pic");
break;
}
return logFiles;
}
#endregion
#region Information Extraction Methods
/// <summary>
/// Get a formatted datfile from the PS3 CFW output, if possible
/// </summary>
/// <param name="iso">Path to ISO file</param>
/// <returns></returns>
private static Datafile? GeneratePS3CFWDatafile(string iso)
{
// If the ISO file doesn't exist, we can't get info from it
if (!File.Exists(iso))
return null;
try
{
if (HashTool.GetStandardHashes(iso, out long size, out string? crc, out string? md5, out string? sha1))
{
return new Datafile
{
Game = [new Game { Rom = [new Rom { Name = Path.GetFileName(iso), Size = size.ToString(), CRC = crc, MD5 = md5, SHA1 = sha1 }] }]
};
}
return null;
}
catch
{
// We don't care what the exception is right now
return null;
}
}
#endregion
#region Helper Functions
/// <summary>
/// Estimate the base filename of the .getkey.log file associated with the dump
/// </summary>
/// <param name="iso">Path to ISO file</param>
/// <returns>Base filename, null if not found</returns>
private string? GetCFWBasePath(string iso)
{
string? dir = Path.GetDirectoryName(iso);
dir ??= ".";
string[] files = Directory.GetFiles(dir, "*.getkey.log");
if (files.Length != 1)
return null;
return files[0].Substring(0, files[0].Length - 11);
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,42 +2,28 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using MPF.Core.Converters;
using MPF.Core.Data;
using SabreTools.Hashing;
using SabreTools.Models.Logiqx;
using SabreTools.RedumpLib;
using SabreTools.RedumpLib.Data;
namespace MPF.Core.Modules.UmdImageCreator
namespace MPF.Processors
{
/// <summary>
/// Represents a generic set of UmdImageCreator parameters
/// Represents processing UmdImageCreator outputs
/// </summary>
public class Parameters : BaseParameters
public sealed class UmdImageCreator : BaseProcessor
{
#region Metadata
/// <inheritdoc/>
public override InternalProgram InternalProgram => InternalProgram.UmdImageCreator;
public UmdImageCreator(RedumpSystem? system, MediaType? type) : base(system, type) { }
#endregion
/// <inheritdoc/>
public Parameters(string? parameters) : base(parameters) { }
/// <inheritdoc/>
public Parameters(RedumpSystem? system, MediaType? type, string? drivePath, string filename, int? driveSpeed, Options options)
: base(system, type, drivePath, filename, driveSpeed, options)
{
}
#region BaseParameters Implementations
#region BaseProcessor Implementations
/// <inheritdoc/>
public override (bool, List<string>) CheckAllOutputFilesExist(string basePath, bool preCheck)
{
var missingFiles = new List<string>();
switch (this.Type)
switch (Type)
{
case MediaType.UMD:
if (!File.Exists($"{basePath}_logs.zip") || !preCheck)
@@ -63,17 +49,39 @@ namespace MPF.Core.Modules.UmdImageCreator
}
/// <inheritdoc/>
public override void GenerateSubmissionInfo(SubmissionInfo info, Options options, string basePath, Drive? drive, bool includeArtifacts)
public override void GenerateArtifacts(SubmissionInfo info, string basePath)
{
info.Artifacts ??= [];
if (File.Exists($"{basePath}_disc.txt"))
info.Artifacts["disc"] = ProcessingTool.GetBase64(ProcessingTool.GetFullFile($"{basePath}_disc.txt")) ?? string.Empty;
if (File.Exists($"{basePath}_drive.txt"))
info.Artifacts["drive"] = ProcessingTool.GetBase64(ProcessingTool.GetFullFile($"{basePath}_drive.txt")) ?? string.Empty;
if (File.Exists($"{basePath}_mainError.txt"))
info.Artifacts["mainError"] = ProcessingTool.GetBase64(ProcessingTool.GetFullFile($"{basePath}_mainError.txt")) ?? string.Empty;
if (File.Exists($"{basePath}_mainInfo.txt"))
info.Artifacts["mainInfo"] = ProcessingTool.GetBase64(ProcessingTool.GetFullFile($"{basePath}_mainInfo.txt")) ?? string.Empty;
//if (File.Exists($"{basePath}_PFI.bin"))
// info.Artifacts["pfi"] = Convert.ToBase64String(File.ReadAllBytes($"{basePath}_PFI.bin")) ?? string.Empty;
if (File.Exists($"{basePath}_volDesc.txt"))
info.Artifacts["volDesc"] = ProcessingTool.GetBase64(ProcessingTool.GetFullFile($"{basePath}_volDesc.txt")) ?? string.Empty;
}
/// <inheritdoc/>
public override void GenerateSubmissionInfo(SubmissionInfo info, string basePath, bool redumpCompat)
{
// Ensure that required sections exist
info = Builder.EnsureAllSections(info);
// TODO: Determine if there's a UMDImageCreator version anywhere
info.DumpingInfo!.DumpingProgram = EnumConverter.LongName(this.InternalProgram);
info.DumpingInfo.DumpingDate = InfoTool.GetFileModifiedDate(basePath + "_disc.txt")?.ToString("yyyy-MM-dd HH:mm:ss");
info.DumpingInfo!.DumpingDate = ProcessingTool.GetFileModifiedDate(basePath + "_disc.txt")?.ToString("yyyy-MM-dd HH:mm:ss");
// Fill in the volume labels
if (GetVolumeLabels($"{basePath}_volDesc.txt", out var volLabels))
VolumeLabels = volLabels;
// Extract info based generically on MediaType
switch (this.Type)
switch (Type)
{
case MediaType.UMD:
info.Extras!.PVD = GetPVD(basePath + "_mainInfo.txt") ?? string.Empty;
@@ -83,11 +91,11 @@ namespace MPF.Core.Modules.UmdImageCreator
// Get the Datafile information
var datafile = new Datafile
{
Games = [new Game { Roms = [new Rom { Name = string.Empty, Size = filesize.ToString(), Crc = crc32, Md5 = md5, Sha1 = sha1, }] }]
Game = [new Game { Rom = [new Rom { Name = string.Empty, Size = filesize.ToString(), CRC = crc32, MD5 = md5, SHA1 = sha1 }] }]
};
// Fill in the hash data
info.TracksAndWriteOffsets!.ClrMameProData = InfoTool.GenerateDatfile(datafile);
info.TracksAndWriteOffsets!.ClrMameProData = ProcessingTool.GenerateDatfile(datafile);
info.SizeAndChecksums!.Size = filesize;
info.SizeAndChecksums.CRC32 = crc32;
@@ -108,30 +116,13 @@ namespace MPF.Core.Modules.UmdImageCreator
break;
}
// Fill in any artifacts that exist, Base64-encoded, if we need to
if (includeArtifacts)
{
info.Artifacts ??= [];
if (File.Exists(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")) ?? string.Empty;
if (File.Exists(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")) ?? string.Empty;
}
}
/// <inheritdoc/>
public override List<string> GetLogFilePaths(string basePath)
{
var logFiles = new List<string>();
switch (this.Type)
switch (Type)
{
case MediaType.UMD:
if (File.Exists($"{basePath}_disc.txt"))
@@ -145,6 +136,9 @@ namespace MPF.Core.Modules.UmdImageCreator
if (File.Exists($"{basePath}_volDesc.txt"))
logFiles.Add($"{basePath}_volDesc.txt");
if (File.Exists($"{basePath}_PFI.bin"))
logFiles.Add($"{basePath}_PFI.bin");
break;
}
@@ -217,7 +211,7 @@ namespace MPF.Core.Modules.UmdImageCreator
else if (line.StartsWith("DISC_VERSION") && umdversion == null)
umdversion = line.Split(' ')[1];
else if (line.StartsWith("pspUmdTypes"))
umdcat = InfoTool.GetUMDCategory(line.Split(' ')[1]);
umdcat = ProcessingTool.GetUMDCategory(line.Split(' ')[1]);
else if (line.StartsWith("L0 length"))
umdlayer = line.Split(' ')[2];
else if (line.StartsWith("FileSize:"))
@@ -237,6 +231,90 @@ namespace MPF.Core.Modules.UmdImageCreator
}
}
/// <summary>
/// Get all Volume Identifiers
/// </summary>
/// <param name="volDesc">_volDesc.txt file location</param>
/// <returns>Volume labels (by type), or null if none present</returns>
/// <remarks>This is a copy of the code from DiscImageCreator and has extrandous checks</remarks>
private static bool GetVolumeLabels(string volDesc, out Dictionary<string, List<string>> volLabels)
{
// If the file doesn't exist, can't get the volume labels
volLabels = [];
if (!File.Exists(volDesc))
return false;
try
{
using var sr = File.OpenText(volDesc);
var line = sr.ReadLine();
string volType = "UNKNOWN";
string label;
while (line != null)
{
// Trim the line for later use
line = line.Trim();
// ISO9660 and extensions section
if (line.StartsWith("Volume Descriptor Type: "))
{
Int32.TryParse(line.Substring("Volume Descriptor Type: ".Length), out int volTypeInt);
volType = volTypeInt switch
{
// 0 => "Boot Record" // Should not not contain a Volume Identifier
1 => "ISO", // ISO9660
2 => "Joliet",
// 3 => "Volume Partition Descriptor" // Should not not contain a Volume Identifier
// 255 => "???" // Should not not contain a Volume Identifier
_ => "UNKNOWN" // Should not contain a Volume Identifier
};
}
// UDF section
else if (line.StartsWith("Primary Volume Descriptor Number:"))
{
volType = "UDF";
}
// Identifier
else if (line.StartsWith("Volume Identifier: "))
{
label = line.Substring("Volume Identifier: ".Length);
// Remove leading non-printable character (unsure why DIC outputs this)
if (Convert.ToUInt32(label[0]) == 0x7F || Convert.ToUInt32(label[0]) < 0x20)
label = label.Substring(1);
// Skip if label is blank
if (label == null || label.Length <= 0)
{
volType = "UNKNOWN";
line = sr.ReadLine();
continue;
}
if (volLabels.ContainsKey(label))
volLabels[label].Add(volType);
else
volLabels.Add(label, [volType]);
// Reset volume type
volType = "UNKNOWN";
}
line = sr.ReadLine();
}
// Return true if a volume label was found
return volLabels.Count > 0;
}
catch
{
// We don't care what the exception is right now
volLabels = [];
return false;
}
}
#endregion
}
}

View File

@@ -0,0 +1,666 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SabreTools.Hashing;
using SabreTools.Models.Logiqx;
using SabreTools.RedumpLib;
using SabreTools.RedumpLib.Data;
namespace MPF.Processors
{
/// <summary>
/// Represents processing Xbox Backup Creator outputs
/// </summary>
public sealed class XboxBackupCreator : BaseProcessor
{
/// <inheritdoc/>
public XboxBackupCreator(RedumpSystem? system, MediaType? type) : base(system, type) { }
#region BaseProcessor Implementations
/// <inheritdoc/>
public override (bool, List<string>) CheckAllOutputFilesExist(string basePath, bool preCheck)
{
var missingFiles = new List<string>();
switch (Type)
{
case MediaType.DVD:
if (!File.Exists($"{basePath}_logs.zip") || !preCheck)
{
string baseDir = Path.GetDirectoryName(basePath) + Path.DirectorySeparatorChar;
string? logPath = GetLogName(baseDir);
if (string.IsNullOrEmpty(logPath))
missingFiles.Add($"{baseDir}Log.txt");
if (!File.Exists($"{baseDir}DMI.bin"))
missingFiles.Add($"{baseDir}DMI.bin");
if (!File.Exists($"{baseDir}PFI.bin"))
missingFiles.Add($"{baseDir}PFI.bin");
if (!File.Exists($"{baseDir}SS.bin"))
missingFiles.Add($"{baseDir}SS.bin");
// Not required from XBC
//if (!File.Exists($"{basePath}.dvd"))
// missingFiles.Add($"{basePath}.dvd");
}
break;
default:
missingFiles.Add("Media and system combination not supported for XboxBackupCreator");
break;
}
return (!missingFiles.Any(), missingFiles);
}
/// <inheritdoc/>
public override void GenerateArtifacts(SubmissionInfo info, string basePath)
{
info.Artifacts ??= [];
string baseDir = Path.GetDirectoryName(basePath) + Path.DirectorySeparatorChar;
string? logPath = GetLogName(baseDir);
if (File.Exists(logPath))
info.Artifacts["log"] = ProcessingTool.GetBase64(ProcessingTool.GetFullFile(logPath!)) ?? string.Empty;
if (File.Exists($"{basePath}.dvd"))
info.Artifacts["dvd"] = ProcessingTool.GetBase64(ProcessingTool.GetFullFile($"{basePath}.dvd")) ?? string.Empty;
//if (File.Exists($"{baseDir}DMI.bin"))
// info.Artifacts["dmi"] = Convert.ToBase64String(File.ReadAllBytes($"{baseDir}DMI.bin")) ?? string.Empty;
// TODO: Include PFI artifact only if the hash doesn't match known PFI hashes
//if (File.Exists($"{baseDir}PFI.bin"))
// info.Artifacts["pfi"] = Convert.ToBase64String(File.ReadAllBytes($"{baseDir}PFI.bin")) ?? string.Empty;
//if (File.Exists($"{baseDir}SS.bin"))
// info.Artifacts["ss"] = Convert.ToBase64String(File.ReadAllBytes($"{baseDir}SS.bin")) ?? string.Empty;
//if (File.Exists($"{baseDir}RawSS.bin"))
// info.Artifacts["rawss"] = Convert.ToBase64String(File.ReadAllBytes($"{baseDir}RawSS.bin")) ?? string.Empty;
}
/// <inheritdoc/>
public override void GenerateSubmissionInfo(SubmissionInfo info, string basePath, bool redumpCompat)
{
// Ensure that required sections exist
info = Builder.EnsureAllSections(info);
// Get base directory
string baseDir = Path.GetDirectoryName(basePath) + Path.DirectorySeparatorChar;
// Get log filename
string? logPath = GetLogName(baseDir);
if (string.IsNullOrEmpty(logPath))
return;
// XBC dump info
info.DumpingInfo!.DumpingProgram ??= string.Empty;
info.DumpingInfo.DumpingProgram += $" {GetVersion(logPath) ?? "Unknown Version"}";
info.DumpingInfo.DumpingDate = ProcessingTool.GetFileModifiedDate(logPath)?.ToString("yyyy-MM-dd HH:mm:ss");
info.DumpingInfo.Model = GetDrive(logPath) ?? "Unknown Drive";
// Look for read errors
if (GetReadErrors(logPath, out long readErrors))
info.CommonDiscInfo!.ErrorsCount = readErrors == -1 ? "Error retrieving error count" : readErrors.ToString();
// Extract info based generically on MediaType
switch (Type)
{
case MediaType.DVD:
// Get Layerbreak from .dvd file if possible
if (GetLayerbreak($"{basePath}.dvd", out long layerbreak))
info.SizeAndChecksums!.Layerbreak = layerbreak;
// Hash data
if (HashTool.GetStandardHashes(basePath + ".iso", out long filesize, out var crc32, out var md5, out var sha1))
{
// Get the Datafile information
var datafile = new Datafile
{
Game = [new Game { Rom = [new Rom { Name = string.Empty, Size = filesize.ToString(), CRC = crc32, MD5 = md5, SHA1 = sha1 }] }]
};
// Fill in the hash data
info.TracksAndWriteOffsets!.ClrMameProData = ProcessingTool.GenerateDatfile(datafile);
info.SizeAndChecksums!.Size = filesize;
info.SizeAndChecksums.CRC32 = crc32;
info.SizeAndChecksums.MD5 = md5;
info.SizeAndChecksums.SHA1 = sha1;
}
switch (System)
{
case RedumpSystem.MicrosoftXbox:
// Parse DMI.bin
string xmidString = ProcessingTool.GetXGD1XMID($"{baseDir}DMI.bin");
var xmid = SabreTools.Serialization.Wrappers.XMID.Create(xmidString);
if (xmid != null)
{
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.XMID] = xmidString?.TrimEnd('\0') ?? string.Empty;
info.CommonDiscInfo.Serial = xmid.Serial ?? string.Empty;
if (!redumpCompat)
info.VersionAndEditions!.Version = xmid.Version ?? string.Empty;
info.CommonDiscInfo.Region = ProcessingTool.GetXGDRegion(xmid.Model.RegionIdentifier);
}
break;
case RedumpSystem.MicrosoftXbox360:
// Get PVD from ISO
if (GetPVD(basePath + ".iso", out string? pvd))
info.Extras!.PVD = pvd;
// Parse Media ID
//string? mediaID = GetMediaID(logPath);
// Parse DMI.bin
string xemidString = ProcessingTool.GetXGD23XeMID($"{baseDir}DMI.bin");
var xemid = SabreTools.Serialization.Wrappers.XeMID.Create(xemidString);
if (xemid != null)
{
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.XeMID] = xemidString?.TrimEnd('\0') ?? string.Empty;
info.CommonDiscInfo.Serial = xemid.Serial ?? string.Empty;
if (!redumpCompat)
info.VersionAndEditions!.Version = xemid.Version ?? string.Empty;
info.CommonDiscInfo.Region = ProcessingTool.GetXGDRegion(xemid.Model.RegionIdentifier);
}
break;
}
// Deal with SS.bin
if (File.Exists($"{baseDir}SS.bin"))
{
// Save security sector ranges
string? ranges = ProcessingTool.GetSSRanges($"{baseDir}SS.bin");
if (!string.IsNullOrEmpty(ranges))
info.Extras!.SecuritySectorRanges = ranges;
// TODO: Determine SS version?
//info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.SSVersion] =
// Recreate RawSS.bin
RecreateSS(logPath!, $"{baseDir}SS.bin", $"{baseDir}RawSS.bin");
// Run ss_sector_range to get repeatable SS hash
ProcessingTool.CleanSS($"{baseDir}SS.bin", $"{baseDir}SS.bin");
}
// DMI/PFI/SS CRC32 hashes
if (File.Exists($"{baseDir}DMI.bin"))
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.DMIHash] = HashTool.GetFileHash($"{baseDir}DMI.bin", HashType.CRC32)?.ToUpperInvariant() ?? string.Empty;
if (File.Exists($"{baseDir}PFI.bin"))
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.PFIHash] = HashTool.GetFileHash($"{baseDir}PFI.bin", HashType.CRC32)?.ToUpperInvariant() ?? string.Empty;
if (File.Exists($"{baseDir}SS.bin"))
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.SSHash] = HashTool.GetFileHash($"{baseDir}SS.bin", HashType.CRC32)?.ToUpperInvariant() ?? string.Empty;
break;
}
}
/// <inheritdoc/>
public override List<string> GetLogFilePaths(string basePath)
{
var logFiles = new List<string>();
string baseDir = Path.GetDirectoryName(basePath) + Path.DirectorySeparatorChar;
switch (Type)
{
case MediaType.DVD:
string? logPath = GetLogName(baseDir);
if (!string.IsNullOrEmpty(logPath))
logFiles.Add(logPath!);
if (File.Exists($"{basePath}.dvd"))
logFiles.Add($"{basePath}.dvd");
if (File.Exists($"{baseDir}DMI.bin"))
logFiles.Add($"{baseDir}DMI.bin");
if (File.Exists($"{baseDir}PFI.bin"))
logFiles.Add($"{baseDir}PFI.bin");
if (File.Exists($"{baseDir}SS.bin"))
logFiles.Add($"{baseDir}SS.bin");
if (File.Exists($"{baseDir}RawSS.bin"))
logFiles.Add($"{baseDir}RawSS.bin");
break;
}
return logFiles;
}
#endregion
#region Information Extraction Methods
/// <summary>
/// Determines the file path of the XBC log
/// </summary>
/// <param name="baseDir">Base directory to search in</param>
/// <returns>Log path if found, null otherwise</returns>
private static string? GetLogName(string baseDir)
{
if (IsSuccessfulLog($"{baseDir}Log.txt"))
return $"{baseDir}Log.txt";
// Search for a renamed log file (assume there is only one)
string[] files = Directory.GetFiles(baseDir, "*.txt", SearchOption.TopDirectoryOnly);
foreach (string file in files)
{
if (IsSuccessfulLog(file))
return file;
}
return null;
}
/// <summary>
/// Checks if Log file has a successful read in it
/// </summary>
/// <param name="log">Path to log file</param>
/// <returns>True if successful log found, false otherwise</returns>
private static bool IsSuccessfulLog(string log)
{
if (!File.Exists(log))
return false;
// Successful Example:
// Read completed in 00:50:23
// Failed Example:
// Read failed
try
{
// If Version is not found, not a valid log file
if (string.IsNullOrEmpty(GetVersion(log)))
return false;
// Look for " Read completed in " in log file
using var sr = File.OpenText(log);
while (!sr.EndOfStream)
{
string? line = sr.ReadLine();
if (line?.StartsWith(" Read completed in ") == true)
{
return true;
}
}
// We couldn't find a successful dump
return false;
}
catch
{
// We don't care what the exception is right now
return false;
}
}
/// <summary>
/// Get the XBC version if possible
/// </summary>
/// <param name="log">Path to XBC log file</param>
/// <returns>Version if possible, null on error</returns>
private static string? GetVersion(string? log)
{
if (string.IsNullOrEmpty(log) || !File.Exists(log))
return null;
// Sample:
// ====================================================================
// Xbox Backup Creator v2.9 Build:0425 By Redline99
//
try
{
// Assume version is appended after first mention of Xbox Backup Creator
using var sr = File.OpenText(log);
while (!sr.EndOfStream)
{
string? line = sr.ReadLine()?.Trim();
if (line?.StartsWith("Xbox Backup Creator ") == true)
return line.Substring("Xbox Backup Creator ".Length).Trim();
}
// We couldn't detect the version
return null;
}
catch
{
// We don't care what the exception is right now
return null;
}
}
/// <summary>
/// Get the drive model from the log
/// </summary>
/// <param name="log">Path to XBC log file</param>
/// <returns>Drive model if found, null otherwise</returns>
private static string? GetDrive(string? log)
{
if (string.IsNullOrEmpty(log) || !File.Exists(log))
return null;
// Example:
// ========================================
// < --Security Sector Details -->
// Source Drive: SH-D162D
// ----------------------------------------
try
{
// Parse drive model from log file
using var sr = File.OpenText(log);
while (!sr.EndOfStream)
{
string? line = sr.ReadLine()?.Trim();
if (line?.StartsWith("Source Drive: ") == true)
{
return line.Substring("Source Drive: ".Length).Trim();
}
}
// We couldn't detect the drive model
return null;
}
catch
{
// We don't care what the exception is right now
return null;
}
}
/// <summary>
/// Get the Layerbreak value if possible
/// </summary>
/// <param name="dvd">Path to layerbreak file</param>
/// <param name="layerbreak">Layerbreak value if found</param>
/// <returns>True if successful, otherwise false</returns>
/// <returns></returns>
private static bool GetLayerbreak(string? dvd, out long layerbreak)
{
layerbreak = 0;
if (string.IsNullOrEmpty(dvd) || !File.Exists(dvd))
return false;
// Example:
// LayerBreak=1913776
// track.iso
try
{
// Parse Layerbreak value from DVD file
using var sr = File.OpenText(dvd);
while (!sr.EndOfStream)
{
string? line = sr.ReadLine()?.Trim();
if (line?.StartsWith("LayerBreak=") == true)
{
return long.TryParse(line.Substring("LayerBreak=".Length).Trim(), out layerbreak);
}
}
// We couldn't detect the Layerbreak
return false;
}
catch
{
// We don't care what the exception is right now
return false;
}
}
/// <summary>
/// Get the read error count if possible
/// </summary>
/// <param name="log">Path to XBC log file</param>
/// <param name="readErrors">Read error count if found, -1 otherwise</param>
/// <returns>True if sucessful, otherwise false</returns>
private bool GetReadErrors(string? log, out long readErrors)
{
readErrors = -1;
if (string.IsNullOrEmpty(log) || !File.Exists(log))
return false;
// TODO: Logic when more than one dump is in the logs
// Example: (replace [E] with drive letter)
// Creating SplitVid backup image [E]
// ...
// Reading Game Partition
// Setting read speed to 1x
// Unrecovered read error at Partition LBA: 0
// Example: (replace track with base filename)
// Creating Layer Break File
// LayerBreak file saved as: "track.dvd"
// A total of 1 sectors were zeroed out.
// Example: (for Original Xbox)
// A total of 65,536 sectors were zeroed out.
// A total of 31 sectors with read errors were recovered.
try
{
// Parse Layerbreak value from DVD file
using var sr = File.OpenText(log);
while (!sr.EndOfStream)
{
string? line = sr.ReadLine()?.Trim();
if (line?.StartsWith("Creating Layer Break File") == true)
{
// Read error count is two lines below
line = sr.ReadLine()?.Trim();
line = sr.ReadLine()?.Trim();
if (line?.StartsWith("A total of ") == true && line?.EndsWith(" sectors were zeroed out.") == true)
{
string? errorCount = line.Substring("A total of ".Length, line.Length - 36).Replace(",", "").Trim();
bool success = long.TryParse(errorCount, out readErrors);
// Original Xbox should have 65536 read errors when dumping with XBC
if (System == RedumpSystem.MicrosoftXbox)
{
if (readErrors == 65536)
readErrors = 0;
else if (readErrors > 65536)
readErrors -= 65536;
}
return success;
}
}
}
// We couldn't detect the read error count
return false;
}
catch
{
// We don't care what the exception is right now
return false;
}
}
/// <summary>
/// Get Xbox360 Media ID from XBC log file
/// </summary>
/// <param name="log">Path to XBC log file</param>
/// <returns>Media ID if Log successfully parsed, null otherwise</returns>
private string? GetMediaID(string? log)
{
if (string.IsNullOrEmpty(log) || !File.Exists(log))
return null;
if (System == RedumpSystem.MicrosoftXbox)
return null;
// Example:
// ----------------------------------------
// Media ID
// A76B9983D170EFF8749A892BC-8B62A812
// ----------------------------------------
try
{
// Parse Layerbreak value from DVD file
using var sr = File.OpenText(log);
while (!sr.EndOfStream)
{
string? line = sr.ReadLine()?.Trim();
if (line?.StartsWith("Media ID") == true)
{
line = sr.ReadLine()?.Trim();
return line?.Substring(25).Trim();
}
}
// We couldn't detect the Layerbreak
return null;
}
catch
{
// We don't care what the exception is right now
return null;
}
}
/// <summary>
/// Recreate an SS.bin file from XBC log and write it to a file
/// </summary>
/// <param name="log">Path to XBC log</param>
/// <param name="cleanSS">Path to the clean SS file to read from</param>
/// <param name="rawSS">Path to the raw SS file to write to</param>
/// <returns>True if successful, false otherwise</returns>
private static bool RecreateSS(string log, string cleanSS, string rawSS)
{
if (!File.Exists(log) || !File.Exists(cleanSS))
return false;
byte[] ss = File.ReadAllBytes(cleanSS);
if (ss.Length != 2048)
return false;
if (!RecreateSS(log!, ss))
return false;
File.WriteAllBytes(rawSS, ss);
return true;
}
/// <summary>
/// Recreate an SS.bin byte array from an XBC log.
/// With help from https://github.com/hadzz/SS-Angle-Fixer/
/// </summary>
/// <param name="log">Path to XBC log</param>
/// <param name="ss">Byte array of SS sector</param>
/// <returns>True if successful, false otherwise</returns>
private static bool RecreateSS(string log, byte[] ss)
{
// Log file must exist
if (!File.Exists(log))
return false;
// SS must be complete sector
if (ss.Length != 2048)
return false;
// Ignore XGD1 discs
if (!ProcessingTool.GetXGDType(ss, out int xgdType))
return false;
if (xgdType == 0)
return false;
// Don't recreate an already raw SS
// (but do save to file, so return true)
if (!ProcessingTool.IsCleanSS(ss))
return true;
// Example replay table:
/*
----------------------------------------
RT CID MOD DATA Drive Response
-- -- -- ------------- -------------------
01 14 00 033100 0340FF B7D8C32A B703590100
03 BE 00 244530 24552F F4B9B528 BE46360500
01 97 00 DBBAD0 DBCACF DD7787F4 484977ED00
03 45 00 FCAF00 FCBEFF FB7A7773 AAB662FC00
05 6B 00 033100 033E7F 0A31252A 0200000200
07 46 00 244530 2452AF F8E77EBC 5B00005B00
05 36 00 DBBAD0 DBC84F F5DFA735 B50000B500
07 A1 00 FCAF00 FCBC7F 6B749DBF 0E01000E01
E0 50 00 42F4E1 00B6F7 00000000 0000000000
--------------------------------------------
*/
try
{
// Parse Replay Table from log
using var sr = File.OpenText(log);
while (!sr.EndOfStream)
{
string? line = sr.ReadLine()?.Trim();
if (line?.StartsWith("RT CID MOD DATA Drive Response") == true)
{
// Ignore next line
line = sr.ReadLine()?.Trim();
if (sr.EndOfStream)
return false;
byte[][] responses = new byte[4][];
// Parse the nine rows from replay table
for (int i = 0; i < 9; i++)
{
line = sr.ReadLine()?.Trim();
// Validate line
if (sr.EndOfStream || string.IsNullOrEmpty(line) || line!.Length < 44)
return false;
// Save useful angle responses
if (i >= 4 && i <= 7)
{
byte[]? angles = ProcessingTool.HexStringToByteArray(line!.Substring(34, 10));
if (angles == null || angles.Length != 5)
return false;
responses[i - 4] = angles!;
}
}
int rtOffset = 0x204;
if (xgdType == 3)
rtOffset = 0x24;
// Replace angles
for (int i = 0; i < 4; i++)
{
int offset = rtOffset + (9 * (i + 4));
for (int j = 0; j < 5; j++)
{
// Ignore the middle byte
if (j == 2)
continue;
ss[offset + j] = responses[i][j];
}
}
return true;
}
}
// We couldn't detect the replay table
return false;
}
catch
{
// We don't care what the exception is right now
return false;
}
}
#endregion
}
}

View File

@@ -1,233 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using MPF.Core.Utilities;
using SabreTools.RedumpLib.Data;
using Xunit;
namespace MPF.Test.Core.Utilities
{
public class EnumExtensionsTests
{
/// <summary>
/// MediaType values that support drive speeds
/// </summary>
private static readonly MediaType?[] _supportDriveSpeeds =
[
MediaType.CDROM,
MediaType.DVD,
MediaType.GDROM,
MediaType.HDDVD,
MediaType.BluRay,
MediaType.NintendoGameCubeGameDisc,
MediaType.NintendoWiiOpticalDisc,
];
/// <summary>
/// RedumpSystem values that are considered Audio
/// </summary>
private static readonly RedumpSystem?[] _audioSystems =
[
RedumpSystem.AtariJaguarCDInteractiveMultimediaSystem,
RedumpSystem.AudioCD,
RedumpSystem.DVDAudio,
RedumpSystem.HasbroiONEducationalGamingSystem,
RedumpSystem.HasbroVideoNow,
RedumpSystem.HasbroVideoNowColor,
RedumpSystem.HasbroVideoNowJr,
RedumpSystem.HasbroVideoNowXP,
RedumpSystem.PlayStationGameSharkUpdates,
RedumpSystem.PhilipsCDi,
RedumpSystem.SuperAudioCD,
];
/// <summary>
/// RedumpSystem values that are considered markers
/// </summary>
private static readonly RedumpSystem?[] _markerSystems =
[
RedumpSystem.MarkerArcadeEnd,
RedumpSystem.MarkerComputerEnd,
RedumpSystem.MarkerDiscBasedConsoleEnd,
RedumpSystem.MarkerOtherEnd,
];
/// <summary>
/// RedumpSystem values that are have reversed ringcodes
/// </summary>
private static readonly RedumpSystem?[] _reverseRingcodeSystems =
[
RedumpSystem.SonyPlayStation2,
RedumpSystem.SonyPlayStation3,
RedumpSystem.SonyPlayStation4,
RedumpSystem.SonyPlayStation5,
RedumpSystem.SonyPlayStationPortable,
];
/// <summary>
/// RedumpSystem values that are considered XGD
/// </summary>
private static readonly RedumpSystem?[] _xgdSystems =
[
RedumpSystem.MicrosoftXbox,
RedumpSystem.MicrosoftXbox360,
RedumpSystem.MicrosoftXboxOne,
RedumpSystem.MicrosoftXboxSeriesXS,
];
/// <summary>
/// Check that all optical media support drive speeds
/// </summary>
/// <param name="mediaType">DriveType value to check</param>
/// <param name="expected">The expected value to come from the check</param>
[Theory]
[MemberData(nameof(GenerateSupportDriveSpeedsTestData))]
public void DoesSupportDriveSpeedTest(MediaType? mediaType, bool expected)
{
bool actual = mediaType.DoesSupportDriveSpeed();
Assert.Equal(expected, actual);
}
/// <summary>
/// Check that all systems with reversed ringcodes are marked properly
/// </summary>
/// <param name="redumpSystem">RedumpSystem value to check</param>
/// <param name="expected">The expected value to come from the check</param>
[Theory]
[MemberData(nameof(GenerateReversedRingcodeSystemsTestData))]
public void HasReversedRingcodesTest(RedumpSystem? redumpSystem, bool expected)
{
bool actual = redumpSystem.HasReversedRingcodes();
Assert.Equal(expected, actual);
}
/// <summary>
/// Check that all audio systems are marked properly
/// </summary>
/// <param name="redumpSystem">RedumpSystem value to check</param>
/// <param name="expected">The expected value to come from the check</param>
[Theory]
[MemberData(nameof(GenerateAudioSystemsTestData))]
public void IsAudioTest(RedumpSystem? redumpSystem, bool expected)
{
bool actual = redumpSystem.IsAudio();
Assert.Equal(expected, actual);
}
/// <summary>
/// Check that all marker systems are marked properly
/// </summary>
/// <param name="redumpSystem">RedumpSystem value to check</param>
/// <param name="expected">The expected value to come from the check</param>
[Theory]
[MemberData(nameof(GenerateMarkerSystemsTestData))]
public void IsMarkerTest(RedumpSystem? redumpSystem, bool expected)
{
bool actual = redumpSystem.IsMarker();
Assert.Equal(expected, actual);
}
/// <summary>
/// Check that all XGD systems are marked properly
/// </summary>
/// <param name="redumpSystem">RedumpSystem value to check</param>
/// <param name="expected">The expected value to come from the check</param>
[Theory]
[MemberData(nameof(GenerateXGDSystemsTestData))]
public void IsXGDTest(RedumpSystem? redumpSystem, bool expected)
{
bool actual = redumpSystem.IsXGD();
Assert.Equal(expected, actual);
}
/// <summary>
/// Generate a test set of MediaType values that support drive speeds
/// </summary>
/// <returns>MemberData-compatible list of MediaType values</returns>
public static List<object?[]> GenerateSupportDriveSpeedsTestData()
{
var testData = new List<object?[]>() { new object?[] { null, false } };
foreach (MediaType mediaType in Enum.GetValues(typeof(MediaType)))
{
if (_supportDriveSpeeds.Contains(mediaType))
testData.Add([mediaType, true]);
else
testData.Add([mediaType, false]);
}
return testData;
}
/// <summary>
/// Generate a test set of RedumpSystem values that are considered Audio
/// </summary>
/// <returns>MemberData-compatible list of RedumpSystem values</returns>
public static List<object?[]> GenerateAudioSystemsTestData()
{
var testData = new List<object?[]>() { new object?[] { null, false } };
foreach (RedumpSystem redumpSystem in Enum.GetValues(typeof(RedumpSystem)))
{
if (_audioSystems.Contains(redumpSystem))
testData.Add([redumpSystem, true]);
else
testData.Add([redumpSystem, false]);
}
return testData;
}
/// <summary>
/// Generate a test set of RedumpSystem values that are considered markers
/// </summary>
/// <returns>MemberData-compatible list of RedumpSystem values</returns>
public static List<object?[]> GenerateMarkerSystemsTestData()
{
var testData = new List<object?[]>() { new object?[] { null, false } };
foreach (RedumpSystem redumpSystem in Enum.GetValues(typeof(RedumpSystem)))
{
if (_markerSystems.Contains(redumpSystem))
testData.Add([redumpSystem, true]);
else
testData.Add([redumpSystem, false]);
}
return testData;
}
/// <summary>
/// Generate a test set of RedumpSystem values that are considered markers
/// </summary>
/// <returns>MemberData-compatible list of RedumpSystem values</returns>
public static List<object?[]> GenerateReversedRingcodeSystemsTestData()
{
var testData = new List<object?[]>() { new object?[] { null, false } };
foreach (RedumpSystem redumpSystem in Enum.GetValues(typeof(RedumpSystem)))
{
if (_reverseRingcodeSystems.Contains(redumpSystem))
testData.Add([redumpSystem, true]);
else
testData.Add([redumpSystem, false]);
}
return testData;
}
/// <summary>
/// Generate a test set of RedumpSystem values that are considered XGD
/// </summary>
/// <returns>MemberData-compatible list of RedumpSystem values</returns>
public static List<object?[]> GenerateXGDSystemsTestData()
{
var testData = new List<object?[]>() { new object?[] { null, false } };
foreach (RedumpSystem redumpSystem in Enum.GetValues(typeof(RedumpSystem)))
{
if (_xgdSystems.Contains(redumpSystem))
testData.Add([redumpSystem, true]);
else
testData.Add([redumpSystem, false]);
}
return testData;
}
}
}

View File

@@ -1,11 +1,11 @@
using System;
using System.Collections.Generic;
using MPF.Core.Data;
using MPF.Core.Modules.DiscImageCreator;
using MPF.ExecutionContexts.DiscImageCreator;
using MPF.Frontend;
using SabreTools.RedumpLib.Data;
using Xunit;
namespace MPF.Test.Modules
namespace MPF.Test.ExecutionContexts
{
public class DiscImageCreatorTests
{
@@ -21,7 +21,7 @@ namespace MPF.Test.Modules
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 actual = new ExecutionContext(knownSystem, mediaType, "D:\\", "disc.bin", 16, options.Settings);
Assert.Equal(expected, actual.BaseCommand);
}
@@ -32,7 +32,7 @@ namespace MPF.Test.Modules
public void ParametersFromOptionsSpecialDefaultTest(RedumpSystem? knownSystem, MediaType? mediaType, string[]? expected)
{
var options = new Options();
var actual = new Parameters(knownSystem, mediaType, "D:\\", "disc.bin", 16, options);
var actual = new ExecutionContext(knownSystem, mediaType, "D:\\", "disc.bin", 16, options.Settings);
var expectedSet = new HashSet<string>(expected ?? Array.Empty<string>());
HashSet<string> actualSet = GenerateUsedKeys(actual);
@@ -45,7 +45,7 @@ namespace MPF.Test.Modules
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 actual = new ExecutionContext(knownSystem, mediaType, "D:\\", "disc.bin", 16, options.Settings);
var expectedSet = new HashSet<string>(expected ?? Array.Empty<string>());
HashSet<string> actualSet = GenerateUsedKeys(actual);
@@ -65,7 +65,7 @@ namespace MPF.Test.Modules
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 actual = new ExecutionContext(knownSystem, mediaType, "D:\\", "disc.bin", 16, options.Settings);
var expectedSet = new HashSet<string>(expected ?? Array.Empty<string>());
HashSet<string> actualSet = GenerateUsedKeys(actual);
@@ -89,7 +89,7 @@ namespace MPF.Test.Modules
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 actual = new ExecutionContext(knownSystem, mediaType, "D:\\", "disc.bin", 16, options.Settings);
var expectedSet = new HashSet<string>(expected ?? Array.Empty<string>());
HashSet<string> actualSet = GenerateUsedKeys(actual);
@@ -110,7 +110,7 @@ namespace MPF.Test.Modules
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 actual = new ExecutionContext(knownSystem, mediaType, "D:\\", "disc.bin", 16, options.Settings);
var expectedSet = new HashSet<string>(expected ?? Array.Empty<string>());
HashSet<string> actualSet = GenerateUsedKeys(actual);
@@ -149,7 +149,7 @@ namespace MPF.Test.Modules
[InlineData("ls", false)]
public void ValidateParametersTest(string? parameters, bool expected)
{
var actual = new Parameters(parameters);
var actual = new ExecutionContext(parameters);
Assert.Equal(expected, actual.IsValid());
}
@@ -223,11 +223,11 @@ namespace MPF.Test.Modules
string originalParameters = "audio F \"ISO\\Audio CD\\Audio CD.bin\" 72 -5 0";
// Validate that a common audio commandline is parsed
var parametersObject = new Parameters(originalParameters);
Assert.NotNull(parametersObject);
var executionContext = new ExecutionContext(originalParameters);
Assert.NotNull(executionContext);
// Validate that the same set of parameters are generated on the output
var newParameters = parametersObject.GenerateParameters();
var newParameters = executionContext.GenerateParameters();
Assert.NotNull(newParameters);
Assert.Equal(originalParameters, newParameters);
}
@@ -238,11 +238,11 @@ namespace MPF.Test.Modules
string originalParameters = "data F \"ISO\\Data CD\\Data CD.bin\" 72 -5 0";
// Validate that a common audio commandline is parsed
var parametersObject = new Parameters(originalParameters);
Assert.NotNull(parametersObject);
var executionContext = new ExecutionContext(originalParameters);
Assert.NotNull(executionContext);
// Validate that the same set of parameters are generated on the output
var newParameters = parametersObject.GenerateParameters();
var newParameters = executionContext.GenerateParameters();
Assert.NotNull(newParameters);
Assert.Equal(originalParameters, newParameters);
}
@@ -250,17 +250,17 @@ namespace MPF.Test.Modules
/// <summary>
/// Generate a HashSet of keys that are considered to be set
/// </summary>
/// <param name="parameters">Parameters object to get keys from</param>
/// <param name="executionContext">ExecutionContext object representing how to invoke the internal program</param>
/// <returns>HashSet representing the strings</returns>
private static HashSet<string> GenerateUsedKeys(Parameters parameters)
private static HashSet<string> GenerateUsedKeys(ExecutionContext executionContext)
{
var usedKeys = new HashSet<string>();
if (parameters?.Keys == null)
if (executionContext?.Keys == null)
return usedKeys;
foreach (string key in parameters.Keys)
foreach (string key in executionContext.Keys)
{
if (parameters[key] == true)
if (executionContext[key] == true)
usedKeys.Add(key);
}

View File

@@ -1,8 +1,8 @@
using MPF.Core.Data;
using MPF.Frontend;
using SabreTools.RedumpLib.Data;
using Xunit;
namespace MPF.Test.Data
namespace MPF.Test.Frontend
{
public class UIElementsTest
{
@@ -14,7 +14,7 @@ namespace MPF.Test.Data
[InlineData(null, 1)]
public void GetAllowedDriveSpeedForMediaTypeTest(MediaType? mediaType, int maxExpected)
{
var actual = Interface.GetSpeedsForMediaType(mediaType);
var actual = InterfaceConstants.GetSpeedsForMediaType(mediaType);
Assert.Equal(maxExpected, actual[actual.Count - 1]);
}
}

View File

@@ -1,9 +1,8 @@
using MPF.Core;
using MPF.Core.Data;
using MPF.Frontend;
using SabreTools.RedumpLib.Data;
using Xunit;
namespace MPF.Test.Library
namespace MPF.Test.Frontend
{
public class DumpEnvironmentTests
{

View File

@@ -2,11 +2,10 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using MPF.Core.Converters;
using MPF.Core.Data;
using MPF.Frontend;
using Xunit;
namespace MPF.Test.Core.Converters
namespace MPF.Test.Frontend
{
public class EnumConverterTests
{
@@ -31,7 +30,7 @@ namespace MPF.Test.Core.Converters
[MemberData(nameof(GenerateDriveTypeMappingTestData))]
public void ToInternalDriveTypeTest(DriveType driveType, bool expectNull)
{
var actual = driveType.ToInternalDriveType();
var actual = Drive.ToInternalDriveType(driveType);
if (expectNull)
Assert.Null(actual);

View File

@@ -1,14 +1,14 @@
using MPF.Core.Data;
using MPF.Frontend;
using Xunit;
namespace MPF.Test.Core.Data
namespace MPF.Test.Frontend
{
public class ResultTests
public class ResultEventArgsTests
{
[Fact]
public void EmptySuccessTest()
{
var actual = Result.Success();
var actual = ResultEventArgs.Success();
Assert.True(actual);
Assert.Empty(actual.Message);
}
@@ -17,7 +17,7 @@ namespace MPF.Test.Core.Data
public void CustomMessageSuccessTest()
{
string message = "Success!";
var actual = Result.Success(message);
var actual = ResultEventArgs.Success(message);
Assert.True(actual);
Assert.Equal(message, actual.Message);
}
@@ -25,7 +25,7 @@ namespace MPF.Test.Core.Data
[Fact]
public void EmptyFailureTest()
{
var actual = Result.Failure();
var actual = ResultEventArgs.Failure();
Assert.False(actual);
Assert.Empty(actual.Message);
}
@@ -34,7 +34,7 @@ namespace MPF.Test.Core.Data
public void CustomMessageFailureTest()
{
string message = "Failure!";
var actual = Result.Failure(message);
var actual = ResultEventArgs.Failure(message);
Assert.False(actual);
Assert.Equal(message, actual.Message);
}

View File

@@ -1,11 +1,11 @@
using System.Collections.Generic;
using System.IO;
using MPF.Core;
using MPF.Frontend.Tools;
using SabreTools.RedumpLib;
using SabreTools.RedumpLib.Data;
using Xunit;
namespace MPF.Test.Library
namespace MPF.Test.Frontend.Tools
{
public class InfoToolTests
{
@@ -23,7 +23,7 @@ namespace MPF.Test.Library
if (!string.IsNullOrEmpty(expectedPath))
expectedPath = Path.GetFullPath(expectedPath);
string actualPath = InfoTool.NormalizeOutputPaths(outputPath, true);
string actualPath = FrontendTool.NormalizeOutputPaths(outputPath, true);
Assert.Equal(expectedPath, actualPath);
}

View File

@@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using MPF.Core;
using MPF.Frontend.Tools;
using Xunit;
namespace MPF.Test.Library
namespace MPF.Test.Frontend.Tools
{
public class ProtectionTests
{
@@ -17,7 +17,7 @@ namespace MPF.Test.Library
"ActiveMARK 5",
];
string sanitized = Protection.SanitizeFoundProtections(protections);
string sanitized = ProtectionTool.SanitizeFoundProtections(protections);
Assert.Equal("ActiveMARK 5", sanitized);
}
@@ -30,7 +30,7 @@ namespace MPF.Test.Library
"Cactus Data Shield 200 (Build 3.0.100a)",
];
string sanitized = Protection.SanitizeFoundProtections(protections);
string sanitized = ProtectionTool.SanitizeFoundProtections(protections);
Assert.Equal("Cactus Data Shield 200 (Build 3.0.100a)", sanitized);
}
@@ -43,7 +43,7 @@ namespace MPF.Test.Library
"Executable-Based CD Check",
];
string sanitized = Protection.SanitizeFoundProtections(protections);
string sanitized = ProtectionTool.SanitizeFoundProtections(protections);
Assert.Equal("Anything Else Protection", sanitized);
}
@@ -56,7 +56,7 @@ namespace MPF.Test.Library
"CD-Cops v1.2.0",
];
string sanitized = Protection.SanitizeFoundProtections(protections);
string sanitized = ProtectionTool.SanitizeFoundProtections(protections);
Assert.Equal("CD-Cops v1.2.0", sanitized);
}
@@ -69,7 +69,7 @@ namespace MPF.Test.Library
"CD-Key / Serial",
];
string sanitized = Protection.SanitizeFoundProtections(protections);
string sanitized = ProtectionTool.SanitizeFoundProtections(protections);
Assert.Equal("Anything Else Protection", sanitized);
}
@@ -82,7 +82,7 @@ namespace MPF.Test.Library
"EA CdKey Registration Module v1.2.0",
];
string sanitized = Protection.SanitizeFoundProtections(protections);
string sanitized = ProtectionTool.SanitizeFoundProtections(protections);
Assert.Equal("EA CdKey Registration Module v1.2.0", sanitized);
}
@@ -95,7 +95,7 @@ namespace MPF.Test.Library
"EA DRM Protection v1.2.0",
];
string sanitized = Protection.SanitizeFoundProtections(protections);
string sanitized = ProtectionTool.SanitizeFoundProtections(protections);
Assert.Equal("EA DRM Protection v1.2.0", sanitized);
}
@@ -108,7 +108,7 @@ namespace MPF.Test.Library
"Games for Windows LIVE v1.2.0",
];
string sanitized = Protection.SanitizeFoundProtections(protections);
string sanitized = ProtectionTool.SanitizeFoundProtections(protections);
Assert.Equal("Games for Windows LIVE v1.2.0", sanitized);
}
@@ -121,7 +121,7 @@ namespace MPF.Test.Library
"Games for Windows LIVE Zero Day Piracy Protection",
];
string sanitized = Protection.SanitizeFoundProtections(protections);
string sanitized = ProtectionTool.SanitizeFoundProtections(protections);
Assert.Equal("Games for Windows LIVE, Games for Windows LIVE Zero Day Piracy Protection", sanitized);
}
@@ -134,7 +134,7 @@ namespace MPF.Test.Library
"Impulse Reactor Core Module v1.2.0",
];
string sanitized = Protection.SanitizeFoundProtections(protections);
string sanitized = ProtectionTool.SanitizeFoundProtections(protections);
Assert.Equal("Impulse Reactor Core Module v1.2.0", sanitized);
}
@@ -161,7 +161,7 @@ namespace MPF.Test.Library
// The list is in order of preference
protections = protections.Skip(skip).ToList();
string sanitized = Protection.SanitizeFoundProtections(protections);
string sanitized = ProtectionTool.SanitizeFoundProtections(protections);
Assert.Equal(protections[0], sanitized);
}
@@ -174,7 +174,7 @@ namespace MPF.Test.Library
"Executable-Based Online Registration",
];
string sanitized = Protection.SanitizeFoundProtections(protections);
string sanitized = ProtectionTool.SanitizeFoundProtections(protections);
Assert.Equal("Anything Else Protection", sanitized);
}
@@ -201,7 +201,7 @@ namespace MPF.Test.Library
// The list is in order of preference
protections = protections.Skip(skip).ToList();
string sanitized = Protection.SanitizeFoundProtections(protections);
string sanitized = ProtectionTool.SanitizeFoundProtections(protections);
Assert.Equal(protections[0], sanitized);
}
@@ -214,7 +214,7 @@ namespace MPF.Test.Library
"Sysiphus v1.2.0",
];
string sanitized = Protection.SanitizeFoundProtections(protections);
string sanitized = ProtectionTool.SanitizeFoundProtections(protections);
Assert.Equal("Sysiphus v1.2.0", sanitized);
}
@@ -227,7 +227,7 @@ namespace MPF.Test.Library
"XCP v1.2.0",
];
string sanitized = Protection.SanitizeFoundProtections(protections);
string sanitized = ProtectionTool.SanitizeFoundProtections(protections);
Assert.Equal("XCP v1.2.0", sanitized);
}
}

View File

@@ -1,38 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0</TargetFrameworks>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<PropertyGroup>
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MPF.Core\MPF.Core.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MPF.ExecutionContexts\MPF.ExecutionContexts.csproj" />
<ProjectReference Include="..\MPF.Frontend\MPF.Frontend.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeCoverage" Version="17.10.0-release-24177-07" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0-release-24177-07" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="SabreTools.RedumpLib" Version="1.3.6" />
<PackageReference Include="xunit" Version="2.7.0" />
<PackageReference Include="xunit.abstractions" Version="2.0.3" />
<PackageReference Include="xunit.analyzers" Version="1.11.0" />
<PackageReference Include="xunit.assert" Version="2.7.0" />
<PackageReference Include="xunit.core" Version="2.7.0" />
<PackageReference Include="xunit.extensibility.core" Version="2.7.0" />
<PackageReference Include="xunit.extensibility.execution" Version="2.7.0" />
<PackageReference Include="xunit.runner.console" Version="2.7.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeCoverage" Version="17.10.0-release-24177-07" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0-release-24177-07" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="SabreTools.RedumpLib" Version="1.4.1" />
<PackageReference Include="xunit" Version="2.8.0" />
<PackageReference Include="xunit.abstractions" Version="2.0.3" />
<PackageReference Include="xunit.analyzers" Version="1.13.0" />
<PackageReference Include="xunit.assert" Version="2.8.0" />
<PackageReference Include="xunit.core" Version="2.8.0" />
<PackageReference Include="xunit.extensibility.core" Version="2.8.0" />
<PackageReference Include="xunit.extensibility.execution" Version="2.8.0" />
<PackageReference Include="xunit.runner.console" Version="2.8.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
</Project>

View File

@@ -1,717 +0,0 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using SabreTools.RedumpLib.Data;
using Xunit;
namespace MPF.Test.RedumpLib
{
// TODO: Add tests for string-to-enum conversion
public class ExtensionsTests
{
#region Cross-Enumeration
/// <summary>
/// DiscType values that map to MediaType
/// </summary>
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,
DiscType.NintendoWiiUOpticalDiscSL,
DiscType.UMDSL,
DiscType.UMDDL,
};
/// <summary>
/// MediaType values that map to DiscType
/// </summary>
private static readonly MediaType?[] _mappableMediaTypes = new MediaType?[]
{
MediaType.BluRay,
MediaType.CDROM,
MediaType.DVD,
MediaType.GDROM,
MediaType.HDDVD,
MediaType.NintendoGameCubeGameDisc,
MediaType.NintendoWiiOpticalDisc,
MediaType.NintendoWiiUOpticalDisc,
MediaType.UMD,
};
/// <summary>
/// Check that every supported system has some set of MediaTypes supported
/// </summary>
/// <param name="redumpSystem">RedumpSystem value to check</param>
[Theory]
[MemberData(nameof(GenerateRedumpSystemMappingTestData))]
public void MediaTypesTest(RedumpSystem? redumpSystem)
{
var actual = redumpSystem.MediaTypes();
Assert.NotEmpty(actual);
}
/// <summary>
/// Check that both mappable and unmappable media types output correctly
/// </summary>
/// <param name="mediaType">MediaType value to check</param>
/// <param name="expectNull">True to expect a null mapping, false otherwise</param>
[Theory]
[MemberData(nameof(GenerateMediaTypeMappingTestData))]
public void ToDiscTypeTest(MediaType? mediaType, bool expectNull)
{
DiscType? actual = mediaType.ToDiscType();
Assert.Equal(expectNull, actual == null);
}
/// <summary>
/// Check that DiscType values all map to something appropriate
/// </summary>
/// <param name="discType">DiscType value to check</param>
/// <param name="expectNull">True to expect a null mapping, false otherwise</param>
[Theory]
[MemberData(nameof(GenerateDiscTypeMappingTestData))]
public void ToMediaTypeTest(DiscType? discType, bool expectNull)
{
MediaType? actual = discType.ToMediaType();
Assert.Equal(expectNull, actual == null);
}
/// <summary>
/// Generate a test set of DiscType values
/// </summary>
/// <returns>MemberData-compatible list of DiscType values</returns>
public static List<object?[]> GenerateDiscTypeMappingTestData()
{
var testData = new List<object?[]>() { new object?[] { null, true } };
foreach (DiscType? discType in Enum.GetValues(typeof(DiscType)))
{
if (_mappableDiscTypes.Contains(discType))
testData.Add(new object?[] { discType, false });
else
testData.Add(new object?[] { discType, true });
}
return testData;
}
/// <summary>
/// Generate a test set of RedumpSystem values
/// </summary>
/// <returns>MemberData-compatible list of RedumpSystem values</returns>
public static List<object?[]> GenerateRedumpSystemMappingTestData()
{
var testData = new List<object?[]>() { new object?[] { null } };
foreach (RedumpSystem? redumpSystem in Enum.GetValues(typeof(RedumpSystem)))
{
testData.Add(new object?[] { redumpSystem });
}
return testData;
}
/// <summary>
/// Generate a test set of mappable media types
/// </summary>
/// <returns>MemberData-compatible list of MediaTypes</returns>
public static List<object?[]> GenerateMediaTypeMappingTestData()
{
var testData = new List<object?[]>() { new object?[] { null, true } };
foreach (MediaType? mediaType in Enum.GetValues(typeof(MediaType)))
{
if (_mappableMediaTypes.Contains(mediaType))
testData.Add(new object?[] { mediaType, false });
else
testData.Add(new object?[] { mediaType, true });
}
return testData;
}
#endregion
#region Disc Category
/// <summary>
/// Check that every DiscCategory has a long name provided
/// </summary>
/// <param name="discCategory">DiscCategory value to check</param>
/// <param name="expectNull">True to expect a null value, false otherwise</param>
[Theory]
[MemberData(nameof(GenerateDiscCategoryTestData))]
public void DiscCategoryLongNameTest(DiscCategory? discCategory, bool expectNull)
{
var actual = discCategory.LongName();
if (expectNull)
Assert.Null(actual);
else
Assert.NotNull(actual);
}
/// <summary>
/// Generate a test set of DiscCategory values
/// </summary>
/// <returns>MemberData-compatible list of DiscCategory values</returns>
public static List<object?[]> GenerateDiscCategoryTestData()
{
var testData = new List<object?[]>() { new object?[] { null, true } };
foreach (DiscCategory? discCategory in Enum.GetValues(typeof(DiscCategory)))
{
testData.Add(new object?[] { discCategory, false });
}
return testData;
}
#endregion
#region Disc Type
/// <summary>
/// Check that every DiscType has a long name provided
/// </summary>
/// <param name="discType">DiscType value to check</param>
/// <param name="expectNull">True to expect a null value, false otherwise</param>
[Theory]
[MemberData(nameof(GenerateDiscTypeTestData))]
public void DiscTypeLongNameTest(DiscType? discType, bool expectNull)
{
var actual = discType.LongName();
if (expectNull)
Assert.Null(actual);
else
Assert.NotNull(actual);
}
/// <summary>
/// Generate a test set of DiscType values
/// </summary>
/// <returns>MemberData-compatible list of DiscType values</returns>
public static List<object?[]> GenerateDiscTypeTestData()
{
var testData = new List<object?[]>() { new object?[] { null, true } };
foreach (DiscType? discType in Enum.GetValues(typeof(DiscType)))
{
if (discType == DiscType.NONE)
testData.Add(new object?[] { discType, true });
else
testData.Add(new object?[] { discType, false });
}
return testData;
}
#endregion
#region Language
/// <summary>
/// Check that every Language has a long name provided
/// </summary>
/// <param name="language">Language value to check</param>
/// <param name="expectNull">True to expect a null value, false otherwise</param>
[Theory]
[MemberData(nameof(GenerateLanguageTestData))]
public void LanguageLongNameTest(Language? language, bool expectNull)
{
var actual = language.LongName();
if (expectNull)
Assert.Null(actual);
else
Assert.NotNull(actual);
}
/// <summary>
/// Check that every Language has a short name provided
/// </summary>
/// <param name="language">Language value to check</param>
/// <param name="expectNull">True to expect a null value, false otherwise</param>
[Theory]
[MemberData(nameof(GenerateLanguageTestData))]
public void LanguageShortNameTest(Language? language, bool expectNull)
{
var actual = language.ShortName();
if (expectNull)
Assert.Null(actual);
else
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.IsNullOrEmpty(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.IsNullOrEmpty(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.IsNullOrEmpty(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>
public static List<object?[]> GenerateLanguageTestData()
{
var testData = new List<object?[]>() { new object?[] { null, true } };
foreach (Language? language in Enum.GetValues(typeof(Language)))
{
testData.Add(new object?[] { language, false });
}
return testData;
}
#endregion
#region Language Selection
/// <summary>
/// Check that every LanguageSelection has a long name provided
/// </summary>
/// <param name="languageSelection">LanguageSelection value to check</param>
/// <param name="expectNull">True to expect a null value, false otherwise</param>
[Theory]
[MemberData(nameof(GenerateLanguageSelectionTestData))]
public void LanguageSelectionLongNameTest(LanguageSelection? languageSelection, bool expectNull)
{
var actual = languageSelection.LongName();
if (expectNull)
Assert.Null(actual);
else
Assert.NotNull(actual);
}
/// <summary>
/// Generate a test set of LanguageSelection values
/// </summary>
/// <returns>MemberData-compatible list of LanguageSelection values</returns>
public static List<object?[]> GenerateLanguageSelectionTestData()
{
var testData = new List<object?[]>() { new object?[] { null, true } };
foreach (LanguageSelection? languageSelection in Enum.GetValues(typeof(LanguageSelection)))
{
testData.Add(new object?[] { languageSelection, false });
}
return testData;
}
#endregion
#region Media Type
/// <summary>
/// Check that every MediaType has a long name provided
/// </summary>
/// <param name="mediaType">MediaType value to check</param>
/// <param name="expectNull">True to expect a null value, false otherwise</param>
[Theory]
[MemberData(nameof(GenerateMediaTypeTestData))]
public void MediaTypeLongNameTest(MediaType? mediaType, bool expectNull)
{
var actual = mediaType.LongName();
if (expectNull)
Assert.Null(actual);
else
Assert.NotNull(actual);
}
/// <summary>
/// Check that every MediaType has a short name provided
/// </summary>
/// <param name="mediaType">MediaType value to check</param>
/// <param name="expectNull">True to expect a null value, false otherwise</param>
[Theory]
[MemberData(nameof(GenerateMediaTypeTestData))]
public void MediaTypeShortNameTest(MediaType? mediaType, bool expectNull)
{
var actual = mediaType.ShortName();
if (expectNull)
Assert.Null(actual);
else
Assert.NotNull(actual);
}
/// <summary>
/// Generate a test set of MediaType values
/// </summary>
/// <returns>MemberData-compatible list of MediaType values</returns>
public static List<object?[]> GenerateMediaTypeTestData()
{
var testData = new List<object?[]>() { new object?[] { null, true } };
foreach (MediaType? mediaType in Enum.GetValues(typeof(MediaType)))
{
testData.Add(new object?[] { mediaType, false });
}
return testData;
}
#endregion
#region Region
/// <summary>
/// Check that every Region has a long name provided
/// </summary>
/// <param name="region">Region value to check</param>
/// <param name="expectNull">True to expect a null value, false otherwise</param>
[Theory]
[MemberData(nameof(GenerateRegionTestData))]
public void RegionLongNameTest(Region? region, bool expectNull)
{
var actual = region.LongName();
if (expectNull)
Assert.Null(actual);
else
Assert.NotNull(actual);
}
/// <summary>
/// Check that every Region has a short name provided
/// </summary>
/// <param name="region">Region value to check</param>
/// <param name="expectNull">True to expect a null value, false otherwise</param>
[Theory]
[MemberData(nameof(GenerateRegionTestData))]
public void RegionShortNameTest(Region? region, bool expectNull)
{
var actual = region.ShortName();
if (expectNull)
Assert.Null(actual);
else
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.IsNullOrEmpty(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>
public static List<object?[]> GenerateRegionTestData()
{
var testData = new List<object?[]>() { new object?[] { null, true } };
foreach (Region? region in Enum.GetValues(typeof(Region)))
{
testData.Add(new object?[] { region, false });
}
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>
public static List<object?[]> GenerateSiteCodeTestData()
{
var testData = new List<object?[]>() { new object?[] { null, true } };
foreach (SiteCode? siteCode in Enum.GetValues(typeof(SiteCode)))
{
testData.Add(new object?[] { siteCode, false });
}
return testData;
}
#endregion
#region System
/// <summary>
/// Check that every RedumpSystem has a long name provided
/// </summary>
/// <param name="redumpSystem">RedumpSystem value to check</param>
/// <param name="expectNull">True to expect a null value, false otherwise</param>
[Theory]
[MemberData(nameof(GenerateRedumpSystemTestData))]
public void RedumpSystemLongNameTest(RedumpSystem? redumpSystem, bool expectNull)
{
var actual = redumpSystem.LongName();
if (expectNull)
Assert.Null(actual);
else
Assert.NotNull(actual);
}
// TODO: Re-enable the following test once non-Redump systems are accounted for
/// <summary>
/// Check that every RedumpSystem has a short name provided
/// </summary>
/// <param name="redumpSystem">RedumpSystem value to check</param>
/// <param name="expectNull">True to expect a null value, false otherwise</param>
//[Theory]
//[MemberData(nameof(GenerateRedumpSystemTestData))]
//public void RedumpSystemShortNameTest(RedumpSystem? redumpSystem, bool expectNull)
//{
// string actual = redumpSystem.ShortName();
// if (expectNull)
// Assert.Null(actual);
// else
// Assert.NotNull(actual);
//}
// TODO: Test the other attributes as well
// Most are bool checks so they're not as interesting to have unit tests around
// SystemCategory always returns something as well, so is it worth testing?
/// <summary>
/// Generate a test set of RedumpSystem values
/// </summary>
/// <returns>MemberData-compatible list of RedumpSystem values</returns>
public static List<object?[]> GenerateRedumpSystemTestData()
{
var testData = new List<object?[]>() { new object?[] { null, true } };
foreach (RedumpSystem? redumpSystem in Enum.GetValues(typeof(RedumpSystem)))
{
// We want to skip all markers for this
if (redumpSystem.IsMarker())
continue;
testData.Add(new object?[] { redumpSystem, false });
}
return testData;
}
#endregion
#region System Category
/// <summary>
/// Check that every SystemCategory has a long name provided
/// </summary>
/// <param name="systemCategory">SystemCategory value to check</param>
/// <param name="expectNull">True to expect a null value, false otherwise</param>
[Theory]
[MemberData(nameof(GenerateSystemCategoryTestData))]
public void SystemCategoryLongNameTest(SystemCategory? systemCategory, bool expectNull)
{
var actual = systemCategory.LongName();
if (expectNull)
Assert.Null(actual);
else
Assert.NotNull(actual);
}
/// <summary>
/// Generate a test set of SystemCategory values
/// </summary>
/// <returns>MemberData-compatible list of SystemCategory values</returns>
public static List<object?[]> GenerateSystemCategoryTestData()
{
var testData = new List<object?[]>() { new object?[] { null, true } };
foreach (SystemCategory? systemCategory in Enum.GetValues(typeof(SystemCategory)))
{
if (systemCategory == SystemCategory.NONE)
testData.Add(new object?[] { systemCategory, true });
else
testData.Add(new object?[] { systemCategory, false });
}
return testData;
}
#endregion
#region Yes/No
/// <summary>
/// Check that every YesNo has a long name provided
/// </summary>
/// <param name="yesNo">YesNo value to check</param>
/// <param name="expectNull">True to expect a null value, false otherwise</param>
[Theory]
[MemberData(nameof(GenerateYesNoTestData))]
public void YesNoLongNameTest(YesNo? yesNo, bool expectNull)
{
string actual = yesNo.LongName();
if (expectNull)
Assert.Null(actual);
else
Assert.NotNull(actual);
}
/// <summary>
/// Generate a test set of YesNo values
/// </summary>
/// <returns>MemberData-compatible list of YesNo values</returns>
public static List<object?[]> GenerateYesNoTestData()
{
var testData = new List<object?[]>() { new object?[] { null, false } };
foreach (YesNo? yesNo in Enum.GetValues(typeof(YesNo)))
{
testData.Add(new object?[] { yesNo, false });
}
return testData;
}
#endregion
}
}

View File

@@ -1,180 +0,0 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using SabreTools.RedumpLib.Data;
using Xunit;
namespace MPF.Test.RedumpLib
{
public class SubmissionInfoTests
{
[Fact]
public void EmptySerializationTest()
{
var submissionInfo = new SubmissionInfo();
string json = JsonConvert.SerializeObject(submissionInfo, Formatting.Indented);
Assert.NotNull(json);
}
[Fact]
public void PartialSerializationTest()
{
var submissionInfo = new SubmissionInfo()
{
CommonDiscInfo = new CommonDiscInfoSection(),
VersionAndEditions = new VersionAndEditionsSection(),
EDC = new EDCSection(),
ParentCloneRelationship = new ParentCloneRelationshipSection(),
Extras = new ExtrasSection(),
CopyProtection = new CopyProtectionSection(),
DumpersAndStatus = new DumpersAndStatusSection(),
TracksAndWriteOffsets = new TracksAndWriteOffsetsSection(),
SizeAndChecksums = new SizeAndChecksumsSection(),
};
string json = JsonConvert.SerializeObject(submissionInfo, Formatting.Indented);
Assert.NotNull(json);
}
[Fact]
public void FullSerializationTest()
{
var submissionInfo = new SubmissionInfo()
{
SchemaVersion = 1,
FullyMatchedID = 3,
PartiallyMatchedIDs = new List<int> { 0, 1, 2, 3 },
Added = DateTime.UtcNow,
LastModified = DateTime.UtcNow,
CommonDiscInfo = new CommonDiscInfoSection()
{
System = RedumpSystem.IBMPCcompatible,
Media = DiscType.CD,
Title = "Game Title",
ForeignTitleNonLatin = "Foreign Game Title",
DiscNumberLetter = "1",
DiscTitle = "Install Disc",
Category = DiscCategory.Games,
Region = Region.World,
Languages = new Language?[] { Language.English, Language.Spanish, Language.French },
LanguageSelection = new LanguageSelection?[] { LanguageSelection.BiosSettings },
Serial = "Disc Serial",
Layer0MasteringRing = "L0 Mastering Ring",
Layer0MasteringSID = "L0 Mastering SID",
Layer0ToolstampMasteringCode = "L0 Toolstamp",
Layer0MouldSID = "L0 Mould SID",
Layer0AdditionalMould = "L0 Additional Mould",
Layer1MasteringRing = "L1 Mastering Ring",
Layer1MasteringSID = "L1 Mastering SID",
Layer1ToolstampMasteringCode = "L1 Toolstamp",
Layer1MouldSID = "L1 Mould SID",
Layer1AdditionalMould = "L1 Additional Mould",
Layer2MasteringRing = "L2 Mastering Ring",
Layer2MasteringSID = "L2 Mastering SID",
Layer2ToolstampMasteringCode = "L2 Toolstamp",
Layer3MasteringRing = "L3 Mastering Ring",
Layer3MasteringSID = "L3 Mastering SID",
Layer3ToolstampMasteringCode = "L3 Toolstamp",
RingWriteOffset = "+12",
Barcode = "UPC Barcode",
EXEDateBuildDate = "19xx-xx-xx",
ErrorsCount = "0",
Comments = "Comment data line 1\r\nComment data line 2",
CommentsSpecialFields = new Dictionary<SiteCode, string>()
{
[SiteCode.ISBN] = "ISBN",
},
Contents = "Special contents 1\r\nSpecial contents 2",
ContentsSpecialFields = new Dictionary<SiteCode, string>()
{
[SiteCode.PlayableDemos] = "Game Demo 1",
},
},
VersionAndEditions = new VersionAndEditionsSection()
{
Version = "Original",
VersionDatfile = "Alt",
CommonEditions = new string[] { "Taikenban" },
OtherEditions = "Rerelease",
},
EDC = new EDCSection()
{
EDC = YesNo.Yes,
},
ParentCloneRelationship = new ParentCloneRelationshipSection()
{
ParentID = "12345",
RegionalParent = false,
},
Extras = new ExtrasSection()
{
PVD = "PVD",
DiscKey = "Disc key",
DiscID = "Disc ID",
PIC = "PIC",
Header = "Header",
BCA = "BCA",
SecuritySectorRanges = "SSv1 Ranges",
},
CopyProtection = new CopyProtectionSection()
{
AntiModchip = YesNo.Yes,
LibCrypt = YesNo.No,
LibCryptData = "LibCrypt data",
Protection = "List of protections",
SecuROMData = "SecuROM data",
},
DumpersAndStatus = new DumpersAndStatusSection()
{
Status = DumpStatus.TwoOrMoreGreen,
Dumpers = new string[] { "Dumper1", "Dumper2" },
OtherDumpers = "Dumper3",
},
TracksAndWriteOffsets = new TracksAndWriteOffsetsSection()
{
ClrMameProData = "Datfile",
Cuesheet = "Cuesheet",
CommonWriteOffsets = new int[] { 0, 12, -12 },
OtherWriteOffsets = "-2",
},
SizeAndChecksums = new SizeAndChecksumsSection()
{
Layerbreak = 0,
Layerbreak2 = 1,
Layerbreak3 = 2,
Size = 12345,
CRC32 = "CRC32",
MD5 = "MD5",
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",
},
};
string json = JsonConvert.SerializeObject(submissionInfo, Formatting.Indented);
Assert.NotNull(json);
}
}
}

View File

@@ -1,27 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Media;
using static MPF.Core.Data.Interface;
namespace MPF.UI.Core
{
/// <summary>
/// Variables for UI elements
/// </summary>
public static class Constants
{
// 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);
#if NET20 || NET35 || NET40
private static DoubleCollection GetDoubleCollectionFromIntList(IList<int> list)
#else
private static DoubleCollection GetDoubleCollectionFromIntList(IReadOnlyList<int> list)
#endif
=> new(list.Select(i => Convert.ToDouble(i)).ToList());
}
}

View File

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

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