Compare commits

...

165 Commits
3.0.3 ... 3.1.9

Author SHA1 Message Date
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
Matt Nadareski
771483ac14 Bump version 2024-04-05 16:03:28 -04:00
Matt Nadareski
ccde878286 Update BinaryObjectScanner to 3.1.5 2024-04-05 15:58:44 -04:00
Matt Nadareski
e0ab3e048b Enable remaining fields for label-side information 2024-04-05 15:35:54 -04:00
Matt Nadareski
cf2ae163c4 Enable label-side mastering SID and toolstamp 2024-04-05 15:30:37 -04:00
Deterous
5025a3e91a Fix CleanRip hash output for Check (#685)
* ClrMamePro format is for CDs, dont assume hashes are in CleanRip logs

* Rename folder

* Rename folder again

* Update changelog

* Don't trust cleanrip log hashes if any are missing
2024-04-05 08:54:07 -04:00
Matt Nadareski
dab774dab3 Fix test project project includes 2024-04-02 22:58:02 -04:00
Matt Nadareski
04c6131d28 Enable Windows targeting for test project 2024-04-02 22:28:10 -04:00
Deterous
47561baee8 Detect Xbox Series X discs (#683)
* Detect Xbox Series X discs via catalog.js deserialization

* Update changelog
2024-04-02 19:02:05 -07:00
Matt Nadareski
a8b1a8342d Update BinaryObjectScanner to 3.1.4 2024-04-02 17:10:49 -04:00
Matt Nadareski
7b8ef00d59 Merge with main 2024-04-02 16:39:42 -04:00
Matt Nadareski
65cc502a94 Update packages 2024-04-02 16:38:51 -04:00
fuzzball
d38b465b08 Fix information pulling for redumper (#682)
* Fix dat pull failure

* Fix c2 error exception

* Update changelist
2024-03-30 09:00:58 -07:00
Matt Nadareski
783c323fd0 Update BinaryObjectScanner to 3.1.3 2024-03-27 12:09:19 -04:00
Matt Nadareski
04af8807e5 Language selections unchecked by default (fixes #681) 2024-03-23 12:17:37 -04:00
Matt Nadareski
1260dfdff2 Fix missing information in README 2024-03-18 12:45:11 -04:00
Matt Nadareski
e5b883fb73 Fix outdated information in README 2024-03-18 12:44:24 -04:00
Matt Nadareski
1c08451487 Add Konami Python 2 system detection (fixes #680) 2024-03-18 12:30:02 -04:00
Matt Nadareski
29b483f805 Read last instance of hash data from Redumper 2024-03-18 12:20:11 -04:00
Matt Nadareski
2eff4a7488 Read C2 error count from Redumper logs (fixes #666) 2024-03-18 12:15:56 -04:00
Matt Nadareski
5e94d02503 Handle .0.physical files from Redumper (fixes #679) 2024-03-17 19:23:25 -04:00
Matt Nadareski
ccf2166b72 Bump version 2024-03-16 11:50:02 -04:00
Matt Nadareski
024394bbec Update BinaryObjectScanner to 3.1.2 2024-03-16 11:46:32 -04:00
Matt Nadareski
301a0cb188 Bump version 2024-03-15 13:43:36 -04:00
Matt Nadareski
64231da666 Update to RedumpLib 1.3.6 2024-03-15 13:00:34 -04:00
Deterous
5f56977021 Add MPF version to Submission info (#678) 2024-03-15 09:38:02 -07:00
Deterous
436ccf7a34 Fix Redumper generic drive type (#677) 2024-03-15 06:56:54 -07:00
Matt Nadareski
ef7510804e Update packages 2024-03-14 13:29:14 -04:00
Deterous
8c61b87954 Update LibIRD to 0.9.0 (#674) 2024-03-12 21:32:20 -07:00
Deterous
17ba117949 Update LibIRD to 0.8.0 (#673) 2024-03-12 07:12:22 -07:00
Matt Nadareski
0737ba7641 Fix formatting output formatting 2024-03-09 21:36:12 -05:00
Deterous
e9dba0767e Fix Check UI deadlock (#672)
* Fix Check UI deadlock

* Add region

* Niceties
2024-03-07 02:04:07 -08:00
Deterous
2d142e9e9d Fix config access permissions (#671) 2024-03-06 23:08:37 -08:00
Matt Nadareski
7a928decff Try updating PR check action 2024-03-06 22:46:08 -05:00
Deterous
eb5409bdee Enable LibIRD for all .NET frameworks (#670) 2024-03-06 19:11:54 -08:00
Matt Nadareski
1578193068 Update packages to latest 2024-03-06 11:04:50 -05:00
Matt Nadareski
131c95e6ef Update to SabreTools.RedumpLib 1.3.5 2024-03-05 12:56:06 -05:00
Matt Nadareski
a7790a271f Use SabreTools.Hashing 2024-03-04 21:23:45 -05:00
Matt Nadareski
1b342d56ef Ensure no labels are empty (fixes #669) 2024-03-01 12:57:51 -05:00
Deterous
a500211129 Fix title normalization (#668)
* Perform title normalization properly

* Use strings for old .net
2024-03-01 06:25:16 -08:00
Deterous
4d798fa547 Hide Size in DIW if value is 0 (#664)
* Hide Size field in DIW when size=0

* Update CHANGELIST.md
2024-02-28 22:26:07 -08:00
Matt Nadareski
597ebdc973 Update changelog 2024-02-28 22:00:31 -05:00
Deterous
c6a8a9265f Add PS3 CFW support to MPF.Check (#663)
* Add PS3 CFW support to MPF.Check

* Do everything better
2024-02-28 18:59:49 -08:00
Matt Nadareski
393c53769d Don't link to AppVeyor artifacts page anymore 2024-02-27 11:33:44 -05:00
Matt Nadareski
fa21999d3f Add PR check workflow 2024-02-27 11:20:53 -05:00
Matt Nadareski
dafbb05b16 Remove GHA pull request builds 2024-02-27 11:13:07 -05:00
Matt Nadareski
1c1b23a84b Make GHA debug-only 2024-02-27 10:41:50 -05:00
Matt Nadareski
fd0fe4912d Hide layerbreaks if value is 0 (fixes #662) 2024-02-27 10:04:50 -05:00
Matt Nadareski
b5b54d13a2 Gate debug publishing behind use all flag 2024-02-27 09:57:30 -05:00
Matt Nadareski
da77987db3 Bump version 2024-02-27 09:31:10 -05:00
Deterous
774f44c8ce Pull PS3 Disc Key from redump (#656)
* Pull PS3 Disc Key from redump

* Bump RedumpLib to 1.3.3
2024-02-26 17:35:04 -08:00
Matt Nadareski
251b3754e4 Add Mattel HyperScan detection (fixes #661) 2024-02-26 13:17:46 -05:00
Matt Nadareski
963acc3336 Update to BinaryObjectScanner 3.1.0 2024-02-26 12:24:40 -05:00
Matt Nadareski
90588a0f8b Show hashes in readonly data (fixes #660) 2024-02-23 22:10:21 -05:00
Matt Nadareski
a56c212488 Change link to WIP builds in README 2024-02-23 21:18:46 -05:00
Matt Nadareski
6484ab5fe0 Remove generation, just in case 2024-02-23 21:04:17 -05:00
Matt Nadareski
1ff48258b8 Generate release notes automatically 2024-02-23 21:00:16 -05:00
Matt Nadareski
81019f9d56 Unified tag for rolling release 2024-02-23 20:57:46 -05:00
Matt Nadareski
d47c435236 Remove unnecessary empty section 2024-02-23 13:47:35 -05:00
Matt Nadareski
d59b114cba Don't omit body when setting body 2024-02-23 13:37:55 -05:00
Matt Nadareski
7f26dcba4e Use commit SHA as body of rolling releases 2024-02-23 13:35:20 -05:00
Matt Nadareski
5e2766f982 Change link to WIP builds in README 2024-02-23 13:32:26 -05:00
Matt Nadareski
c883f899bb Build artifact before upload 2024-02-23 13:28:45 -05:00
Matt Nadareski
8c9950d5fa Use newer download version 2024-02-23 13:11:33 -05:00
Matt Nadareski
3e842af273 Download all artifacts? 2024-02-23 13:06:19 -05:00
Matt Nadareski
b837623da2 Try using download-artifact 2024-02-23 13:01:03 -05:00
Matt Nadareski
6742901243 Revert artifact ID, use name? 2024-02-23 12:53:21 -05:00
Matt Nadareski
d6460a2b68 Use recommendation from upload-artifact 2024-02-23 12:46:23 -05:00
Matt Nadareski
7af59dacc6 Try fixing the artifact upload 2024-02-23 12:41:24 -05:00
Matt Nadareski
fc3ef36fef Attempt to add CD to existing actions 2024-02-23 12:36:55 -05:00
Matt Nadareski
6298487346 Fix link in README 2024-02-23 11:26:45 -05:00
Matt Nadareski
727d9844d5 Fix whitespace that got unwhitespaced 2024-02-23 11:18:36 -05:00
Matt Nadareski
72e7619e2d Remove net35 from MPF... again 2024-02-23 11:17:44 -05:00
Matt Nadareski
24b4647037 Remove now-unnecessary restore step 2024-02-23 11:16:44 -05:00
Matt Nadareski
713b3f0557 Fix net35 build issue 2024-02-23 11:11:48 -05:00
Matt Nadareski
f796a9b131 More tweaks to CI 2024-02-23 10:59:40 -05:00
Matt Nadareski
2cdf92bf92 Rename badges for GHA 2024-02-23 10:26:27 -05:00
Matt Nadareski
ccc1687f1a Add GHA CI status badges 2024-02-23 10:23:18 -05:00
Matt Nadareski
6057ec3a59 Split CI workflow files 2024-02-23 10:20:30 -05:00
Matt Nadareski
2a5e736285 Reorganize solution items 2024-02-23 10:11:39 -05:00
Deterous
010ef9016b Add CI via GitHub Workflows (#657)
* Create ci.yml

* Update ci.yml

* Rename to master

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

* Add tabs before an endregion

* Prevent double checking for existing files

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

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

* Add PIC and getkey.log options for IRDs

* Disable IRD creation window for unsupport .NET versions

* Finalise UI and parse inputs

* Add LibIRD package

* Manually define PIC in IRD creation

* Better output file browser

* Bump LibIRD version

* Update changelog

* Custom Disc ID, bump LibIRD version

* Ignore custom Disc ID for BD-50

* Provide a status message when creating IRD

* Better logpath enabled logic

* Nicer PIC UX

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

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

* Parse EXE date from DIC logs

* Fix DIC exe date parsing

* Split PS EXE name from EXE info

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

* Functional MPF.Check GUI

* Update changelog

* Show DiscInformationWindow after Check Dump

* Change layout

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

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

* Parse hex strings properly

* Helper function for hex numbers

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

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

* Fix logic

* Remove unnecessary using

* Update changelog

* Sanitise label somewhat

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

* Better changelog message

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

* Remove angle brackets when normalising path

* Add tooltip hover text for default output path

* Better tooltip formatting

* Use percent sign rather than angle brackets as variable delimiter

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

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

* Update changelog

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

* Sort files in !protectionInfo.txt

* Add option for removing drive letter

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

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

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

* Change the method of determining PIC length

* Update change logs

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

* Improvements to .skeleton/.hash code

* Revert redumper update, remove redudant string creation

* Deal with nullable strings

* Update changelog

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

* Update changelog
2024-01-02 17:50:48 -08:00
Matt Nadareski
32c12e1332 Make missing hash data clearer 2023-12-14 20:27:38 -05:00
Matt Nadareski
09b307aa24 Fix commented out code 2023-12-07 23:22:12 -05:00
Matt Nadareski
a5a8fbbf51 Update Redumper to build 294 2023-12-07 21:38:38 -05:00
Matt Nadareski
b366d236c8 Update RedumpLib 2023-12-05 11:18:58 -05:00
55 changed files with 7145 additions and 1677 deletions

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

@@ -0,0 +1,50 @@
name: MPF Check
on:
push:
branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
project: [MPF.Check]
runtime: [win-x86, win-x64, linux-x64, osx-x64] #[win-x86, win-x64, win-arm64, linux-x64, linux-arm64, osx-x64]
framework: [net8.0] #[net20, net35, net40, net452, net472, net48, netcoreapp3.1, net5.0, net6.0, net7.0, net8.0]
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

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

@@ -0,0 +1,57 @@
name: MPF UI
on:
push:
branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
project: [MPF]
runtime: [win-x86, win-x64]
framework: [net8.0-windows] #[net40, net452, net472, net48, netcoreapp3.1, net5.0-windows, net6.0-windows, net7.0-windows, net8.0-windows]
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: Bundle Redumper
run: |
wget https://github.com/superg/redumper/releases/download/build_325/redumper-2024.05.06_build325-win64.zip
unzip redumper-2024.05.06_build325-win64.zip
mkdir -p MPF/bin/Debug/${{ matrix.framework }}/${{ matrix.runtime }}/publish/Programs/Redumper
mv redumper-2024.05.06_build325-win64/bin/redumper.exe MPF/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/
- 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

23
.github/workflows/check_pr.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
name: Build PR
on: [pull_request]
jobs:
build:
runs-on: ubuntu-latest
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 build --no-restore
- name: Test
run: dotnet test --no-restore --verbosity normal

View File

@@ -1,3 +1,180 @@
### 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
- Read C2 error count from Redumper logs
- Read last instance of hash data from Redumper
- Add Konami Python 2 system detection
- Fix outdated information in README
- Fix missing information in README
- Language selections unchecked by default
- Update BinaryObjectScanner to 3.1.3
- Fix information pulling for redumper (fuzz6001)
- Update packages
- Update BinaryObjectScanner to 3.1.4
- Detect Xbox Series X discs (Deterous)
- Enable Windows targeting for test project
- Fix test project project includes
- Fix CleanRip hash output for Check (Deterous)
- Enable label-side mastering SID and toolstamp
- Enable remaining fields for label-side information
- Update BinaryObjectScanner to 3.1.5
### 3.1.4 (2024-03-16)
- Update BinaryObjectScanner to 3.1.2
### 3.1.3 (2024-03-15)
- Gate debug publishing behind use all flag
- Hide layerbreaks if value is 0
- Make GHA debug-only
- Remove GHA pull request builds
- Add PR check workflow
- Don't link to AppVeyor artifacts page anymore
- Add PS3 CFW support to MPF.Check (Deterous)
- Hide size if value is 0 (Deterous)
- Fix title normalization (Deterous)
- Ensure no labels are empty
- Use SabreTools.Hashing
- Update to SabreTools.RedumpLib 1.3.5
- Update packages to latest
- Enable LibIRD for all .NET frameworks (Deterous)
- Try updating PR check action
- Fix config access persmission (Deterous)
- Fix Check UI deadlock (Deterous)
- Fix formatting output formatting
- Update LibIRD to 0.9.0 (Deterous)
- Update packages
- Fix Redumper generic drive type (Deterous)
- Add MPF version to Submission info (Deterous)
- Update to RedumpLib 1.3.6
### 3.1.2 (2024-02-27)
- Remove debugging lines from build script
- Port build script fixes from BOS
- Fix double git hash version (feat. Deterous)
- Readd x86 builds by default
- Hide unavailable dumping programs (Deterous)
- Remove DIC and Aaru bundles from CI
- Add x86 builds to AppVeyor
- Make AppVeyor builds framework-dependent
- Fix misattributed artifact
- Update README with current build instructions
- Opt-in automatic IRD creation after PS3 dump (Deterous)
- Add CI via Github Workflows (Deterous)
- Reorganize solution items
- Split CI workflow files
- Add GHA CI status badges
- Rename badges for GHA
- More tweaks to CI
- Fix net35 build issue
- Remove now-unnecessary restore step
- Remove net35 from MPF... again
- Fix whitespace that got unwhitespaced
- Fix link in README
- Attempt to add CD to existing actions
- Try fixing the artifact upload
- Use recommendation from upload-artifact
- Revert artifact ID, use name?
- Try using download-artifact
- Download all artifacts?
- Use newer download version
- Build artifact before upload
- Change link to WIP builds in README
- Use commit SHA as body of rolling releases
- Don't omit body when setting body
- Remove unnecessary empty section
- Unified tag for rolling release
- Generate release notes automatically
- Remove generation, just in case
- Change link to WIP builds in README
- Show hashes in readonly data
- Update to BinaryObjectScanner 3.1.0
- Add Mattel HyperScan detection
- Pull PS3 Disc Key from redump (Deterous)
### 3.1.1 (2024-02-20)
- Remove .NET 6 from auto-builds
- Make Redumper the default for new users
- Fix DIC log parsing for SS version (Deterous)
- Write outputs with UTF-8
- Add funworld Photo Play detection
- Fix Aaru drive parameter generation
- Limit DVD protection outputs
- Add a GUI for PS3 IRD Creation (Deterous)
- Update LibIRD, disable UI elements when creating IRD (Deterous)
### 3.1.0 (2024-02-06)
- Update RedumpLib
- Update Redumper to build 294
- Fix commented out code
- Make missing hash data clearer
- Get BD PIC Identifier for redumper (Deterous)
- Support redumper skeleton and hash files (Deterous)
- Support ringcode and PIC for triple/quad-layer (fuzz6001)
- Cleanup !protectionInfo.txt (Deterous)
- Update Redumper to build 311 (Deterous)
- Use PSX/PS2 serial as filename when Volume Label not present (Deterous)
- Allow variables in output path (Deterous)
- Check for presence of complete dump from other programs (Deterous)
- Retrieve volume label from logs (Deterous)
- Correct missing space in PVD (fuzz6001)
- Prevent crashing on invalid parameters (Deterous)
- Detect CDTV discs (Deterous)
- Differentiate CD32 from CDTV (Deterous)
- Normalise Disc Titles in Submission Info (Deterous)
- Skip warning line in Redumper log
- Add a GUI for MPF.Check (Deterous)
- Fix information pulling for CleanRip and UIC
- Add UMD handling for the disc info window
- Detect Photo CD
- Parse PSX/PS2/KP2 exe date from logs (Deterous)
- Exclude extra tracks when finding disc matches (Deterous)
- Verbose Redumper log by default (Deterous)
- Retrieve serial from Cleanrip
- Fix build from rushed code
- Remove `-disc2` from Cleanrip serial
- Enable Windows builds on Linux and Mac
- Fix compiler warning (Deterous)
### 3.0.3 (2023-12-04)
- Fix broken tests

View File

@@ -6,17 +6,18 @@
<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.0.3</VersionPrefix>
<VersionPrefix>3.1.9</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-2023</Copyright>
<Copyright>Copyright (c) Matt Nadareski 2019-2024</Copyright>
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
<RepositoryUrl>https://github.com/SabreTools/MPF</RepositoryUrl>
<RepositoryType>git</RepositoryType>
@@ -31,10 +32,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="BinaryObjectScanner" PrivateAssets="build; analyzers" ExcludeAssets="contentFiles" Version="3.0.2" GeneratePathProperty="true">
<PackageReference Include="BinaryObjectScanner" PrivateAssets="build; analyzers" ExcludeAssets="contentFiles" Version="3.1.12" GeneratePathProperty="true">
<IncludeAssets>runtime; compile; build; native; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="SabreTools.RedumpLib" Version="1.3.1" />
<PackageReference Include="SabreTools.RedumpLib" Version="1.3.8" />
</ItemGroup>
<ItemGroup>

View File

@@ -99,6 +99,7 @@ namespace MPF.Check
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");

View File

@@ -101,7 +101,9 @@ namespace MPF.Core.Converters
InternalProgram.CleanRip => "CleanRip",
InternalProgram.DCDumper => "DCDumper",
InternalProgram.PS3CFW => "PS3 CFW",
InternalProgram.UmdImageCreator => "UmdImageCreator",
InternalProgram.XboxBackupCreator => "XboxBackupCreator",
#endregion
@@ -110,7 +112,44 @@ namespace MPF.Core.Converters
};
}
#endregion
/// <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
@@ -141,10 +180,20 @@ namespace MPF.Core.Converters
"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,
"xbc"
or "xbox"
or "xbox360"
or "xboxcreator"
or "xboxbackupcreator" => InternalProgram.XboxBackupCreator,
_ => InternalProgram.NONE,
};
@@ -293,6 +342,56 @@ namespace MPF.Core.Converters
};
}
/// <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(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(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

@@ -7,6 +7,7 @@ using Microsoft.Management.Infrastructure;
using Microsoft.Management.Infrastructure.Generic;
#endif
using MPF.Core.Converters;
using SabreTools.IO;
using SabreTools.RedumpLib.Data;
namespace MPF.Core.Data
@@ -58,18 +59,36 @@ namespace MPF.Core.Data
/// <summary>
/// Media label as read by Windows, formatted to avoid odd outputs
/// If no volume label present, use PSX or PS2 serial if valid
/// Otherwise, use "track" as volume label
/// </summary>
public string? FormattedVolumeLabel
{
get
{
string? volumeLabel = Template.DiscNotDetected;
if (this.MarkedActive)
if (!this.MarkedActive)
return volumeLabel;
if (!string.IsNullOrEmpty(this.VolumeLabel))
{
if (string.IsNullOrEmpty(this.VolumeLabel))
volumeLabel = "track";
else
volumeLabel = this.VolumeLabel;
volumeLabel = this.VolumeLabel;
}
else
{
// No Volume Label found, fallback to something sensible
switch (this.GetRedumpSystem(null))
{
case RedumpSystem.SonyPlayStation:
case RedumpSystem.SonyPlayStation2:
InfoTool.GetPlayStationExecutableInfo(this.Name, out string? serial, out _, out _);
volumeLabel = serial ?? "track";
break;
default:
volumeLabel = "track";
break;
}
}
foreach (char c in Path.GetInvalidFileNameChars())
@@ -259,11 +278,28 @@ namespace MPF.Core.Data
return RedumpSystem.IBMPCcompatible;
// Check volume labels first
RedumpSystem? systemFromLabel = GetRedumpSystemFromVolumeLabel();
RedumpSystem? systemFromLabel = GetRedumpSystemFromVolumeLabel(this.VolumeLabel);
if (systemFromLabel != null)
return systemFromLabel;
// Get a list of files for quicker checking
#region Arcade
// funworld Photo Play
if (File.Exists(Path.Combine(this.Name, "PP.INF"))
&& Directory.Exists(Path.Combine(this.Name, "PPINC")))
{
return RedumpSystem.funworldPhotoPlay;
}
// Konami Python 2
if (Directory.Exists(Path.Combine(this.Name, "PY2.D")))
{
return RedumpSystem.KonamiPython2;
}
#endregion
#region Consoles
// Bandai Playdia Quick Interactive System
@@ -289,6 +325,25 @@ namespace MPF.Core.Data
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")))
@@ -315,17 +370,43 @@ namespace MPF.Core.Data
}
catch { }
// Microsoft Xbox One
// Microsoft Xbox One and Series X
try
{
if (Directory.Exists(Path.Combine(this.Name, "MSXC"))
#if NET20 || NET35
&& Directory.GetFiles(Path.Combine(this.Name, "MSXC")).Any())
#else
&& Directory.EnumerateFiles(Path.Combine(this.Name, "MSXC")).Any())
#endif
if (Directory.Exists(Path.Combine(this.Name, "MSXC")))
{
return RedumpSystem.MicrosoftXboxOne;
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 { }
@@ -408,7 +489,7 @@ namespace MPF.Core.Data
return RedumpSystem.VTechVFlashVSmilePro;
}
#endregion
#endregion
#region Computers
@@ -469,6 +550,21 @@ namespace MPF.Core.Data
}
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
{
@@ -484,7 +580,7 @@ namespace MPF.Core.Data
}
catch { }
#endregion
#endregion
// Default return
return defaultValue;
@@ -494,48 +590,48 @@ namespace MPF.Core.Data
/// Get the current system from the drive volume label
/// </summary>
/// <returns>The system based on volume label, null if none detected</returns>
public RedumpSystem? GetRedumpSystemFromVolumeLabel()
public static RedumpSystem? GetRedumpSystemFromVolumeLabel(string? volumeLabel)
{
// If the volume label is empty, we can't do anything
if (string.IsNullOrEmpty(this.VolumeLabel))
if (string.IsNullOrEmpty(volumeLabel))
return null;
// Audio CD
if (this.VolumeLabel!.Equals("Audio CD", StringComparison.OrdinalIgnoreCase))
if (volumeLabel!.Equals("Audio CD", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.AudioCD;
// Microsoft Xbox
if (this.VolumeLabel.Equals("SEP13011042", StringComparison.OrdinalIgnoreCase))
if (volumeLabel.Equals("SEP13011042", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.MicrosoftXbox;
else if (this.VolumeLabel.Equals("SEP13011042072", StringComparison.OrdinalIgnoreCase))
else if (volumeLabel.Equals("SEP13011042072", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.MicrosoftXbox;
// Microsoft Xbox 360
if (this.VolumeLabel.Equals("XBOX360", StringComparison.OrdinalIgnoreCase))
if (volumeLabel.Equals("XBOX360", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.MicrosoftXbox360;
else if (this.VolumeLabel.Equals("XGD2DVD_NTSC", StringComparison.OrdinalIgnoreCase))
else if (volumeLabel.Equals("XGD2DVD_NTSC", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.MicrosoftXbox360;
// Microsoft Xbox 360 - Too overly broad even if a lot of discs use this
//if (this.VolumeLabel.Equals("CD_ROM", StringComparison.OrdinalIgnoreCase))
//if (volumeLabel.Equals("CD_ROM", StringComparison.OrdinalIgnoreCase))
// return RedumpSystem.MicrosoftXbox360; // Also for Xbox One?
//if (this.VolumeLabel.Equals("DVD_ROM", StringComparison.OrdinalIgnoreCase))
//if (volumeLabel.Equals("DVD_ROM", StringComparison.OrdinalIgnoreCase))
// return RedumpSystem.MicrosoftXbox360;
// Sega Mega-CD / Sega-CD
if (this.VolumeLabel.Equals("Sega_CD", StringComparison.OrdinalIgnoreCase))
if (volumeLabel.Equals("Sega_CD", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.SegaMegaCDSegaCD;
// Sony PlayStation 3
if (this.VolumeLabel.Equals("PS3VOLUME", StringComparison.OrdinalIgnoreCase))
if (volumeLabel.Equals("PS3VOLUME", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.SonyPlayStation3;
// Sony PlayStation 4
if (this.VolumeLabel.Equals("PS4VOLUME", StringComparison.OrdinalIgnoreCase))
if (volumeLabel.Equals("PS4VOLUME", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.SonyPlayStation4;
// Sony PlayStation 5
if (this.VolumeLabel.Equals("PS5VOLUME", StringComparison.OrdinalIgnoreCase))
if (volumeLabel.Equals("PS5VOLUME", StringComparison.OrdinalIgnoreCase))
return RedumpSystem.SonyPlayStation5;
return null;
@@ -550,7 +646,7 @@ namespace MPF.Core.Data
this.PopulateFromDriveInfo(driveInfo);
}
#endregion
#endregion
#region Helpers

View File

@@ -1,25 +1,5 @@
namespace MPF.Core.Data
{
/// <summary>
/// Available hashing types
/// </summary>
public enum Hash
{
CRC32,
#if NET6_0_OR_GREATER
CRC64,
#endif
MD5,
SHA1,
SHA256,
SHA384,
SHA512,
#if NET6_0_OR_GREATER
XxHash32,
XxHash64,
#endif
}
/// <summary>
/// Drive type for dumping
/// </summary>
@@ -46,7 +26,34 @@
// Verification support only
CleanRip,
DCDumper,
PS3CFW,
UmdImageCreator,
XboxBackupCreator,
}
/// <summary>
/// Drive read method option
/// </summary>
public enum RedumperReadMethod
{
NONE = 0,
BE,
D8,
BE_CDDA,
}
/// <summary>
/// Drive sector order option
/// </summary>
public enum RedumperSectorOrder
{
NONE = 0,
DATA_C2_SUB,
DATA_SUB_C2,
DATA_SUB,
DATA_C2,
}
/// <summary>

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

@@ -56,9 +56,9 @@ namespace MPF.Core.Data
{
get
{
var valueString = GetStringSetting(Settings, "InternalProgram", InternalProgram.DiscImageCreator.ToString());
var valueString = GetStringSetting(Settings, "InternalProgram", InternalProgram.Redumper.ToString());
var valueEnum = EnumConverter.ToInternalProgram(valueString);
return valueEnum == InternalProgram.NONE ? InternalProgram.DiscImageCreator : valueEnum;
return valueEnum == InternalProgram.NONE ? InternalProgram.Redumper : valueEnum;
}
set
{
@@ -316,22 +316,40 @@ namespace MPF.Core.Data
set { Settings["RedumperEnableDebug"] = value.ToString(); }
}
/// <summary>
/// Enable Redumper custom lead-in retries for Plextor drives
/// </summary>
public bool RedumperEnableLeadinRetry
{
get { return GetBooleanSetting(Settings, "RedumperEnableLeadinRetry", false); }
set { Settings["RedumperEnableLeadinRetry"] = value.ToString(); }
}
/// <summary>
/// Enable verbose output while dumping by default
/// </summary>
public bool RedumperEnableVerbose
{
get { return GetBooleanSetting(Settings, "RedumperEnableVerbose", false); }
get { return GetBooleanSetting(Settings, "RedumperEnableVerbose", true); }
set { Settings["RedumperEnableVerbose"] = 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, "RedumperLeadinRetryCount", 4); }
set { Settings["RedumperLeadinRetryCount"] = 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>
@@ -343,6 +361,38 @@ namespace MPF.Core.Data
set { Settings["RedumperUseGenericDriveType"] = value.ToString(); }
}
/// <summary>
/// Currently selected default redumper read method
/// </summary>
public RedumperReadMethod RedumperReadMethod
{
get
{
var valueString = GetStringSetting(Settings, "RedumperReadMethod", RedumperReadMethod.NONE.ToString());
return EnumConverter.ToRedumperReadMethod(valueString);
}
set
{
Settings["RedumperReadMethod"] = value.ToString();
}
}
/// <summary>
/// Currently selected default redumper sector order
/// </summary>
public RedumperSectorOrder RedumperSectorOrder
{
get
{
var valueString = GetStringSetting(Settings, "RedumperSectorOrder", RedumperSectorOrder.NONE.ToString());
return EnumConverter.ToRedumperSectorOrder(valueString);
}
set
{
Settings["RedumperSectorOrder"] = value.ToString();
}
}
/// <summary>
/// Default number of rereads
/// </summary>
@@ -500,6 +550,15 @@ namespace MPF.Core.Data
set { Settings["DeleteUnnecessaryFiles"] = value.ToString(); }
}
/// <summary>
/// Create a PS3 IRD file after dumping PS3 BD-ROM discs
/// </summary>
public bool CreateIRDAfterDumping
{
get { return GetBooleanSetting(Settings, "CreateIRDAfterDumping", false); }
set { Settings["CreateIRDAfterDumping"] = value.ToString(); }
}
#endregion
#region Skip Options
@@ -553,6 +612,15 @@ namespace MPF.Core.Data
set { Settings["IncludeDebugProtectionInformation"] = value.ToString(); }
}
/// <summary>
/// Remove drive letters from protection scan output
/// </summary>
public bool HideDriveLetters
{
get { return GetBooleanSetting(Settings, "HideDriveLetters", false); }
set { Settings["HideDriveLetters"] = value.ToString(); }
}
#endregion
#region Logging Options

View File

@@ -148,10 +148,15 @@ namespace MPF.Core
// 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 },
InternalProgram.XboxBackupCreator => new Modules.XboxBackupCreator.Parameters(parameters) { ExecutablePath = null },
// If no dumping program found, set to null
InternalProgram.NONE => null,
// This should never happen, but it needs a fallback
_ => new Modules.DiscImageCreator.Parameters(parameters) { ExecutablePath = Options.DiscImageCreatorPath },
_ => new Modules.Redumper.Parameters(parameters) { ExecutablePath = Options.RedumperPath },
};
// Set system and type
@@ -183,12 +188,15 @@ namespace MPF.Core
InternalProgram.DiscImageCreator => new Modules.DiscImageCreator.Parameters(System, Type, Drive.Name, OutputPath, driveSpeed, Options),
InternalProgram.Redumper => new Modules.Redumper.Parameters(System, Type, Drive.Name, OutputPath, driveSpeed, Options),
// If no dumping program found, set to null
InternalProgram.NONE => null,
// This should never happen, but it needs a fallback
_ => new Modules.DiscImageCreator.Parameters(System, Type, Drive.Name, OutputPath, driveSpeed, Options),
_ => new Modules.Redumper.Parameters(System, Type, Drive.Name, OutputPath, driveSpeed, Options),
};
// Generate and return the param string
return Parameters.GenerateParameters();
return Parameters?.GenerateParameters();
}
return null;
@@ -281,6 +289,9 @@ namespace MPF.Core
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
@@ -353,9 +364,9 @@ namespace MPF.Core
resultProgress?.Report(Result.Success("Formatting information..."));
(var formattedValues, var formatResult) = Formatter.FormatOutputData(submissionInfo, Options.EnableRedumpCompatibility);
if (formattedValues == null)
resultProgress?.Report(Result.Success(formatResult));
else
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;
@@ -374,7 +385,7 @@ namespace MPF.Core
if (Options.ScanForProtection && Options.OutputSeparateProtectionFile)
{
resultProgress?.Report(Result.Success("Writing protection to !protectionInfo.txt..."));
bool scanSuccess = InfoTool.WriteProtectionData(outputDirectory, filenameSuffix, submissionInfo);
bool scanSuccess = InfoTool.WriteProtectionData(outputDirectory, filenameSuffix, submissionInfo, Options.HideDriveLetters);
if (scanSuccess)
resultProgress?.Report(Result.Success("Writing complete!"));
else
@@ -415,6 +426,17 @@ namespace MPF.Core
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();
}

View File

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

View File

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

View File

@@ -1,81 +0,0 @@
using System;
using System.IO;
using System.Threading;
//namespace Compress.ThreadReaders
namespace MPF.Core.Hashing
{
public sealed class ThreadLoadBuffer : IDisposable
{
private readonly AutoResetEvent _waitEvent;
private readonly AutoResetEvent _outEvent;
private readonly Thread _tWorker;
private byte[]? _buffer;
private int _size;
private readonly Stream _ds;
private bool _finished;
public bool errorState;
public int SizeRead;
public ThreadLoadBuffer(Stream ds)
{
_waitEvent = new AutoResetEvent(false);
_outEvent = new AutoResetEvent(false);
_finished = false;
_ds = ds;
errorState = false;
_tWorker = new Thread(MainLoop);
_tWorker.Start();
}
public void Dispose()
{
_waitEvent.Close();
_outEvent.Close();
}
private void MainLoop()
{
while (true)
{
_waitEvent.WaitOne();
if (_finished)
{
break;
}
try
{
if (_buffer != null)
SizeRead = _ds.Read(_buffer, 0, _size);
}
catch (Exception)
{
errorState = true;
}
_outEvent.Set();
}
}
public void Trigger(byte[] buffer, int size)
{
_buffer = buffer;
_size = size;
_waitEvent.Set();
}
public void Wait()
{
_outEvent.WaitOne();
}
public void Finish()
{
_finished = true;
_waitEvent.Set();
_tWorker.Join();
}
}
}

View File

@@ -14,6 +14,7 @@ using MPF.Core.Modules;
using MPF.Core.Utilities;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using SabreTools.IO;
using SabreTools.Models.PIC;
using SabreTools.RedumpLib.Data;
using Formatting = Newtonsoft.Json.Formatting;
@@ -168,7 +169,7 @@ namespace MPF.Core
{
try
{
return new SabreTools.Serialization.Files.PIC().Deserialize(pic);
return SabreTools.Serialization.Deserializers.PIC.DeserializeFile(pic);
}
catch
{
@@ -385,56 +386,43 @@ namespace MPF.Core
}
/// <summary>
/// Get the EXE date from a PlayStation disc, if possible
/// Get the EXE name from a PlayStation disc, if possible
/// </summary>
/// <param name="driveLetter">Drive letter to use to check</param>
/// <param name="serial">Internal disc serial, if possible</param>
/// <param name="region">Output region, if possible</param>
/// <param name="date">Output EXE date in "yyyy-mm-dd" format if possible, null on error</param>
/// <returns></returns>
internal static bool GetPlayStationExecutableInfo(char? driveLetter, out string? serial, out Region? region, out string? date)
/// <returns>Executable name on success, null otherwise</returns>
internal static string? GetPlayStationExecutableName(char? driveLetter)
{
serial = null; region = null; date = null;
// If there's no drive letter, we can't do this part
// If there's no drive letter, we can't get exe name
if (driveLetter == null)
return false;
return null;
// If the folder no longer exists, we can't do this part
// Convert drive letter to drive path
string drivePath = driveLetter + ":\\";
return GetPlayStationExecutableInfo(drivePath, out serial, out region, out date);
return GetPlayStationExecutableName(drivePath);
}
/// <summary>
/// Get the EXE date from a PlayStation disc, if possible
/// Get the EXE name from a PlayStation disc, if possible
/// </summary>
/// <param name="drivePath">Drive path to use to check</param>
/// <param name="serial">Internal disc serial, if possible</param>
/// <param name="region">Output region, if possible</param>
/// <param name="date">Output EXE date in "yyyy-mm-dd" format if possible, null on error</param>
/// <returns></returns>
internal static bool GetPlayStationExecutableInfo(string? drivePath, out string? serial, out Region? region, out string? date)
/// <returns>Executable name on success, null otherwise</returns>
internal static string? GetPlayStationExecutableName(string? drivePath)
{
serial = null; region = null; date = null;
// If there's no drive path, we can't do this part
// If there's no drive path, we can't get exe name
if (string.IsNullOrEmpty(drivePath))
return false;
return null;
// If the folder no longer exists, we can't do this part
// If the folder no longer exists, we can't get exe name
if (!Directory.Exists(drivePath))
return false;
return null;
// Get the two paths that we will need to check
string psxExePath = Path.Combine(drivePath, "PSX.EXE");
string systemCnfPath = Path.Combine(drivePath, "SYSTEM.CNF");
// Try both of the common paths that contain information
string? exeName = null;
// Read the CNF file as an INI file
var systemCnf = new IniFile(systemCnfPath);
string bootValue = string.Empty;
string? bootValue = string.Empty;
// PlayStation uses "BOOT" as the key
if (systemCnf.ContainsKey("BOOT"))
@@ -450,30 +438,82 @@ namespace MPF.Core
var match = Regex.Match(bootValue, @"cdrom.?:\\?(.*)", RegexOptions.Compiled);
if (match.Groups.Count > 1)
{
// EXE name may have a trailing `;` after
// EXE name should always be in all caps
exeName = match.Groups[1].Value
.Split(';')[0]
.ToUpperInvariant();
// Serial is most of the EXE name normalized
serial = exeName
.Replace('_', '-')
.Replace(".", string.Empty);
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 (string.IsNullOrEmpty(exeName) && File.Exists(psxExePath))
exeName = "PSX.EXE";
if (File.Exists(psxExePath))
return "PSX.EXE";
// If neither can be found, we return false
if (string.IsNullOrEmpty(exeName))
// If neither can be found, we return null
return null;
}
/// <summary>
/// Get the EXE date from a PlayStation disc, if possible
/// </summary>
/// <param name="driveLetter">Drive letter to use to check</param>
/// <param name="serial">Internal disc serial, if possible</param>
/// <param name="region">Output region, if possible</param>
/// <param name="date">Output EXE date in "yyyy-mm-dd" format if possible, null on error</param>
/// <returns>True if information could be determined, false otherwise</returns>
internal static bool GetPlayStationExecutableInfo(char? driveLetter, out string? serial, out Region? region, out string? date)
{
serial = null; region = null; date = null;
// If there's no drive letter, we can't do this part
if (driveLetter == null)
return false;
// Convert drive letter to drive path
string drivePath = driveLetter + ":\\";
return GetPlayStationExecutableInfo(drivePath, out serial, out region, out date);
}
/// <summary>
/// Get the EXE date from a PlayStation disc, if possible
/// </summary>
/// <param name="drivePath">Drive path to use to check</param>
/// <param name="serial">Internal disc serial, if possible</param>
/// <param name="region">Output region, if possible</param>
/// <param name="date">Output EXE date in "yyyy-mm-dd" format if possible, null on error</param>
/// <returns>True if information could be determined, false otherwise</returns>
internal static bool GetPlayStationExecutableInfo(string? drivePath, 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(drivePath))
return false;
// If the folder no longer exists, we can't do this part
if (!Directory.Exists(drivePath))
return false;
// Get the executable name
string? exeName = GetPlayStationExecutableName(drivePath);
// 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 = GetPlayStationRegion(exeName);
@@ -974,7 +1014,7 @@ namespace MPF.Core
}
}
#endregion
#endregion
#region Category Extraction
@@ -1275,7 +1315,7 @@ namespace MPF.Core
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));
using var sw = new StreamWriter(File.Open(path, FileMode.Create, FileAccess.Write), Encoding.UTF8);
foreach (string line in lines)
{
sw.WriteLine(line);
@@ -1361,8 +1401,9 @@ namespace MPF.Core
/// <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>
public static bool WriteProtectionData(string? outputDirectory, string? filenameSuffix, SubmissionInfo? info)
public 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())
@@ -1381,13 +1422,21 @@ namespace MPF.Core
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));
foreach (var kvp in info.CopyProtection.FullProtections)
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)
{
if (kvp.Value == null)
sw.WriteLine($"{kvp.Key}: None");
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($"{kvp.Key}: {string.Join(", ", [.. kvp.Value])}");
sw.WriteLine($"{scanPath}: {string.Join(", ", [.. scanResult])}");
}
}
catch
@@ -1457,6 +1506,61 @@ namespace MPF.Core
return files;
}
/// <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>
public 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 = Tools.ParseHexKey(discKeyString);
if (discKey == null)
return (false, "Failed to create IRD: No key provided");
// Parse Disc ID from submission info (Optional)
byte[]? discID = Tools.ParseDiscID(discIDString);
// Parse PIC from submission info (Optional)
byte[]? pic = Tools.ParsePIC(picString);
// Parse CRC32 strings into ISO hash for Unique ID field (Optional)
uint? uid = Tools.ParseCRC32(crc32);
// Ensure layerbreak value is valid (Optional)
layerbreak = Tools.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
#region Normalization
@@ -1467,7 +1571,7 @@ namespace MPF.Core
/// <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)
public static string? NormalizeDiscTitle(string? title, Language?[]? languages)
{
// If we have no set languages, then assume English
if (languages == null || languages.Length == 0)
@@ -1477,8 +1581,8 @@ namespace MPF.Core
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)
string? newTitle = NormalizeDiscTitle(title, language);
if (newTitle != title)
return newTitle;
}
@@ -1500,14 +1604,18 @@ namespace MPF.Core
/// 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)
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();
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)
@@ -1578,6 +1686,8 @@ namespace MPF.Core
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"
@@ -1820,8 +1930,6 @@ namespace MPF.Core
case "אַן"
when language is Language.Yiddish:
// Seen by Redump, unknown origin
case "du":
break;
// Otherwise, just return it as-is
@@ -1835,14 +1943,29 @@ namespace MPF.Core
for (int i = 1; i < splitTitle.Length; i++)
{
string segment = splitTitle[i];
if (segment.EndsWith(":") || segment.EndsWith("-"))
if (!itemInserted && segment == ":")
{
itemInserted = true;
newTitleBuilder.Append($"{segment}, {firstItem}");
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} ");
newTitleBuilder.Append($" {segment}");
}
}
@@ -1867,8 +1990,10 @@ namespace MPF.Core
if (string.IsNullOrEmpty(path))
return string.Empty;
// Remove quotes from path
// 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;

View File

@@ -5,16 +5,17 @@
<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.0.3</VersionPrefix>
<VersionPrefix>3.1.9</VersionPrefix>
<!-- Package Properties -->
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
<Description>Common code for all MPF implementations</Description>
<Copyright>Copyright (c) Matt Nadareski 2019-2023</Copyright>
<Copyright>Copyright (c) Matt Nadareski 2019-2024</Copyright>
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
<RepositoryUrl>https://github.com/SabreTools/MPF</RepositoryUrl>
<RepositoryType>git</RepositoryType>
@@ -45,19 +46,18 @@
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
</ItemGroup>
<ItemGroup Condition="$(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`))">
<PackageReference Include="System.IO.Hashing" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BinaryObjectScanner" PrivateAssets="build; analyzers" ExcludeAssets="contentFiles" Version="3.0.2" GeneratePathProperty="true">
<PackageReference Include="BinaryObjectScanner" PrivateAssets="build; analyzers" ExcludeAssets="contentFiles" Version="3.1.12" 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="psxt001z.Library" Version="0.21.0-beta3" />
<PackageReference Include="SabreTools.Models" Version="1.3.0" />
<PackageReference Include="SabreTools.RedumpLib" Version="1.3.1" />
<PackageReference Include="SabreTools.Serialization" Version="1.3.0" />
<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.3.8" />
<PackageReference Include="SabreTools.Serialization" Version="1.6.5" />
</ItemGroup>
</Project>

View File

@@ -1067,10 +1067,8 @@ namespace MPF.Core.Modules.Aaru
// Handle filenames based on command, if necessary
switch (BaseCommand)
{
// Input value only
// Input value only (file path)
case CommandStrings.ArchivePrefixLong + " " + CommandStrings.ArchiveInfo:
case CommandStrings.DevicePrefixLong + " " + CommandStrings.DeviceInfo:
case CommandStrings.DevicePrefixLong + " " + CommandStrings.DeviceReport:
case CommandStrings.FilesystemPrefixLong + " " + CommandStrings.FilesystemInfo:
case CommandStrings.FilesystemPrefixLong + " " + CommandStrings.FilesystemListLong:
case CommandStrings.ImagePrefixLong + " " + CommandStrings.ImageChecksumLong:
@@ -1080,12 +1078,21 @@ namespace MPF.Core.Modules.Aaru
case CommandStrings.ImagePrefixLong + " " + CommandStrings.ImageInfo:
case CommandStrings.ImagePrefixLong + " " + CommandStrings.ImagePrint:
case CommandStrings.ImagePrefixLong + " " + CommandStrings.ImageVerify:
if (string.IsNullOrEmpty(InputValue))
return null;
parameters.Add($"\"{InputValue}\"");
break;
// Input value only (device path)
case CommandStrings.DevicePrefixLong + " " + CommandStrings.DeviceInfo:
case CommandStrings.DevicePrefixLong + " " + CommandStrings.DeviceReport:
case CommandStrings.MediaPrefixLong + " " + CommandStrings.MediaInfo:
case CommandStrings.MediaPrefixLong + " " + CommandStrings.MediaScan:
if (string.IsNullOrEmpty(InputValue))
return null;
parameters.Add($"\"{InputValue}\"");
parameters.Add(InputValue!.TrimEnd('\\'));
break;
// Two input values
@@ -1097,10 +1104,9 @@ namespace MPF.Core.Modules.Aaru
parameters.Add($"\"{Input2Value}\"");
break;
// Input and Output value
// Input and Output value (file path)
case CommandStrings.FilesystemPrefixLong + " " + CommandStrings.FilesystemExtract:
case CommandStrings.ImagePrefixLong + " " + CommandStrings.ImageConvert:
case CommandStrings.MediaPrefixLong + " " + CommandStrings.MediaDump:
if (string.IsNullOrEmpty(InputValue) || string.IsNullOrEmpty(OutputValue))
return null;
@@ -1108,6 +1114,15 @@ namespace MPF.Core.Modules.Aaru
parameters.Add($"\"{OutputValue}\"");
break;
// Input and Output value (device path)
case CommandStrings.MediaPrefixLong + " " + CommandStrings.MediaDump:
if (string.IsNullOrEmpty(InputValue) || string.IsNullOrEmpty(OutputValue))
return null;
parameters.Add(InputValue!.TrimEnd('\\'));
parameters.Add($"\"{OutputValue}\"");
break;
// Remote host value only
case CommandStrings.DevicePrefixLong + " " + CommandStrings.DeviceList:
case CommandStrings.Remote:
@@ -2471,7 +2486,7 @@ namespace MPF.Core.Modules.Aaru
cueSheet.Files = [.. cueFiles];
if (cueSheet != null && cueSheet != default)
{
var ms = new SabreTools.Serialization.Streams.CueSheet().Serialize(cueSheet);
var ms = SabreTools.Serialization.Serializers.CueSheet.SerializeStream(cueSheet);
if (ms == null)
return null;

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
@@ -74,6 +75,11 @@ 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
@@ -634,7 +640,12 @@ namespace MPF.Core.Modules
i++;
(string value, long factor) = ExtractFactorFromValue(parts[i]);
return (sbyte)(sbyte.Parse(value) * factor);
if (sbyte.TryParse(value, out sbyte sByteValue))
return (sbyte)(sByteValue * factor);
string hexValue = RemoveHexIdentifier(value);
if (sbyte.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out sbyte sByteHexValue))
return (sbyte)(sByteHexValue * factor);
return null;
}
else if (parts[i].StartsWith(shortFlagString + "=") || parts[i].StartsWith(longFlagString + "="))
{
@@ -649,7 +660,12 @@ namespace MPF.Core.Modules
this[longFlagString] = true;
(string value, long factor) = ExtractFactorFromValue(valuePart);
return (sbyte)(sbyte.Parse(value) * factor);
if (sbyte.TryParse(value, out sbyte sByteValue))
return (sbyte)(sByteValue * factor);
string hexValue = RemoveHexIdentifier(value);
if (sbyte.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out sbyte sByteHexValue))
return (sbyte)(sByteHexValue * factor);
return null;
}
return SByte.MinValue;
@@ -711,7 +727,12 @@ namespace MPF.Core.Modules
this[longFlagString] = true;
i++;
(string value, long factor) = ExtractFactorFromValue(parts[i]);
return (short)(short.Parse(value) * factor);
if (short.TryParse(value, out short shortValue))
return (short)(shortValue * factor);
string hexValue = RemoveHexIdentifier(value);
if (short.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out short shortHexValue))
return (short)(shortHexValue * factor);
return null;
}
else if (parts[i].StartsWith(shortFlagString + "=") || parts[i].StartsWith(longFlagString + "="))
{
@@ -726,7 +747,12 @@ namespace MPF.Core.Modules
this[longFlagString] = true;
(string value, long factor) = ExtractFactorFromValue(valuePart);
return (short)(short.Parse(value) * factor);
if (short.TryParse(value, out short shortValue))
return (short)(shortValue * factor);
string hexValue = RemoveHexIdentifier(value);
if (short.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out short shortHexValue))
return (short)(shortHexValue * factor);
return null;
}
return Int16.MinValue;
@@ -788,7 +814,12 @@ namespace MPF.Core.Modules
this[longFlagString] = true;
i++;
(string value, long factor) = ExtractFactorFromValue(parts[i]);
return (int)(int.Parse(value) * factor);
if (int.TryParse(value, out int intValue))
return (int)(intValue * factor);
string hexValue = RemoveHexIdentifier(value);
if (int.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out int intHexValue))
return (int)(intHexValue * factor);
return null;
}
else if (parts[i].StartsWith(shortFlagString + "=") || parts[i].StartsWith(longFlagString + "="))
{
@@ -803,7 +834,12 @@ namespace MPF.Core.Modules
this[longFlagString] = true;
(string value, long factor) = ExtractFactorFromValue(valuePart);
return (int)(int.Parse(value) * factor);
if (int.TryParse(value, out int intValue))
return (int)(intValue * factor);
string hexValue = RemoveHexIdentifier(value);
if (int.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out int intHexValue))
return (int)(intHexValue * factor);
return null;
}
return Int32.MinValue;
@@ -865,7 +901,12 @@ namespace MPF.Core.Modules
this[longFlagString] = true;
i++;
(string value, long factor) = ExtractFactorFromValue(parts[i]);
return long.Parse(value) * factor;
if (long.TryParse(value, out long longValue))
return (long)(longValue * factor);
string hexValue = RemoveHexIdentifier(value);
if (long.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out long longHexValue))
return (long)(longHexValue * factor);
return null;
}
else if (parts[i].StartsWith(shortFlagString + "=") || parts[i].StartsWith(longFlagString + "="))
{
@@ -880,7 +921,12 @@ namespace MPF.Core.Modules
this[longFlagString] = true;
(string value, long factor) = ExtractFactorFromValue(valuePart);
return long.Parse(value) * factor;
if (long.TryParse(value, out long longValue))
return (long)(longValue * factor);
string hexValue = RemoveHexIdentifier(value);
if (long.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out long longHexValue))
return (long)(longHexValue * factor);
return null;
}
return Int64.MinValue;
@@ -948,11 +994,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('"');
@@ -1018,7 +1062,12 @@ namespace MPF.Core.Modules
i++;
(string value, long factor) = ExtractFactorFromValue(parts[i]);
return (byte)(byte.Parse(value) * factor);
if (byte.TryParse(value, out byte byteValue))
return (byte)(byteValue * factor);
string hexValue = RemoveHexIdentifier(value);
if (byte.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out byte byteHexValue))
return (byte)(byteHexValue * factor);
return null;
}
else if (parts[i].StartsWith(shortFlagString + "=") || parts[i].StartsWith(longFlagString + "="))
{
@@ -1033,14 +1082,19 @@ namespace MPF.Core.Modules
this[longFlagString] = true;
(string value, long factor) = ExtractFactorFromValue(valuePart);
return (byte)(byte.Parse(value) * factor);
if (byte.TryParse(value, out byte byteValue))
return (byte)(byteValue * factor);
string hexValue = RemoveHexIdentifier(value);
if (byte.TryParse(hexValue, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out byte byteHexValue))
return (byte)(byteHexValue * factor);
return null;
}
return Byte.MinValue;
}
/// <summary>
/// Get yhe trimmed value and multiplication factor from a value
/// Get the trimmed value and multiplication factor from a value
/// </summary>
/// <param name="value">String value to treat as suffixed number</param>
/// <returns>Trimmed value and multiplication factor</returns>
@@ -1101,6 +1155,23 @@ namespace MPF.Core.Modules
return (value, factor);
}
/// <summary>
/// Removes a leading 0x if it exists, case insensitive
/// </summary>
/// <param name="value">String with removed leading 0x</param>
/// <returns></returns>
private static string RemoveHexIdentifier(string value)
{
if (value.Length <= 2)
return value;
if (value[0] != '0')
return value;
if (value[1] != 'x' && value[1] != 'X')
return value;
return value.Substring(2);
}
#endregion
#region Methods to Move
@@ -1127,6 +1198,8 @@ namespace MPF.Core.Modules
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
@@ -1136,6 +1209,60 @@ namespace MPF.Core.Modules
}
}
/// <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;
}
}
#endregion
}
}

View File

@@ -4,6 +4,7 @@ using System.Linq;
using System.Text.RegularExpressions;
using MPF.Core.Converters;
using MPF.Core.Data;
using SabreTools.Hashing;
using SabreTools.RedumpLib;
using SabreTools.RedumpLib.Data;
@@ -69,7 +70,9 @@ namespace MPF.Core.Modules.CleanRip
info.DumpingInfo!.DumpingProgram = EnumConverter.LongName(this.InternalProgram);
info.DumpingInfo.DumpingDate = InfoTool.GetFileModifiedDate(basePath + "-dumpinfo.txt")?.ToString("yyyy-MM-dd HH:mm:ss");
// Get the Datafile information
var datafile = GenerateCleanripDatafile(basePath + ".iso", basePath + "-dumpinfo.txt");
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))
@@ -93,11 +96,12 @@ namespace MPF.Core.Modules.CleanRip
if (File.Exists(basePath + ".bca"))
info.Extras!.BCA = GetBCA(basePath + ".bca");
if (GetGameCubeWiiInformation(basePath + "-dumpinfo.txt", out Region? gcRegion, out var gcVersion, out var gcName))
if (GetGameCubeWiiInformation(basePath + "-dumpinfo.txt", out Region? gcRegion, out var gcVersion, out var gcName, out var gcSerial))
{
info.CommonDiscInfo!.Region = gcRegion ?? info.CommonDiscInfo.Region;
info.VersionAndEditions!.Version = gcVersion ?? info.VersionAndEditions.Version;
info.CommonDiscInfo.CommentsSpecialFields![SiteCode.InternalName] = gcName ?? string.Empty;
info.CommonDiscInfo.CommentsSpecialFields![SiteCode.InternalSerialName] = gcSerial ?? string.Empty;
}
break;
@@ -177,6 +181,17 @@ namespace MPF.Core.Modules.CleanRip
sha1 = line.Substring(7);
}
// Ensure all checksums were found in log
if (crc == string.Empty || md5 == string.Empty || sha1 == string.Empty)
{
if (HashTool.GetStandardHashes(iso, out long isoSize, out string? isoCRC, out string? isoMD5, out string? isoSHA1))
{
crc = isoCRC ?? crc;
md5 = isoMD5 ?? md5;
sha1 = isoSHA1 ?? sha1;
}
}
return new Datafile
{
Games =
@@ -279,10 +294,11 @@ namespace MPF.Core.Modules.CleanRip
/// <param name="region">Output region, if possible</param>
/// <param name="version">Output internal version of the game</param>
/// <param name="name">Output internal name of the game</param>
/// <param name="serial">Output internal serial of the game</param>
/// <returns></returns>
private static bool GetGameCubeWiiInformation(string dumpinfo, out Region? region, out string? version, out string? name)
private static bool GetGameCubeWiiInformation(string dumpinfo, out Region? region, out string? version, out string? name, out string? serial)
{
region = null; version = null; name = null;
region = null; version = null; name = null; serial = null;
// If the file doesn't exist, we can't get info from it
if (!File.Exists(dumpinfo))
@@ -313,7 +329,9 @@ namespace MPF.Core.Modules.CleanRip
}
else if (line.StartsWith("Filename"))
{
string serial = line.Substring("Filename: ".Length);
serial = line.Substring("Filename: ".Length);
if (serial.EndsWith("-disc2"))
serial = serial.Replace("-disc2", string.Empty);
// char gameType = serial[0];
// string gameid = serial[1] + serial[2];

View File

@@ -5,6 +5,7 @@ using System.Linq;
using System.Text.RegularExpressions;
using MPF.Core.Converters;
using MPF.Core.Data;
using MPF.Core.Utilities;
using SabreTools.RedumpLib;
using SabreTools.RedumpLib.Data;
@@ -409,6 +410,10 @@ namespace MPF.Core.Modules.DiscImageCreator
// Fill in the hash data
info.TracksAndWriteOffsets!.ClrMameProData = InfoTool.GenerateDatfile(datafile);
// Fill in the volume labels
if (GetVolumeLabels($"{basePath}_volDesc.txt", out var volLabels))
VolumeLabels = volLabels;
// Extract info based generically on MediaType
switch (this.Type)
{
@@ -493,10 +498,17 @@ namespace MPF.Core.Modules.DiscImageCreator
int trimLength = -1;
switch (this.System)
{
case RedumpSystem.MicrosoftXboxOne:
case RedumpSystem.MicrosoftXboxSeriesXS:
case RedumpSystem.SonyPlayStation3:
case RedumpSystem.SonyPlayStation4:
case RedumpSystem.SonyPlayStation5:
trimLength = 264;
if (info.SizeAndChecksums!.Layerbreak3 != default)
trimLength = 520;
else if (info.SizeAndChecksums!.Layerbreak2 != default)
trimLength = 392;
else
trimLength = 264;
break;
}
@@ -522,22 +534,24 @@ namespace MPF.Core.Modules.DiscImageCreator
}
// Needed for some odd copy protections
info.CopyProtection!.Protection = GetDVDProtection($"{basePath}_CSSKey.txt", $"{basePath}_disc.txt") ?? string.Empty;
info.CopyProtection!.Protection = GetDVDProtection($"{basePath}_CSSKey.txt", $"{basePath}_disc.txt", false) ?? string.Empty;
break;
case RedumpSystem.DVDAudio:
case RedumpSystem.DVDVideo:
info.CopyProtection!.Protection = GetDVDProtection($"{basePath}_CSSKey.txt", $"{basePath}_disc.txt") ?? string.Empty;
info.CopyProtection!.Protection = GetDVDProtection($"{basePath}_CSSKey.txt", $"{basePath}_disc.txt", true) ?? string.Empty;
break;
case RedumpSystem.KonamiPython2:
info.CommonDiscInfo!.EXEDateBuildDate = GetPlayStationEXEDate($"{basePath}_volDesc.txt", InfoTool.GetPlayStationExecutableName(drive?.Name));
if (InfoTool.GetPlayStationExecutableInfo(drive?.Name, out var pythonTwoSerial, out Region? pythonTwoRegion, out var pythonTwoDate))
{
// Ensure internal serial is pulled from local data
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = pythonTwoSerial ?? string.Empty;
info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? pythonTwoRegion;
info.CommonDiscInfo.EXEDateBuildDate = pythonTwoDate;
info.CommonDiscInfo.EXEDateBuildDate ??= pythonTwoDate;
}
info.VersionAndEditions!.Version = InfoTool.GetPlayStation2Version(drive?.Name) ?? string.Empty;
@@ -547,9 +561,9 @@ namespace MPF.Core.Modules.DiscImageCreator
string xmidString;
if (string.IsNullOrEmpty(outputDirectory))
xmidString = GetXGD1XMID($"{basePath}_DMI.bin");
xmidString = Tools.GetXGD1XMID($"{basePath}_DMI.bin");
else
xmidString = GetXGD1XMID(Path.Combine(outputDirectory, $"{basePath}_DMI.bin"));
xmidString = Tools.GetXGD1XMID(Path.Combine(outputDirectory, $"{basePath}_DMI.bin"));
var xmid = SabreTools.Serialization.Wrappers.XMID.Create(xmidString);
if (xmid != null)
@@ -596,9 +610,9 @@ namespace MPF.Core.Modules.DiscImageCreator
case RedumpSystem.MicrosoftXbox360:
string xemidString;
if (string.IsNullOrEmpty(outputDirectory))
xemidString = GetXGD23XeMID($"{basePath}_DMI.bin");
xemidString = Tools.GetXGD23XeMID($"{basePath}_DMI.bin");
else
xemidString = GetXGD23XeMID(Path.Combine(outputDirectory, $"{basePath}_DMI.bin"));
xemidString = Tools.GetXGD23XeMID(Path.Combine(outputDirectory, $"{basePath}_DMI.bin"));
var xemid = SabreTools.Serialization.Wrappers.XeMID.Create(xemidString);
if (xemid != null)
@@ -776,12 +790,14 @@ namespace MPF.Core.Modules.DiscImageCreator
break;
case RedumpSystem.SonyPlayStation:
info.CommonDiscInfo!.EXEDateBuildDate = GetPlayStationEXEDate($"{basePath}_volDesc.txt", InfoTool.GetPlayStationExecutableName(drive?.Name), true);
if (InfoTool.GetPlayStationExecutableInfo(drive?.Name, out var playstationSerial, out Region? playstationRegion, out var playstationDate))
{
// Ensure internal serial is pulled from local data
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = playstationSerial ?? string.Empty;
info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? playstationRegion;
info.CommonDiscInfo.EXEDateBuildDate = playstationDate;
info.CommonDiscInfo.EXEDateBuildDate ??= playstationDate;
}
bool? psEdcStatus = null;
@@ -795,12 +811,14 @@ namespace MPF.Core.Modules.DiscImageCreator
break;
case RedumpSystem.SonyPlayStation2:
info.CommonDiscInfo!.EXEDateBuildDate = GetPlayStationEXEDate($"{basePath}_volDesc.txt", InfoTool.GetPlayStationExecutableName(drive?.Name));
if (InfoTool.GetPlayStationExecutableInfo(drive?.Name, out var playstationTwoSerial, out Region? playstationTwoRegion, out var playstationTwoDate))
{
// Ensure internal serial is pulled from local data
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = playstationTwoSerial ?? string.Empty;
info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? playstationTwoRegion;
info.CommonDiscInfo.EXEDateBuildDate = playstationTwoDate;
info.CommonDiscInfo.EXEDateBuildDate ??= playstationTwoDate;
}
info.VersionAndEditions!.Version = InfoTool.GetPlayStation2Version(drive?.Name) ?? string.Empty;
@@ -2717,13 +2735,97 @@ namespace MPF.Core.Modules.DiscImageCreator
}
}
/// <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>
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;
}
}
/// <summary>
/// Get the DVD protection information, if possible
/// </summary>
/// <param name="cssKey">_CSSKey.txt file location</param>
/// <param name="disc">_disc.txt file location</param>
/// <param name="includeAlways">Indicates whether region and protection type are always included</param>
/// <returns>Formatted string representing the DVD protection, null on error</returns>
private static string? GetDVDProtection(string cssKey, string disc)
private static string? GetDVDProtection(string cssKey, string disc, bool includeAlways)
{
// If one of the files doesn't exist, we can't get info from them
if (!File.Exists(disc))
@@ -2805,6 +2907,15 @@ namespace MPF.Core.Modules.DiscImageCreator
catch { }
}
// Filter out if we're not always including information
if (!includeAlways)
{
if (region == "1 2 3 4 5 6 7 8")
region = null;
if (copyrightProtectionSystemType == "No")
copyrightProtectionSystemType = null;
}
// Now we format everything we can
string protection = string.Empty;
if (!string.IsNullOrEmpty(region))
@@ -2879,6 +2990,72 @@ namespace MPF.Core.Modules.DiscImageCreator
}
}
/// <summary>
/// Get the PSX/PS2/KP2 EXE Date from the log, if possible
/// </summary>
/// <param name="log">Log file location</param>
/// <param name="serial">Internal serial</param>
/// <param name="psx">True if PSX disc, false otherwise</param>
/// <returns>EXE date if possible, null otherwise</returns>
public static string? GetPlayStationEXEDate(string log, string? exeName, bool psx = false)
{
// If the file doesn't exist, we can't get the info
if (!File.Exists(log))
return null;
// If the EXE name is not valid, we can't get the info
if (string.IsNullOrEmpty(exeName))
return null;
try
{
string? exeDate = null;
using var sr = File.OpenText(log);
var line = sr.ReadLine();
while (line != null)
{
// Trim the line for later use
line = line.Trim();
// The exe date is listed in a single line, File Identifier: ABCD_123.45;1
if (line.Length >= "File Identifier: ".Length + 11 &&
line.StartsWith("File Identifier:") &&
line.Substring("File Identifier: ".Length) == exeName)
{
// Account for Y2K date problem
if (exeDate != null && exeDate!.Substring(0, 2) == "19")
{
string decade = exeDate!.Substring(2, 1);
// Does only PSX need to account for 1920s-60s?
if (decade == "0" || decade == "1" ||
psx && (decade == "2" || decade == "3" || decade == "4" || decade == "5" || decade == "6"))
exeDate = $"20{exeDate!.Substring(2)}";
}
// Currently stored date is the EXE date, return it
return exeDate;
}
// The exe datetime is listed in a single line
if (line.Length >= "Recording Date and Time: ".Length + 10 &&
line.StartsWith("Recording Date and Time:"))
{
// exe date: ISO datetime (yyyy-MM-ddT.....)
exeDate = line.Substring("Recording Date and Time: ".Length, 10);
}
line = sr.ReadLine();
}
return null;
}
catch
{
// We don't care what the exception is right now
return null;
}
}
/// <summary>
/// Get the build info from a GD-ROM LD area, if possible
/// </summary>
@@ -3556,6 +3733,9 @@ namespace MPF.Core.Modules.DiscImageCreator
// This flag is needed because recent versions of DIC include security data twice
bool foundSecuritySectors = false;
// SS version for all Kreon DIC dumps is v1
ssver = "01";
try
{
using var sr = File.OpenText(disc);
@@ -3565,11 +3745,13 @@ namespace MPF.Core.Modules.DiscImageCreator
if (line == null)
break;
// Security Sector version
// XGD version (1 = Xbox, 2 = Xbox360)
/*
if (line.StartsWith("Version of challenge table"))
{
ssver = line.Split(' ')[4]; // "Version of challenge table: <VER>"
xgdver = line.Split(' ')[4]; // "Version of challenge table: <VER>"
}
*/
// Security Sector ranges
else if (line.StartsWith("Number of security sector ranges:") && !foundSecuritySectors)
@@ -3644,6 +3826,9 @@ namespace MPF.Core.Modules.DiscImageCreator
// This flag is needed because recent versions of DIC include security data twice
bool foundSecuritySectors = false;
// SS version for all Kreon DIC dumps is v1
ssver = "01";
try
{
using var sr = File.OpenText(disc);
@@ -3653,11 +3838,13 @@ namespace MPF.Core.Modules.DiscImageCreator
if (line == null)
break;
// Security Sector version
// XGD version (1 = Xbox, 2 = Xbox360)
/*
if (line.StartsWith("Version of challenge table"))
{
ssver = line.Split(' ')[4]; // "Version of challenge table: <VER>"
xgdver = line.Split(' ')[4]; // "Version of challenge table: <VER>"
}
*/
// Security Sector ranges
else if (line.StartsWith("Number of security sector ranges:") && !foundSecuritySectors)
@@ -3700,50 +3887,6 @@ namespace MPF.Core.Modules.DiscImageCreator
}
}
/// <summary>
/// Get the XGD1 Master ID (XMID) information
/// </summary>
/// <param name="dmi">DMI.bin file location</param>
/// <returns>String representation of the XGD1 DMI information, empty string on error</returns>
private static string GetXGD1XMID(string dmi)
{
if (!File.Exists(dmi))
return string.Empty;
try
{
using var br = new BinaryReader(File.OpenRead(dmi));
br.BaseStream.Seek(8, SeekOrigin.Begin);
return new string(br.ReadChars(8));
}
catch
{
return string.Empty;
}
}
/// <summary>
/// Get the XGD2/3 Master ID (XeMID) information
/// </summary>
/// <param name="dmi">DMI.bin file location</param>
/// <returns>String representation of the XGD2/3 DMI information, empty string on error</returns>
private static string GetXGD23XeMID(string dmi)
{
if (!File.Exists(dmi))
return string.Empty;
try
{
using var br = new BinaryReader(File.OpenRead(dmi));
br.BaseStream.Seek(64, SeekOrigin.Begin);
return new string(br.ReadChars(14));
}
catch
{
return string.Empty;
}
}
#endregion
}
}

View File

@@ -0,0 +1,237 @@
using System.Collections.Generic;
using System.IO;
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;
}
}
/// <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

@@ -10,8 +10,12 @@ namespace MPF.Core.Modules.Redumper
public const string DVD = "dvd"; // Synonym for CD
public const string BluRay = "bd"; // Synonym for CD
public const string SACD = "sacd"; // Synonym for CD
public const string 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";
@@ -30,6 +34,7 @@ namespace MPF.Core.Modules.Redumper
// General
public const string HelpLong = "--help";
public const string HelpShort = "-h";
public const string Version = "--version";
public const string Verbose = "--verbose";
public const string Debug = "--debug";
public const string Drive = "--drive";
@@ -48,7 +53,7 @@ namespace MPF.Core.Modules.Redumper
public const string DriveSectorOrder = "--drive-sector-order";
// Drive Specific
public const string PlextorLeadinSkip = "--plextor-leadin-skip";
public const string PlextorSkipLeadin = "--plextor-skip-leadin";
public const string PlextorLeadinRetries = "--plextor-leadin-retries";
public const string AsusSkipLeadout = "--asus-skip-leadout";
@@ -70,7 +75,11 @@ namespace MPF.Core.Modules.Redumper
public const string LBAEnd = "--lba-end";
public const string RefineSubchannel = "--refine-subchannel";
public const string Skip = "--skip";
public const string DumpWriteOffset = "--dump-write-offset";
public const string DumpReadSize = "--dump-read-size";
public const string OverreadLeadout = "--overread-leadout";
public const string ForceUnscrambled = "--force-unscrambled";
public const string LegacySubs = "--legacy-subs";
public const string DisableCDText = "--disable-cdtext";
}
}

View File

@@ -5,6 +5,7 @@ using System.Linq;
using System.Text.RegularExpressions;
using MPF.Core.Converters;
using MPF.Core.Data;
using SabreTools.Models.CueSheets;
using SabreTools.RedumpLib;
using SabreTools.RedumpLib.Data;
@@ -145,6 +146,11 @@ namespace MPF.Core.Modules.Redumper
/// </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>
@@ -190,7 +196,7 @@ namespace MPF.Core.Modules.Redumper
if (!File.Exists($"{basePath}.log"))
missingFiles.Add($"{basePath}.log");
else if (GetDatfile($"{basePath}.log") == null)
missingFiles.Add($"{basePath}.dat");
missingFiles.Add($"{basePath}.log (dat section)");
if (!File.Exists($"{basePath}.state"))
missingFiles.Add($"{basePath}.state");
if (!File.Exists($"{basePath}.subcode"))
@@ -204,6 +210,14 @@ namespace MPF.Core.Modules.Redumper
// // Depends on the disc
// if (!File.Exists($"{basePath}.cdtext"))
// missingFiles.Add($"{basePath}.cdtext");
//
// // Not available in all versions
// if (!File.Exists($"{basePath}.hash"))
// missingFiles.Add($"{basePath}.hash");
// // Also: "{basePath} (Track X).hash" (get from cuesheet)
// if (!File.Exists($"{basePath}.skeleton"))
// missingFiles.Add($"{basePath}.skeleton");
// // Also: "{basePath} (Track X).skeleton" (get from cuesheet)
//}
break;
@@ -217,12 +231,21 @@ namespace MPF.Core.Modules.Redumper
missingFiles.Add($"{basePath}.dat");
if (!File.Exists($"{basePath}.manufacturer") && !File.Exists($"{basePath}.1.manufacturer") && !File.Exists($"{basePath}.2.manufacturer"))
missingFiles.Add($"{basePath}.manufacturer");
if (!File.Exists($"{basePath}.physical") && !File.Exists($"{basePath}.1.physical") && !File.Exists($"{basePath}.2.physical"))
if (!File.Exists($"{basePath}.physical") && !File.Exists($"{basePath}.0.physical") && !File.Exists($"{basePath}.1.physical") && !File.Exists($"{basePath}.2.physical"))
missingFiles.Add($"{basePath}.physical");
if (!File.Exists($"{basePath}.state"))
missingFiles.Add($"{basePath}.state");
}
// Removed or inconsistent files
//{
// // Not available in all versions
// if (!File.Exists($"{basePath}.hash"))
// missingFiles.Add($"{basePath}.hash");
// if (!File.Exists($"{basePath}.skeleton"))
// missingFiles.Add($"{basePath}.skeleton");
//}
break;
case MediaType.HDDVD: // TODO: Verify that this is output
@@ -233,12 +256,21 @@ namespace MPF.Core.Modules.Redumper
missingFiles.Add($"{basePath}.log");
else if (GetDatfile($"{basePath}.log") == null)
missingFiles.Add($"{basePath}.dat");
if (!File.Exists($"{basePath}.physical") && !File.Exists($"{basePath}.1.physical") && !File.Exists($"{basePath}.2.physical"))
if (!File.Exists($"{basePath}.physical") && !File.Exists($"{basePath}.0.physical") && !File.Exists($"{basePath}.1.physical") && !File.Exists($"{basePath}.2.physical"))
missingFiles.Add($"{basePath}.physical");
if (!File.Exists($"{basePath}.state"))
missingFiles.Add($"{basePath}.state");
}
// Removed or inconsistent files
//{
// // Not available in all versions
// if (!File.Exists($"{basePath}.hash"))
// missingFiles.Add($"{basePath}.hash");
// if (!File.Exists($"{basePath}.skeleton"))
// missingFiles.Add($"{basePath}.skeleton");
//}
break;
default:
@@ -271,6 +303,10 @@ namespace MPF.Core.Modules.Redumper
if (GetDiscType($"{basePath}.log", out var discTypeOrBookType))
info.DumpingInfo.ReportedDiscType = discTypeOrBookType;
// Fill in the volume labels
if (GetVolumeLabels($"{basePath}.log", out var volLabels))
VolumeLabels = volLabels;
switch (this.Type)
{
case MediaType.CDROM:
@@ -284,8 +320,11 @@ namespace MPF.Core.Modules.Redumper
info.TracksAndWriteOffsets.OtherWriteOffsets = cdWriteOffset;
// Attempt to get the error count
long errorCount = GetErrorCount($"{basePath}.log");
info.CommonDiscInfo.ErrorsCount = (errorCount == -1 ? "Error retrieving error count" : errorCount.ToString());
if (GetErrorCount($"{basePath}.log", out long redumpErrors, out long c2Errors))
{
info.CommonDiscInfo.ErrorsCount = (redumpErrors == -1 ? "Error retrieving error count" : redumpErrors.ToString());
info.DumpingInfo.C2ErrorsCount = (c2Errors == -1 ? "Error retrieving error count" : c2Errors.ToString());
}
// Attempt to get multisession data
string cdMultiSessionInfo = GetMultisessionInformation($"{basePath}.log") ?? string.Empty;
@@ -337,14 +376,29 @@ namespace MPF.Core.Modules.Redumper
int trimLength = -1;
switch (this.System)
{
case RedumpSystem.MicrosoftXboxOne:
case RedumpSystem.MicrosoftXboxSeriesXS:
case RedumpSystem.SonyPlayStation3:
case RedumpSystem.SonyPlayStation4:
case RedumpSystem.SonyPlayStation5:
trimLength = 264;
if (info.SizeAndChecksums!.Layerbreak3 != default)
trimLength = 520;
else if (info.SizeAndChecksums!.Layerbreak2 != default)
trimLength = 392;
else
trimLength = 264;
break;
}
info.Extras!.PIC = GetPIC($"{basePath}.physical", trimLength) ?? string.Empty;
info.Extras!.PIC = GetPIC($"{basePath}.physical", trimLength)
?? GetPIC($"{basePath}.0.physical", trimLength)
?? GetPIC($"{basePath}.1.physical", trimLength)
?? string.Empty;
var di = InfoTool.GetDiscInformation($"{basePath}.physical")
?? InfoTool.GetDiscInformation($"{basePath}.0.physical")
?? InfoTool.GetDiscInformation($"{basePath}.1.physical");
info.SizeAndChecksums!.PICIdentifier = InfoTool.GetPICIdentifier(di);
}
break;
@@ -360,24 +414,37 @@ namespace MPF.Core.Modules.Redumper
info.CopyProtection!.SecuROMData = GetSecuROMData($"{basePath}.log") ?? string.Empty;
// Needed for some odd copy protections
info.CopyProtection!.Protection = GetDVDProtection($"{basePath}.log") ?? string.Empty;
info.CopyProtection!.Protection = GetDVDProtection($"{basePath}.log", false) ?? string.Empty;
break;
case RedumpSystem.DVDAudio:
case RedumpSystem.DVDVideo:
info.CopyProtection!.Protection = GetDVDProtection($"{basePath}.log") ?? string.Empty;
info.CopyProtection!.Protection = GetDVDProtection($"{basePath}.log", true) ?? string.Empty;
break;
case RedumpSystem.KonamiPython2:
// Get metadata from log if possible
if (GetPlayStationInfo($"{basePath}.log", out string? kp2EXEDate, out string? kp2Serial, out string? kp2Version, out var _))
{
info.CommonDiscInfo!.EXEDateBuildDate = kp2EXEDate;
info.CommonDiscInfo.CommentsSpecialFields![SiteCode.InternalSerialName] = kp2Serial ?? string.Empty;
if (!string.IsNullOrEmpty(info.CommonDiscInfo.CommentsSpecialFields![SiteCode.InternalSerialName]))
info.CommonDiscInfo.Region = InfoTool.GetPlayStationRegion(info.CommonDiscInfo.CommentsSpecialFields![SiteCode.InternalSerialName]);
info.VersionAndEditions!.Version = kp2Version ?? string.Empty;
}
// Get metadata from drive if not available from log
if (InfoTool.GetPlayStationExecutableInfo(drive?.Name, out var pythonTwoSerial, out Region? pythonTwoRegion, out var pythonTwoDate))
{
// Ensure internal serial is pulled from local data
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = pythonTwoSerial ?? string.Empty;
info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? pythonTwoRegion;
info.CommonDiscInfo.EXEDateBuildDate = pythonTwoDate;
if (string.IsNullOrEmpty(info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName]))
info.CommonDiscInfo.CommentsSpecialFields![SiteCode.InternalSerialName] = pythonTwoSerial ?? string.Empty;
info.CommonDiscInfo.Region ??= pythonTwoRegion;
if (string.IsNullOrEmpty(info.CommonDiscInfo.EXEDateBuildDate))
info.CommonDiscInfo.EXEDateBuildDate = pythonTwoDate;
}
info.VersionAndEditions!.Version = InfoTool.GetPlayStation2Version(drive?.Name) ?? string.Empty;
if (string.IsNullOrEmpty(info.VersionAndEditions!.Version))
info.VersionAndEditions.Version = InfoTool.GetPlayStation2Version(drive?.Name) ?? string.Empty;
break;
case RedumpSystem.MicrosoftXbox:
@@ -433,12 +500,24 @@ namespace MPF.Core.Modules.Redumper
break;
case RedumpSystem.SonyPlayStation:
// Get metadata from log if possible
if (GetPlayStationInfo($"{basePath}.log", out string? psxEXEDate, out string? psxSerial, out var _, out var _))
{
info.CommonDiscInfo!.EXEDateBuildDate = psxEXEDate;
info.CommonDiscInfo.CommentsSpecialFields![SiteCode.InternalSerialName] = psxSerial ?? string.Empty;
if (!string.IsNullOrEmpty(info.CommonDiscInfo.CommentsSpecialFields![SiteCode.InternalSerialName]))
info.CommonDiscInfo.Region = InfoTool.GetPlayStationRegion(info.CommonDiscInfo.CommentsSpecialFields![SiteCode.InternalSerialName]);
}
// Get metadata from drive if not available from log
if (InfoTool.GetPlayStationExecutableInfo(drive?.Name, out var playstationSerial, out Region? playstationRegion, out var playstationDate))
{
// Ensure internal serial is pulled from local data
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = playstationSerial ?? string.Empty;
info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? playstationRegion;
info.CommonDiscInfo.EXEDateBuildDate = playstationDate;
if (string.IsNullOrEmpty(info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName]))
info.CommonDiscInfo.CommentsSpecialFields![SiteCode.InternalSerialName] = playstationSerial ?? string.Empty;
info.CommonDiscInfo.Region ??= playstationRegion;
if (string.IsNullOrEmpty(info.CommonDiscInfo.EXEDateBuildDate))
info.CommonDiscInfo.EXEDateBuildDate = playstationDate;
}
info.CopyProtection!.AntiModchip = GetPlayStationAntiModchipDetected($"{basePath}.log").ToYesNo();
@@ -448,33 +527,70 @@ namespace MPF.Core.Modules.Redumper
break;
case RedumpSystem.SonyPlayStation2:
// Get metadata from log if possible
if (GetPlayStationInfo($"{basePath}.log", out string? ps2EXEDate, out string? ps2Serial, out var ps2Version, out var _))
{
info.CommonDiscInfo!.EXEDateBuildDate = ps2EXEDate;
info.CommonDiscInfo.CommentsSpecialFields![SiteCode.InternalSerialName] = ps2Serial ?? string.Empty;
if (!string.IsNullOrEmpty(info.CommonDiscInfo.CommentsSpecialFields![SiteCode.InternalSerialName]))
info.CommonDiscInfo.Region = InfoTool.GetPlayStationRegion(info.CommonDiscInfo.CommentsSpecialFields![SiteCode.InternalSerialName]);
info.VersionAndEditions!.Version = ps2Version ?? string.Empty;
}
// Get metadata from drive if not available from log
if (InfoTool.GetPlayStationExecutableInfo(drive?.Name, out var playstationTwoSerial, out Region? playstationTwoRegion, out var playstationTwoDate))
{
// Ensure internal serial is pulled from local data
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = playstationTwoSerial ?? string.Empty;
if (string.IsNullOrEmpty(info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName]))
info.CommonDiscInfo.CommentsSpecialFields![SiteCode.InternalSerialName] = playstationTwoSerial ?? string.Empty;
info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? playstationTwoRegion;
info.CommonDiscInfo.EXEDateBuildDate = playstationTwoDate;
if (string.IsNullOrEmpty(info.CommonDiscInfo.EXEDateBuildDate))
info.CommonDiscInfo.EXEDateBuildDate ??= playstationTwoDate;
}
info.VersionAndEditions!.Version = InfoTool.GetPlayStation2Version(drive?.Name) ?? string.Empty;
if (string.IsNullOrEmpty(info.VersionAndEditions!.Version))
info.VersionAndEditions.Version = InfoTool.GetPlayStation2Version(drive?.Name) ?? string.Empty;
break;
case RedumpSystem.SonyPlayStation3:
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);
// Get metadata from log if possible
if (GetPlayStationInfo($"{basePath}.log", out var _, out string? ps3Serial, out var ps3Version, out string? firmwareVersion))
{
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = ps3Serial ?? string.Empty;
info.VersionAndEditions!.Version = ps3Version ?? string.Empty;
}
// Get metadata from drive if not available from log
if (string.IsNullOrEmpty(info.VersionAndEditions!.Version))
info.VersionAndEditions.Version = InfoTool.GetPlayStation3Version(drive?.Name) ?? string.Empty;
if (string.IsNullOrEmpty(info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName]))
info.CommonDiscInfo.CommentsSpecialFields![SiteCode.InternalSerialName] = InfoTool.GetPlayStation3Serial(drive?.Name) ?? string.Empty;
firmwareVersion ??= InfoTool.GetPlayStation3FirmwareVersion(drive?.Name);
if (firmwareVersion != null)
info.CommonDiscInfo!.ContentsSpecialFields![SiteCode.Patches] = $"PS3 Firmware {firmwareVersion}";
info.CommonDiscInfo.ContentsSpecialFields![SiteCode.Patches] = $"PS3 Firmware {firmwareVersion}";
break;
case RedumpSystem.SonyPlayStation4:
info.VersionAndEditions!.Version = InfoTool.GetPlayStation4Version(drive?.Name) ?? string.Empty;
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = InfoTool.GetPlayStation4Serial(drive?.Name) ?? string.Empty;
if (GetPlayStationInfo($"{basePath}.log", out var _, out string? ps4Serial, out var ps4Version, out var _))
{
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = ps4Serial ?? string.Empty;
info.VersionAndEditions!.Version = ps4Version ?? string.Empty;
}
if (string.IsNullOrEmpty(info.VersionAndEditions!.Version))
info.VersionAndEditions.Version = InfoTool.GetPlayStation4Version(drive?.Name) ?? string.Empty;
if (string.IsNullOrEmpty(info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName]))
info.CommonDiscInfo.CommentsSpecialFields![SiteCode.InternalSerialName] = InfoTool.GetPlayStation4Serial(drive?.Name) ?? string.Empty;
break;
case RedumpSystem.SonyPlayStation5:
info.VersionAndEditions!.Version = InfoTool.GetPlayStation5Version(drive?.Name) ?? string.Empty;
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = InfoTool.GetPlayStation5Serial(drive?.Name) ?? string.Empty;
if (GetPlayStationInfo($"{basePath}.log", out var _, out string? ps5Serial, out var ps5Version, out var _))
{
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = ps5Serial ?? string.Empty;
info.VersionAndEditions!.Version = ps5Version ?? string.Empty;
}
if (string.IsNullOrEmpty(info.VersionAndEditions!.Version))
info.VersionAndEditions.Version = InfoTool.GetPlayStation5Version(drive?.Name) ?? string.Empty;
if (string.IsNullOrEmpty(info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName]))
info.CommonDiscInfo.CommentsSpecialFields![SiteCode.InternalSerialName] = InfoTool.GetPlayStation5Serial(drive?.Name) ?? string.Empty;
break;
}
@@ -489,6 +605,9 @@ namespace MPF.Core.Modules.Redumper
info.Artifacts["cue"] = GetBase64(GetFullFile($"{basePath}.cue")) ?? string.Empty;
if (File.Exists($"{basePath}.fulltoc"))
info.Artifacts["fulltoc"] = GetBase64(GetFullFile($"{basePath}.fulltoc")) ?? string.Empty;
if (File.Exists($"{basePath}.hash"))
info.Artifacts["hash"] = GetBase64(GetFullFile($"{basePath}.hash")) ?? string.Empty;
// TODO: "{basePath} (Track X).hash" (get from cuesheet)
if (File.Exists($"{basePath}.log"))
info.Artifacts["log"] = GetBase64(GetFullFile($"{basePath}.log")) ?? string.Empty;
if (File.Exists($"{basePath}.manufacturer"))
@@ -499,10 +618,15 @@ namespace MPF.Core.Modules.Redumper
info.Artifacts["manufacturer2"] = GetBase64(GetFullFile($"{basePath}.2.manufacturer")) ?? string.Empty;
if (File.Exists($"{basePath}.physical"))
info.Artifacts["physical"] = GetBase64(GetFullFile($"{basePath}.physical")) ?? string.Empty;
if (File.Exists($"{basePath}.0.physical"))
info.Artifacts["physical0"] = GetBase64(GetFullFile($"{basePath}.0.physical")) ?? string.Empty;
if (File.Exists($"{basePath}.1.physical"))
info.Artifacts["physical1"] = GetBase64(GetFullFile($"{basePath}.1.physical")) ?? string.Empty;
if (File.Exists($"{basePath}.2.physical"))
info.Artifacts["physical2"] = GetBase64(GetFullFile($"{basePath}.2.physical")) ?? string.Empty;
// if (File.Exists($"{basePath}.skeleton"))
// info.Artifacts["skeleton"] = GetBase64(GetFullFile($"{basePath}.skeleton")) ?? string.Empty;
// // Also: "{basePath} (Track X).skeleton" (get from cuesheet)
// if (File.Exists($"{basePath}.scram"))
// info.Artifacts["scram"] = GetBase64(GetFullFile($"{basePath}.scram")) ?? string.Empty;
// if (File.Exists($"{basePath}.scrap"))
@@ -537,6 +661,10 @@ namespace MPF.Core.Modules.Redumper
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);
@@ -635,8 +763,8 @@ namespace MPF.Core.Modules.Redumper
#region Drive Specific
// Plextor Leadin Skip
if (this[FlagStrings.PlextorLeadinSkip] == true)
parameters.Add(FlagStrings.PlextorLeadinSkip);
if (this[FlagStrings.PlextorSkipLeadin] == true)
parameters.Add(FlagStrings.PlextorSkipLeadin);
// Plextor Leadin Retries
if (this[FlagStrings.PlextorLeadinRetries] == true)
@@ -731,6 +859,13 @@ namespace MPF.Core.Modules.Redumper
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)
{
@@ -742,6 +877,18 @@ namespace MPF.Core.Modules.Redumper
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]);
@@ -758,6 +905,7 @@ namespace MPF.Core.Modules.Redumper
// General
FlagStrings.HelpLong,
FlagStrings.HelpShort,
FlagStrings.Version,
FlagStrings.Verbose,
FlagStrings.Debug,
FlagStrings.Drive,
@@ -776,7 +924,7 @@ namespace MPF.Core.Modules.Redumper
FlagStrings.DriveSectorOrder,
// Drive Specific
FlagStrings.PlextorLeadinSkip,
FlagStrings.PlextorSkipLeadin,
FlagStrings.PlextorLeadinRetries,
FlagStrings.AsusSkipLeadout,
@@ -798,8 +946,12 @@ namespace MPF.Core.Modules.Redumper
FlagStrings.LBAEnd,
FlagStrings.RefineSubchannel,
FlagStrings.Skip,
FlagStrings.DumpWriteOffset,
FlagStrings.DumpReadSize,
FlagStrings.OverreadLeadout,
FlagStrings.ForceUnscrambled,
FlagStrings.LegacySubs,
FlagStrings.DisableCDText,
],
};
}
@@ -840,9 +992,38 @@ namespace MPF.Core.Modules.Redumper
logFiles.Add($"{basePath}.subcode");
if (File.Exists($"{basePath}.toc"))
logFiles.Add($"{basePath}.toc");
// Include .hash and .skeleton for all files in cuesheet
var cueSheet = SabreTools.Serialization.Deserializers.CueSheet.DeserializeFile($"{basePath}.cue");
string? baseDir = Path.GetDirectoryName(basePath);
if (cueSheet?.Files != null && baseDir != null)
{
foreach (CueFile? file in cueSheet.Files)
{
string? trackName = Path.GetFileNameWithoutExtension(file?.FileName);
if (trackName == null)
continue;
string trackPath = Path.Combine(baseDir, trackName);
if (File.Exists($"{trackPath}.hash"))
logFiles.Add($"{trackPath}.hash");
if (File.Exists($"{trackPath}.skeleton"))
logFiles.Add($"{trackPath}.skeleton");
}
}
else
{
if (File.Exists($"{basePath}.hash"))
logFiles.Add($"{basePath}.hash");
if (File.Exists($"{basePath}.skeleton"))
logFiles.Add($"{basePath}.skeleton");
}
break;
case MediaType.DVD:
if (File.Exists($"{basePath}.hash"))
logFiles.Add($"{basePath}.hash");
if (File.Exists($"{basePath}.log"))
logFiles.Add($"{basePath}.log");
if (File.Exists($"{basePath}.manufacturer"))
@@ -853,24 +1034,34 @@ namespace MPF.Core.Modules.Redumper
logFiles.Add($"{basePath}.2.manufacturer");
if (File.Exists($"{basePath}.physical"))
logFiles.Add($"{basePath}.physical");
if (File.Exists($"{basePath}.0.physical"))
logFiles.Add($"{basePath}.0.physical");
if (File.Exists($"{basePath}.1.physical"))
logFiles.Add($"{basePath}.1.physical");
if (File.Exists($"{basePath}.2.physical"))
logFiles.Add($"{basePath}.2.physical");
if (File.Exists($"{basePath}.skeleton"))
logFiles.Add($"{basePath}.skeleton");
if (File.Exists($"{basePath}.state"))
logFiles.Add($"{basePath}.state");
break;
case MediaType.HDDVD: // TODO: Confirm that this information outputs
case MediaType.BluRay:
if (File.Exists($"{basePath}.hash"))
logFiles.Add($"{basePath}.hash");
if (File.Exists($"{basePath}.log"))
logFiles.Add($"{basePath}.log");
if (File.Exists($"{basePath}.physical"))
logFiles.Add($"{basePath}.physical");
if (File.Exists($"{basePath}.0.physical"))
logFiles.Add($"{basePath}.0.physical");
if (File.Exists($"{basePath}.1.physical"))
logFiles.Add($"{basePath}.1.physical");
if (File.Exists($"{basePath}.2.physical"))
logFiles.Add($"{basePath}.2.physical");
if (File.Exists($"{basePath}.skeleton"))
logFiles.Add($"{basePath}.skeleton");
if (File.Exists($"{basePath}.state"))
logFiles.Add($"{basePath}.state");
break;
@@ -887,7 +1078,9 @@ namespace MPF.Core.Modules.Redumper
|| this.BaseCommand?.Contains(CommandStrings.DVD) == true
|| this.BaseCommand?.Contains(CommandStrings.BluRay) == true
|| this.BaseCommand?.Contains(CommandStrings.SACD) == true
|| this.BaseCommand?.Contains(CommandStrings.Dump) == true;
|| this.BaseCommand?.Contains(CommandStrings.New) == true
|| this.BaseCommand?.Contains(CommandStrings.Dump) == true
|| this.BaseCommand?.Contains(CommandStrings.DumpNew) == true;
}
/// <inheritdoc/>
@@ -973,15 +1166,20 @@ namespace MPF.Core.Modules.Redumper
this[FlagStrings.Verbose] = options.RedumperEnableVerbose;
if (options.RedumperEnableDebug)
this[FlagStrings.Debug] = options.RedumperEnableDebug;
if (options.RedumperUseBEReading)
if (options.RedumperReadMethod != RedumperReadMethod.NONE)
{
this[FlagStrings.DriveReadMethod] = true;
DriveReadMethodValue = "BE_CDDA";
DriveReadMethodValue = options.RedumperReadMethod.ToString();
}
if (options.RedumperSectorOrder != RedumperSectorOrder.NONE)
{
this[FlagStrings.DriveSectorOrder] = true;
DriveSectorOrderValue = options.RedumperSectorOrder.ToString();
}
if (options.RedumperUseGenericDriveType)
{
this[FlagStrings.DriveType] = true;
DriveTypeValue = "Generic";
DriveTypeValue = "GENERIC";
}
// Set the output paths
@@ -1004,6 +1202,12 @@ namespace MPF.Core.Modules.Redumper
this[FlagStrings.Retries] = true;
RetriesValue = options.RedumperRereadCount;
if (options.RedumperEnableLeadinRetry)
{
this[FlagStrings.PlextorLeadinRetries] = true;
PlextorLeadinRetriesValue = options.RedumperLeadinRetryCount;
}
}
/// <inheritdoc/>
@@ -1040,8 +1244,12 @@ namespace MPF.Core.Modules.Redumper
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:
@@ -1084,6 +1292,9 @@ namespace MPF.Core.Modules.Redumper
// Help
ProcessFlagParameter(parts, FlagStrings.HelpShort, FlagStrings.HelpLong, ref i);
// Version
ProcessFlagParameter(parts, FlagStrings.Version, ref i);
// Verbose
ProcessFlagParameter(parts, FlagStrings.Verbose, ref i);
@@ -1156,8 +1367,8 @@ namespace MPF.Core.Modules.Redumper
#region Drive Specific
// Plextor Leadin Skip
ProcessFlagParameter(parts, FlagStrings.PlextorLeadinSkip, ref i);
// Plextor Skip Leadin
ProcessFlagParameter(parts, FlagStrings.PlextorSkipLeadin, ref i);
// Plextor Leadin Retries
intValue = ProcessInt32Parameter(parts, FlagStrings.PlextorLeadinRetries, ref i);
@@ -1230,14 +1441,28 @@ namespace MPF.Core.Modules.Redumper
if (!string.IsNullOrEmpty(stringValue))
SkipValue = stringValue;
// Skip
// 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 (!string.IsNullOrEmpty(stringValue))
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
}
@@ -1301,25 +1526,31 @@ namespace MPF.Core.Modules.Redumper
try
{
// Fast forward to the dat line
using var sr = File.OpenText(log);
while (!sr.EndOfStream && sr.ReadLine()?.TrimStart()?.StartsWith("dat:") == false) ;
if (sr.EndOfStream)
return null;
string? datString = null;
// Now that we're at the relevant entries, read each line in and concatenate
var datString = string.Empty;
var line = sr.ReadLine()?.Trim();
while (line?.StartsWith("<rom") == true)
// Find all occurrences of the hash information
while (!sr.EndOfStream)
{
datString += line + "\n";
// Fast forward to the dat line
while (!sr.EndOfStream && sr.ReadLine()?.TrimStart()?.StartsWith("dat:") == false) ;
if (sr.EndOfStream)
break;
line = sr.ReadLine()?.Trim();
// Now that we're at the relevant entries, read each line in and concatenate
datString = string.Empty;
var line = sr.ReadLine()?.Trim();
while (line?.StartsWith("<rom") == true)
{
datString += line + "\n";
if (sr.EndOfStream)
break;
line = sr.ReadLine()?.Trim();
}
}
return datString.TrimEnd('\n');
return datString?.TrimEnd('\n');
}
catch
{
@@ -1331,20 +1562,20 @@ namespace MPF.Core.Modules.Redumper
/// <summary>
/// Get reported disc type information, if possible
/// </summary>
/// <param name="drive">_disc.txt file location</param>
/// <param name="log">Log file location</param>
/// <returns>True if disc type info was set, false otherwise</returns>
private static bool GetDiscType(string drive, out string? discTypeOrBookType)
private static bool GetDiscType(string log, out string? discTypeOrBookType)
{
// Set the default values
discTypeOrBookType = null;
// If the file doesn't exist, we can't get the info
if (!File.Exists(drive))
if (!File.Exists(log))
return false;
try
{
using var sr = File.OpenText(drive);
using var sr = File.OpenText(log);
var line = sr.ReadLine();
while (line != null)
{
@@ -1371,12 +1602,67 @@ namespace MPF.Core.Modules.Redumper
}
}
/// <summary>
/// Get all Volume Identifiers
/// </summary>
/// <param name="log">Log file location</param>
/// <returns>Volume labels (by type), or null if none present</returns>
private static bool GetVolumeLabels(string log, out Dictionary<string, List<string>> volLabels)
{
// If the file doesn't exist, can't get the volume labels
volLabels = [];
if (!File.Exists(log))
return false;
try
{
using var sr = File.OpenText(log);
var line = sr.ReadLine();
while (line != null)
{
// Trim the line for later use
line = line.Trim();
// ISO9660 Volume Identifier
if (line.StartsWith("volume identifier: "))
{
string label = line.Substring("volume identifier: ".Length);
// Skip if label is blank
if (label == null || label.Length <= 0)
break;
if (volLabels.ContainsKey(label))
volLabels[label].Add("ISO");
else
volLabels[label] = ["ISO"];
// Redumper log currently only outputs ISO9660 label, end here
break;
}
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;
}
}
/// <summary>
/// Get the DVD protection information, if possible
/// </summary>
/// <param name="log">Log file location</param>
/// <param name="includeAlways">Indicates whether region and protection type are always included</param>
/// <returns>Formatted string representing the DVD protection, null on error</returns>
private static string? GetDVDProtection(string log)
private static string? GetDVDProtection(string log, bool includeAlways)
{
// If one of the files doesn't exist, we can't get info from them
if (!File.Exists(log))
@@ -1446,6 +1732,15 @@ namespace MPF.Core.Modules.Redumper
catch { }
}
// Filter out if we're not always including information
if (!includeAlways)
{
if (region == "1 2 3 4 5 6 7 8")
region = null;
if (copyrightProtectionSystemType == "No")
copyrightProtectionSystemType = null;
}
// Now we format everything we can
string protection = string.Empty;
if (!string.IsNullOrEmpty(region))
@@ -1463,47 +1758,54 @@ namespace MPF.Core.Modules.Redumper
}
/// <summary>
/// Get the detected error count from the input files, if possible
/// Get the detected error counts from the input files, if possible
/// </summary>
/// <param name="log">Log file location</param>
/// <returns>Error count if possible, -1 on error</returns>
public static long GetErrorCount(string log)
/// <returns>True if error counts could be retrieved, false otherwise</returns>
public static bool GetErrorCount(string log, out long redumpErrors, out long c2Errors)
{
// Set the default values for error counts
redumpErrors = -1; c2Errors = -1;
// If the file doesn't exist, we can't get info from it
if (!File.Exists(log))
return -1;
return false;
try
{
// Fast forward to the errors lines
using var sr = File.OpenText(log);
while (!sr.EndOfStream && sr.ReadLine()?.Trim()?.StartsWith("CD-ROM [") == false) ;
if (sr.EndOfStream)
return 0;
// Now that we're at the relevant lines, find the error count
// Find the error counts
while (!sr.EndOfStream)
{
// Skip forward to the "REDUMP.ORG" line
var line = string.Empty;
while (!sr.EndOfStream && (line = sr.ReadLine()?.Trim())?.StartsWith("REDUMP.ORG errors") == false) ;
if (string.IsNullOrEmpty(line))
var line = sr.ReadLine()?.Trim();
if (line == null)
break;
// C2: <error count>
if (line.StartsWith("C2:"))
{
string[] parts = line.Split(' ');
if (!long.TryParse(parts[1], out c2Errors))
c2Errors = -1;
}
// REDUMP.ORG errors: <error count>
string[] parts = line!.Split(' ');
if (long.TryParse(parts[2], out long redump))
return redump;
else
return -1;
else if (line.StartsWith("REDUMP.ORG errors:"))
{
string[] parts = line!.Split(' ');
if (!long.TryParse(parts[2], out redumpErrors))
redumpErrors = -1;
}
}
return -1;
// If the Redump error count is -1, then an issue occurred
return redumpErrors != -1;
}
catch
{
// We don't care what the exception is right now
return -1;
return false;
}
}
@@ -1758,6 +2060,75 @@ namespace MPF.Core.Modules.Redumper
}
}
/// <summary>
/// Get the info from a PlayStation disc, if possible
/// </summary>
/// <param name="log">Log file location</param>
/// <returns>True if section found, null on error</returns>
private static bool GetPlayStationInfo(string log, out string? exeDate, out string? serial, out string? version, out string? firmware)
{
// Set the default values
exeDate = null; serial = null; version = null; firmware = null;
// If the file doesn't exist, we can't get info from it
if (!File.Exists(log))
return false;
try
{
// Fast forward to the PS info line
using var sr = File.OpenText(log);
string? line;
while (!sr.EndOfStream)
{
line = sr.ReadLine()?.TrimStart();
if (line?.StartsWith("PSX [") == true ||
line?.StartsWith("PS2 [") == true ||
line?.StartsWith("PS3 [") == true ||
line?.StartsWith("PS4 [") == true ||
line?.StartsWith("PS5 [") == true)
break;
}
if (sr.EndOfStream)
return false;
while (!sr.EndOfStream)
{
line = sr.ReadLine()?.TrimStart();
if (line == null)
break;
if (line.StartsWith("EXE date:"))
{
exeDate = line.Substring("EXE date: ".Length).Trim();
}
else if (line.StartsWith("serial:"))
{
serial = line.Substring("serial: ".Length).Trim();
}
else if (line.StartsWith("version:"))
{
version = line.Substring("version: ".Length).Trim();
}
else if (line.StartsWith("firmware:"))
{
firmware = line.Substring("firmware: ".Length).Trim();
}
else
{
break;
}
}
return true;
}
catch
{
// We don't care what the exception is right now
return false;
}
}
/// <summary>
/// Get the LibCrypt data from the input file, if possible
/// </summary>
@@ -1852,11 +2223,11 @@ namespace MPF.Core.Modules.Redumper
return null;
// Now that we're at the relevant entries, read each line in and concatenate
string? pvdString = "", line = sr.ReadLine()?.Trim();
string? pvdString = "", line = sr.ReadLine();
while (line?.StartsWith("03") == true)
{
pvdString += line + "\n";
line = sr.ReadLine()?.Trim();
line = sr.ReadLine();
}
return pvdString.TrimEnd('\n');
@@ -2178,6 +2549,11 @@ namespace MPF.Core.Modules.Redumper
using var sr = File.OpenText(log);
sr.ReadLine();
// Get the next non-warning line
string nextLine = sr.ReadLine()?.Trim() ?? string.Empty;
if (nextLine.StartsWith("warning:", StringComparison.OrdinalIgnoreCase))
nextLine = sr.ReadLine()?.Trim() ?? string.Empty;
// Generate regex
// Permissive
var regex = new Regex(@"^redumper (v.+) \[.+\]", RegexOptions.Compiled);
@@ -2185,7 +2561,7 @@ namespace MPF.Core.Modules.Redumper
//var regex = new Regex(@"^redumper (v\d{4}\.\d{2}\.\d{2}(| build_\d+)) \[.+\]", RegexOptions.Compiled);
// Extract the version string
var match = regex.Match(sr.ReadLine()?.Trim() ?? string.Empty);
var match = regex.Match(nextLine);
var version = match.Groups[1].Value;
return string.IsNullOrEmpty(version) ? null : version;
}

View File

@@ -4,7 +4,7 @@ using System.IO;
using System.Linq;
using MPF.Core.Converters;
using MPF.Core.Data;
using MPF.Core.Hashing;
using SabreTools.Hashing;
using SabreTools.RedumpLib;
using SabreTools.RedumpLib.Data;
@@ -72,14 +72,27 @@ namespace MPF.Core.Modules.UmdImageCreator
info.DumpingInfo!.DumpingProgram = EnumConverter.LongName(this.InternalProgram);
info.DumpingInfo.DumpingDate = InfoTool.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)
{
case MediaType.UMD:
info.Extras!.PVD = GetPVD(basePath + "_mainInfo.txt") ?? string.Empty;
if (Hasher.GetFileHashes(basePath + ".iso", out long filesize, out var crc32, out var md5, out var sha1))
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
{
Games = [new Game { Roms = [new Rom { Name = string.Empty, Size = filesize.ToString(), Crc = crc32, Md5 = md5, Sha1 = sha1, }] }]
};
// Fill in the hash data
info.TracksAndWriteOffsets!.ClrMameProData = InfoTool.GenerateDatfile(datafile);
info.SizeAndChecksums!.Size = filesize;
info.SizeAndChecksums.CRC32 = crc32;
info.SizeAndChecksums.MD5 = md5;
@@ -105,16 +118,18 @@ namespace MPF.Core.Modules.UmdImageCreator
{
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;
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}_PFI.bin"))
// info.Artifacts["pfi"] = Convert.ToBase64String(File.ReadAllBytes($"{basePath}_PFI.bin")) ?? string.Empty;
if (File.Exists($"{basePath}_volDesc.txt"))
info.Artifacts["volDesc"] = GetBase64(GetFullFile($"{basePath}_volDesc.txt")) ?? string.Empty;
}
}
@@ -136,6 +151,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;
}
@@ -228,6 +246,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,677 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using MPF.Core.Converters;
using MPF.Core.Data;
using MPF.Core.Utilities;
using SabreTools.Hashing;
using SabreTools.RedumpLib;
using SabreTools.RedumpLib.Data;
namespace MPF.Core.Modules.XboxBackupCreator
{
/// <summary>
/// Represents a generic set of Xbox Backup Creator parameters
/// </summary>
public class Parameters : BaseParameters
{
#region Metadata
/// <inheritdoc/>
public override InternalProgram InternalProgram => InternalProgram.XboxBackupCreator;
#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>();
switch (this.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 GenerateSubmissionInfo(SubmissionInfo info, Options options, string basePath, Drive? drive, bool includeArtifacts)
{
// 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 = $"{EnumConverter.LongName(this.InternalProgram)} {GetVersion(logPath) ?? "Unknown Version"}";
info.DumpingInfo.DumpingDate = InfoTool.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 (this.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
{
Games = [new Game { Roms = [new Rom { Name = string.Empty, Size = filesize.ToString(), Crc = crc32, Md5 = md5, Sha1 = sha1, }] }]
};
// Fill in the hash data
info.TracksAndWriteOffsets!.ClrMameProData = InfoTool.GenerateDatfile(datafile);
info.SizeAndChecksums!.Size = filesize;
info.SizeAndChecksums.CRC32 = crc32;
info.SizeAndChecksums.MD5 = md5;
info.SizeAndChecksums.SHA1 = sha1;
}
switch (this.System)
{
case RedumpSystem.MicrosoftXbox:
// Parse DMI.bin
string xmidString = Tools.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 (!options.EnableRedumpCompatibility)
info.VersionAndEditions!.Version = xmid.Version ?? string.Empty;
info.CommonDiscInfo.Region = InfoTool.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 = Tools.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 (!options.EnableRedumpCompatibility)
info.VersionAndEditions!.Version = xemid.Version ?? string.Empty;
info.CommonDiscInfo.Region = InfoTool.GetXGDRegion(xemid.Model.RegionIdentifier);
}
break;
}
// Deal with SS.bin
if (File.Exists($"{baseDir}SS.bin"))
{
// Save security sector ranges
string? ranges = Tools.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
Tools.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;
}
// Fill in any artifacts that exist, Base64-encoded, if we need to
if (includeArtifacts)
{
info.Artifacts ??= [];
if (File.Exists(logPath))
info.Artifacts["log"] = GetBase64(GetFullFile(logPath!)) ?? string.Empty;
if (File.Exists($"{basePath}.dvd"))
info.Artifacts["dvd"] = GetBase64(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 List<string> GetLogFilePaths(string basePath)
{
var logFiles = new List<string>();
string baseDir = Path.GetDirectoryName(basePath) + Path.DirectorySeparatorChar;
switch (this.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 (this.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 (this.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 (!Tools.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 (!Tools.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 = Tools.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

@@ -296,7 +296,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 +307,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 +317,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

@@ -89,6 +89,10 @@ namespace MPF.Core
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
@@ -110,8 +114,9 @@ namespace MPF.Core
info.TracksAndWriteOffsets.ClrMameProData = null;
// Add the volume label to comments, if possible or necessary
if (drive?.VolumeLabel != null && drive.GetRedumpSystemFromVolumeLabel() == null)
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.VolumeLabel] = drive.VolumeLabel;
string? volLabels = FormatVolumeLabels(drive?.VolumeLabel, parameters?.VolumeLabels);
if (volLabels != null)
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.VolumeLabel] = volLabels;
// Extract info based generically on MediaType
switch (mediaType)
@@ -141,6 +146,38 @@ namespace MPF.Core
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;
@@ -153,6 +190,14 @@ namespace MPF.Core
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;
@@ -238,6 +283,12 @@ namespace MPF.Core
info.CopyProtection.FullProtections = fullProtections as Dictionary<string, List<string>?> ?? [];
resultProgress?.Report(Result.Success("Copy protection scan complete!"));
if (system == RedumpSystem.EnhancedCD)
info.CommonDiscInfo!.Category ??= DiscCategory.Audio;
if (system == RedumpSystem.SonyElectronicBook)
info.CommonDiscInfo!.Category ??= DiscCategory.Multimedia;
break;
case RedumpSystem.AudioCD:
@@ -247,68 +298,78 @@ namespace MPF.Core
break;
case RedumpSystem.BandaiPlaydiaQuickInteractiveSystem:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
info.CommonDiscInfo.Region = info.CommonDiscInfo.Region ?? Region.Japan;
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;
case RedumpSystem.DVDVideo:
case RedumpSystem.HDDVDVideo:
info.CommonDiscInfo!.Category ??= DiscCategory.Video;
info.CopyProtection!.Protection ??= options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
break;
case RedumpSystem.CommodoreAmigaCD:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
info.CommonDiscInfo!.EXEDateBuildDate ??= options.AddPlaceholders ? Template.RequiredValue : string.Empty;
break;
case RedumpSystem.CommodoreAmigaCD32:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
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!.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;
info.CommonDiscInfo!.EXEDateBuildDate ??= options.AddPlaceholders ? Template.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:
info.CommonDiscInfo!.Category ??= DiscCategory.Multimedia;
break;
case RedumpSystem.IncredibleTechnologiesEagle:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
info.CommonDiscInfo!.EXEDateBuildDate ??= options.AddPlaceholders ? Template.RequiredValue : string.Empty;
break;
case RedumpSystem.KonamieAmusement:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
info.CommonDiscInfo!.EXEDateBuildDate ??= options.AddPlaceholders ? Template.RequiredValue : string.Empty;
break;
case RedumpSystem.KonamiFireBeat:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
info.CommonDiscInfo!.EXEDateBuildDate ??= options.AddPlaceholders ? Template.RequiredValue : string.Empty;
break;
case RedumpSystem.KonamiSystemGV:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
info.CommonDiscInfo!.EXEDateBuildDate ??= options.AddPlaceholders ? Template.RequiredValue : string.Empty;
break;
case RedumpSystem.KonamiSystem573:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
info.CommonDiscInfo!.EXEDateBuildDate ??= options.AddPlaceholders ? Template.RequiredValue : string.Empty;
break;
case RedumpSystem.KonamiTwinkle:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
info.CommonDiscInfo!.EXEDateBuildDate ??= options.AddPlaceholders ? Template.RequiredValue : string.Empty;
break;
case RedumpSystem.MattelHyperScan:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
info.CommonDiscInfo!.EXEDateBuildDate ??= options.AddPlaceholders ? Template.RequiredValue : string.Empty;
break;
case RedumpSystem.MicrosoftXboxOne:
@@ -338,7 +399,7 @@ namespace MPF.Core
break;
case RedumpSystem.NamcoSegaNintendoTriforce:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
info.CommonDiscInfo!.EXEDateBuildDate ??= options.AddPlaceholders ? Template.RequiredValue : string.Empty;
break;
case RedumpSystem.NavisoftNaviken21:
@@ -360,23 +421,23 @@ namespace MPF.Core
break;
case RedumpSystem.SegaChihiro:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
info.CommonDiscInfo!.EXEDateBuildDate ??= options.AddPlaceholders ? Template.RequiredValue : string.Empty;
break;
case RedumpSystem.SegaDreamcast:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
info.CommonDiscInfo!.EXEDateBuildDate ??= options.AddPlaceholders ? Template.RequiredValue : string.Empty;
break;
case RedumpSystem.SegaNaomi:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
info.CommonDiscInfo!.EXEDateBuildDate ??= options.AddPlaceholders ? Template.RequiredValue : string.Empty;
break;
case RedumpSystem.SegaNaomi2:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
info.CommonDiscInfo!.EXEDateBuildDate ??= options.AddPlaceholders ? Template.RequiredValue : string.Empty;
break;
case RedumpSystem.SegaTitanVideo:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
info.CommonDiscInfo!.EXEDateBuildDate ??= options.AddPlaceholders ? Template.RequiredValue : string.Empty;
break;
case RedumpSystem.SharpX68000:
@@ -384,7 +445,7 @@ namespace MPF.Core
break;
case RedumpSystem.SNKNeoGeoCD:
info.CommonDiscInfo!.EXEDateBuildDate = options.AddPlaceholders ? Template.RequiredValue : string.Empty;
info.CommonDiscInfo!.EXEDateBuildDate ??= options.AddPlaceholders ? Template.RequiredValue : string.Empty;
break;
case RedumpSystem.SonyPlayStation:
@@ -407,20 +468,21 @@ namespace MPF.Core
break;
case RedumpSystem.SonyPlayStation2:
info.CommonDiscInfo!.LanguageSelection = [LanguageSelection.BiosSettings, LanguageSelection.LanguageSelector, LanguageSelection.OptionsMenu];
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;
info.Extras!.DiscKey ??= options.AddPlaceholders ? Template.RequiredValue : string.Empty;
info.Extras.DiscID ??= options.AddPlaceholders ? Template.RequiredValue : string.Empty;
break;
case RedumpSystem.TomyKissSite:
info.CommonDiscInfo!.Category ??= DiscCategory.Video;
info.CommonDiscInfo!.Region ??= Region.Japan;
break;
case RedumpSystem.ZAPiTGamesGameWaveFamilyEntertainmentSystem:
info.CopyProtection!.Protection = options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
info.CopyProtection!.Protection ??= options.AddPlaceholders ? Template.RequiredIfExistsValue : string.Empty;
break;
}
@@ -503,7 +565,9 @@ namespace MPF.Core
|| hashData.Contains("(Track 00).bin")
|| hashData.Contains("(Track 00.2).bin")
|| hashData.Contains("(Track A).bin")
|| hashData.Contains("(Track AA).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!"));
@@ -518,11 +582,11 @@ namespace MPF.Core
}
#if NET40
var validateTask = Validator.ValidateSingleTrack(wc, info, sha1!);
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!);
(bool singleFound, var foundIds, string? result) = await Validator.ValidateSingleTrack(wc, info, sha1);
#endif
if (singleFound)
resultProgress?.Report(Result.Success(result));
@@ -630,6 +694,67 @@ namespace MPF.Core
return true;
}
#endregion
#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

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

File diff suppressed because it is too large Load Diff

View File

@@ -217,11 +217,13 @@ namespace MPF.Core.UI.ViewModels
/// TODO: Convert selected list item to binding
public void Save()
{
if (SubmissionInfo.CommonDiscInfo == null) SubmissionInfo.CommonDiscInfo = new CommonDiscInfoSection();
if (SubmissionInfo.CommonDiscInfo == null)
SubmissionInfo.CommonDiscInfo = new CommonDiscInfoSection();
SubmissionInfo.CommonDiscInfo.Languages = Languages.Where(l => l.IsChecked).Select(l => l?.Value).ToArray();
if (!SubmissionInfo.CommonDiscInfo.Languages.Any())
SubmissionInfo.CommonDiscInfo.Languages = new Language?[] { null };
SubmissionInfo.CommonDiscInfo.Languages = [null];
SubmissionInfo.CommonDiscInfo.LanguageSelection = LanguageSelections.Where(ls => ls.IsChecked).Select(ls => ls?.Value).ToArray();
SubmissionInfo.CommonDiscInfo.Title = InfoTool.NormalizeDiscTitle(SubmissionInfo.CommonDiscInfo.Title, SubmissionInfo.CommonDiscInfo.Languages);
}
/// <summary>

View File

@@ -50,7 +50,7 @@ namespace MPF.Core.UI.ViewModels
/// </summary>
/// <remarks>
/// T1 - Title to display to the user
/// T1 - Message to display to the user
/// T2 - Message to display to the user
/// T3 - Number of default options to display
/// T4 - true for inquiry, false otherwise
/// TResult - true for positive, false for negative, null for neutral
@@ -76,6 +76,34 @@ namespace MPF.Core.UI.ViewModels
#region Properties
/// <summary>
/// Indicates the status of the check dump menu item
/// </summary>
public bool CheckDumpMenuItemEnabled
{
get => _checkDumpMenuItemEnabled;
set
{
_checkDumpMenuItemEnabled = value;
TriggerPropertyChanged(nameof(CheckDumpMenuItemEnabled));
}
}
private bool _checkDumpMenuItemEnabled;
/// <summary>
/// Indicates the status of the create IRD menu item
/// </summary>
public bool CreateIRDMenuItemEnabled
{
get => _createIRDMenuItemEnabled;
set
{
_createIRDMenuItemEnabled = value;
TriggerPropertyChanged(nameof(CreateIRDMenuItemEnabled));
}
}
private bool _createIRDMenuItemEnabled;
/// <summary>
/// Indicates the status of the options menu item
/// </summary>
@@ -148,6 +176,7 @@ namespace MPF.Core.UI.ViewModels
/// <summary>
/// Currently provided output path
/// Not guaranteed to be a valid path
/// </summary>
public string OutputPath
{
@@ -506,6 +535,8 @@ namespace MPF.Core.UI.ViewModels
_systems = [];
OptionsMenuItemEnabled = true;
CheckDumpMenuItemEnabled = true;
CreateIRDMenuItemEnabled = true;
SystemTypeComboBoxEnabled = true;
MediaTypeComboBoxEnabled = true;
OutputPathTextBoxEnabled = true;
@@ -670,16 +701,23 @@ namespace MPF.Core.UI.ViewModels
bool cachedCanExecuteSelectionChanged = CanExecuteSelectionChanged;
DisableEventHandlers();
// Create a static list of supported programs, not everything
InternalPrograms = Enum.GetValues(typeof(InternalProgram)).Cast<InternalProgram>().Where(ip => InternalProgramExists(ip)).Select(ip => new Element<InternalProgram>(ip)).ToList();
// Get the current internal program
InternalProgram internalProgram = this.Options.InternalProgram;
// Create a static list of supported programs, not everything
var internalPrograms = new List<InternalProgram> { InternalProgram.DiscImageCreator, InternalProgram.Aaru, InternalProgram.Redumper };
InternalPrograms = internalPrograms.Select(ip => new Element<InternalProgram>(ip)).ToList();
// Select the current default dumping program
int currentIndex = InternalPrograms.FindIndex(m => m == internalProgram);
this.CurrentProgram = (currentIndex > -1 ? InternalPrograms[currentIndex].Value : InternalPrograms[0].Value);
if (InternalPrograms.Count == 0)
{
// If no programs are found, default to InternalProgram.NONE
this.CurrentProgram = InternalProgram.NONE;
}
else
{
int currentIndex = InternalPrograms.FindIndex(m => m == internalProgram);
this.CurrentProgram = (currentIndex > -1 ? InternalPrograms[currentIndex].Value : InternalPrograms[0].Value);
}
// Reenable event handlers, if necessary
if (cachedCanExecuteSelectionChanged) EnableEventHandlers();
@@ -1190,7 +1228,7 @@ namespace MPF.Core.UI.ViewModels
{
return new DumpEnvironment(
this.Options,
this.OutputPath,
EvaluateOutputPath(this.OutputPath),
this.CurrentDrive,
this.CurrentSystem,
this.CurrentMediaType,
@@ -1238,6 +1276,8 @@ namespace MPF.Core.UI.ViewModels
public void DisableAllUIElements()
{
OptionsMenuItemEnabled = false;
CheckDumpMenuItemEnabled = false;
CreateIRDMenuItemEnabled = false;
SystemTypeComboBoxEnabled = false;
MediaTypeComboBoxEnabled = false;
OutputPathTextBoxEnabled = false;
@@ -1258,6 +1298,8 @@ namespace MPF.Core.UI.ViewModels
public void EnableAllUIElements()
{
OptionsMenuItemEnabled = true;
CheckDumpMenuItemEnabled = true;
CreateIRDMenuItemEnabled = true;
SystemTypeComboBoxEnabled = true;
MediaTypeComboBoxEnabled = true;
OutputPathTextBoxEnabled = true;
@@ -1282,7 +1324,10 @@ namespace MPF.Core.UI.ViewModels
// Get the status to write out
Result result = Tools.GetSupportStatus(_environment.System, _environment.Type);
this.Status = result.Message;
if (this.CurrentProgram == InternalProgram.NONE || _environment.Parameters == null)
this.Status = "No dumping program found";
else
this.Status = result.Message;
// Enable or disable the button
this.StartStopButtonEnabled = result && ShouldEnableDumpingButton();
@@ -1299,6 +1344,49 @@ namespace MPF.Core.UI.ViewModels
}
}
/// <summary>
/// Replaces %-delimited variables inside a path string with their values
/// </summary>
/// <param name="outputPath">Path to be evaluated</param>
/// <returns>String with %-delimited variables evaluated</returns>
public string EvaluateOutputPath(string outputPath)
{
string systemLong = this._currentSystem.LongName() ?? "Unknown System";
if (string.IsNullOrEmpty(systemLong))
systemLong = "Unknown System";
string systemShort = this._currentSystem.ShortName() ?? "unknown";
if (string.IsNullOrEmpty(systemShort))
systemShort = "unknown";
string mediaLong = this._currentMediaType.LongName() ?? "Unknown Media";
if (string.IsNullOrEmpty(mediaLong))
mediaLong = "Unknown Media";
string program = this._currentProgram.ToString() ?? "Unknown Program";
if (string.IsNullOrEmpty(program))
program = "Unknown Program";
string programShort = program == "DiscImageCreator" ? "DIC" : program;
if (string.IsNullOrEmpty(programShort))
programShort = "Unknown Program";
string label = this._currentDrive?.FormattedVolumeLabel ?? "track";
if (string.IsNullOrEmpty(label))
label = "track";
string date = DateTime.Today.ToString("yyyyMMdd");
if (string.IsNullOrEmpty(date))
date = "UNKNOWN";
string datetime = DateTime.Now.ToString("yyyyMMdd-HHmmss");
if (string.IsNullOrEmpty(datetime))
datetime = "UNKNOWN";
return outputPath
.Replace("%SYSTEM%", systemLong)
.Replace("%SYS%", systemShort)
.Replace("%MEDIA%", mediaLong)
.Replace("%PROGRAM%", program)
.Replace("%PROG%", programShort)
.Replace("%LABEL%", label)
.Replace("%DATE%", date)
.Replace("%DATETIME%", datetime);
}
/// <summary>
/// Get the default output directory name from the currently selected drive
/// </summary>
@@ -1596,8 +1684,8 @@ namespace MPF.Core.UI.ViewModels
_environment.Drive?.RefreshDrive();
// Output to the label and log
this.Status = "Starting dumping process... Please wait!";
LogLn("Starting dumping process... Please wait!");
this.Status = "Starting dumping process... please wait!";
LogLn("Starting dumping process... please wait!");
if (this.Options.ToolsInSeparateWindow)
LogLn("Look for the separate command window for more details");
else
@@ -1690,6 +1778,9 @@ namespace MPF.Core.UI.ViewModels
/// <returns>True if dumping should start, false otherwise</returns>
private bool ValidateBeforeDumping()
{
if (Parameters == null)
return false;
// Validate that we have an output path of any sort
if (string.IsNullOrEmpty(_environment?.OutputPath))
{
@@ -1729,6 +1820,53 @@ namespace MPF.Core.UI.ViewModels
return false;
}
}
else
{
// If a complete dump exists from a different program
InternalProgram? programFound = null;
if (programFound == null && _environment.InternalProgram != InternalProgram.Aaru)
{
Modules.Aaru.Parameters parameters = new("")
{
Type = _environment.Type,
System = _environment.System
};
(bool foundOtherFiles, _) = parameters.FoundAllFiles(outputDirectory, outputFilename, true);
if (foundOtherFiles)
programFound = InternalProgram.Aaru;
}
if (programFound == null && _environment.InternalProgram != InternalProgram.DiscImageCreator)
{
Modules.DiscImageCreator.Parameters parameters = new("")
{
Type = _environment.Type,
System = _environment.System
};
(bool foundOtherFiles, _) = parameters.FoundAllFiles(outputDirectory, outputFilename, true);
if (foundOtherFiles)
programFound = InternalProgram.DiscImageCreator;
}
if (programFound == null && _environment.InternalProgram != InternalProgram.Redumper)
{
Modules.Redumper.Parameters parameters = new("")
{
Type = _environment.Type,
System = _environment.System
};
(bool foundOtherFiles, _) = parameters.FoundAllFiles(outputDirectory, outputFilename, true);
if (foundOtherFiles)
programFound = InternalProgram.Redumper;
}
if (programFound != null && _displayUserMessage != null)
{
bool? mbresult = _displayUserMessage("Overwrite?", $"A complete dump from {programFound} already exists! Dumping here may cause issues. Are you sure you want to overwrite?", 2, true);
if (mbresult != true)
{
LogLn("Dumping aborted!");
return false;
}
}
}
// Validate that at least some space exists
// TODO: Tie this to the size of the disc, type of disc, etc.
@@ -1753,7 +1891,30 @@ namespace MPF.Core.UI.ViewModels
return true;
}
#endregion
/// <summary>
/// Checks whether a internal program is found in its path
/// </summary>
/// <param name="program">Program to check for</param>
/// <returns>True if the program is found, false otherwise</returns>
private bool InternalProgramExists(InternalProgram program)
{
try
{
return program switch
{
InternalProgram.Redumper => File.Exists(this.Options.RedumperPath),
InternalProgram.Aaru => File.Exists(this.Options.AaruPath),
InternalProgram.DiscImageCreator => File.Exists(this.Options.DiscImageCreatorPath),
_ => false,
};
}
catch
{
return false;
}
}
#endregion
#region Progress Reporting

View File

@@ -51,6 +51,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,14 +87,32 @@ 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()
{
var internalPrograms = new List<InternalProgram> { InternalProgram.DiscImageCreator, InternalProgram.Aaru, InternalProgram.Redumper };
var internalPrograms = new List<InternalProgram> { InternalProgram.Redumper, InternalProgram.DiscImageCreator, InternalProgram.Aaru };
return internalPrograms.Select(ip => new Element<InternalProgram>(ip)).ToList();
}
/// <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
@@ -107,6 +135,17 @@ namespace MPF.Core.UI.ViewModels
#endif
}
/// <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
#region Property Updates

View File

@@ -26,7 +26,17 @@ namespace MPF.Core.Utilities
return false;
// List options
if (args[0] == "-lm" || args[0] == "--listmedia")
if (args[0] == "-lc" || args[0] == "--listcodes")
{
Console.WriteLine("Supported Site Codes:");
foreach (string siteCode in Extensions.ListSiteCodes())
{
Console.WriteLine(siteCode);
}
Console.ReadLine();
return true;
}
else if (args[0] == "-lm" || args[0] == "--listmedia")
{
Console.WriteLine("Supported Media Types:");
foreach (string mediaType in Extensions.ListMediaTypes())
@@ -105,7 +115,7 @@ namespace MPF.Core.Utilities
string? parsedPath = null;
// These values require multiple parts to be active
bool scan = false, protectFile = false;
bool scan = false, protectFile = false, hideDriveLetters = false;
// If we have no arguments, just return
if (args == null || args.Length == 0)
@@ -174,6 +184,12 @@ namespace MPF.Core.Utilities
protectFile = true;
}
// Hide drive letters from scan output (requires --protect-file)
else if (args[startIndex].Equals("-g") || args[startIndex].Equals("--hide-drive-letters"))
{
hideDriveLetters = true;
}
// Include seed info file
else if (args[startIndex].StartsWith("-l=") || args[startIndex].StartsWith("--load-seed="))
{
@@ -221,6 +237,7 @@ namespace MPF.Core.Utilities
// 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);
}
@@ -238,6 +255,7 @@ namespace MPF.Core.Utilities
"-p, --path <drivepath> Physical drive path for additional checks",
"-s, --scan Enable copy protection scan (requires --path)",
"-f, --protect-file Output protection to separate file (requires --scan)",
"-g, --hide-drive-letters Hide drive letters from scan output (requires --protect-file)",
"-l, --load-seed <path> Load a seed submission JSON for user information",
"-x, --suffix Enable adding filename suffix",
"-j, --json Enable submission JSON output",
@@ -264,7 +282,8 @@ namespace MPF.Core.Utilities
}
var serializer = JsonSerializer.Create();
var reader = new StreamReader(ConfigurationPath);
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);
}

View File

@@ -1,4 +1,6 @@
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using MPF.Core.Data;
@@ -71,6 +73,36 @@ namespace MPF.Core.Utilities
return true;
}
/// <summary>
/// Converts a hex string into a byte array
/// </summary>
/// <param name="hex">Hex string</param>
/// <returns>Converted byte array, or null if invalid hex string</returns>
public static byte[]? HexStringToByteArray(string? hexString)
{
// Valid hex string must be an even number of characters
if (string.IsNullOrEmpty(hexString) || hexString!.Length % 2 == 1)
return null;
// Convert ASCII to byte via lookup table
int[] hexLookup = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F];
byte[] byteArray = new byte[hexString.Length / 2];
for (int i = 0; i < hexString.Length; i += 2)
{
// Convert next two chars to ASCII value relative to '0'
int a = Char.ToUpper(hexString[i]) - '0';
int b = Char.ToUpper(hexString[i + 1]) - '0';
// Ensure hex string only has '0' through '9' and 'A' through 'F' (case insensitive)
if ((a < 0 || b < 0 || a > 22 || b > 22) || (a > 10 && a < 17) || (b > 10 && b < 17))
return null;
byteArray[i / 2] = (byte)(hexLookup[a] << 4 | hexLookup[b]);
}
return byteArray;
}
#endregion
#region Support
@@ -261,5 +293,759 @@ namespace MPF.Core.Utilities
}
#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
#region Xbox/Xbox360 specific tools
/// <summary>
/// Get the XGD1 Master ID (XMID) information
/// </summary>
/// <param name="dmi">DMI.bin file location</param>
/// <returns>String representation of the XGD1 DMI information, empty string on error</returns>
public static string GetXGD1XMID(string dmi)
{
if (!File.Exists(dmi))
return string.Empty;
try
{
using var br = new BinaryReader(File.OpenRead(dmi));
br.BaseStream.Seek(8, SeekOrigin.Begin);
return new string(br.ReadChars(8));
}
catch
{
return string.Empty;
}
}
/// <summary>
/// Get the XGD2/3 Master ID (XeMID) information
/// </summary>
/// <param name="dmi">DMI.bin file location</param>
/// <returns>String representation of the XGD2/3 DMI information, empty string on error</returns>
public static string GetXGD23XeMID(string dmi)
{
if (!File.Exists(dmi))
return string.Empty;
try
{
using var br = new BinaryReader(File.OpenRead(dmi));
br.BaseStream.Seek(64, SeekOrigin.Begin);
return new string(br.ReadChars(14));
}
catch
{
return string.Empty;
}
}
/// <summary>
/// Get XGD type from SS.bin file
/// </summary>
/// <param name="ss"></param>
/// <param name="xgdType"></param>
/// <returns></returns>
public static bool GetXGDType(string? ss, out int xgdType)
{
xgdType = 0;
if (string.IsNullOrEmpty(ss) || !File.Exists(ss))
return false;
using FileStream fs = File.OpenRead(ss);
byte[] buf = new byte[3];
int numBytes = fs.Read(buf, 13, 16);
if (numBytes != 3)
return false;
return GetXGDType(buf, out xgdType);
}
/// <summary>
/// Get XGD type from SS.bin sector
/// </summary>
/// <param name="ss">Byte array of SS.bin sector</param>
/// <param name="xgdType">XGD type</param>
/// <returns>True if successful, false otherwise</returns>
public static bool GetXGDType(byte[] ss, out int xgdType)
{
xgdType = 0;
// Concatenate the last three values
long lastThree = (((ss[13] << 8) | ss[14]) << 8) | ss[15];
// Return XGD type based on value
switch (lastThree)
{
case 0x2033AF:
xgdType = 1;
return true;
case 0x20339F:
xgdType = 2;
return true;
case 0x238E0F:
xgdType = 3;
return true;
default:
return false;
}
}
/// <summary>
/// Determine if a given SS has already been cleaned
/// </summary>
/// <param name="ss">Byte array of SS sector</param>
/// <returns>True if SS is clean, false otherwise</returns>
public static bool IsCleanSS(byte[] ss)
{
if (ss.Length != 2048)
return false;
if (!GetXGDType(ss, out int xgdType))
return false;
if (xgdType == 3 && ss.Skip(32).Take(72).All(x => x == 0))
{
// Check for a cleaned SSv2
int rtOffset = 0x24;
if (ss[rtOffset + 36] != 0x01)
return false;
if (ss[rtOffset + 37] != 0x00)
return false;
if (ss[rtOffset + 39] != 0x01)
return false;
if (ss[rtOffset + 40] != 0x00)
return false;
if (ss[rtOffset + 45] != 0x5B)
return false;
if (ss[rtOffset + 46] != 0x00)
return false;
if (ss[rtOffset + 48] != 0x5B)
return false;
if (ss[rtOffset + 49] != 0x00)
return false;
if (ss[rtOffset + 54] != 0xB5)
return false;
if (ss[rtOffset + 55] != 0x00)
return false;
if (ss[rtOffset + 57] != 0xB5)
return false;
if (ss[rtOffset + 58] != 0x00)
return false;
if (ss[rtOffset + 63] != 0x0F)
return false;
if (ss[rtOffset + 64] != 0x01)
return false;
if (ss[rtOffset + 66] != 0x0F)
return false;
if (ss[rtOffset + 67] != 0x01)
return false;
}
else
{
// Check for a cleaned SSv1
int rtOffset = 0x204;
if (ss[rtOffset + 36] != 0x01)
return false;
if (ss[rtOffset + 37] != 0x00)
return false;
if (xgdType == 2 && ss[rtOffset + 39] != 0x00)
return false;
if (xgdType == 2 && ss[rtOffset + 40] != 0x00)
return false;
if (ss[rtOffset + 45] != 0x5B)
return false;
if (ss[rtOffset + 46] != 0x00)
return false;
if (xgdType == 2 && ss[rtOffset + 48] != 0x00)
return false;
if (xgdType == 2 && ss[rtOffset + 49] != 0x00)
return false;
if (ss[rtOffset + 54] != 0xB5)
return false;
if (ss[rtOffset + 55] != 0x00)
return false;
if (xgdType == 2 && ss[rtOffset + 57] != 0x00)
return false;
if (xgdType == 2 && ss[rtOffset + 58] != 0x00)
return false;
if (ss[rtOffset + 63] != 0x0F)
return false;
if (ss[rtOffset + 64] != 0x01)
return false;
if (xgdType == 2 && ss[rtOffset + 66] != 0x00)
return false;
if (xgdType == 2 && ss[rtOffset + 67] != 0x00)
return false;
}
// All angles are as expected, it is clean
return true;
}
/// <summary>
/// Clean a rawSS.bin file and write it to a file
/// </summary>
/// <param name="rawSS">Path to the raw SS file to read from</param>
/// <param name="cleanSS">Path to the clean SS file to write to</param>
/// <returns>True if successful, false otherwise</returns>
public static bool CleanSS(string rawSS, string cleanSS)
{
if (!File.Exists(rawSS))
return false;
byte[] ss = File.ReadAllBytes(rawSS);
if (ss.Length != 2048)
return false;
if (!CleanSS(ss))
return false;
File.WriteAllBytes(cleanSS, ss);
return true;
}
/// <summary>
/// Fix a SS sector to its predictable clean form.
/// With help from ss_sector_range
/// </summary>
/// <param name="ss">Byte array of raw SS sector</param>
/// <returns>True if successful, false otherwise</returns>
public static bool CleanSS(byte[] ss)
{
// Must be entire sector
if (ss.Length != 2048)
return false;
// Determine XGD type
if (!GetXGDType(ss, out int xgdType))
return false;
switch (xgdType)
{
case 1:
// Leave Original Xbox SS.bin unchanged
return true;
case 2:
// Fix standard SSv1 ss.bin
ss[552] = 1; // 0x01
ss[553] = 0; // 0x00
ss[555] = 0; // 0x00
ss[556] = 0; // 0x00
ss[561] = 91; // 0x5B
ss[562] = 0; // 0x00
ss[564] = 0; // 0x00
ss[565] = 0; // 0x00
ss[570] = 181; // 0xB5
ss[571] = 0; // 0x00
ss[573] = 0; // 0x00
ss[574] = 0; // 0x00
ss[579] = 15; // 0x0F
ss[580] = 1; // 0x01
ss[582] = 0; // 0x00
ss[583] = 0; // 0x00
return true;
case 3:
// Determine if XGD3 SS.bin is SSv1 (Kreon) or SSv2 (0800)
bool ssv2 = ss.Skip(32).Take(72).All(x => x == 0);
if (ssv2)
{
ss[72] = 1; // 0x01
ss[73] = 0; // 0x00
ss[75] = 1; // 0x01
ss[76] = 0; // 0x00
ss[81] = 91; // 0x5B
ss[82] = 0; // 0x00
ss[84] = 91; // 0x5B
ss[85] = 0; // 0x00
ss[90] = 181; // 0xB5
ss[91] = 0; // 0x00
ss[93] = 181; // 0xB5
ss[94] = 0; // 0x00
ss[99] = 15; // 0x0F
ss[100] = 1; // 0x01
ss[102] = 15; // 0x0F
ss[103] = 1; // 0x01
}
else
{
ss[552] = 1; // 0x01
ss[553] = 0; // 0x00
ss[561] = 91; // 0x5B
ss[562] = 0; // 0x00
ss[570] = 181; // 0xB5
ss[571] = 0; // 0x00
ss[579] = 15; // 0x0F
ss[580] = 1; // 0x01
}
return true;
default:
// Unknown XGD type
return false;
}
}
/// <summary>
/// Get Security Sector ranges from SS.bin
/// </summary>
/// <param name="ssBin">Path to SS.bin file</param>
/// <returns>Sector ranges if found, null otherwise</returns>
public static string? GetSSRanges(string ssBin)
{
if (!File.Exists(ssBin))
return null;
byte[] ss = File.ReadAllBytes(ssBin);
if (ss.Length != 2048)
return null;
return GetSSRanges(ss);
}
/// <summary>
/// Get Security Sector ranges from SS sector.
/// With help from ss_sector_range
/// </summary>
/// <param name="ss">Byte array of SS sector</param>
/// <returns>Sector ranges if found, null otherwise</returns>
public static string? GetSSRanges(byte[] ss)
{
if (ss.Length != 2048)
return null;
if (!GetXGDType(ss, out int xgdType))
return null;
//uint numRanges = ss[1632];
uint numRanges;
if (xgdType == 1)
numRanges = 16;
else
numRanges = 4;
uint[] startLBA = new uint[numRanges];
uint[] endLBA = new uint[numRanges];
for (uint i = 0; i < numRanges; i++)
{
// Determine range Physical Sector Number
uint startPSN = (uint)((((ss[i * 9 + 1636] << 8) | ss[i * 9 + 1637]) << 8) | ss[i * 9 + 1638]);
uint endPSN = (uint)((((ss[i * 9 + 1639] << 8) | ss[i * 9 + 1640]) << 8) | ss[i * 9 + 1641]);
// Determine range Logical Sector Number
if (xgdType == 1 && startPSN >= (1913776 + 0x030000))
{
// Layer 1 of XGD1
startLBA[i] = (1913776 + 0x030000) * 2 - (startPSN ^ 0xFFFFFF) - 0x030000 - 1;
endLBA[i] = (1913776 + 0x030000) * 2 - (endPSN ^ 0xFFFFFF) - 0x030000 - 1;
}
else if (xgdType > 1 && startPSN >= (1913760 + 0x030000))
{
// Layer 1 of XGD2 or XGD3
startLBA[i] = (1913760 + 0x030000) * 2 - (startPSN ^ 0xFFFFFF) - 0x030000 - 1;
endLBA[i] = (1913760 + 0x030000) * 2 - (endPSN ^ 0xFFFFFF) - 0x030000 - 1;
}
else
{
// Layer 0
startLBA[i] = startPSN - 0x030000;
endLBA[i] = endPSN - 0x030000;
}
}
// Sort ranges for XGD1
if (xgdType == 1)
Array.Sort(startLBA, endLBA);
// Represent ranges as string
string? ranges = null;
if (xgdType == 1)
{
for (int i = 0; i < 16; i++)
{
ranges += $"{startLBA[i]}-{endLBA[i]}";
if (i != numRanges - 1)
ranges += "\r\n";
}
}
else
{
ranges = $"{startLBA[0]}-{endLBA[0]}\r\n{startLBA[3]}-{endLBA[3]}";
}
return ranges;
}
#endregion
}
}

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0-windows;net8.0-windows</TargetFrameworks>
<TargetFrameworks>net8.0</TargetFrameworks>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
@@ -10,26 +10,26 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MPF\MPF.csproj" />
<ProjectReference Include="..\MPF.Core\MPF.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeCoverage" Version="17.9.0-preview-23531-01" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0-preview-23531-01" />
<PackageReference Include="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.1" />
<PackageReference Include="xunit" Version="2.6.2" />
<PackageReference Include="SabreTools.RedumpLib" Version="1.3.8" />
<PackageReference Include="xunit" Version="2.8.0" />
<PackageReference Include="xunit.abstractions" Version="2.0.3" />
<PackageReference Include="xunit.analyzers" Version="1.6.0" />
<PackageReference Include="xunit.assert" Version="2.6.2" />
<PackageReference Include="xunit.core" Version="2.6.2" />
<PackageReference Include="xunit.extensibility.core" Version="2.6.2" />
<PackageReference Include="xunit.extensibility.execution" Version="2.6.2" />
<PackageReference Include="xunit.runner.console" Version="2.6.2">
<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.5.4">
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@@ -16,6 +16,8 @@ namespace MPF.UI.Core
DiscCategory discCategory => new Element<DiscCategory>(discCategory),
InternalProgram internalProgram => new Element<InternalProgram>(internalProgram),
MediaType mediaType => new Element<MediaType>(mediaType),
RedumperReadMethod readMethod => new Element<RedumperReadMethod>(readMethod),
RedumperSectorOrder sectorOrder => new Element<RedumperSectorOrder>(sectorOrder),
RedumpSystem redumpSystem => new RedumpSystemComboBoxItem(redumpSystem),
Region region => new Element<Region>(region),
@@ -35,6 +37,8 @@ namespace MPF.UI.Core
Element<DiscCategory> dcElement => dcElement.Value,
Element<InternalProgram> ipElement => ipElement.Value,
Element<MediaType> mtElement => mtElement.Value,
Element<RedumperReadMethod> rmElement => rmElement.Value,
Element<RedumperSectorOrder> soElement => soElement.Value,
RedumpSystemComboBoxItem rsElement => rsElement.Value,
Element<Region> reValue => reValue.Value,
_ => null,

View File

@@ -5,19 +5,21 @@
<TargetFrameworks>net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0-windows;net6.0-windows;net7.0-windows;net8.0-windows</TargetFrameworks>
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<ImportFrameworkWinFXTargets Condition="$(TargetFramework.StartsWith(`net3`))">true</ImportFrameworkWinFXTargets>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<VersionPrefix>3.0.3</VersionPrefix>
<UseWindowsForms>true</UseWindowsForms>
<UseWPF>true</UseWPF>
<VersionPrefix>3.1.9</VersionPrefix>
<!-- Package Properties -->
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
<Description>Common code for all MPF UI implementations</Description>
<Copyright>Copyright (c) Matt Nadareski 2019-2023</Copyright>
<Copyright>Copyright (c) Matt Nadareski 2019-2024</Copyright>
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
<RepositoryUrl>https://github.com/SabreTools/MPF</RepositoryUrl>
<RepositoryType>git</RepositoryType>
@@ -41,7 +43,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="SabreTools.RedumpLib" Version="1.3.1" />
<PackageReference Include="SabreTools.RedumpLib" Version="1.3.8" />
</ItemGroup>
<ItemGroup>

View File

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

View File

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

View File

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

View File

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

View File

@@ -172,6 +172,8 @@
<controls:UserInput x:Name="CommentsTextBox" Label="Comments"
Text="{Binding SubmissionInfo.CommonDiscInfo.Comments, Mode=TwoWay}" TextHeight="50"
Enter="True" TextWrapping="Wrap" VerticalContentAlignmentValue="Top" />
<controls:UserInput x:Name="CompatibleOSTextBox" Label="Compatible OS"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)CompatibleOS], Mode=TwoWay}"/>
<controls:UserInput x:Name="DiscKeyTextBox" Label="Disc Key" Visibility="Collapsed"
Text="{Binding Path=SubmissionInfo.Extras.DiscKey, Mode=TwoWay}"/>
<controls:UserInput x:Name="DiscIDTextBox" Label="Disc ID" Visibility="Collapsed"
@@ -235,8 +237,12 @@
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)ActivisionID], Mode=TwoWay}"/>
<controls:UserInput x:Name="BandaiIDTextBox" Label="Bandai ID"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)BandaiID], Mode=TwoWay}"/>
<controls:UserInput x:Name="BethesdaIDTextBox" Label="Bandai ID"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)BethesdaID], Mode=TwoWay}"/>
<controls:UserInput x:Name="CDProjektIDTextBox" Label="CD Projekt ID"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)CDProjektID], Mode=TwoWay}"/>
<controls:UserInput x:Name="EidosIDTextBox" Label="Eidos ID"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)EidosID], Mode=TwoWay}"/>
<controls:UserInput x:Name="ElectronicArtsIDTextBox" Label="Electronic Arts ID"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.CommentsSpecialFields[(redump:SiteCode)ElectronicArtsID], Mode=TwoWay}"/>
<controls:UserInput x:Name="FoxInteractiveIDTextBox" Label="Fox Interactive ID"
@@ -290,6 +296,11 @@
Text="{Binding SubmissionInfo.CommonDiscInfo.Contents, Mode=TwoWay}" TextHeight="50"
Enter="True" TextWrapping="Wrap" VerticalContentAlignmentValue="Top"/>
<!-- Applications -->
<controls:UserInput x:Name="ApplicationsTextBox" Label="Applications" TextHeight="50"
Enter="True" TextWrapping="Wrap" VerticalContentAlignmentValue="Top"
Text="{Binding Path=SubmissionInfo.CommonDiscInfo.ContentsSpecialFields[(redump:SiteCode)Applications], Mode=TwoWay}"/>
<!-- Games -->
<controls:UserInput x:Name="GamesTextBox" Label="Games" TextHeight="50"
Enter="True" TextWrapping="Wrap" VerticalContentAlignmentValue="Top"
@@ -399,6 +410,23 @@
<controls:UserInput x:Name="FullyMatchedID" Label="Fully Matched ID" IsReadOnly="True"
Text="{Binding SubmissionInfo.FullyMatchedID, Mode=TwoWay}"/>
<controls:UserInput x:Name="PartiallyMatchedIDs" Label="Partially Matched ID(s)" IsReadOnly="True"/>
<controls:UserInput x:Name="HashData" Label="Hash Data" IsReadOnly="True"
Text="{Binding SubmissionInfo.TracksAndWriteOffsets.ClrMameProData, Mode=TwoWay}" TextHeight="75" TextWrapping="NoWrap"
VerticalContentAlignmentValue="Top" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"/>
<controls:UserInput x:Name="HashDataSize" Label="Size" IsReadOnly="True"
Text="{Binding SubmissionInfo.SizeAndChecksums.Size, Mode=TwoWay}"/>
<controls:UserInput x:Name="HashDataCRC" Label="CRC-32" IsReadOnly="True"
Text="{Binding SubmissionInfo.SizeAndChecksums.CRC32, Mode=TwoWay}"/>
<controls:UserInput x:Name="HashDataMD5" Label="MD5" IsReadOnly="True"
Text="{Binding SubmissionInfo.SizeAndChecksums.MD5, Mode=TwoWay}"/>
<controls:UserInput x:Name="HashDataSHA1" Label="SHA-1" IsReadOnly="True"
Text="{Binding SubmissionInfo.SizeAndChecksums.SHA1, Mode=TwoWay}"/>
<controls:UserInput x:Name="HashDataLayerbreak1" Label="Layerbreak 1" IsReadOnly="True"
Text="{Binding SubmissionInfo.SizeAndChecksums.Layerbreak, Mode=TwoWay}"/>
<controls:UserInput x:Name="HashDataLayerbreak2" Label="Layerbreak 2" IsReadOnly="True"
Text="{Binding SubmissionInfo.SizeAndChecksums.Layerbreak2, Mode=TwoWay}"/>
<controls:UserInput x:Name="HashDataLayerbreak3" Label="Layerbreak 3" IsReadOnly="True"
Text="{Binding SubmissionInfo.SizeAndChecksums.Layerbreak3, Mode=TwoWay}"/>
<controls:UserInput x:Name="AntiModchip" Label="Anti-Modchip" IsReadOnly="True"
Text="{Binding SubmissionInfo.CopyProtection.AntiModchip, Mode=TwoWay}"/>
<controls:UserInput x:Name="DiscOffset" Label="Disc Offset" IsReadOnly="True"

View File

@@ -77,6 +77,14 @@ namespace MPF.UI.Core.Windows
private UserInput? _FullyMatchedID => ItemHelper.FindChild<UserInput>(this, "FullyMatchedID");
private UserInput? _PartiallyMatchedIDs => ItemHelper.FindChild<UserInput>(this, "PartiallyMatchedIDs");
private UserInput? _HashData => ItemHelper.FindChild<UserInput>(this, "HashData");
private UserInput? _HashDataSize => ItemHelper.FindChild<UserInput>(this, "HashDataSize");
private UserInput? _HashDataCRC => ItemHelper.FindChild<UserInput>(this, "HashDataCRC");
private UserInput? _HashDataMD5 => ItemHelper.FindChild<UserInput>(this, "HashDataMD5");
private UserInput? _HashDataSHA1 => ItemHelper.FindChild<UserInput>(this, "HashDataSHA1");
private UserInput? _HashDataLayerbreak1 => ItemHelper.FindChild<UserInput>(this, "HashDataLayerbreak1");
private UserInput? _HashDataLayerbreak2 => ItemHelper.FindChild<UserInput>(this, "HashDataLayerbreak2");
private UserInput? _HashDataLayerbreak3 => ItemHelper.FindChild<UserInput>(this, "HashDataLayerbreak3");
private UserInput? _AntiModchip => ItemHelper.FindChild<UserInput>(this, "AntiModchip");
private UserInput? _DiscOffset => ItemHelper.FindChild<UserInput>(this, "DiscOffset");
private UserInput? _DMIHash => ItemHelper.FindChild<UserInput>(this, "DMIHash");
@@ -295,6 +303,22 @@ namespace MPF.UI.Core.Windows
_PartiallyMatchedIDs!.Visibility = Visibility.Collapsed;
else
_PartiallyMatchedIDs!.Text = string.Join(", ", submissionInfo.PartiallyMatchedIDs.Select(i => i.ToString()).ToArray());
if (string.IsNullOrEmpty(submissionInfo.TracksAndWriteOffsets?.ClrMameProData))
_HashData!.Visibility = Visibility.Collapsed;
if (submissionInfo.SizeAndChecksums?.Size == null || submissionInfo.SizeAndChecksums.Size == 0)
_HashDataSize!.Visibility = Visibility.Collapsed;
if (string.IsNullOrEmpty(submissionInfo.SizeAndChecksums?.CRC32))
_HashDataCRC!.Visibility = Visibility.Collapsed;
if (string.IsNullOrEmpty(submissionInfo.SizeAndChecksums?.MD5))
_HashDataMD5!.Visibility = Visibility.Collapsed;
if (string.IsNullOrEmpty(submissionInfo.SizeAndChecksums?.SHA1))
_HashDataSHA1!.Visibility = Visibility.Collapsed;
if (submissionInfo.SizeAndChecksums?.Layerbreak == null || submissionInfo.SizeAndChecksums.Layerbreak == 0)
_HashDataLayerbreak1!.Visibility = Visibility.Collapsed;
if (submissionInfo.SizeAndChecksums?.Layerbreak2 == null || submissionInfo.SizeAndChecksums.Layerbreak2 == 0)
_HashDataLayerbreak2!.Visibility = Visibility.Collapsed;
if (submissionInfo.SizeAndChecksums?.Layerbreak3 == null || submissionInfo.SizeAndChecksums.Layerbreak3 == 0)
_HashDataLayerbreak3!.Visibility = Visibility.Collapsed;
if (submissionInfo.CopyProtection?.AntiModchip == null)
_AntiModchip!.Visibility = Visibility.Collapsed;
if (submissionInfo.TracksAndWriteOffsets?.OtherWriteOffsets == null)
@@ -352,6 +376,22 @@ namespace MPF.UI.Core.Windows
PartiallyMatchedIDs.Visibility = Visibility.Collapsed;
else
PartiallyMatchedIDs.Text = string.Join(", ", submissionInfo.PartiallyMatchedIDs.Select(i => i.ToString()).ToArray());
if (string.IsNullOrEmpty(submissionInfo.TracksAndWriteOffsets?.ClrMameProData))
HashData!.Visibility = Visibility.Collapsed;
if (submissionInfo.SizeAndChecksums?.Size == null)
HashDataSize!.Visibility = Visibility.Collapsed;
if (string.IsNullOrEmpty(submissionInfo.SizeAndChecksums?.CRC32))
HashDataCRC!.Visibility = Visibility.Collapsed;
if (string.IsNullOrEmpty(submissionInfo.SizeAndChecksums?.MD5))
HashDataMD5!.Visibility = Visibility.Collapsed;
if (string.IsNullOrEmpty(submissionInfo.SizeAndChecksums?.SHA1))
HashDataSHA1!.Visibility = Visibility.Collapsed;
if (submissionInfo.SizeAndChecksums?.Layerbreak == null || submissionInfo.SizeAndChecksums.Layerbreak == 0)
HashDataLayerbreak1!.Visibility = Visibility.Collapsed;
if (submissionInfo.SizeAndChecksums?.Layerbreak2 == null || submissionInfo.SizeAndChecksums.Layerbreak2 == 0)
HashDataLayerbreak2!.Visibility = Visibility.Collapsed;
if (submissionInfo.SizeAndChecksums?.Layerbreak3 == null || submissionInfo.SizeAndChecksums.Layerbreak3 == 0)
HashDataLayerbreak3!.Visibility = Visibility.Collapsed;
if (submissionInfo.CopyProtection?.AntiModchip == null)
AntiModchip.Visibility = Visibility.Collapsed;
if (submissionInfo.TracksAndWriteOffsets?.OtherWriteOffsets == null)
@@ -437,16 +477,16 @@ namespace MPF.UI.Core.Windows
#if NET35
_L1Info!.Header = "Label Side";
_L1MasteringRing!.Visibility = Visibility.Collapsed;
_L1MasteringSID!.Visibility = Visibility.Collapsed;
_L1Toolstamp!.Visibility = Visibility.Collapsed;
_L1MasteringRing!.Label = "Mastering Ring";
_L1MasteringSID!.Label = "Mastering SID";
_L1Toolstamp!.Label = "Mastering SID";
_L1MouldSID!.Label = "Mould SID";
_L1AdditionalMould!.Label = "Additional Mould";
#else
L1Info.Header = "Label Side";
L1MasteringRing.Visibility = Visibility.Collapsed;
L1MasteringSID.Visibility = Visibility.Collapsed;
L1Toolstamp.Visibility = Visibility.Collapsed;
L1MasteringRing.Label = "Mastering Ring";
L1MasteringSID.Label = "Mastering SID";
L1Toolstamp.Label = "Mastering SID";
L1MouldSID.Label = "Mould SID";
L1AdditionalMould.Label = "Additional Mould";
#endif
@@ -466,6 +506,8 @@ namespace MPF.UI.Core.Windows
case DiscType.NintendoWiiOpticalDiscSL:
case DiscType.NintendoWiiOpticalDiscDL:
case DiscType.NintendoWiiUOpticalDiscSL:
case DiscType.UMDSL:
case DiscType.UMDDL:
// Quad-layer discs
if (submissionInfo?.SizeAndChecksums?.Layerbreak3 != default(long))
{
@@ -645,16 +687,16 @@ namespace MPF.UI.Core.Windows
#if NET35
_L1Info!.Header = "Label Side";
_L1MasteringRing!.Visibility = Visibility.Collapsed;
_L1MasteringSID!.Visibility = Visibility.Collapsed;
_L1Toolstamp!.Visibility = Visibility.Collapsed;
_L1MasteringRing!.Label = "Mastering Ring";
_L1MasteringSID!.Label = "Mastering SID";
_L1Toolstamp!.Label = "Toolstamp/Mastering Code";
_L1MouldSID!.Label = "Mould SID";
_L1AdditionalMould!.Label = "Additional Mould";
#else
L1Info.Header = "Label Side";
L1MasteringRing.Visibility = Visibility.Collapsed;
L1MasteringSID.Visibility = Visibility.Collapsed;
L1Toolstamp.Visibility = Visibility.Collapsed;
L1MasteringRing.Label = "Mastering Ring";
L1MasteringSID.Label = "Mastering SID";
L1Toolstamp.Label = "Toolstamp/Mastering Code";
L1MouldSID.Label = "Mould SID";
L1AdditionalMould.Label = "Additional Mould";
#endif

View File

@@ -49,6 +49,16 @@
Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
Foreground="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"
Template="{DynamicResource CustomMenuItemTemplate}">
<MenuItem x:Name="CheckDumpMenuItem" Header="_Check Dump" HorizontalAlignment="Left" Width="185"
Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
Foreground="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"
Template="{DynamicResource CustomMenuItemTemplate}"
IsEnabled="{Binding CheckDumpMenuItemEnabled}"/>
<MenuItem x:Name="CreateIRDMenuItem" Header="_Create PS3 IRD" HorizontalAlignment="Left" Width="185"
Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
Foreground="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"
Template="{DynamicResource CustomMenuItemTemplate}"
IsEnabled="{Binding CreateIRDMenuItemEnabled}"/>
<MenuItem x:Name="OptionsMenuItem" Header="_Options" HorizontalAlignment="Left" Width="185"
Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
Foreground="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"

View File

@@ -28,6 +28,8 @@ namespace MPF.UI.Core.Windows
private MenuItem? _AppExitMenuItem => ItemHelper.FindChild<MenuItem>(this, "AppExitMenuItem");
private MenuItem? _CheckForUpdatesMenuItem => ItemHelper.FindChild<MenuItem>(this, "CheckForUpdatesMenuItem");
private MenuItem? _DebugViewMenuItem => ItemHelper.FindChild<MenuItem>(this, "DebugViewMenuItem");
private MenuItem? _CheckDumpMenuItem => ItemHelper.FindChild<MenuItem>(this, "CheckDumpMenuItem");
private MenuItem? _CreateIRDMenuItem => ItemHelper.FindChild<MenuItem>(this, "CreateIRDMenuItem");
private MenuItem? _OptionsMenuItem => ItemHelper.FindChild<MenuItem>(this, "OptionsMenuItem");
#endregion
@@ -139,12 +141,16 @@ namespace MPF.UI.Core.Windows
_AppExitMenuItem!.Click += AppExitClick;
_CheckForUpdatesMenuItem!.Click += CheckForUpdatesClick;
_DebugViewMenuItem!.Click += DebugViewClick;
_CheckDumpMenuItem!.Click += CheckDumpMenuItemClick;
_CreateIRDMenuItem!.Click += CreateIRDMenuItemClick;
_OptionsMenuItem!.Click += OptionsMenuItemClick;
#else
AboutMenuItem.Click += AboutClick;
AppExitMenuItem.Click += AppExitClick;
CheckForUpdatesMenuItem.Click += CheckForUpdatesClick;
DebugViewMenuItem.Click += DebugViewClick;
CheckDumpMenuItem.Click += CheckDumpMenuItemClick;
CreateIRDMenuItem.Click += CreateIRDMenuItemClick;
OptionsMenuItem.Click += OptionsMenuItemClick;
#endif
@@ -313,6 +319,56 @@ namespace MPF.UI.Core.Windows
return (result, submissionInfo!);
}
/// <summary>
/// Show the Check Dump window
/// </summary>
public void ShowCheckDumpWindow()
{
// Hide MainWindow while Check GUI is open
this.Hide();
var checkDumpWindow = new CheckDumpWindow(this)
{
Focusable = true,
Owner = this,
ShowActivated = true,
ShowInTaskbar = true,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
};
checkDumpWindow.Closed += delegate {
// Unhide Main window after Check window has been closed
this.Show();
this.Activate();
};
checkDumpWindow.Show();
}
/// <summary>
/// Show the Create IRD window
/// </summary>
public void ShowCreateIRDWindow()
{
// Hide MainWindow while Create IRD UI is open
this.Hide();
var createIRDWindow = new CreateIRDWindow(this)
{
Focusable = true,
Owner = this,
ShowActivated = true,
ShowInTaskbar = true,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
};
createIRDWindow.Closed += delegate {
// Unhide Main window after Create IRD window has been closed
this.Show();
this.Activate();
};
createIRDWindow.Show();
}
/// <summary>
/// Show the Options window
/// </summary>
@@ -386,6 +442,18 @@ namespace MPF.UI.Core.Windows
public void AppExitClick(object sender, RoutedEventArgs e) =>
Application.Current.Shutdown();
/// <summary>
/// Handler for CheckDumpMenuItem Click event
/// </summary>
public void CheckDumpMenuItemClick(object sender, RoutedEventArgs e) =>
ShowCheckDumpWindow();
/// <summary>
/// Handler for CreateIRDMenuItem Click event
/// </summary>
public void CreateIRDMenuItemClick(object sender, RoutedEventArgs e) =>
ShowCreateIRDWindow();
/// <summary>
/// Handler for CheckForUpdatesMenuItem Click event
/// </summary>

View File

@@ -181,7 +181,8 @@
<Label Grid.Row="4" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Right" Content="Default Output Path" />
<TextBox x:Name="DefaultOutputPathTextBox" Grid.Row="4" Grid.Column="1" Height="22" HorizontalAlignment="Stretch"
Text="{Binding Options.DefaultOutputPath}" VerticalContentAlignment="Center" />
Text="{Binding Options.DefaultOutputPath}" VerticalContentAlignment="Center"
ToolTip="Variables allowed:&#x0a; &#37;SYSTEM&#37;&#9;(System name, long)&#x0a; &#37;SYS&#37;&#9;&#9;(System name, short)&#x0a; &#37;MEDIA&#37;&#9;(Media type)&#x0a; &#37;PROGRAM&#37;&#9;(Program name, long)&#x0a; &#37;PROG&#37;&#9;(Program name, short)&#x0a; &#37;LABEL&#37;&#9;(Volume label)&#x0a; &#37;DATE&#37;&#9;(Current date)&#x0a; &#37;DATETIME&#37;&#9;(Current date and time)"/>
<Button x:Name="DefaultOutputPathButton" Grid.Row="4" Grid.Column="2" Height="22" Width="22" Content="..."
Style="{DynamicResource CustomButtonStyle}" />
</Grid>
@@ -190,7 +191,7 @@
<TabItem Header="Dumping" Style="{DynamicResource CustomTabItemStyle}">
<StackPanel>
<GroupBox Margin="5,5,5,5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Header="Dumping">
<UniformGrid Columns="2" Rows="7">
<UniformGrid Columns="2" Rows="8">
<CheckBox VerticalAlignment="Center" Content="Show Separate Window"
IsChecked="{Binding Options.ToolsInSeparateWindow}"
ToolTip="Show program output in separate command window instead of in the log. Enable this if you have a weaker system as there is an increased processing load otherwise." Margin="0,4"
@@ -260,11 +261,16 @@
IsChecked="{Binding Options.DeleteUnnecessaryFiles}"
ToolTip="Delete unnecesary output files to reduce space" Margin="0,4"
/>
<CheckBox VerticalAlignment="Center" Content="Create PS3 IRD After Dumping"
IsChecked="{Binding Options.CreateIRDAfterDumping}"
ToolTip="Automatically creates an IRD file after dumping a PS3 disc" Margin="0,4"
/>
</UniformGrid>
</GroupBox>
<GroupBox Margin="5,5,5,5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Header="Protection">
<UniformGrid Columns="2" Rows="2">
<UniformGrid Columns="2" Rows="3">
<CheckBox VerticalAlignment="Center" Content="Enable Protection Scan"
IsChecked="{Binding Options.ScanForProtection}"
ToolTip="Enable automatic checking for copy protection on dumped media" Margin="0,4,0,0"
@@ -284,6 +290,11 @@
IsChecked="{Binding Options.IncludeDebugProtectionInformation}"
ToolTip="Include debug information during protection scans" Margin="0,4"
/>
<CheckBox VerticalAlignment="Center" Content="Hide Drive Letters"
IsChecked="{Binding Options.HideDriveLetters}"
ToolTip="Remove drive letters from protection scan output" Margin="0,4"
/>
</UniformGrid>
</GroupBox>
@@ -359,7 +370,7 @@
/>
<Label Content="Reread Tries:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
<TextBox VerticalAlignment="Center" VerticalContentAlignment="Center"
<TextBox VerticalAlignment="Center" VerticalContentAlignment="Center" Width="200" HorizontalAlignment="Left"
Text="{Binding Options.AaruRereadCount}"
ToolTip="Specifies how many rereads are attempted for sector and subchannel errors"
/>
@@ -392,22 +403,23 @@
IsChecked="{Binding Options.DICMultiSectorRead}"
ToolTip="Enable the /mr flag for BD drive dumping" Margin="0,4"
/>
<Label/> <!-- Empty label for padding -->
<Label/>
<!-- Empty label for padding -->
<Label Content="Multi-Sector Read Value:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
<TextBox VerticalAlignment="Center" VerticalContentAlignment="Center"
<TextBox VerticalAlignment="Center" VerticalContentAlignment="Center" Width="200" HorizontalAlignment="Left"
Text="{Binding Options.DICMultiSectorReadValue}" IsEnabled="{Binding Options.DICMultiSectorRead}"
ToolTip="Set the default value for the /mr flag"
/>
<Label Content="CD Reread Tries:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
<TextBox VerticalAlignment="Center" VerticalContentAlignment="Center"
<TextBox VerticalAlignment="Center" VerticalContentAlignment="Center" Width="200" HorizontalAlignment="Left"
Text="{Binding Options.DICRereadCount}"
ToolTip="Specifies how many rereads are attempted on C2 error [CD only]"
/>
<Label Content="DVD/HD-DVD/BD Reread Tries:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
<TextBox VerticalAlignment="Center" VerticalContentAlignment="Center"
<TextBox VerticalAlignment="Center" VerticalContentAlignment="Center" Width="200" HorizontalAlignment="Left"
Text="{Binding Options.DICDVDRereadCount}"
ToolTip="Specifies how many rereads are attempted on read error [DVD/HD-DVD/BD only]"
/>
@@ -415,7 +427,7 @@
</GroupBox>
<GroupBox Margin="5,5,5,5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Header="Redumper">
<UniformGrid Columns="2" Rows="3">
<UniformGrid Columns="2" Rows="7">
<CheckBox VerticalAlignment="Center" Content="Enable Debug Output"
IsChecked="{Binding Options.RedumperEnableDebug}"
ToolTip="Enable debug output in logs" Margin="0,4"
@@ -426,20 +438,45 @@
ToolTip="Enable verbose output in logs" Margin="0,4"
/>
<CheckBox VerticalAlignment="Center" Content="Enable BE Drive Reading"
IsChecked="{Binding Options.RedumperUseBEReading}"
ToolTip="Enable setting drive read method to BE_CDDA by default" Margin="0,4"
<Label Content="Reread Tries:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
<TextBox VerticalAlignment="Center" VerticalContentAlignment="Center" Width="200" HorizontalAlignment="Left"
Text="{Binding Options.RedumperRereadCount}"
ToolTip="Specifies how many rereads are attempted on read error"
/>
<CheckBox VerticalAlignment="Center" Content="Set Default Plextor Lead-in Retries"
IsChecked="{Binding Options.RedumperEnableLeadinRetry}"
ToolTip="Enable custom lead-in retries for Plextor drives" Margin="0,4"
/>
<Label/>
<!-- Empty label for padding -->
<Label Content="Plextor Lead-in Retries:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
<TextBox VerticalAlignment="Center" VerticalContentAlignment="Center" Width="200" HorizontalAlignment="Left"
Text="{Binding Options.RedumperLeadinRetryCount}" IsEnabled ="{Binding Options.RedumperEnableLeadinRetry}"
ToolTip="Specifies how many retries are attempted for lead-in on Plextor drives"
/>
<CheckBox VerticalAlignment="Center" Content="Non-Redump Options" Click="NonRedumpModeClicked"
IsChecked="{Binding Options.RedumperNonRedumpMode}"
ToolTip="Enable non-redump options" Margin="0,4"
/>
<CheckBox VerticalAlignment="Center" Content="Set Generic Drive Type"
IsChecked="{Binding Options.RedumperUseGenericDriveType}"
IsChecked="{Binding Options.RedumperUseGenericDriveType}" IsEnabled="{Binding Options.RedumperNonRedumpMode}"
ToolTip="Enable setting drive type to Generic by default" Margin="0,4"
/>
<Label Content="Reread Tries:" VerticalAlignment="Center" HorizontalAlignment="Right"/>
<TextBox VerticalAlignment="Center" VerticalContentAlignment="Center"
Text="{Binding Options.RedumperRereadCount}"
ToolTip="Specifies how many rereads are attempted on read error"
<Label VerticalAlignment="Center" Content="Default Read Method:" HorizontalAlignment="Right" />
<ComboBox x:Name="DefaultRedumperReadMethodComboBox" Height="22" Width="200" HorizontalAlignment="Left"
ItemsSource="{Binding RedumperReadMethods}" SelectedItem="{Binding Options.RedumperReadMethod, Converter={StaticResource ElementConverter}, Mode=TwoWay}"
Style="{DynamicResource CustomComboBoxStyle}" IsEnabled="{Binding Options.RedumperNonRedumpMode}"
/>
<Label VerticalAlignment="Center" Content="Default Sector Order:" HorizontalAlignment="Right" />
<ComboBox x:Name="DefaultRedumperSectorOrderComboBox" Height="22" Width="200" HorizontalAlignment="Left"
ItemsSource="{Binding RedumperSectorOrders}" SelectedItem="{Binding Options.RedumperSectorOrder, Converter={StaticResource ElementConverter}, Mode=TwoWay}"
Style="{DynamicResource CustomComboBoxStyle}" IsEnabled="{Binding Options.RedumperNonRedumpMode}"
/>
</UniformGrid>
</GroupBox>

View File

@@ -214,7 +214,7 @@ namespace MPF.UI.Core.Windows
CustomMessageBox.Show(this, message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
#endregion
#endregion
#region Event Handlers
@@ -224,6 +224,17 @@ namespace MPF.UI.Core.Windows
private void BrowseForPathClick(object sender, EventArgs e) =>
BrowseForPath(this, sender as System.Windows.Controls.Button);
/// <summary>
/// Alert user of non-redump mode implications
/// </summary>
private void NonRedumpModeClicked(object sender, EventArgs e)
{
if (OptionsViewModel.Options.RedumperNonRedumpMode)
CustomMessageBox.Show(this, "All logs generated with these options will not be acceptable for Redump submission", "Warning", MessageBoxButton.OK, MessageBoxImage.Warning);
else
OptionsViewModel.NonRedumpModeUnChecked();
}
/// <summary>
/// Handler for AcceptButton Click event
/// </summary>

19
MPF.sln
View File

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

View File

@@ -2,24 +2,26 @@
<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>
<TargetFrameworks>net40;net452;net462;net472;net48;netcoreapp3.1;net5.0-windows;net6.0-windows;net7.0-windows;net8.0-windows</TargetFrameworks>
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
<OutputType>WinExe</OutputType>
<ApplicationIcon>Images\Icon.ico</ApplicationIcon>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<ImportFrameworkWinFXTargets Condition="$(TargetFramework.StartsWith(`net3`))">true</ImportFrameworkWinFXTargets>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<VersionPrefix>3.0.3</VersionPrefix>
<UseWindowsForms>true</UseWindowsForms>
<UseWPF>true</UseWPF>
<VersionPrefix>3.1.9</VersionPrefix>
<!-- Package Properties -->
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
<Description>Frontend for various dumping programs</Description>
<Copyright>Copyright (c) Matt Nadareski 2019-2023</Copyright>
<Copyright>Copyright (c) Matt Nadareski 2019-2024</Copyright>
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
<RepositoryUrl>https://github.com/SabreTools/MPF</RepositoryUrl>
<RepositoryType>git</RepositoryType>
@@ -66,15 +68,12 @@
<ItemGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net4`))">
<Reference Include="PresentationFramework.Aero" />
</ItemGroup>
<ItemGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`)) OR $(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`))">
<Reference Include="PresentationFramework.Aero2" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BinaryObjectScanner" PrivateAssets="build; analyzers" ExcludeAssets="contentFiles" Version="3.0.2" GeneratePathProperty="true">
<PackageReference Include="BinaryObjectScanner" PrivateAssets="build; analyzers" ExcludeAssets="contentFiles" Version="3.1.12" GeneratePathProperty="true">
<IncludeAssets>runtime; compile; build; native; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="SabreTools.RedumpLib" Version="1.3.1" />
<PackageReference Include="SabreTools.RedumpLib" Version="1.3.8" />
</ItemGroup>
<ItemGroup>

View File

@@ -2,7 +2,9 @@
Redumper/Aaru/DiscImageCreator UI in C#
[![Build status](https://ci.appveyor.com/api/projects/status/3ldav3v0c373jeqa?svg=true)](https://ci.appveyor.com/project/mnadareski/MPF/build/artifacts)
[![Build status](https://ci.appveyor.com/api/projects/status/3ldav3v0c373jeqa?svg=true)](https://ci.appveyor.com/project/mnadareski/MPF)
[![UI Build](https://github.com/SabreTools/MPF/actions/workflows/build_ui.yml/badge.svg)](https://github.com/SabreTools/MPF/actions/workflows/build_ui.yml)
[![Check Build](https://github.com/SabreTools/MPF/actions/workflows/build_check.yml/badge.svg)](https://github.com/SabreTools/MPF/actions/workflows/build_check.yml)
This is a community project, so if you have some time and knowledge to give, we'll be glad to add you as a contributor to this project. If you have any suggestions, issues, bugs, or crashes, please look at the [Issues](https://github.com/SabreTools/MPF/issues) page first to see if it has been reported before and try out the latest AppVeyor WIP build below to see if it has already been addressed. If it hasn't, please open an issue that's as descriptive as you can be. Help me make this a better program for everyone :)
@@ -10,7 +12,7 @@ This is a community project, so if you have some time and knowledge to give, we'
For the most recent stable build, download the latest release here: [Releases Page](https://github.com/SabreTools/MPF/releases)
For the latest AppVeyor WIP build here: [AppVeyor](https://ci.appveyor.com/project/mnadareski/MPF/build/artifacts)
For the latest WIP build here: [Rolling Release](https://github.com/SabreTools/MPF/releases/tag/rolling)
## Media Preservation Frontend UI (MPF)
@@ -24,6 +26,11 @@ The main UI has some known limitations that are documented in code and in some p
- MAUI is not a viable alternative due to lack of out-of-box support for Linux
- Avalonia is being heavily considered as an alternative
- For those who need .NET Framework 4.8, there is an official fork: [MPF Legacy](https://github.com/Deterous/MPF-Legacy)
- For those who require broader archive/installer compatibility for protection scanning (Windows-only), please use the x86 builds as there are some specific scanning libraries that only work with that build
- This is actively being worked on as part of [Binary Object Scanner](https://github.com/SabreTools/BinaryObjectScanner)
- Please consider contributing if you have experience in dealing with multiple archive and installer types
- Consider using a third-party scanning tool, such as Protection ID, if this is not sufficient for your needs
- See [Compatibility Notes](https://github.com/SabreTools/BinaryObjectScanner?tab=readme-ov-file#compatibility-notes) for more details
## Media Preservation Frontend Checker (MPF.Check)
@@ -33,8 +40,6 @@ MPF.Check is a commandline-only program that allows users to generate submission
Both MPF UI and MPF.Check have the same system requirements for running, with the exception that MPF UI is Windows-only.
- [Supported OS versions for .NET 6](https://github.com/dotnet/core/blob/main/release-notes/6.0/supported-os.md)
- Requires [.NET 6.0 Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) if built without bundled runtime
- [Supported OS versions for .NET 8](https://github.com/dotnet/core/blob/main/release-notes/8.0/supported-os.md)
- Requires [.NET 8.0 Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) if built without bundled runtime
@@ -42,30 +47,28 @@ Ensure that your operating system and runtimes are as up-to-date as possible, si
## Build Instructions
To build for .NET 6.0 or .NET 8.0, ensure that the [.NET 8.0 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) (or later) is installed and included in your `PATH`. Then, run the following commands from command prompt, Powershell, Terminal, or shell:
To build for .NET 8.0, ensure that the [.NET 8.0 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) (or later) is installed and included in your `PATH`. Then, run the following commands from command prompt, Powershell, Terminal, or shell:
**MPF UI (Windows only):**
```bash
dotnet build MPF/MPF.csproj --framework [net6.0-windows|net8.0-windows] --runtime win-x64
dotnet build MPF/MPF.csproj --framework net8.0-windows --runtime [win-x86|win-x64]
```
**MPF.Check (Windows, OSX, Linux):**
```bash
dotnet build MPF.Check/MPF.Check.csproj --framework [net6.0|net8.0] --runtime [win-x64|linux-x64|osx-x64]
dotnet build MPF.Check/MPF.Check.csproj --framework net8.0 --runtime [win-x86|win-x64|linux-x64|osx-x64]
```
Choose one of `win-x64`, `linux-x64`, or `osx-x64` depending on the machine you are targeting.
Choose one of `win-x86`, `win-x64`, `linux-x64`, or `osx-x64` depending on the machine you are targeting.
### Build Scripts
Windows users may run `publish-win.ps1` and Linux users may run `publish-nix.sh` to perform a full release build.
Windows users may run `publish-win.ps1` and Linux users may run `publish-nix.sh` to perform a full release build. Both scripts will build and package all variants of MPF UI and MPF.Check with commandline switches to control what is included.
- `publish-win.ps1` will build and package all variants of MPF UI and MPF.Check
- The script requires [7-zip commandline](https://www.7-zip.org/download.html) and [Git for Windows](https://git-scm.com/downloads) to be installed and in `PATH`
- `publish-nix.sh` will _only_ build and package all variants of MPF.Check
- The script requires `zip` and `git` to be installed and in `PATH`
- `publish-win.ps1` requires [7-zip commandline](https://www.7-zip.org/download.html) and [Git for Windows](https://git-scm.com/downloads) to be installed and in `PATH`
- `publish-nix.sh` requires `zip` and `git` to be installed and in `PATH`
## Information
@@ -79,7 +82,7 @@ A list of all changes in each stable release and current WIP builds can now be f
MPF uses some external libraries to assist with additional information gathering after the dumping process.
- **BinaryObjectScanner** - Protection scanning - [GitHub](https://github.com/SabreTools/BinaryObjectScanner)
- **Binary Object Scanner** - Protection scanning - [GitHub](https://github.com/SabreTools/BinaryObjectScanner)
- **WPFCustomMessageBox.thabse** - Custom message boxes in UI - [GitHub](https://github.com/thabse/WPFCustomMessageBox)
## Contributors

View File

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

View File

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

View File

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

View File

@@ -14,15 +14,15 @@ param(
[Alias("UseAll")]
[switch]$USE_ALL,
[Parameter(Mandatory = $false)]
[Parameter(Mandatory = $false)]
[Alias("IncludePrograms")]
[switch]$INCLUDE_PROGRAMS,
[Parameter(Mandatory = $false)]
[Parameter(Mandatory = $false)]
[Alias("NoBuild")]
[switch]$NO_BUILD,
[Parameter(Mandatory = $false)]
[Parameter(Mandatory = $false)]
[Alias("NoArchive")]
[switch]$NO_ARCHIVE
)
@@ -34,14 +34,13 @@ $BUILD_FOLDER = $PSScriptRoot
$COMMIT = git log --pretty=format:"%H" -1
# Create the build matrix arrays
$UI_FRAMEWORKS = @('net6.0-windows', 'net8.0-windows')
$UI_RUNTIMES = @('win-x64')
$CHECK_FRAMEWORKS = @('net6.0', 'net8.0')
$CHECK_RUNTIMES = @('win-x64', 'linux-x64', 'osx-x64')
$UI_FRAMEWORKS = @('net8.0-windows')
$UI_RUNTIMES = @('win-x86', 'win-x64')
$CHECK_FRAMEWORKS = @('net8.0')
$CHECK_RUNTIMES = @('win-x86', 'win-x64', 'linux-x64', 'osx-x64')
# Use expanded lists, if requested
if ($USE_ALL.IsPresent)
{
if ($USE_ALL.IsPresent) {
$UI_FRAMEWORKS = @('net40', 'net452', 'net462', 'net472', 'net48', 'netcoreapp3.1', 'net5.0-windows', 'net6.0-windows', 'net7.0-windows', 'net8.0-windows')
$UI_RUNTIMES = @('win-x86', 'win-x64')
$CHECK_FRAMEWORKS = @('net20', 'net35', 'net40', 'net452', 'net462', 'net472', 'net48', 'netcoreapp3.1', 'net5.0', 'net6.0', 'net7.0', 'net8.0')
@@ -54,104 +53,108 @@ $VALID_CROSS_PLATFORM_FRAMEWORKS = @('netcoreapp3.1', 'net5.0', 'net6.0', 'net7.
$VALID_CROSS_PLATFORM_RUNTIMES = @('win-arm64', 'linux-x64', 'linux-arm64', 'osx-x64')
# Only build if requested
if (!$NO_BUILD.IsPresent)
{
# Restore Nuget packages for all builds
Write-Host "Restoring Nuget packages"
dotnet restore
if (!$NO_BUILD.IsPresent) {
# Restore Nuget packages for all builds
Write-Host "Restoring Nuget packages"
dotnet restore
# Build UI
foreach ($FRAMEWORK in $UI_FRAMEWORKS)
{
foreach ($RUNTIME in $UI_RUNTIMES)
{
# Only .NET 5 and above can publish to a single file
if ($SINGLE_FILE_CAPABLE -contains $FRAMEWORK)
{
dotnet publish MPF\MPF.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true
dotnet publish MPF\MPF.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
}
else
{
dotnet publish MPF\MPF.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT
dotnet publish MPF\MPF.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:DebugType=None -p:DebugSymbols=false
}
}
}
# Build UI
foreach ($FRAMEWORK in $UI_FRAMEWORKS) {
foreach ($RUNTIME in $UI_RUNTIMES) {
# If we have an invalid combination of framework and runtime
if ($VALID_CROSS_PLATFORM_FRAMEWORKS -notcontains $FRAMEWORK -and $VALID_CROSS_PLATFORM_RUNTIMES -contains $RUNTIME) {
continue
}
# Build Check
foreach ($FRAMEWORK in $CHECK_FRAMEWORKS)
{
foreach ($RUNTIME in $CHECK_RUNTIMES)
{
# If we have an invalid combination of framework and runtime
if ($VALID_CROSS_PLATFORM_FRAMEWORKS -notcontains $FRAMEWORK -and $VALID_CROSS_PLATFORM_RUNTIMES -contains $RUNTIME)
{
continue
}
# Only .NET 5 and above can publish to a single file
if ($SINGLE_FILE_CAPABLE -contains $FRAMEWORK) {
# Only include Debug if building all
if ($USE_ALL.IsPresent) {
dotnet publish MPF\MPF.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true
}
dotnet publish MPF\MPF.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
}
else {
# Only include Debug if building all
if ($USE_ALL.IsPresent) {
dotnet publish MPF\MPF.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT
}
dotnet publish MPF\MPF.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:DebugType=None -p:DebugSymbols=false
}
}
}
# Only .NET 5 and above can publish to a single file
if ($SINGLE_FILE_CAPABLE -contains $FRAMEWORK)
{
dotnet publish MPF.Check\MPF.Check.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true
dotnet publish MPF.Check\MPF.Check.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
}
else
{
dotnet publish MPF.Check\MPF.Check.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT
dotnet publish MPF.Check\MPF.Check.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:DebugType=None -p:DebugSymbols=false
}
}
}
# Build Check
foreach ($FRAMEWORK in $CHECK_FRAMEWORKS) {
foreach ($RUNTIME in $CHECK_RUNTIMES) {
# If we have an invalid combination of framework and runtime
if ($VALID_CROSS_PLATFORM_FRAMEWORKS -notcontains $FRAMEWORK -and $VALID_CROSS_PLATFORM_RUNTIMES -contains $RUNTIME) {
continue
}
# Only .NET 5 and above can publish to a single file
if ($SINGLE_FILE_CAPABLE -contains $FRAMEWORK) {
# Only include Debug if building all
if ($USE_ALL.IsPresent) {
dotnet publish MPF.Check\MPF.Check.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true
}
dotnet publish MPF.Check\MPF.Check.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
}
else {
# Only include Debug if building all
if ($USE_ALL.IsPresent) {
dotnet publish MPF.Check\MPF.Check.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT
}
dotnet publish MPF.Check\MPF.Check.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:DebugType=None -p:DebugSymbols=false
}
}
}
}
# Only create archives if requested
if (!$NO_ARCHIVE.IsPresent)
{
# Create UI archives
foreach ($FRAMEWORK in $UI_FRAMEWORKS)
{
foreach ($RUNTIME in $UI_RUNTIMES)
{
Set-Location -Path $BUILD_FOLDER\MPF\bin\Debug\$FRAMEWORK\$RUNTIME\publish\
if ($INCLUDE_PROGRAMS.IsPresent)
{
7z a -tzip $BUILD_FOLDER\MPF_${FRAMEWORK}_${RUNTIME}_debug.zip *
}
else
{
7z a -tzip -x!Programs\* $BUILD_FOLDER\MPF_${FRAMEWORK}_${RUNTIME}_debug.zip *
}
Set-Location -Path $BUILD_FOLDER\MPF\bin\Release\$FRAMEWORK\$RUNTIME\publish\
if ($INCLUDE_PROGRAMS.IsPresent)
{
7z a -tzip $BUILD_FOLDER\MPF_${FRAMEWORK}_${RUNTIME}_release.zip *
}
else
{
7z a -tzip -x!Programs\* $BUILD_FOLDER\MPF_${FRAMEWORK}_${RUNTIME}_release.zip *
}
}
}
if (!$NO_ARCHIVE.IsPresent) {
# Create UI archives
foreach ($FRAMEWORK in $UI_FRAMEWORKS) {
foreach ($RUNTIME in $UI_RUNTIMES) {
# Only include Debug if building all
if ($USE_ALL.IsPresent) {
Set-Location -Path $BUILD_FOLDER\MPF\bin\Debug\${FRAMEWORK}\${RUNTIME}\publish\
if ($INCLUDE_PROGRAMS.IsPresent) {
7z a -tzip $BUILD_FOLDER\MPF_${FRAMEWORK}_${RUNTIME}_debug.zip *
}
else {
7z a -tzip -x!Programs\* $BUILD_FOLDER\MPF_${FRAMEWORK}_${RUNTIME}_debug.zip *
}
}
Set-Location -Path $BUILD_FOLDER\MPF\bin\Release\${FRAMEWORK}\${RUNTIME}\publish\
if ($INCLUDE_PROGRAMS.IsPresent) {
7z a -tzip $BUILD_FOLDER\MPF_${FRAMEWORK}_${RUNTIME}_release.zip *
}
else {
7z a -tzip -x!Programs\* $BUILD_FOLDER\MPF_${FRAMEWORK}_${RUNTIME}_release.zip *
}
}
}
# Create Check archives
foreach ($FRAMEWORK in $CHECK_FRAMEWORKS)
{
foreach ($RUNTIME in $CHECK_RUNTIMES)
{
# If we have an invalid combination of framework and runtime
if ($VALID_CROSS_PLATFORM_FRAMEWORKS -notcontains $FRAMEWORK -and $VALID_CROSS_PLATFORM_RUNTIMES -contains $RUNTIME)
{
continue
}
# Create Check archives
foreach ($FRAMEWORK in $CHECK_FRAMEWORKS) {
foreach ($RUNTIME in $CHECK_RUNTIMES) {
# If we have an invalid combination of framework and runtime
if ($VALID_CROSS_PLATFORM_FRAMEWORKS -notcontains $FRAMEWORK -and $VALID_CROSS_PLATFORM_RUNTIMES -contains $RUNTIME) {
continue
}
Set-Location -Path $BUILD_FOLDER\MPF.Check\bin\Debug\$FRAMEWORK\$RUNTIME\publish\
7z a -tzip $BUILD_FOLDER\MPF.Check_${FRAMEWORK}_${RUNTIME}_debug.zip *
Set-Location -Path $BUILD_FOLDER\MPF.Check\bin\Release\$FRAMEWORK\${RUNTIME}\publish\
7z a -tzip $BUILD_FOLDER\MPF.Check_${FRAMEWORK}_${RUNTIME}_release.zip *
}
}
# Only include Debug if building all
if ($USE_ALL.IsPresent) {
Set-Location -Path $BUILD_FOLDER\MPF.Check\bin\Debug\${FRAMEWORK}\${RUNTIME}\publish\
7z a -tzip $BUILD_FOLDER\MPF.Check_${FRAMEWORK}_${RUNTIME}_debug.zip *
}
Set-Location -Path $BUILD_FOLDER\MPF.Check\bin\Release\${FRAMEWORK}\${RUNTIME}\publish\
7z a -tzip $BUILD_FOLDER\MPF.Check_${FRAMEWORK}_${RUNTIME}_release.zip *
}
}
# Reset the directory
Set-Location -Path $PSScriptRoot
# Reset the directory
Set-Location -Path $PSScriptRoot
}